Skip to content

Commit cd1d311

Browse files
authored
Add event fired when the user performs actions on interactive session responses (microsoft#176829)
* Add an id to responses that comes from the provider * Add event fired when the user performs actions on interactive session responses * Only show user action buttons when the provider has added a response ID
1 parent 1d01754 commit cd1d311

16 files changed

Lines changed: 306 additions & 79 deletions

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@
181181
"invalidate",
182182
"open",
183183
"override",
184+
"perform",
184185
"receive",
185186
"register",
186187
"remove",

src/vs/workbench/api/browser/mainThreadInteractiveSession.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { DisposableMap } from 'vs/base/common/lifecycle';
6+
import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';
77
import { URI } from 'vs/base/common/uri';
88
import { ExtHostContext, ExtHostInteractiveSessionShape, IInteractiveRequestDto, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol';
99
import { IInteractiveSessionContributionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContributionService';
1010
import { IInteractiveProgress, IInteractiveRequest, IInteractiveResponse, IInteractiveSession, IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
1111
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
1212

1313
@extHostNamedCustomer(MainContext.MainThreadInteractiveSession)
14-
export class MainThreadInteractiveSession implements MainThreadInteractiveSessionShape {
14+
export class MainThreadInteractiveSession extends Disposable implements MainThreadInteractiveSessionShape {
1515

16-
private readonly _inputRegistrations = new DisposableMap<number>();
17-
18-
private readonly _registrations = new DisposableMap<number>();
16+
private readonly _registrations = this._register(new DisposableMap<number>());
1917
private readonly _activeRequestProgressCallbacks = new Map<string, (progress: IInteractiveProgress) => void>();
2018

2119
private readonly _proxy: ExtHostInteractiveSessionShape;
@@ -24,14 +22,13 @@ export class MainThreadInteractiveSession implements MainThreadInteractiveSessio
2422
extHostContext: IExtHostContext,
2523
@IInteractiveSessionService private readonly _interactiveSessionService: IInteractiveSessionService,
2624
@IInteractiveSessionContributionService private readonly interactiveSessionContribService: IInteractiveSessionContributionService,
27-
// @ILogService private readonly logService: ILogService,
2825
) {
26+
super();
2927
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostInteractiveSession);
30-
}
3128

32-
dispose(): void {
33-
this._inputRegistrations.dispose();
34-
this._registrations.dispose();
29+
this._register(this._interactiveSessionService.onDidPerformUserAction(e => {
30+
this._proxy.$onDidPerformUserAction(e);
31+
}));
3532
}
3633

3734
async $registerInteractiveSessionProvider(handle: number, id: string, implementsProgress: boolean): Promise<void> {

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
12291229
addInteractiveRequest(context: vscode.InteractiveSessionRequestArgs) {
12301230
checkProposedApiEnabled(extension, 'interactive');
12311231
return extHostInteractiveSession.addInteractiveSessionRequest(context);
1232+
},
1233+
get onDidPerformUserAction() {
1234+
return extHostInteractiveSession.onDidPerformUserAction;
12321235
}
12331236
};
12341237

@@ -1435,7 +1438,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
14351438
TabInputInteractiveWindow: extHostTypes.InteractiveWindowInput,
14361439
TelemetryTrustedValue: TelemetryTrustedValue,
14371440
LogLevel: LogLevel,
1438-
EditSessionIdentityMatch: EditSessionIdentityMatch
1441+
EditSessionIdentityMatch: EditSessionIdentityMatch,
1442+
InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection
14391443
};
14401444
};
14411445
}

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import * as languages from 'vs/editor/common/languages';
2626
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/languages/languageConfiguration';
2727
import { EndOfLineSequence } from 'vs/editor/common/model';
2828
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
29-
import { IInteractiveEditorResponse, IInteractiveEditorSession, IInteractiveEditorRequest } from 'vs/editor/contrib/interactive/common/interactiveEditor';
29+
import { IInteractiveEditorRequest, IInteractiveEditorResponse, IInteractiveEditorSession } from 'vs/editor/contrib/interactive/common/interactiveEditor';
3030
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
3131
import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
3232
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
@@ -52,7 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views
5252
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
5353
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
5454
import { IInteractiveResponseErrorDetails, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
55-
import { IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
55+
import { IInteractiveProgress, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
5656
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
5757
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
5858
import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@@ -1110,25 +1110,22 @@ export interface IInteractiveResponseDto {
11101110
};
11111111
}
11121112

1113-
export interface IInteractiveResponseProgressDto {
1114-
responsePart: string;
1115-
}
1116-
11171113
export interface MainThreadInteractiveSessionShape extends IDisposable {
11181114
$registerInteractiveSessionProvider(handle: number, id: string, implementsProgress: boolean): Promise<void>;
11191115
$acceptInteractiveSessionState(sessionId: number, state: any): Promise<void>;
11201116
$addInteractiveSessionRequest(context: any): void;
11211117
$unregisterInteractiveSessionProvider(handle: number): Promise<void>;
1122-
$acceptInteractiveResponseProgress(handle: number, sessionId: number, progress: IInteractiveResponseProgressDto): void;
1118+
$acceptInteractiveResponseProgress(handle: number, sessionId: number, progress: IInteractiveProgress): void;
11231119
}
11241120

11251121
export interface ExtHostInteractiveSessionShape {
11261122
$prepareInteractiveSession(handle: number, initialState: any, token: CancellationToken): Promise<IInteractiveSessionDto | undefined>;
1127-
$resolveInteractiveRequest(handle: number, sessionId: number, context: any, token: CancellationToken): Promise<IInteractiveRequestDto | undefined>;
1123+
$resolveInteractiveRequest(handle: number, sessionId: number, context: any, token: CancellationToken): Promise<Omit<IInteractiveRequestDto, 'id'> | undefined>;
11281124
$provideInitialSuggestions(handle: number, token: CancellationToken): Promise<string[] | undefined>;
11291125
$provideInteractiveReply(handle: number, sessionId: number, request: IInteractiveRequestDto, token: CancellationToken): Promise<IInteractiveResponseDto | undefined>;
11301126
$provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<IInteractiveSlashCommand[] | undefined>;
11311127
$releaseSession(sessionId: number): void;
1128+
$onDidPerformUserAction(event: IInteractiveSessionUserActionEvent): Promise<void>;
11321129
}
11331130

11341131
export interface ExtHostUrlsShape {

src/vs/workbench/api/common/extHostInteractiveSession.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
76
import { CancellationToken } from 'vs/base/common/cancellation';
7+
import { Emitter } from 'vs/base/common/event';
88
import { toDisposable } from 'vs/base/common/lifecycle';
99
import { StopWatch } from 'vs/base/common/stopwatch';
1010
import { withNullAsUndefined } from 'vs/base/common/types';
1111
import { localize } from 'vs/nls';
1212
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
1313
import { ILogService } from 'vs/platform/log/common/log';
1414
import { ExtHostInteractiveSessionShape, IInteractiveRequestDto, IInteractiveResponseDto, IInteractiveSessionDto, IMainContext, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol';
15-
import { IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
15+
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
16+
import { IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
1617
import type * as vscode from 'vscode';
1718

1819
class InteractiveSessionProviderWrapper {
@@ -32,6 +33,10 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
3233

3334
private readonly _interactiveSessionProvider = new Map<number, InteractiveSessionProviderWrapper>();
3435
private readonly _interactiveSessions = new Map<number, vscode.InteractiveSession>();
36+
// private readonly _providerResponsesByRequestId = new Map<number, { response: vscode.ProviderResult<vscode.InteractiveResponse | vscode.InteractiveResponseForProgress>; sessionId: number }>();
37+
38+
private readonly _onDidPerformUserAction = new Emitter<vscode.InteractiveSessionUserActionEvent>();
39+
public readonly onDidPerformUserAction = this._onDidPerformUserAction.event;
3540

3641
private readonly _proxy: MainThreadInteractiveSessionShape;
3742

@@ -81,7 +86,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
8186
};
8287
}
8388

84-
async $resolveInteractiveRequest(handle: number, sessionId: number, context: any, token: CancellationToken): Promise<IInteractiveRequestDto | undefined> {
89+
async $resolveInteractiveRequest(handle: number, sessionId: number, context: any, token: CancellationToken): Promise<Omit<IInteractiveRequestDto, 'id'> | undefined> {
8590
const entry = this._interactiveSessionProvider.get(handle);
8691
if (!entry) {
8792
return undefined;
@@ -142,7 +147,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
142147
firstProgress = stopWatch.elapsed();
143148
}
144149

145-
this._proxy.$acceptInteractiveResponseProgress(handle, sessionId, { responsePart: progress.content });
150+
this._proxy.$acceptInteractiveResponseProgress(handle, sessionId, progress);
146151
}
147152
};
148153
let result: vscode.InteractiveResponseForProgress | undefined | null;
@@ -195,5 +200,9 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
195200
this._interactiveSessions.delete(sessionId);
196201
}
197202

203+
async $onDidPerformUserAction(event: IInteractiveSessionUserActionEvent): Promise<void> {
204+
this._onDidPerformUserAction.fire(event);
205+
}
206+
198207
//#endregion
199208
}

src/vs/workbench/api/common/extHostTypes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3954,3 +3954,12 @@ export class InteractiveWindowInput {
39543954
constructor(readonly uri: URI, readonly inputBoxUri: URI) { }
39553955
}
39563956
//#endregion
3957+
3958+
//#region Interactive session
3959+
3960+
export enum InteractiveSessionVoteDirection {
3961+
Up = 1,
3962+
Down = 2
3963+
}
3964+
3965+
//#endregion

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

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,21 @@ import { localize } from 'vs/nls';
1212
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
1313
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
1414
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions';
15+
import { interactiveSessionResponseHasProviderId } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
16+
import { IInteractiveSessionService, IInteractiveSessionUserActionEvent } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
17+
import { IInteractiveResponseViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
1518
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
1619

20+
export interface IInteractiveSessionCodeBlockActionContext {
21+
code: string;
22+
codeBlockIndex: number;
23+
element: IInteractiveResponseViewModel;
24+
}
25+
26+
function isCodeBlockActionContext(thing: unknown): thing is IInteractiveSessionCodeBlockActionContext {
27+
return typeof thing === 'object' && thing !== null && 'code' in thing && 'element' in thing;
28+
}
29+
1730
export function registerInteractiveSessionCodeBlockActions() {
1831
registerAction2(class CopyCodeBlockAction extends Action2 {
1932
constructor() {
@@ -28,19 +41,30 @@ export function registerInteractiveSessionCodeBlockActions() {
2841
icon: Codicon.copy,
2942
menu: {
3043
id: MenuId.InteractiveSessionCodeBlock,
44+
when: interactiveSessionResponseHasProviderId,
3145
group: 'navigation',
3246
}
3347
});
3448
}
3549

3650
run(accessor: ServicesAccessor, ...args: any[]) {
37-
const code = args[0];
38-
if (typeof code !== 'string') {
51+
const context = args[0];
52+
if (!isCodeBlockActionContext(context)) {
3953
return;
4054
}
4155

4256
const clipboardService = accessor.get(IClipboardService);
43-
clipboardService.writeText(code);
57+
clipboardService.writeText(context.code);
58+
59+
const interactiveSessionService = accessor.get(IInteractiveSessionService);
60+
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
61+
providerId: context.element.providerId,
62+
action: {
63+
kind: 'copy',
64+
responseId: context.element.providerResponseId,
65+
codeBlockIndex: context.codeBlockIndex,
66+
}
67+
});
4468
}
4569
});
4670

@@ -56,18 +80,20 @@ export function registerInteractiveSessionCodeBlockActions() {
5680
category: INTERACTIVE_SESSION_CATEGORY,
5781
menu: {
5882
id: MenuId.InteractiveSessionCodeBlock,
83+
when: interactiveSessionResponseHasProviderId,
5984
}
6085
});
6186
}
6287

6388
async run(accessor: ServicesAccessor, ...args: any[]) {
64-
const code = args[0];
65-
if (typeof code !== 'string') {
89+
const context = args[0];
90+
if (!isCodeBlockActionContext(context)) {
6691
return;
6792
}
6893

6994
const editorService = accessor.get(IEditorService);
7095
const bulkEditService = accessor.get(IBulkEditService);
96+
const interactiveSessionService = accessor.get(IInteractiveSessionService);
7197
const activeEditorControl = editorService.activeTextEditorControl;
7298
if (isCodeEditor(activeEditorControl)) {
7399
const activeModel = activeEditorControl.getModel();
@@ -78,9 +104,18 @@ export function registerInteractiveSessionCodeBlockActions() {
78104
const activeSelection = activeEditorControl.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1);
79105
await bulkEditService.apply([new ResourceTextEdit(activeModel.uri, {
80106
range: activeSelection,
81-
text: code,
107+
text: context.code,
82108
insertAsSnippet: true,
83109
})]);
110+
111+
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
112+
providerId: context.element.providerId,
113+
action: {
114+
kind: 'insert',
115+
responseId: context.element.providerResponseId,
116+
codeBlockIndex: context.codeBlockIndex,
117+
}
118+
});
84119
}
85120
}
86121
});

src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionTitleActions.ts renamed to src/vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionTitleActions.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
88
import { localize } from 'vs/nls';
99
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
1010
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions';
11-
import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
11+
import { interactiveSessionResponseHasProviderId } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
12+
import { IInteractiveSessionService, IInteractiveSessionUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
13+
import { isResponseVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
1214

1315
export function registerInteractiveSessionTitleActions() {
1416
registerAction2(class VoteUpAction extends Action2 {
@@ -24,18 +26,27 @@ export function registerInteractiveSessionTitleActions() {
2426
icon: Codicon.thumbsup,
2527
menu: {
2628
id: MenuId.InteractiveSessionTitle,
29+
when: interactiveSessionResponseHasProviderId,
2730
group: 'navigation',
2831
}
2932
});
3033
}
3134

3235
run(accessor: ServicesAccessor, ...args: any[]) {
3336
const item = args[0];
34-
if (!isRequestVM(item) && !isResponseVM(item)) {
37+
if (!isResponseVM(item)) {
3538
return;
3639
}
3740

38-
// TODO call provider method
41+
const interactiveSessionService = accessor.get(IInteractiveSessionService);
42+
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
43+
providerId: item.providerId,
44+
action: {
45+
kind: 'vote',
46+
direction: InteractiveSessionVoteDirection.Up,
47+
responseId: item.providerResponseId
48+
}
49+
});
3950
}
4051
});
4152

@@ -52,18 +63,27 @@ export function registerInteractiveSessionTitleActions() {
5263
icon: Codicon.thumbsdown,
5364
menu: {
5465
id: MenuId.InteractiveSessionTitle,
66+
when: interactiveSessionResponseHasProviderId,
5567
group: 'navigation',
5668
}
5769
});
5870
}
5971

6072
run(accessor: ServicesAccessor, ...args: any[]) {
6173
const item = args[0];
62-
if (!isRequestVM(item) && !isResponseVM(item)) {
74+
if (!isResponseVM(item)) {
6375
return;
6476
}
6577

66-
// TODO call provider method
78+
const interactiveSessionService = accessor.get(IInteractiveSessionService);
79+
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
80+
providerId: item.providerId,
81+
action: {
82+
kind: 'vote',
83+
direction: InteractiveSessionVoteDirection.Down,
84+
responseId: item.providerResponseId
85+
}
86+
});
6787
}
6888
});
6989
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
2626
import '../common/interactiveSessionColors';
2727
import { IInteractiveSessionWidgetService, InteractiveSessionWidgetService } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionWidget';
2828
import { registerInteractiveSessionCopyActions } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCopyActions';
29-
import { registerInteractiveSessionCodeBlockActions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionCodeblockActions';
30-
import { registerInteractiveSessionTitleActions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionTitleActions';
29+
import { registerInteractiveSessionCodeBlockActions } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCodeblockActions';
30+
import { registerInteractiveSessionTitleActions } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionTitleActions';
3131

3232

3333
// Register configuration

0 commit comments

Comments
 (0)