Skip to content

Commit cd95e93

Browse files
authored
Add codeblock actions to command palette, and some keybindings (microsoft#182349)
1 parent 7f8c7c3 commit cd95e93

9 files changed

+126
-90
lines changed

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

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

6-
import { localize } from 'vs/nls';
76
import { Codicon } from 'vs/base/common/codicons';
7+
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
88
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
9-
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
9+
import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
1010
import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
11+
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
1112
import { Range } from 'vs/editor/common/core/range';
1213
import { ILanguageService } from 'vs/editor/common/languages/language';
1314
import { ITextModel } from 'vs/editor/common/model';
15+
import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard';
16+
import { localize } from 'vs/nls';
1417
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
1518
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
19+
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
1620
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
1721
import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
1822
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions';
23+
import { codeBlockInfoByModelUri } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer';
1924
import { IInteractiveSessionCopyAction, IInteractiveSessionService, IInteractiveSessionUserActionEvent, InteractiveSessionCopyKind } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
2025
import { IInteractiveResponseViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
26+
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
27+
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
28+
import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
2129
import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
2230
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
2331
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
24-
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
25-
import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
26-
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
2732

2833
export interface IInteractiveSessionCodeBlockActionContext {
2934
code: string;
@@ -80,15 +85,64 @@ export function registerInteractiveSessionCodeBlockActions() {
8085
}
8186
});
8287

83-
registerAction2(class InsertCodeBlockAction extends Action2 {
88+
CopyAction?.addImplementation(50000, 'interactiveSession-codeblock', (accessor) => {
89+
// get active code editor
90+
const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
91+
if (!editor) {
92+
return false;
93+
}
94+
95+
const editorModel = editor.getModel();
96+
if (!editorModel) {
97+
return false;
98+
}
99+
100+
const context = getContextFromEditor(editor);
101+
if (!context) {
102+
return false;
103+
}
104+
105+
const noSelection = editor.getSelections()?.length === 1 && editor.getSelection()?.isEmpty();
106+
const copiedText = noSelection ?
107+
editorModel.getValue() :
108+
editor.getSelections()?.reduce((acc, selection) => acc + editorModel.getValueInRange(selection), '') ?? '';
109+
const totalCharacters = editorModel.getValueLength();
110+
111+
// Report copy to extensions
112+
if (context.element.providerResponseId) {
113+
const interactiveSessionService = accessor.get(IInteractiveSessionService);
114+
interactiveSessionService.notifyUserAction({
115+
providerId: context.element.providerId,
116+
action: {
117+
kind: 'copy',
118+
codeBlockIndex: context.codeBlockIndex,
119+
responseId: context.element.providerResponseId,
120+
copyType: InteractiveSessionCopyKind.Action,
121+
copiedText,
122+
copiedCharacters: copiedText.length,
123+
totalCharacters,
124+
}
125+
});
126+
}
127+
128+
// Copy full cell if no selection, otherwise fall back on normal editor implementation
129+
if (noSelection) {
130+
accessor.get(IClipboardService).writeText(context.code);
131+
return true;
132+
}
133+
134+
return false;
135+
});
136+
137+
registerAction2(class InsertCodeBlockAction extends EditorAction2 {
84138
constructor() {
85139
super({
86140
id: 'workbench.action.interactiveSession.insertCodeBlock',
87141
title: {
88142
value: localize('interactive.insertCodeBlock.label', "Insert at Cursor"),
89143
original: 'Insert at Cursor'
90144
},
91-
f1: false,
145+
f1: true,
92146
category: INTERACTIVE_SESSION_CATEGORY,
93147
icon: Codicon.insert,
94148
menu: {
@@ -98,10 +152,13 @@ export function registerInteractiveSessionCodeBlockActions() {
98152
});
99153
}
100154

101-
async run(accessor: ServicesAccessor, ...args: any[]) {
102-
const context = args[0];
155+
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
156+
let context = args[0];
103157
if (!isCodeBlockActionContext(context)) {
104-
return;
158+
context = getContextFromEditor(editor);
159+
if (!isCodeBlockActionContext(context)) {
160+
return;
161+
}
105162
}
106163

107164
const editorService = accessor.get(IEditorService);
@@ -160,15 +217,14 @@ export function registerInteractiveSessionCodeBlockActions() {
160217
}
161218

162219
private async handleTextEditor(accessor: ServicesAccessor, codeEditor: ICodeEditor, activeModel: ITextModel, context: IInteractiveSessionCodeBlockActionContext) {
220+
this.notifyUserAction(accessor, context);
163221
const bulkEditService = accessor.get(IBulkEditService);
164222

165223
const activeSelection = codeEditor.getSelection() ?? new Range(activeModel.getLineCount(), 1, activeModel.getLineCount(), 1);
166224
await bulkEditService.apply([new ResourceTextEdit(activeModel.uri, {
167225
range: activeSelection,
168226
text: context.code,
169227
})]);
170-
171-
this.notifyUserAction(accessor, context);
172228
}
173229

174230
private notifyUserAction(accessor: ServicesAccessor, context: IInteractiveSessionCodeBlockActionContext) {
@@ -186,15 +242,15 @@ export function registerInteractiveSessionCodeBlockActions() {
186242

187243
});
188244

189-
registerAction2(class InsertIntoNewFileAction extends Action2 {
245+
registerAction2(class InsertIntoNewFileAction extends EditorAction2 {
190246
constructor() {
191247
super({
192248
id: 'workbench.action.interactiveSession.insertIntoNewFile',
193249
title: {
194250
value: localize('interactive.insertIntoNewFile.label', "Insert Into New File"),
195251
original: 'Insert Into New File'
196252
},
197-
f1: false,
253+
f1: true,
198254
category: INTERACTIVE_SESSION_CATEGORY,
199255
icon: Codicon.newFile,
200256
menu: {
@@ -205,10 +261,13 @@ export function registerInteractiveSessionCodeBlockActions() {
205261
});
206262
}
207263

208-
async run(accessor: ServicesAccessor, ...args: any[]) {
209-
const context = args[0];
264+
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
265+
let context = args[0];
210266
if (!isCodeBlockActionContext(context)) {
211-
return;
267+
context = getContextFromEditor(editor);
268+
if (!isCodeBlockActionContext(context)) {
269+
return;
270+
}
212271
}
213272

214273
const editorService = accessor.get(IEditorService);
@@ -228,29 +287,39 @@ export function registerInteractiveSessionCodeBlockActions() {
228287
}
229288
});
230289

231-
registerAction2(class RunInTerminalAction extends Action2 {
290+
registerAction2(class RunInTerminalAction extends EditorAction2 {
232291
constructor() {
233292
super({
234293
id: 'workbench.action.interactiveSession.runInTerminal',
235294
title: {
236295
value: localize('interactive.runInTerminal.label', "Run in Terminal"),
237296
original: 'Run in Terminal'
238297
},
239-
f1: false,
298+
f1: true,
240299
category: INTERACTIVE_SESSION_CATEGORY,
241300
icon: Codicon.terminal,
242301
menu: {
243302
id: MenuId.InteractiveSessionCodeBlock,
244303
group: 'navigation',
245304
isHiddenByDefault: true,
305+
},
306+
keybinding: {
307+
primary: KeyMod.WinCtrl | KeyCode.Enter,
308+
win: {
309+
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter
310+
},
311+
weight: KeybindingWeight.EditorContrib
246312
}
247313
});
248314
}
249315

250-
async run(accessor: ServicesAccessor, ...args: any[]) {
251-
const context = args[0];
316+
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
317+
let context = args[0];
252318
if (!isCodeBlockActionContext(context)) {
253-
return;
319+
context = getContextFromEditor(editor);
320+
if (!isCodeBlockActionContext(context)) {
321+
return;
322+
}
254323
}
255324

256325
const interactiveSessionService = accessor.get(IInteractiveSessionService);
@@ -275,7 +344,7 @@ export function registerInteractiveSessionCodeBlockActions() {
275344
terminalGroupService.showPanel(true);
276345
}
277346

278-
terminal.sendText(context.code, false);
347+
terminal.sendText(context.code, false, true);
279348

280349
interactiveSessionService.notifyUserAction(<IInteractiveSessionUserActionEvent>{
281350
providerId: context.element.providerId,
@@ -289,3 +358,22 @@ export function registerInteractiveSessionCodeBlockActions() {
289358
}
290359
});
291360
}
361+
362+
function getContextFromEditor(editor: ICodeEditor): IInteractiveSessionCodeBlockActionContext | undefined {
363+
const model = editor.getModel();
364+
if (!model) {
365+
return;
366+
}
367+
368+
const codeBlockInfo = codeBlockInfoByModelUri.get(model.uri);
369+
if (!codeBlockInfo) {
370+
return;
371+
}
372+
373+
return {
374+
element: codeBlockInfo.element,
375+
codeBlockIndex: codeBlockInfo.codeBlockIndex,
376+
code: editor.getValue(),
377+
languageId: editor.getModel()!.getLanguageId(),
378+
};
379+
}

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

Lines changed: 0 additions & 48 deletions
This file was deleted.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,5 @@ registerSingleton(IInteractiveSessionContributionService, InteractiveSessionCont
130130
registerSingleton(IInteractiveSessionWidgetService, InteractiveSessionWidgetService, InstantiationType.Delayed);
131131
registerSingleton(IInteractiveSessionWidgetHistoryService, InteractiveSessionWidgetHistoryService, InstantiationType.Delayed);
132132

133-
import 'vs/workbench/contrib/interactiveSession/browser/contrib/interactiveSessionCodeBlockCopy';
134133
import 'vs/workbench/contrib/interactiveSession/browser/contrib/interactiveSessionInputEditorContrib';
135134
import { Schemas } from 'vs/base/common/network';

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class InteractiveSessionEditorInput extends EditorInput {
7070

7171
override async resolve(): Promise<InteractiveSessionEditorModel | null> {
7272
const model = typeof this.sessionId === 'string' ?
73-
this.interactiveSessionService.retrieveSession(this.sessionId) :
73+
this.interactiveSessionService.getOrRestoreSession(this.sessionId) :
7474
this.interactiveSessionService.startSession(this.providerId!, CancellationToken.None);
7575

7676
if (!model) {

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
4949
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
5050
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
5151
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
52-
import { IInteractiveSessionCodeBlockActionContext } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCodeblockActions';
5352
import { InteractiveSessionFollowups } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionFollowups';
5453
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
5554
import { CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
@@ -526,13 +525,13 @@ interface IInteractiveResultCodeBlockPart {
526525
dispose(): void;
527526
}
528527

529-
export interface IInteractiveResultCodeBlockInfo {
530-
providerId: string;
531-
responseId: string;
528+
export interface IInteractiveSessionCodeBlockInfo {
532529
codeBlockIndex: number;
530+
element: IInteractiveResponseViewModel;
533531
}
534532

535-
export const codeBlockInfosByModelUri = new ResourceMap<IInteractiveResultCodeBlockInfo>();
533+
// Enable actions to look this up by editor URI. An alternative would be writing lots of details to element attributes.
534+
export const codeBlockInfoByModelUri = new ResourceMap<IInteractiveSessionCodeBlockInfo>();
536535

537536
const defaultCodeblockPadding = 10;
538537

@@ -671,17 +670,15 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
671670
this.layout(width);
672671

673672
if (isResponseVM(data.element) && data.element.providerResponseId) {
674-
// For telemetry reporting
675-
codeBlockInfosByModelUri.set(this.textModel.uri, {
676-
providerId: data.element.providerId,
677-
responseId: data.element.providerResponseId,
678-
codeBlockIndex: data.codeBlockIndex
673+
codeBlockInfoByModelUri.set(this.textModel.uri, {
674+
element: data.element,
675+
codeBlockIndex: data.codeBlockIndex,
679676
});
680677
} else {
681-
codeBlockInfosByModelUri.delete(this.textModel.uri);
678+
codeBlockInfoByModelUri.delete(this.textModel.uri);
682679
}
683680

684-
this.toolbar.context = <IInteractiveSessionCodeBlockActionContext>{
681+
this.toolbar.context = <IInteractiveSessionCodeBlockInfo>{
685682
code: data.text,
686683
codeBlockIndex: data.codeBlockIndex,
687684
element: data.element,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class InteractiveSessionViewPane extends ViewPane implements IInteractive
9797
}));
9898
this._widget.render(parent);
9999

100-
const initialModel = this.viewState.sessionId ? this.interactiveSessionService.retrieveSession(this.viewState.sessionId) : undefined;
100+
const initialModel = this.viewState.sessionId ? this.interactiveSessionService.getOrRestoreSession(this.viewState.sessionId) : undefined;
101101
this.updateModel(initialModel);
102102
}
103103

src/vs/workbench/contrib/interactiveSession/common/interactiveSessionService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export interface IInteractiveSessionService {
177177
registerSlashCommandProvider(provider: IInteractiveSlashCommandProvider): IDisposable;
178178
getProviderInfos(): IInteractiveProviderInfo[];
179179
startSession(providerId: string, token: CancellationToken): InteractiveSessionModel | undefined;
180-
retrieveSession(sessionId: string): IInteractiveSessionModel | undefined;
180+
getOrRestoreSession(sessionId: string): IInteractiveSessionModel | undefined;
181181

182182
/**
183183
* Returns whether the request was accepted.

src/vs/workbench/contrib/interactiveSession/common/interactiveSessionServiceImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
286286
return model;
287287
}
288288

289-
retrieveSession(sessionId: string): InteractiveSessionModel | undefined {
289+
getOrRestoreSession(sessionId: string): InteractiveSessionModel | undefined {
290290
const model = this._sessionModels.get(sessionId);
291291
if (model) {
292292
return model;

0 commit comments

Comments
 (0)