Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change interactive session followups to be returned from a provider method #176879

Merged
merged 4 commits into from
Mar 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/base/browser/ui/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import 'vs/css!./button';

export interface IButtonOptions extends IButtonStyles {
export interface IButtonOptions extends Partial<IButtonStyles> {
readonly title?: boolean | string;
readonly supportIcons?: boolean;
readonly supportShortLabel?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/api/browser/mainThreadInteractiveSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export class MainThreadInteractiveSession extends Disposable implements MainThre
},
provideSlashCommands: (session, token) => {
return this._proxy.$provideSlashCommands(handle, session.id, token);
},
provideFollowups: (session, token) => {
return this._proxy.$provideFollowups(handle, session.id, token);
}
});

Expand Down
7 changes: 3 additions & 4 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import { SaveReason } from 'vs/workbench/common/editor';
import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { IInteractiveResponseErrorDetails, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveProgress, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveResponseErrorDetails } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveProgress, IInteractiveSessionFollowup, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
Expand Down Expand Up @@ -1101,8 +1101,6 @@ export interface IInteractiveRequestDto {
}

export interface IInteractiveResponseDto {
followups?: string[];
commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
errorDetails?: IInteractiveResponseErrorDetails;
timings: {
firstProgress: number;
Expand All @@ -1122,6 +1120,7 @@ export interface ExtHostInteractiveSessionShape {
$prepareInteractiveSession(handle: number, initialState: any, token: CancellationToken): Promise<IInteractiveSessionDto | undefined>;
$resolveInteractiveRequest(handle: number, sessionId: number, context: any, token: CancellationToken): Promise<Omit<IInteractiveRequestDto, 'id'> | undefined>;
$provideInitialSuggestions(handle: number, token: CancellationToken): Promise<string[] | undefined>;
$provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise<IInteractiveSessionFollowup[] | undefined>;
$provideInteractiveReply(handle: number, sessionId: number, request: IInteractiveRequestDto, token: CancellationToken): Promise<IInteractiveResponseDto | undefined>;
$provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<IInteractiveSlashCommand[] | undefined>;
$releaseSession(sessionId: number): void;
Expand Down
40 changes: 38 additions & 2 deletions src/vs/workbench/api/common/extHostInteractiveSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostInteractiveSessionShape, IInteractiveRequestDto, IInteractiveResponseDto, IInteractiveSessionDto, IMainContext, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveSessionFollowup, IInteractiveSessionReplyFollowup, IInteractiveSessionResponseCommandFollowup, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import type * as vscode from 'vscode';

class InteractiveSessionProviderWrapper {
Expand Down Expand Up @@ -123,6 +123,42 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
return withNullAsUndefined(await entry.provider.provideInitialSuggestions(token));
}

async $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise<IInteractiveSessionFollowup[] | undefined> {
const entry = this._interactiveSessionProvider.get(handle);
if (!entry) {
return undefined;
}

const realSession = this._interactiveSessions.get(sessionId);
if (!realSession) {
return;
}

if (!entry.provider.provideFollowups) {
return undefined;
}

const rawFollowups = await entry.provider.provideFollowups(realSession, token);
return rawFollowups?.map(f => {
if (typeof f === 'string') {
return <IInteractiveSessionReplyFollowup>{ title: f, message: f, kind: 'reply' };
} else if ('commandId' in f) {
return <IInteractiveSessionResponseCommandFollowup>{
kind: 'command',
title: f.title,
commandId: f.commandId,
args: f.args
};
} else {
return <IInteractiveSessionReplyFollowup>{
kind: 'reply',
title: f.title,
message: f.message
};
}
});
}

async $provideInteractiveReply(handle: number, sessionId: number, request: IInteractiveRequestDto, token: CancellationToken): Promise<IInteractiveResponseDto | undefined> {
const entry = this._interactiveSessionProvider.get(handle);
if (!entry) {
Expand Down Expand Up @@ -171,7 +207,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
}

const timings = { firstProgress: firstProgress ?? 0, totalElapsed: stopWatch.elapsed() };
return { followups: result.followups, commandFollowups: result.commands, errorDetails: result.errorDetails, timings };
return { errorDetails: result.errorDetails, timings };
}

async $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<IInteractiveSlashCommand[] | undefined> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import { IInteractiveSessionFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { Button } from 'vs/base/browser/ui/button/button';

const $ = dom.$;

export class InteractiveSessionFollowups<T extends IInteractiveSessionFollowup> extends Disposable {
constructor(
container: HTMLElement,
followups: T[],
private readonly clickHandler: (followup: T) => void,
) {
super();

const followupsContainer = dom.append(container, $('.interactive-session-followups'));
followups.forEach(followup => this.renderFollowup(followupsContainer, followup));
}

private renderFollowup(container: HTMLElement, followup: T): void {
const button = this._register(new Button(container, { supportIcons: typeof followup !== 'string' }));
const label = followup.kind === 'reply' ?
'$(wand) ' + (followup.title || followup.message) :
followup.title;
button.label = label;

this._register(button.onDidClick(() => this.clickHandler(followup)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/

import * as dom from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
Expand Down Expand Up @@ -38,14 +37,13 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILogService } from 'vs/platform/log/common/log';
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { IInteractiveSessionCodeBlockActionContext } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCodeblockActions';
import { InteractiveSessionFollowups } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionFollowups';
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
import { interactiveSessionResponseHasProviderId } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
import { IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveRequestViewModel, IInteractiveResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
import { getNWords } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionWordCounter';
Expand Down Expand Up @@ -86,9 +84,6 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
protected readonly _onDidChangeItemHeight = this._register(new Emitter<IItemHeightChangeParams>());
readonly onDidChangeItemHeight: Event<IItemHeightChangeParams> = this._onDidChangeItemHeight.event;

protected readonly _onDidSelectFollowup = this._register(new Emitter<string>());
readonly onDidSelectFollowup: Event<string> = this._onDidSelectFollowup.event;

private readonly _editorPool: EditorPool;

private _currentLayoutWidth: number = 0;
Expand Down Expand Up @@ -212,7 +207,7 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
}
};
runProgressiveRender(true);
timer.cancelAndSet(runProgressiveRender, 100);
timer.cancelAndSet(runProgressiveRender, 50);
} else if (isResponseVM(element)) {
this.basicRenderElement(element.response.value, element, index, templateData);
} else {
Expand All @@ -231,24 +226,12 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
errorDetails.appendChild($('span', undefined, element.errorDetails.message));
}

if (isResponseVM(element) && index === this.delegate.getListLength() - 1) {
if (isResponseVM(element) && element.commandFollowups) {
const followupsContainer = dom.append(templateData.value, $('.interactive-response-followups'));
const followups = element.commandFollowups ?? element.followups ?? [];
followups.forEach(q => this.renderFollowup(followupsContainer, templateData, q));
}
}

private renderFollowup(container: HTMLElement, templateData: IInteractiveListItemTemplate, followup: string | IInteractiveSessionResponseCommandFollowup): void {
const button = templateData.elementDisposables.add(new Button(container, { ...defaultButtonStyles, supportIcons: typeof followup !== 'string' }));
const label = typeof followup === 'string' ? `"${followup}"` : followup.title;
button.label = label;
if (typeof followup === 'string') {
// This should probably be a command as well?
templateData.elementDisposables.add(button.onDidClick(() => this._onDidSelectFollowup.fire(followup)));
} else {
templateData.elementDisposables.add(button.onDidClick(() => {
this.commandService.executeCommand(followup.commandId, ...(followup.args ?? []));
}));
templateData.elementDisposables.add(new InteractiveSessionFollowups(
followupsContainer,
element.commandFollowups,
followup => this.commandService.executeCommand(followup.commandId, ...(followup.args ?? []))));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import { foreground } from 'vs/platform/theme/common/colorRegistry';
import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { IInteractiveSessionWidget } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
import { InteractiveSessionFollowups } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionFollowups';
import { IInteractiveSessionRendererDelegate, InteractiveListItemRenderer, InteractiveSessionAccessibilityProvider, InteractiveSessionListDelegate, InteractiveTreeItem } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer';
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
import { IInteractiveSessionService, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveSessionReplyFollowup, IInteractiveSessionService, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveSessionViewModel, InteractiveSessionViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';

Expand All @@ -56,7 +57,7 @@ function revealLastElement(list: WorkbenchObjectTree<any>) {
list.scrollTop = list.scrollHeight - list.renderHeight;
}

const INPUT_EDITOR_MAX_HEIGHT = 275;
const INPUT_EDITOR_MAX_HEIGHT = 250;

export class InteractiveSessionWidget extends Disposable implements IInteractiveSessionWidget {
public static readonly CONTRIBS: { new(...args: [IInteractiveSessionWidget, ...any]): any }[] = [];
Expand Down Expand Up @@ -85,6 +86,10 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
private listContainer!: HTMLElement;
private container!: HTMLElement;
private welcomeViewContainer!: HTMLElement;

private followupsContainer!: HTMLElement;
private followupsDisposables = this._register(new DisposableStore());

private welcomeViewDisposables = this._register(new DisposableStore());
private bodyDimension: dom.Dimension | undefined;
private visible = false;
Expand Down Expand Up @@ -179,15 +184,33 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
this.setWelcomeViewVisible(false);
}

const lastItem = treeItems[treeItems.length - 1];
this.tree.setChildren(null, treeItems, {
diffIdentityProvider: {
getId(element) {
const isLastAndResponse = isResponseVM(element) && element === lastItem.element;
return element.id + (isLastAndResponse ? '_last' : '');
return element.id;
},
}
});

const lastItem = items[items.length - 1];
if (lastItem && isResponseVM(lastItem) && lastItem.isComplete) {
this.renderFollowups(lastItem.replyFollowups);
} else {
this.renderFollowups(undefined);
}
}
}

private async renderFollowups(items?: IInteractiveSessionReplyFollowup[]): Promise<void> {
this.followupsDisposables.clear();
dom.clearNode(this.followupsContainer);

if (items) {
this.followupsDisposables.add(new InteractiveSessionFollowups(this.followupsContainer, items, followup => this.acceptInput(followup.message)));
}

if (this.bodyDimension) {
this.layout(this.bodyDimension.height, this.bodyDimension.width);
}
}

Expand Down Expand Up @@ -298,9 +321,6 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
this._register(this.renderer.onDidChangeItemHeight(e => {
this.tree.updateElementHeight(e.element, e.height);
}));
this._register(this.renderer.onDidSelectFollowup(followup => {
this.acceptInput(followup);
}));
this._register(this.tree.onDidFocus(() => {
this._onDidFocus.fire();
}));
Expand Down Expand Up @@ -337,7 +357,10 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
}

private createInput(container: HTMLElement): void {
const inputContainer = dom.append(container, $('.interactive-input-wrapper'));
const inputPart = dom.append(container, $('.interactive-input-part'));
this.followupsContainer = dom.append(inputPart, $('.interactive-input-followups'));

const inputContainer = dom.append(inputPart, $('.interactive-input-wrapper'));

const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer));
CONTEXT_IN_INTERACTIVE_INPUT.bindTo(inputScopedContextKeyService).set(true);
Expand Down Expand Up @@ -434,10 +457,12 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive

layout(height: number, width: number): void {
this.bodyDimension = new dom.Dimension(width, height);
const inputHeight = Math.min(this._inputEditor.getContentHeight(), height, INPUT_EDITOR_MAX_HEIGHT);
const inputWrapperPadding = 24;
const followupsHeight = this.followupsContainer.offsetHeight;
const inputPartPadding = 24;
const inputEditorHeight = Math.min(this._inputEditor.getContentHeight(), height - followupsHeight - inputPartPadding, INPUT_EDITOR_MAX_HEIGHT);
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
const listHeight = height - inputHeight - inputWrapperPadding;
const inputPartHeight = followupsHeight + inputEditorHeight + inputPartPadding;
const listHeight = height - inputPartHeight;

this.tree.layout(listHeight, width);
this.tree.getHTMLElement().style.height = `${listHeight}px`;
Expand All @@ -446,10 +471,10 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
revealLastElement(this.tree);
}

this.welcomeViewContainer.style.height = `${height - inputHeight - inputWrapperPadding}px`;
this.listContainer.style.height = `${height - inputHeight - inputWrapperPadding}px`;
this.welcomeViewContainer.style.height = `${height - inputPartHeight}px`;
this.listContainer.style.height = `${height - inputPartHeight}px`;

this._inputEditor.layout({ width: width - inputWrapperPadding, height: inputHeight });
this._inputEditor.layout({ width: width - inputPartPadding, height: inputEditorHeight });
}
}

Expand Down