Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.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 * as nls from '../../../../../nls.js';
7
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
8
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
9
import { IBulkEditService, ResourceTextEdit } from '../../../../../editor/browser/services/bulkEditService.js';
10
import { Range } from '../../../../../editor/common/core/range.js';
11
import { ITextModel } from '../../../../../editor/common/model.js';
12
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
13
import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js';
14
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
15
import { ILogService } from '../../../../../platform/log/common/log.js';
16
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';
17
import { INotebookEditorService } from '../services/notebookEditorService.js';
18
import { NotebookSetting } from '../../common/notebookCommon.js';
19
import { isNotebookEditorInput } from '../../common/notebookEditorInput.js';
20
import { IEditorService } from '../../../../services/editor/common/editorService.js';
21
22
export class NotebookIndentUsingTabs extends Action2 {
23
public static readonly ID = 'notebook.action.indentUsingTabs';
24
25
constructor() {
26
super({
27
id: NotebookIndentUsingTabs.ID,
28
title: nls.localize('indentUsingTabs', "Indent Using Tabs"),
29
precondition: undefined,
30
});
31
}
32
33
override run(accessor: ServicesAccessor, ...args: any[]): void {
34
changeNotebookIndentation(accessor, false, false);
35
}
36
}
37
38
export class NotebookIndentUsingSpaces extends Action2 {
39
public static readonly ID = 'notebook.action.indentUsingSpaces';
40
41
constructor() {
42
super({
43
id: NotebookIndentUsingSpaces.ID,
44
title: nls.localize('indentUsingSpaces', "Indent Using Spaces"),
45
precondition: undefined,
46
});
47
}
48
49
override run(accessor: ServicesAccessor, ...args: any[]): void {
50
changeNotebookIndentation(accessor, true, false);
51
}
52
}
53
54
export class NotebookChangeTabDisplaySize extends Action2 {
55
public static readonly ID = 'notebook.action.changeTabDisplaySize';
56
57
constructor() {
58
super({
59
id: NotebookChangeTabDisplaySize.ID,
60
title: nls.localize('changeTabDisplaySize', "Change Tab Display Size"),
61
precondition: undefined,
62
});
63
}
64
65
override run(accessor: ServicesAccessor, ...args: any[]): void {
66
changeNotebookIndentation(accessor, true, true);
67
}
68
}
69
70
export class NotebookIndentationToSpacesAction extends Action2 {
71
public static readonly ID = 'notebook.action.convertIndentationToSpaces';
72
73
constructor() {
74
super({
75
id: NotebookIndentationToSpacesAction.ID,
76
title: nls.localize('convertIndentationToSpaces', "Convert Indentation to Spaces"),
77
precondition: undefined,
78
});
79
}
80
81
override run(accessor: ServicesAccessor, ...args: any[]): void {
82
convertNotebookIndentation(accessor, true);
83
}
84
}
85
86
export class NotebookIndentationToTabsAction extends Action2 {
87
public static readonly ID = 'notebook.action.convertIndentationToTabs';
88
89
constructor() {
90
super({
91
id: NotebookIndentationToTabsAction.ID,
92
title: nls.localize('convertIndentationToTabs', "Convert Indentation to Tabs"),
93
precondition: undefined,
94
});
95
}
96
97
override run(accessor: ServicesAccessor, ...args: any[]): void {
98
convertNotebookIndentation(accessor, false);
99
}
100
}
101
102
function changeNotebookIndentation(accessor: ServicesAccessor, insertSpaces: boolean, displaySizeOnly: boolean) {
103
const editorService = accessor.get(IEditorService);
104
const configurationService = accessor.get(IConfigurationService);
105
const notebookEditorService = accessor.get(INotebookEditorService);
106
const quickInputService = accessor.get(IQuickInputService);
107
108
// keep this check here to pop on non-notebook actions
109
const activeInput = editorService.activeEditorPane?.input;
110
const isNotebook = isNotebookEditorInput(activeInput);
111
if (!isNotebook) {
112
return;
113
}
114
115
// get notebook editor to access all codeEditors
116
const notebookEditor = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value;
117
if (!notebookEditor) {
118
return;
119
}
120
121
const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({
122
id: n.toString(),
123
label: n.toString(),
124
}));
125
126
// store the initial values of the configuration
127
const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any;
128
const initialInsertSpaces = initialConfig['editor.insertSpaces'];
129
// remove the initial values from the configuration
130
delete initialConfig['editor.indentSize'];
131
delete initialConfig['editor.tabSize'];
132
delete initialConfig['editor.insertSpaces'];
133
134
setTimeout(() => {
135
quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File") }).then(pick => {
136
if (pick) {
137
const pickedVal = parseInt(pick.label, 10);
138
if (displaySizeOnly) {
139
configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, {
140
...initialConfig,
141
'editor.tabSize': pickedVal,
142
'editor.indentSize': pickedVal,
143
'editor.insertSpaces': initialInsertSpaces
144
});
145
} else {
146
configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, {
147
...initialConfig,
148
'editor.tabSize': pickedVal,
149
'editor.indentSize': pickedVal,
150
'editor.insertSpaces': insertSpaces
151
});
152
}
153
154
}
155
});
156
}, 50/* quick input is sensitive to being opened so soon after another */);
157
}
158
159
function convertNotebookIndentation(accessor: ServicesAccessor, tabsToSpaces: boolean): void {
160
const editorService = accessor.get(IEditorService);
161
const configurationService = accessor.get(IConfigurationService);
162
const logService = accessor.get(ILogService);
163
const textModelService = accessor.get(ITextModelService);
164
const notebookEditorService = accessor.get(INotebookEditorService);
165
const bulkEditService = accessor.get(IBulkEditService);
166
167
// keep this check here to pop on non-notebook
168
const activeInput = editorService.activeEditorPane?.input;
169
const isNotebook = isNotebookEditorInput(activeInput);
170
if (!isNotebook) {
171
return;
172
}
173
174
// get notebook editor to access all codeEditors
175
const notebookTextModel = notebookEditorService.retrieveExistingWidgetFromURI(activeInput.resource)?.value?.textModel;
176
if (!notebookTextModel) {
177
return;
178
}
179
180
const disposable = new DisposableStore();
181
try {
182
Promise.all(notebookTextModel.cells.map(async cell => {
183
const ref = await textModelService.createModelReference(cell.uri);
184
disposable.add(ref);
185
const textEditorModel = ref.object.textEditorModel;
186
187
const modelOpts = cell.textModel?.getOptions();
188
if (!modelOpts) {
189
return;
190
}
191
192
const edits = getIndentationEditOperations(textEditorModel, modelOpts.tabSize, tabsToSpaces);
193
194
bulkEditService.apply(edits, { label: nls.localize('convertIndentation', "Convert Indentation"), code: 'undoredo.convertIndentation', });
195
196
})).then(() => {
197
// store the initial values of the configuration
198
const initialConfig = configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations) as any;
199
const initialIndentSize = initialConfig['editor.indentSize'];
200
const initialTabSize = initialConfig['editor.tabSize'];
201
// remove the initial values from the configuration
202
delete initialConfig['editor.indentSize'];
203
delete initialConfig['editor.tabSize'];
204
delete initialConfig['editor.insertSpaces'];
205
206
configurationService.updateValue(NotebookSetting.cellEditorOptionsCustomizations, {
207
...initialConfig,
208
'editor.tabSize': initialTabSize,
209
'editor.indentSize': initialIndentSize,
210
'editor.insertSpaces': tabsToSpaces
211
});
212
disposable.dispose();
213
});
214
} catch {
215
logService.error('Failed to convert indentation to spaces for notebook cells.');
216
}
217
}
218
219
function getIndentationEditOperations(model: ITextModel, tabSize: number, tabsToSpaces: boolean): ResourceTextEdit[] {
220
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
221
// Model is empty
222
return [];
223
}
224
225
let spaces = '';
226
for (let i = 0; i < tabSize; i++) {
227
spaces += ' ';
228
}
229
230
const spacesRegExp = new RegExp(spaces, 'gi');
231
232
const edits: ResourceTextEdit[] = [];
233
for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
234
let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
235
if (lastIndentationColumn === 0) {
236
lastIndentationColumn = model.getLineMaxColumn(lineNumber);
237
}
238
239
if (lastIndentationColumn === 1) {
240
continue;
241
}
242
243
const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn);
244
const originalIndentation = model.getValueInRange(originalIndentationRange);
245
const newIndentation = (
246
tabsToSpaces
247
? originalIndentation.replace(/\t/ig, spaces)
248
: originalIndentation.replace(spacesRegExp, '\t')
249
);
250
edits.push(new ResourceTextEdit(model.uri, { range: originalIndentationRange, text: newIndentation }));
251
}
252
return edits;
253
}
254
255
registerAction2(NotebookIndentUsingSpaces);
256
registerAction2(NotebookIndentUsingTabs);
257
registerAction2(NotebookChangeTabDisplaySize);
258
registerAction2(NotebookIndentationToSpacesAction);
259
registerAction2(NotebookIndentationToTabsAction);
260
261