Skip to content

Commit

Permalink
Implement "followup commands" for interactive session API (#176436)
Browse files Browse the repository at this point in the history
* Implement "followup commands" for interactive session API

* Support icons

* Only support icons for command followups

* Remove todo
  • Loading branch information
roblourens authored Mar 7, 2023
1 parent 9413812 commit 3d6e57b
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 15 deletions.
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ 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 { IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
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 @@ -1100,6 +1101,7 @@ export interface IInteractiveRequestDto {

export interface IInteractiveResponseDto {
followups?: string[];
commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
}

export interface IInteractiveResponseProgressDto {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHostInteractiveSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
return;
}

return { followups: res.followups };
return { followups: res.followups, commandFollowups: res.commands };
}

throw new Error('provider must implement either provideResponse or provideResponseWithProgress');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/contrib/markd
import { ViewportSemanticTokensContribution } from 'vs/editor/contrib/semanticTokens/browser/viewportSemanticTokens';
import { SmartSelectController } from 'vs/editor/contrib/smartSelect/browser/smartSelect';
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
Expand All @@ -37,6 +38,7 @@ import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreve
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
import { IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
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 @@ -82,6 +84,7 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configService: IConfigurationService,
@ILogService private readonly logService: ILogService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
this.renderer = this.instantiationService.createInstance(MarkdownRenderer, {});
Expand Down Expand Up @@ -193,13 +196,24 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
templateData.value.appendChild(result.element);
templateData.elementDisposables.add(result);

if (isResponseVM(element) && element.followups?.length && index === this.delegate.getListLength() - 1) {
if (isResponseVM(element) && index === this.delegate.getListLength() - 1) {
const followupsContainer = dom.append(templateData.value, $('.interactive-response-followups'));
element.followups.forEach(q => {
const button = templateData.elementDisposables.add(new Button(followupsContainer, defaultButtonStyles));
button.label = `"${q}"`;
templateData.elementDisposables.add(button.onDidClick(() => this._onDidSelectFollowup.fire(q)));
});
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 ?? []));
}));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@
margin: 0;
}

.interactive-session .interactive-response .interactive-session-response-followups {
.interactive-session .interactive-response .interactive-response-followups {
display: flex;
flex-direction: column;
gap: 8px;
align-items: start;
margin-bottom: 1em; /* This is matching the margin on rendered markdown */
}

.interactive-session .interactive-response .interactive-session-response-followups .monaco-button {
.interactive-session .interactive-response .interactive-response-followups .monaco-button {
width: 100%;
padding: 2px 8px;
font-size: 11px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export interface IInteractiveRequestModel {
readonly response: IInteractiveResponseModel | undefined;
}

export interface IInteractiveSessionResponseCommandFollowup {
commandId: string;
args: any[];
title: string; // supports codicon strings
}

export interface IInteractiveResponseModel {
readonly onDidChange: Event<void>;
readonly id: string;
Expand All @@ -26,6 +32,7 @@ export interface IInteractiveResponseModel {
readonly response: IMarkdownString;
readonly isComplete: boolean;
readonly followups?: string[];
readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
}

export function isRequest(item: unknown): item is IInteractiveRequestModel {
Expand Down Expand Up @@ -72,6 +79,11 @@ export class InteractiveResponseModel extends Disposable implements IInteractive
return this._followups;
}

private _commandFollowups: IInteractiveSessionResponseCommandFollowup[] | undefined;
public get commandFollowups(): IInteractiveSessionResponseCommandFollowup[] | undefined {
return this._commandFollowups;
}

private _response: IMarkdownString;
public get response(): IMarkdownString {
return this._response;
Expand All @@ -90,9 +102,10 @@ export class InteractiveResponseModel extends Disposable implements IInteractive
this._onDidChange.fire();
}

complete(followups: string[] | undefined): void {
complete(followups: string[] | undefined, commandFollowups: IInteractiveSessionResponseCommandFollowup[] | undefined): void {
this._isComplete = true;
this._followups = followups;
this._commandFollowups = commandFollowups;
this._onDidChange.fire();
}
}
Expand Down Expand Up @@ -211,8 +224,8 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
}
}

completeResponse(request: InteractiveRequestModel, followups?: string[]): void {
request.response!.complete(followups);
completeResponse(request: InteractiveRequestModel, followups?: string[], commandFollowups?: IInteractiveSessionResponseCommandFollowup[]): void {
request.response!.complete(followups, commandFollowups);
}

setResponse(request: InteractiveRequestModel, response: InteractiveResponseModel): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ProviderResult } from 'vs/editor/common/languages';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveSessionResponseCommandFollowup, InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';

export interface IInteractiveSession {
id: number;
Expand All @@ -27,6 +27,7 @@ export interface IInteractiveRequest {
export interface IInteractiveResponse {
session: IInteractiveSession;
followups?: string[];
commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
}

export interface IInteractiveProgress {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
return;
}

model.completeResponse(request, rawResponse.followups);
model.completeResponse(request, rawResponse.followups, rawResponse.commandFollowups);
this.trace('sendRequest', `Provider returned response for session ${sessionId} with ${rawResponse.followups} followups`);
} finally {
this._pendingRequestSessions.delete(sessionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IInteractiveRequestModel, IInteractiveResponseModel, IInteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveRequestModel, IInteractiveResponseModel, IInteractiveSessionModel, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { countWords } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionWordCounter';

Expand Down Expand Up @@ -57,6 +57,7 @@ export interface IInteractiveResponseViewModel {
readonly response: IMarkdownString;
readonly isComplete: boolean;
readonly followups?: string[];
readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
readonly progressiveResponseRenderingEnabled: boolean;
readonly contentUpdateTimings?: IInteractiveSessionLiveUpdateData;
renderData?: IInteractiveResponseRenderData;
Expand Down Expand Up @@ -191,6 +192,10 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac
return this._model.followups;
}

get commandFollowups() {
return this._model.commandFollowups;
}

renderData: IInteractiveResponseRenderData | undefined = undefined;

currentRenderedHeight: number | undefined;
Expand Down
7 changes: 7 additions & 0 deletions src/vscode-dts/vscode.proposed.interactive.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,19 @@ declare module 'vscode' {

export interface InteractiveResponseForProgress {
followups?: string[];
commands?: InteractiveResponseCommand[];
}

export interface InteractiveProgress {
content: string;
}

export interface InteractiveResponseCommand {
commandId: string;
args: any[];
title: string; // supports codicon strings
}

export interface InteractiveSessionProvider {
provideInitialSuggestions?(token: CancellationToken): ProviderResult<string[]>;
prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult<InteractiveSession>;
Expand Down

0 comments on commit 3d6e57b

Please sign in to comment.