Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/promptSyntax/runPromptAction.ts
5260 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ChatViewId, IChatWidget, IChatWidgetService } from '../chat.js';
7
import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, CHAT_CONFIG_MENU_ID } from '../actions/chatActions.js';
8
import { URI } from '../../../../../base/common/uri.js';
9
import { OS } from '../../../../../base/common/platform.js';
10
import { Codicon } from '../../../../../base/common/codicons.js';
11
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
12
import { assertDefined } from '../../../../../base/common/types.js';
13
import { ThemeIcon } from '../../../../../base/common/themables.js';
14
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
15
import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js';
16
import { ILocalizedString, localize, localize2 } from '../../../../../nls.js';
17
import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js';
18
import { ICommandAction } from '../../../../../platform/action/common/action.js';
19
import { PromptFilePickers } from './pickers/promptFilePickers.js';
20
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
21
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
22
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
23
import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';
24
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
25
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
26
import { ResourceContextKey } from '../../../../common/contextkeys.js';
27
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
28
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
29
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
30
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
31
import { CancellationToken } from '../../../../../base/common/cancellation.js';
32
33
/**
34
* Condition for the `Run Current Prompt` action.
35
*/
36
const EDITOR_ACTIONS_CONDITION = ContextKeyExpr.and(
37
ChatContextKeys.enabled,
38
ResourceContextKey.HasResource,
39
ResourceContextKey.LangId.isEqualTo(PROMPT_LANGUAGE_ID),
40
);
41
42
/**
43
* Keybinding of the action.
44
*/
45
const COMMAND_KEY_BINDING = KeyMod.WinCtrl | KeyCode.Slash | KeyMod.Alt;
46
47
/**
48
* Action ID for the `Run Current Prompt` action.
49
*/
50
const RUN_CURRENT_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt.current';
51
52
/**
53
* Action ID for the `Run Prompt...` action.
54
*/
55
const RUN_SELECTED_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt';
56
57
/**
58
* Action ID for the `Configure Prompt Files...` action.
59
*/
60
const CONFIGURE_PROMPTS_ACTION_ID = 'workbench.action.chat.configure.prompts';
61
62
/**
63
* Constructor options for the `Run Prompt` base action.
64
*/
65
interface IRunPromptBaseActionConstructorOptions {
66
/**
67
* ID of the action to be registered.
68
*/
69
id: string;
70
71
/**
72
* Title of the action.
73
*/
74
title: ILocalizedString;
75
76
/**
77
* Icon of the action.
78
*/
79
icon: ThemeIcon;
80
81
/**
82
* Keybinding of the action.
83
*/
84
keybinding: number;
85
86
/**
87
* Alt action of the UI menu item.
88
*/
89
alt?: ICommandAction;
90
}
91
92
/**
93
* Base class of the `Run Prompt` action.
94
*/
95
abstract class RunPromptBaseAction extends Action2 {
96
constructor(
97
options: IRunPromptBaseActionConstructorOptions,
98
) {
99
super({
100
id: options.id,
101
title: options.title,
102
f1: false,
103
precondition: ChatContextKeys.enabled,
104
category: CHAT_CATEGORY,
105
icon: options.icon,
106
keybinding: {
107
when: ContextKeyExpr.and(
108
EditorContextKeys.editorTextFocus,
109
EDITOR_ACTIONS_CONDITION,
110
),
111
weight: KeybindingWeight.WorkbenchContrib,
112
primary: options.keybinding,
113
},
114
menu: [
115
{
116
id: MenuId.EditorTitleRun,
117
group: 'navigation',
118
order: options.alt ? 0 : 1,
119
alt: options.alt,
120
when: EDITOR_ACTIONS_CONDITION,
121
},
122
],
123
});
124
}
125
126
/**
127
* Executes the run prompt action with provided options.
128
*/
129
public async execute(
130
resource: URI | undefined,
131
inNewChat: boolean,
132
accessor: ServicesAccessor,
133
): Promise<IChatWidget | undefined> {
134
const commandService = accessor.get(ICommandService);
135
const promptsService = accessor.get(IPromptsService);
136
const widgetService = accessor.get(IChatWidgetService);
137
138
resource ||= getActivePromptFileUri(accessor);
139
assertDefined(
140
resource,
141
'Cannot find URI resource for an active text editor.',
142
);
143
144
if (inNewChat === true) {
145
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
146
}
147
148
const widget = await widgetService.revealWidget();
149
if (widget) {
150
widget.setInput(`/${await promptsService.getPromptSlashCommandName(resource, CancellationToken.None)}`);
151
// submit the prompt immediately
152
await widget.acceptInput();
153
}
154
return widget;
155
}
156
}
157
158
const RUN_CURRENT_PROMPT_ACTION_TITLE = localize2(
159
'run-prompt.capitalized',
160
"Run Prompt in Current Chat"
161
);
162
const RUN_CURRENT_PROMPT_ACTION_ICON = Codicon.playCircle;
163
164
/**
165
* The default `Run Current Prompt` action.
166
*/
167
class RunCurrentPromptAction extends RunPromptBaseAction {
168
constructor() {
169
super({
170
id: RUN_CURRENT_PROMPT_ACTION_ID,
171
title: RUN_CURRENT_PROMPT_ACTION_TITLE,
172
icon: RUN_CURRENT_PROMPT_ACTION_ICON,
173
keybinding: COMMAND_KEY_BINDING,
174
});
175
}
176
177
public override async run(
178
accessor: ServicesAccessor,
179
resource: URI | undefined,
180
): Promise<IChatWidget | undefined> {
181
return await super.execute(
182
resource,
183
false,
184
accessor,
185
);
186
}
187
}
188
189
class RunSelectedPromptAction extends Action2 {
190
constructor() {
191
super({
192
id: RUN_SELECTED_PROMPT_ACTION_ID,
193
title: localize2('run-prompt.capitalized.ellipses', "Run Prompt..."),
194
icon: Codicon.bookmark,
195
f1: true,
196
precondition: ChatContextKeys.enabled,
197
keybinding: {
198
when: ChatContextKeys.enabled,
199
weight: KeybindingWeight.WorkbenchContrib,
200
primary: COMMAND_KEY_BINDING,
201
},
202
category: CHAT_CATEGORY,
203
});
204
}
205
206
public override async run(
207
accessor: ServicesAccessor,
208
): Promise<void> {
209
const commandService = accessor.get(ICommandService);
210
const instaService = accessor.get(IInstantiationService);
211
const promptsService = accessor.get(IPromptsService);
212
const widgetService = accessor.get(IChatWidgetService);
213
214
const pickers = instaService.createInstance(PromptFilePickers);
215
216
const placeholder = localize(
217
'commands.prompt.select-dialog.placeholder',
218
'Select the prompt file to run (hold {0}-key to use in new chat)',
219
UILabelProvider.modifierLabels[OS].ctrlKey
220
);
221
222
const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt });
223
224
if (result === undefined) {
225
return;
226
}
227
228
const { promptFile, keyMods } = result;
229
230
if (keyMods.ctrlCmd === true) {
231
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
232
}
233
234
const widget = await widgetService.revealWidget();
235
if (widget) {
236
widget.setInput(`/${await promptsService.getPromptSlashCommandName(promptFile, CancellationToken.None)}`);
237
// submit the prompt immediately
238
await widget.acceptInput();
239
widget.focusInput();
240
}
241
}
242
}
243
244
class ManagePromptFilesAction extends Action2 {
245
constructor() {
246
super({
247
id: CONFIGURE_PROMPTS_ACTION_ID,
248
title: localize2('configure-prompts', "Configure Prompt Files..."),
249
shortTitle: localize2('configure-prompts.short', "Prompt Files"),
250
icon: Codicon.bookmark,
251
f1: true,
252
precondition: ChatContextKeys.enabled,
253
category: CHAT_CATEGORY,
254
menu: {
255
id: CHAT_CONFIG_MENU_ID,
256
when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),
257
order: 11,
258
group: '0_level'
259
},
260
});
261
}
262
263
public override async run(
264
accessor: ServicesAccessor,
265
): Promise<void> {
266
const openerService = accessor.get(IOpenerService);
267
const instaService = accessor.get(IInstantiationService);
268
269
const pickers = instaService.createInstance(PromptFilePickers);
270
271
const placeholder = localize(
272
'commands.prompt.manage-dialog.placeholder',
273
'Select the prompt file to open'
274
);
275
276
const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt, optionEdit: false });
277
if (result !== undefined) {
278
await openerService.open(result.promptFile);
279
}
280
}
281
}
282
283
284
/**
285
* Gets `URI` of a prompt file open in an active editor instance, if any.
286
*/
287
function getActivePromptFileUri(accessor: ServicesAccessor): URI | undefined {
288
const codeEditorService = accessor.get(ICodeEditorService);
289
const model = codeEditorService.getActiveCodeEditor()?.getModel();
290
if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) {
291
return model.uri;
292
}
293
return undefined;
294
}
295
296
297
/**
298
* Action ID for the `Run Current Prompt In New Chat` action.
299
*/
300
const RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID = 'workbench.action.chat.run-in-new-chat.prompt.current';
301
302
const RUN_IN_NEW_CHAT_ACTION_TITLE = localize2(
303
'run-prompt-in-new-chat.capitalized',
304
"Run Prompt In New Chat",
305
);
306
307
/**
308
* Icon for the `Run Current Prompt In New Chat` action.
309
*/
310
const RUN_IN_NEW_CHAT_ACTION_ICON = Codicon.play;
311
312
/**
313
* `Run Current Prompt In New Chat` action.
314
*/
315
class RunCurrentPromptInNewChatAction extends RunPromptBaseAction {
316
constructor() {
317
super({
318
id: RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID,
319
title: RUN_IN_NEW_CHAT_ACTION_TITLE,
320
icon: RUN_IN_NEW_CHAT_ACTION_ICON,
321
keybinding: COMMAND_KEY_BINDING | KeyMod.CtrlCmd,
322
alt: {
323
id: RUN_CURRENT_PROMPT_ACTION_ID,
324
title: RUN_CURRENT_PROMPT_ACTION_TITLE,
325
icon: RUN_CURRENT_PROMPT_ACTION_ICON,
326
},
327
});
328
}
329
330
public override async run(
331
accessor: ServicesAccessor,
332
resource: URI,
333
): Promise<IChatWidget | undefined> {
334
return await super.execute(
335
resource,
336
true,
337
accessor,
338
);
339
}
340
}
341
342
/**
343
* Helper to register all the `Run Current Prompt` actions.
344
*/
345
export function registerRunPromptActions(): void {
346
registerAction2(RunCurrentPromptInNewChatAction);
347
registerAction2(RunCurrentPromptAction);
348
registerAction2(RunSelectedPromptAction);
349
registerAction2(ManagePromptFilesAction);
350
}
351
352