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
3296 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, showChatView } 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/chatContextKeys.js';
12
import { assertDefined } from '../../../../../base/common/types.js';
13
import { ThemeIcon } from '../../../../../base/common/themables.js';
14
import { ResourceContextKey } from '../../../../common/contextkeys.js';
15
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
16
import { PromptsType, PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/promptTypes.js';
17
import { ILocalizedString, localize, localize2 } from '../../../../../nls.js';
18
import { UILabelProvider } from '../../../../../base/common/keybindingLabels.js';
19
import { ICommandAction } from '../../../../../platform/action/common/action.js';
20
import { PromptsConfig } from '../../common/promptSyntax/config/config.js';
21
import { IViewsService } from '../../../../services/views/common/viewsService.js';
22
import { PromptFilePickers } from './pickers/promptFilePickers.js';
23
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
24
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
25
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
26
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
27
import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';
28
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
29
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
30
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
31
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
32
import { getPromptCommandName } from '../../common/promptSyntax/service/promptsServiceImpl.js';
33
34
/**
35
* Condition for the `Run Current Prompt` action.
36
*/
37
const EDITOR_ACTIONS_CONDITION = ContextKeyExpr.and(
38
ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
39
ResourceContextKey.HasResource,
40
ResourceContextKey.LangId.isEqualTo(PROMPT_LANGUAGE_ID),
41
);
42
43
/**
44
* Keybinding of the action.
45
*/
46
const COMMAND_KEY_BINDING = KeyMod.WinCtrl | KeyCode.Slash | KeyMod.Alt;
47
48
/**
49
* Action ID for the `Run Current Prompt` action.
50
*/
51
const RUN_CURRENT_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt.current';
52
53
/**
54
* Action ID for the `Run Prompt...` action.
55
*/
56
const RUN_SELECTED_PROMPT_ACTION_ID = 'workbench.action.chat.run.prompt';
57
58
/**
59
* Action ID for the `Configure Prompt Files...` action.
60
*/
61
const CONFIGURE_PROMPTS_ACTION_ID = 'workbench.action.chat.configure.prompts';
62
63
/**
64
* Constructor options for the `Run Prompt` base action.
65
*/
66
interface IRunPromptBaseActionConstructorOptions {
67
/**
68
* ID of the action to be registered.
69
*/
70
id: string;
71
72
/**
73
* Title of the action.
74
*/
75
title: ILocalizedString;
76
77
/**
78
* Icon of the action.
79
*/
80
icon: ThemeIcon;
81
82
/**
83
* Keybinding of the action.
84
*/
85
keybinding: number;
86
87
/**
88
* Alt action of the UI menu item.
89
*/
90
alt?: ICommandAction;
91
}
92
93
/**
94
* Base class of the `Run Prompt` action.
95
*/
96
abstract class RunPromptBaseAction extends Action2 {
97
constructor(
98
options: IRunPromptBaseActionConstructorOptions,
99
) {
100
super({
101
id: options.id,
102
title: options.title,
103
f1: false,
104
precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
105
category: CHAT_CATEGORY,
106
icon: options.icon,
107
keybinding: {
108
when: ContextKeyExpr.and(
109
EditorContextKeys.editorTextFocus,
110
EDITOR_ACTIONS_CONDITION,
111
),
112
weight: KeybindingWeight.WorkbenchContrib,
113
primary: options.keybinding,
114
},
115
menu: [
116
{
117
id: MenuId.EditorTitleRun,
118
group: 'navigation',
119
order: options.alt ? 0 : 1,
120
alt: options.alt,
121
when: EDITOR_ACTIONS_CONDITION,
122
},
123
],
124
});
125
}
126
127
/**
128
* Executes the run prompt action with provided options.
129
*/
130
public async execute(
131
resource: URI | undefined,
132
inNewChat: boolean,
133
accessor: ServicesAccessor,
134
): Promise<IChatWidget | undefined> {
135
const viewsService = accessor.get(IViewsService);
136
const commandService = accessor.get(ICommandService);
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 showChatView(viewsService);
149
if (widget) {
150
widget.setInput(`/${getPromptCommandName(resource.path)}`);
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: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
197
keybinding: {
198
when: ContextKeyExpr.and(PromptsConfig.enabledCtx, 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 viewsService = accessor.get(IViewsService);
210
const commandService = accessor.get(ICommandService);
211
const instaService = accessor.get(IInstantiationService);
212
213
const pickers = instaService.createInstance(PromptFilePickers);
214
215
const placeholder = localize(
216
'commands.prompt.select-dialog.placeholder',
217
'Select the prompt file to run (hold {0}-key to use in new chat)',
218
UILabelProvider.modifierLabels[OS].ctrlKey
219
);
220
221
const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt });
222
223
if (result === undefined) {
224
return;
225
}
226
227
const { promptFile, keyMods } = result;
228
229
if (keyMods.ctrlCmd === true) {
230
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
231
}
232
233
const widget = await showChatView(viewsService);
234
if (widget) {
235
widget.setInput(`/${getPromptCommandName(promptFile.path)}`);
236
// submit the prompt immediately
237
await widget.acceptInput();
238
widget.focusInput();
239
}
240
}
241
}
242
243
class ManagePromptFilesAction extends Action2 {
244
constructor() {
245
super({
246
id: CONFIGURE_PROMPTS_ACTION_ID,
247
title: localize2('configure-prompts', "Configure Prompt Files..."),
248
shortTitle: localize2('configure-prompts.short', "Prompt Files"),
249
icon: Codicon.bookmark,
250
f1: true,
251
precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
252
category: CHAT_CATEGORY,
253
menu: {
254
id: CHAT_CONFIG_MENU_ID,
255
when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),
256
order: 10,
257
group: '0_level'
258
},
259
});
260
}
261
262
public override async run(
263
accessor: ServicesAccessor,
264
): Promise<void> {
265
const openerService = accessor.get(IOpenerService);
266
const instaService = accessor.get(IInstantiationService);
267
268
const pickers = instaService.createInstance(PromptFilePickers);
269
270
const placeholder = localize(
271
'commands.prompt.manage-dialog.placeholder',
272
'Select the prompt file to open'
273
);
274
275
const result = await pickers.selectPromptFile({ placeholder, type: PromptsType.prompt, optionEdit: false });
276
if (result !== undefined) {
277
await openerService.open(result.promptFile);
278
}
279
}
280
}
281
282
283
/**
284
* Gets `URI` of a prompt file open in an active editor instance, if any.
285
*/
286
function getActivePromptFileUri(accessor: ServicesAccessor): URI | undefined {
287
const codeEditorService = accessor.get(ICodeEditorService);
288
const model = codeEditorService.getActiveCodeEditor()?.getModel();
289
if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) {
290
return model.uri;
291
}
292
return undefined;
293
}
294
295
296
/**
297
* Action ID for the `Run Current Prompt In New Chat` action.
298
*/
299
const RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID = 'workbench.action.chat.run-in-new-chat.prompt.current';
300
301
const RUN_IN_NEW_CHAT_ACTION_TITLE = localize2(
302
'run-prompt-in-new-chat.capitalized',
303
"Run Prompt In New Chat",
304
);
305
306
/**
307
* Icon for the `Run Current Prompt In New Chat` action.
308
*/
309
const RUN_IN_NEW_CHAT_ACTION_ICON = Codicon.play;
310
311
/**
312
* `Run Current Prompt In New Chat` action.
313
*/
314
class RunCurrentPromptInNewChatAction extends RunPromptBaseAction {
315
constructor() {
316
super({
317
id: RUN_CURRENT_PROMPT_IN_NEW_CHAT_ACTION_ID,
318
title: RUN_IN_NEW_CHAT_ACTION_TITLE,
319
icon: RUN_IN_NEW_CHAT_ACTION_ICON,
320
keybinding: COMMAND_KEY_BINDING | KeyMod.CtrlCmd,
321
alt: {
322
id: RUN_CURRENT_PROMPT_ACTION_ID,
323
title: RUN_CURRENT_PROMPT_ACTION_TITLE,
324
icon: RUN_CURRENT_PROMPT_ACTION_ICON,
325
},
326
});
327
}
328
329
public override async run(
330
accessor: ServicesAccessor,
331
resource: URI,
332
): Promise<IChatWidget | undefined> {
333
return await super.execute(
334
resource,
335
true,
336
accessor,
337
);
338
}
339
}
340
341
/**
342
* Helper to register all the `Run Current Prompt` actions.
343
*/
344
export function registerRunPromptActions(): void {
345
registerAction2(RunCurrentPromptInNewChatAction);
346
registerAction2(RunCurrentPromptAction);
347
registerAction2(RunSelectedPromptAction);
348
registerAction2(ManagePromptFilesAction);
349
}
350
351