Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/controller/editActions.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 { KeyChord, KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
7
import { Mimes } from '../../../../../base/common/mime.js';
8
import { URI } from '../../../../../base/common/uri.js';
9
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
10
import { Selection } from '../../../../../editor/common/core/selection.js';
11
import { CommandExecutor } from '../../../../../editor/common/cursor/cursor.js';
12
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
13
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
14
import { ILanguageConfigurationService } from '../../../../../editor/common/languages/languageConfigurationRegistry.js';
15
import { TrackedRangeStickiness } from '../../../../../editor/common/model.js';
16
import { getIconClasses } from '../../../../../editor/common/services/getIconClasses.js';
17
import { IModelService } from '../../../../../editor/common/services/model.js';
18
import { LineCommentCommand, Type } from '../../../../../editor/contrib/comment/browser/lineCommentCommand.js';
19
import { localize, localize2 } from '../../../../../nls.js';
20
import { MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
21
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
22
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
23
import { InputFocusedContext, InputFocusedContextKey } from '../../../../../platform/contextkey/common/contextkeys.js';
24
import { IConfirmationResult, IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
25
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
26
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
27
import { INotificationService } from '../../../../../platform/notification/common/notification.js';
28
import { IQuickInputService, IQuickPickItem, QuickPickInput } from '../../../../../platform/quickinput/common/quickInput.js';
29
import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js';
30
import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js';
31
import { changeCellToKind, runDeleteAction } from './cellOperations.js';
32
import { CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, executeNotebookCondition, findTargetCellEditor } from './coreActions.js';
33
import { NotebookChangeTabDisplaySize, NotebookIndentUsingSpaces, NotebookIndentUsingTabs, NotebookIndentationToSpacesAction, NotebookIndentationToTabsAction } from './notebookIndentationActions.js';
34
import { CHANGE_CELL_LANGUAGE, CellEditState, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID, getNotebookEditorFromEditorPane } from '../notebookBrowser.js';
35
import * as icons from '../notebookIcons.js';
36
import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState, NotebookSetting } from '../../common/notebookCommon.js';
37
import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_IS_FIRST_OUTPUT, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_OUTPUT_INPUT_FOCUSED, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from '../../common/notebookContextKeys.js';
38
import { INotebookExecutionStateService } from '../../common/notebookExecutionStateService.js';
39
import { INotebookKernelService } from '../../common/notebookKernelService.js';
40
import { ICellRange } from '../../common/notebookRange.js';
41
import { IEditorService } from '../../../../services/editor/common/editorService.js';
42
import { ILanguageDetectionService } from '../../../../services/languageDetection/common/languageDetectionWorkerService.js';
43
import { NotebookInlineVariablesController } from '../contrib/notebookVariables/notebookInlineVariables.js';
44
45
const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs';
46
const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit';
47
const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete';
48
const QUIT_EDIT_ALL_CELLS_COMMAND_ID = 'notebook.quitEditAllCells';
49
export const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs';
50
export const SELECT_NOTEBOOK_INDENTATION_ID = 'notebook.selectIndentation';
51
export const COMMENT_SELECTED_CELLS_ID = 'notebook.commentSelectedCells';
52
53
registerAction2(class EditCellAction extends NotebookCellAction {
54
constructor() {
55
super(
56
{
57
id: EDIT_CELL_COMMAND_ID,
58
title: localize('notebookActions.editCell', "Edit Cell"),
59
keybinding: {
60
when: ContextKeyExpr.and(
61
NOTEBOOK_CELL_LIST_FOCUSED,
62
ContextKeyExpr.not(InputFocusedContextKey),
63
EditorContextKeys.hoverFocused.toNegated(),
64
NOTEBOOK_OUTPUT_INPUT_FOCUSED.toNegated()
65
),
66
primary: KeyCode.Enter,
67
weight: KeybindingWeight.WorkbenchContrib
68
},
69
menu: {
70
id: MenuId.NotebookCellTitle,
71
when: ContextKeyExpr.and(
72
NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true),
73
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
74
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.toNegated(),
75
NOTEBOOK_CELL_EDITABLE),
76
order: CellToolbarOrder.EditCell,
77
group: CELL_TITLE_CELL_GROUP_ID
78
},
79
icon: icons.editIcon,
80
});
81
}
82
83
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
84
if (!context.notebookEditor.hasModel()) {
85
return;
86
}
87
88
await context.notebookEditor.focusNotebookCell(context.cell, 'editor');
89
const foundEditor: ICodeEditor | undefined = context.cell ? findTargetCellEditor(context, context.cell) : undefined;
90
if (foundEditor && foundEditor.hasTextFocus() && InlineChatController.get(foundEditor)?.getWidgetPosition()?.lineNumber === foundEditor.getPosition()?.lineNumber) {
91
InlineChatController.get(foundEditor)?.focus();
92
}
93
}
94
});
95
96
const quitEditCondition = ContextKeyExpr.and(
97
NOTEBOOK_EDITOR_FOCUSED,
98
InputFocusedContext,
99
CTX_INLINE_CHAT_FOCUSED.toNegated()
100
);
101
registerAction2(class QuitEditCellAction extends NotebookCellAction {
102
constructor() {
103
super(
104
{
105
id: QUIT_EDIT_CELL_COMMAND_ID,
106
title: localize('notebookActions.quitEdit', "Stop Editing Cell"),
107
menu: {
108
id: MenuId.NotebookCellTitle,
109
when: ContextKeyExpr.and(
110
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
111
NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
112
NOTEBOOK_CELL_EDITABLE),
113
order: CellToolbarOrder.SaveCell,
114
group: CELL_TITLE_CELL_GROUP_ID
115
},
116
icon: icons.stopEditIcon,
117
keybinding: [
118
{
119
when: ContextKeyExpr.and(quitEditCondition,
120
EditorContextKeys.hoverVisible.toNegated(),
121
EditorContextKeys.hasNonEmptySelection.toNegated(),
122
EditorContextKeys.hasMultipleSelections.toNegated()),
123
primary: KeyCode.Escape,
124
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - 5
125
},
126
{
127
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED,
128
NOTEBOOK_OUTPUT_FOCUSED),
129
primary: KeyCode.Escape,
130
weight: KeybindingWeight.WorkbenchContrib + 5
131
},
132
{
133
when: ContextKeyExpr.and(
134
quitEditCondition,
135
NOTEBOOK_CELL_TYPE.isEqualTo('markup')),
136
primary: KeyMod.WinCtrl | KeyCode.Enter,
137
win: {
138
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter
139
},
140
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - 5
141
},
142
]
143
});
144
}
145
146
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
147
if (context.cell.cellKind === CellKind.Markup) {
148
context.cell.updateEditState(CellEditState.Preview, QUIT_EDIT_CELL_COMMAND_ID);
149
}
150
151
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
152
}
153
});
154
155
registerAction2(class QuitEditAllCellsAction extends NotebookAction {
156
constructor() {
157
super(
158
{
159
id: QUIT_EDIT_ALL_CELLS_COMMAND_ID,
160
title: localize('notebookActions.quitEditAllCells', "Stop Editing All Cells")
161
});
162
}
163
164
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) {
165
if (!context.notebookEditor.hasModel()) {
166
return;
167
}
168
169
const viewModel = context.notebookEditor.getViewModel();
170
if (!viewModel) {
171
return;
172
}
173
174
const activeCell = context.notebookEditor.getActiveCell();
175
176
const editingCells = viewModel.viewCells.filter(cell =>
177
cell.cellKind === CellKind.Markup && cell.getEditState() === CellEditState.Editing
178
);
179
180
editingCells.forEach(cell => {
181
cell.updateEditState(CellEditState.Preview, QUIT_EDIT_ALL_CELLS_COMMAND_ID);
182
});
183
184
if (activeCell) {
185
await context.notebookEditor.focusNotebookCell(activeCell, 'container', { skipReveal: true });
186
}
187
}
188
});
189
190
registerAction2(class DeleteCellAction extends NotebookCellAction {
191
constructor() {
192
super(
193
{
194
id: DELETE_CELL_COMMAND_ID,
195
title: localize('notebookActions.deleteCell', "Delete Cell"),
196
keybinding: {
197
primary: KeyCode.Delete,
198
mac: {
199
primary: KeyMod.CtrlCmd | KeyCode.Backspace
200
},
201
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_OUTPUT_INPUT_FOCUSED.toNegated()),
202
weight: KeybindingWeight.WorkbenchContrib
203
},
204
menu: [
205
{
206
id: MenuId.NotebookCellDelete,
207
when: NOTEBOOK_EDITOR_EDITABLE,
208
group: CELL_TITLE_CELL_GROUP_ID
209
},
210
{
211
id: MenuId.InteractiveCellDelete,
212
group: CELL_TITLE_CELL_GROUP_ID
213
}
214
],
215
icon: icons.deleteCellIcon
216
});
217
}
218
219
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) {
220
if (!context.notebookEditor.hasModel()) {
221
return;
222
}
223
224
let confirmation: IConfirmationResult;
225
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
226
const runState = notebookExecutionStateService.getCellExecution(context.cell.uri)?.state;
227
const configService = accessor.get(IConfigurationService);
228
229
if (runState === NotebookCellExecutionState.Executing && configService.getValue(NotebookSetting.confirmDeleteRunningCell)) {
230
const dialogService = accessor.get(IDialogService);
231
const primaryButton = localize('confirmDeleteButton', "Delete");
232
233
confirmation = await dialogService.confirm({
234
type: 'question',
235
message: localize('confirmDeleteButtonMessage', "This cell is running, are you sure you want to delete it?"),
236
primaryButton: primaryButton,
237
checkbox: {
238
label: localize('doNotAskAgain', "Do not ask me again")
239
}
240
});
241
242
} else {
243
confirmation = { confirmed: true };
244
}
245
246
if (!confirmation.confirmed) {
247
return;
248
}
249
250
if (confirmation.checkboxChecked === true) {
251
await configService.updateValue(NotebookSetting.confirmDeleteRunningCell, false);
252
}
253
254
runDeleteAction(context.notebookEditor, context.cell);
255
}
256
});
257
258
registerAction2(class ClearCellOutputsAction extends NotebookCellAction {
259
constructor() {
260
super({
261
id: CLEAR_CELL_OUTPUTS_COMMAND_ID,
262
title: localize('clearCellOutputs', 'Clear Cell Outputs'),
263
menu: [
264
{
265
id: MenuId.NotebookCellTitle,
266
when: ContextKeyExpr.and(NOTEBOOK_CELL_TYPE.isEqualTo('code'), executeNotebookCondition, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON.toNegated()),
267
order: CellToolbarOrder.ClearCellOutput,
268
group: CELL_TITLE_OUTPUT_GROUP_ID
269
},
270
{
271
id: MenuId.NotebookOutputToolbar,
272
when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_IS_FIRST_OUTPUT, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON)
273
},
274
],
275
keybinding: {
276
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey), NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
277
primary: KeyMod.Alt | KeyCode.Delete,
278
weight: KeybindingWeight.WorkbenchContrib
279
},
280
icon: icons.clearIcon
281
});
282
}
283
284
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
285
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
286
const editor = context.notebookEditor;
287
if (!editor.hasModel() || !editor.textModel.length) {
288
return;
289
}
290
291
const cell = context.cell;
292
const index = editor.textModel.cells.indexOf(cell.model);
293
294
if (index < 0) {
295
return;
296
}
297
298
const computeUndoRedo = !editor.isReadOnly;
299
editor.textModel.applyEdits([{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined, computeUndoRedo);
300
301
const runState = notebookExecutionStateService.getCellExecution(context.cell.uri)?.state;
302
if (runState !== NotebookCellExecutionState.Executing) {
303
context.notebookEditor.textModel.applyEdits([{
304
editType: CellEditType.PartialInternalMetadata, index, internalMetadata: {
305
runStartTime: null,
306
runStartTimeAdjustment: null,
307
runEndTime: null,
308
executionOrder: null,
309
lastRunSuccess: null
310
}
311
}], true, undefined, () => undefined, undefined, computeUndoRedo);
312
}
313
}
314
});
315
316
registerAction2(class ClearAllCellOutputsAction extends NotebookAction {
317
constructor() {
318
super({
319
id: CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID,
320
title: localize('clearAllCellsOutputs', 'Clear All Outputs'),
321
precondition: NOTEBOOK_HAS_OUTPUTS,
322
menu: [
323
{
324
id: MenuId.EditorTitle,
325
when: ContextKeyExpr.and(
326
NOTEBOOK_IS_ACTIVE_EDITOR,
327
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
328
),
329
group: 'navigation',
330
order: 0
331
},
332
{
333
id: MenuId.NotebookToolbar,
334
when: ContextKeyExpr.and(
335
executeNotebookCondition,
336
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
337
),
338
group: 'navigation/execute',
339
order: 10
340
}
341
],
342
icon: icons.clearIcon
343
});
344
}
345
346
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
347
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
348
const editor = context.notebookEditor;
349
if (!editor.hasModel() || !editor.textModel.length) {
350
return;
351
}
352
353
const computeUndoRedo = !editor.isReadOnly;
354
editor.textModel.applyEdits(
355
editor.textModel.cells.map((cell, index) => ({
356
editType: CellEditType.Output, index, outputs: []
357
})), true, undefined, () => undefined, undefined, computeUndoRedo);
358
359
const clearExecutionMetadataEdits = editor.textModel.cells.map((cell, index) => {
360
const runState = notebookExecutionStateService.getCellExecution(cell.uri)?.state;
361
if (runState !== NotebookCellExecutionState.Executing) {
362
return {
363
editType: CellEditType.PartialInternalMetadata, index, internalMetadata: {
364
runStartTime: null,
365
runStartTimeAdjustment: null,
366
runEndTime: null,
367
executionOrder: null,
368
lastRunSuccess: null
369
}
370
};
371
} else {
372
return undefined;
373
}
374
}).filter(edit => !!edit) as ICellEditOperation[];
375
if (clearExecutionMetadataEdits.length) {
376
context.notebookEditor.textModel.applyEdits(clearExecutionMetadataEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
377
}
378
379
const controller = editor.getContribution<NotebookInlineVariablesController>(NotebookInlineVariablesController.id);
380
controller.clearNotebookInlineDecorations();
381
}
382
});
383
384
interface ILanguagePickInput extends IQuickPickItem {
385
languageId: string;
386
description: string;
387
}
388
389
interface IChangeCellContext extends INotebookCellActionContext {
390
// TODO@rebornix : `cells`
391
// range: ICellRange;
392
language?: string;
393
}
394
395
registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellRange> {
396
constructor() {
397
super({
398
id: CHANGE_CELL_LANGUAGE,
399
title: localize('changeLanguage', 'Change Cell Language'),
400
keybinding: {
401
weight: KeybindingWeight.WorkbenchContrib,
402
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyM),
403
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE)
404
},
405
metadata: {
406
description: localize('changeLanguage', 'Change Cell Language'),
407
args: [
408
{
409
name: 'range',
410
description: 'The cell range',
411
schema: {
412
'type': 'object',
413
'required': ['start', 'end'],
414
'properties': {
415
'start': {
416
'type': 'number'
417
},
418
'end': {
419
'type': 'number'
420
}
421
}
422
}
423
},
424
{
425
name: 'language',
426
description: 'The target cell language',
427
schema: {
428
'type': 'string'
429
}
430
}
431
]
432
}
433
});
434
}
435
436
protected override getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): IChangeCellContext | undefined {
437
if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) {
438
return;
439
}
440
441
const language = additionalArgs.length && typeof additionalArgs[0] === 'string' ? additionalArgs[0] : undefined;
442
const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor);
443
444
if (!activeEditorContext || !activeEditorContext.notebookEditor.hasModel() || context.start >= activeEditorContext.notebookEditor.getLength()) {
445
return;
446
}
447
448
// TODO@rebornix, support multiple cells
449
return {
450
notebookEditor: activeEditorContext.notebookEditor,
451
cell: activeEditorContext.notebookEditor.cellAt(context.start)!,
452
language
453
};
454
}
455
456
457
async runWithContext(accessor: ServicesAccessor, context: IChangeCellContext): Promise<void> {
458
if (context.language) {
459
await this.setLanguage(context, context.language);
460
} else {
461
await this.showLanguagePicker(accessor, context);
462
}
463
}
464
465
private async showLanguagePicker(accessor: ServicesAccessor, context: IChangeCellContext) {
466
const topItems: ILanguagePickInput[] = [];
467
const mainItems: ILanguagePickInput[] = [];
468
469
const languageService = accessor.get(ILanguageService);
470
const modelService = accessor.get(IModelService);
471
const quickInputService = accessor.get(IQuickInputService);
472
const languageDetectionService = accessor.get(ILanguageDetectionService);
473
const kernelService = accessor.get(INotebookKernelService);
474
475
let languages = context.notebookEditor.activeKernel?.supportedLanguages;
476
if (!languages) {
477
const matchResult = kernelService.getMatchingKernel(context.notebookEditor.textModel);
478
const allSupportedLanguages = matchResult.all.flatMap(kernel => kernel.supportedLanguages);
479
languages = allSupportedLanguages.length > 0 ? allSupportedLanguages : languageService.getRegisteredLanguageIds();
480
}
481
482
const providerLanguages = new Set([
483
...languages,
484
'markdown'
485
]);
486
487
providerLanguages.forEach(languageId => {
488
let description: string;
489
if (context.cell.cellKind === CellKind.Markup ? (languageId === 'markdown') : (languageId === context.cell.language)) {
490
description = localize('languageDescription', "({0}) - Current Language", languageId);
491
} else {
492
description = localize('languageDescriptionConfigured', "({0})", languageId);
493
}
494
495
const languageName = languageService.getLanguageName(languageId);
496
if (!languageName) {
497
// Notebook has unrecognized language
498
return;
499
}
500
501
const item: ILanguagePickInput = {
502
label: languageName,
503
iconClasses: getIconClasses(modelService, languageService, this.getFakeResource(languageName, languageService)),
504
description,
505
languageId
506
};
507
508
if (languageId === 'markdown' || languageId === context.cell.language) {
509
topItems.push(item);
510
} else {
511
mainItems.push(item);
512
}
513
});
514
515
mainItems.sort((a, b) => {
516
return a.description.localeCompare(b.description);
517
});
518
519
// Offer to "Auto Detect"
520
const autoDetectMode: IQuickPickItem = {
521
label: localize('autoDetect', "Auto Detect")
522
};
523
524
const picks: QuickPickInput[] = [
525
autoDetectMode,
526
{ type: 'separator', label: localize('languagesPicks', "languages (identifier)") },
527
...topItems,
528
{ type: 'separator' },
529
...mainItems
530
];
531
532
const selection = await quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode") });
533
const languageId = selection === autoDetectMode
534
? await languageDetectionService.detectLanguage(context.cell.uri)
535
: (selection as ILanguagePickInput)?.languageId;
536
537
if (languageId) {
538
await this.setLanguage(context, languageId);
539
}
540
}
541
542
private async setLanguage(context: IChangeCellContext, languageId: string) {
543
await setCellToLanguage(languageId, context);
544
}
545
546
/**
547
* Copied from editorStatus.ts
548
*/
549
private getFakeResource(lang: string, languageService: ILanguageService): URI | undefined {
550
let fakeResource: URI | undefined;
551
552
const languageId = languageService.getLanguageIdByLanguageName(lang);
553
if (languageId) {
554
const extensions = languageService.getExtensions(languageId);
555
if (extensions.length) {
556
fakeResource = URI.file(extensions[0]);
557
} else {
558
const filenames = languageService.getFilenames(languageId);
559
if (filenames.length) {
560
fakeResource = URI.file(filenames[0]);
561
}
562
}
563
}
564
565
return fakeResource;
566
}
567
});
568
569
registerAction2(class DetectCellLanguageAction extends NotebookCellAction {
570
constructor() {
571
super({
572
id: DETECT_CELL_LANGUAGE,
573
title: localize2('detectLanguage', "Accept Detected Language for Cell"),
574
f1: true,
575
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
576
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
577
});
578
}
579
580
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
581
const languageDetectionService = accessor.get(ILanguageDetectionService);
582
const notificationService = accessor.get(INotificationService);
583
const kernelService = accessor.get(INotebookKernelService);
584
const kernel = kernelService.getSelectedOrSuggestedKernel(context.notebookEditor.textModel);
585
const providerLanguages = [...kernel?.supportedLanguages ?? []];
586
providerLanguages.push('markdown');
587
const detection = await languageDetectionService.detectLanguage(context.cell.uri, providerLanguages);
588
if (detection) {
589
setCellToLanguage(detection, context);
590
} else {
591
notificationService.warn(localize('noDetection', "Unable to detect cell language"));
592
}
593
}
594
});
595
596
async function setCellToLanguage(languageId: string, context: IChangeCellContext) {
597
if (languageId === 'markdown' && context.cell?.language !== 'markdown') {
598
const idx = context.notebookEditor.getCellIndex(context.cell);
599
await changeCellToKind(CellKind.Markup, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, 'markdown', Mimes.markdown);
600
const newCell = context.notebookEditor.cellAt(idx);
601
602
if (newCell) {
603
await context.notebookEditor.focusNotebookCell(newCell, 'editor');
604
}
605
} else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markup) {
606
await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, languageId);
607
} else {
608
const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model);
609
context.notebookEditor.textModel.applyEdits(
610
[{ editType: CellEditType.CellLanguage, index, language: languageId }],
611
true, undefined, () => undefined, undefined, !context.notebookEditor.isReadOnly
612
);
613
}
614
}
615
616
registerAction2(class SelectNotebookIndentation extends NotebookAction {
617
constructor() {
618
super({
619
id: SELECT_NOTEBOOK_INDENTATION_ID,
620
title: localize2('selectNotebookIndentation', 'Select Indentation'),
621
f1: true,
622
precondition: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
623
});
624
}
625
626
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
627
await this.showNotebookIndentationPicker(accessor, context);
628
}
629
630
private async showNotebookIndentationPicker(accessor: ServicesAccessor, context: INotebookActionContext) {
631
const quickInputService = accessor.get(IQuickInputService);
632
const editorService = accessor.get(IEditorService);
633
const instantiationService = accessor.get(IInstantiationService);
634
635
const activeNotebook = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
636
if (!activeNotebook || activeNotebook.isDisposed) {
637
return quickInputService.pick([{ label: localize('noNotebookEditor', "No notebook editor active at this time") }]);
638
}
639
640
if (activeNotebook.isReadOnly) {
641
return quickInputService.pick([{ label: localize('noWritableCodeEditor', "The active notebook editor is read-only.") }]);
642
}
643
644
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
645
new NotebookIndentUsingTabs(), // indent using tabs
646
new NotebookIndentUsingSpaces(), // indent using spaces
647
new NotebookChangeTabDisplaySize(), // change tab size
648
new NotebookIndentationToTabsAction(), // convert indentation to tabs
649
new NotebookIndentationToSpacesAction() // convert indentation to spaces
650
].map(item => {
651
return {
652
id: item.desc.id,
653
label: item.desc.title.toString(),
654
run: () => {
655
instantiationService.invokeFunction(item.run);
656
}
657
};
658
});
659
660
picks.splice(3, 0, { type: 'separator', label: localize('indentConvert', "convert file") });
661
picks.unshift({ type: 'separator', label: localize('indentView', "change view") });
662
663
const action = await quickInputService.pick(picks, { placeHolder: localize('pickAction', "Select Action"), matchOnDetail: true });
664
if (!action) {
665
return;
666
}
667
action.run();
668
context.notebookEditor.focus();
669
return;
670
}
671
});
672
673
registerAction2(class CommentSelectedCellsAction extends NotebookMultiCellAction {
674
constructor() {
675
super({
676
id: COMMENT_SELECTED_CELLS_ID,
677
title: localize('commentSelectedCells', "Comment Selected Cells"),
678
keybinding: {
679
when: ContextKeyExpr.and(
680
NOTEBOOK_EDITOR_FOCUSED,
681
NOTEBOOK_EDITOR_EDITABLE,
682
ContextKeyExpr.not(InputFocusedContextKey),
683
),
684
primary: KeyMod.CtrlCmd | KeyCode.Slash,
685
weight: KeybindingWeight.WorkbenchContrib
686
}
687
});
688
}
689
690
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext): Promise<void> {
691
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
692
693
context.selectedCells.forEach(async cellViewModel => {
694
const textModel = await cellViewModel.resolveTextModel();
695
696
const commentsOptions = cellViewModel.commentOptions;
697
const cellCommentCommand = new LineCommentCommand(
698
languageConfigurationService,
699
new Selection(1, 1, textModel.getLineCount(), textModel.getLineMaxColumn(textModel.getLineCount())), // comment the entire cell
700
textModel.getOptions().tabSize,
701
Type.Toggle,
702
commentsOptions.insertSpace ?? true,
703
commentsOptions.ignoreEmptyLines ?? true,
704
false
705
);
706
707
// store any selections that are in the cell, allows them to be shifted by comments and preserved
708
const cellEditorSelections = cellViewModel.getSelections();
709
const initialTrackedRangesIDs: string[] = cellEditorSelections.map(selection => {
710
return textModel._setTrackedRange(null, selection, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
711
});
712
713
CommandExecutor.executeCommands(textModel, cellEditorSelections, [cellCommentCommand]);
714
715
const newTrackedSelections = initialTrackedRangesIDs.map(i => {
716
return textModel._getTrackedRange(i);
717
}).filter(r => !!r).map((range,) => {
718
return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
719
});
720
cellViewModel.setSelections(newTrackedSelections ?? []);
721
}); // end of cells forEach
722
}
723
724
});
725
726