Skip to content

Commit 5d91776

Browse files
authored
Merge pull request #318 from ryantam626/rt.editor-auto-save
Editor auto save
2 parents daefb6f + 5a57181 commit 5d91776

File tree

7 files changed

+8595
-11455
lines changed

7 files changed

+8595
-11455
lines changed

Taskfile.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ tasks:
1212
dev:build:
1313
desc: Build docker image for dev
1414
cmds:
15-
- docker compose -f docker-compose.dev.yml build
15+
- BUILDKIT_PROGRESS=plain docker compose -f docker-compose.dev.yml build
1616

1717
dev:up:
1818
desc: Spin up the docker compose stack for dev
1919
cmds:
20+
- docker compose -f docker-compose.dev.yml up -d
21+
- docker exec -it jupyterlab_code_formatter-dev-1 jlpm install
2022
- docker compose -f docker-compose.dev.yml up
2123

2224
dev:down:

dev.Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ RUN --mount=type=cache,target=/root/.cache/pip \
6565
pip install -e ".[test]"
6666
RUN jupyter labextension develop . --overwrite
6767
RUN jupyter server extension enable jupyterlab_code_formatter
68+
RUN jlpm install
6869
RUN jlpm build

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,18 @@
5656
"watch:labextension": "jupyter labextension watch ."
5757
},
5858
"dependencies": {
59-
"@jupyterlab/application": "^3.6.0 || ~4.0.0",
59+
"@jupyterlab/application": "^3.6.0",
6060
"@jupyterlab/coreutils": "^5.1.0",
61-
"@jupyterlab/fileeditor": "^3.6.0 || ~4.0.0",
62-
"@jupyterlab/mainmenu": "^3.6.0 || ~4.0.0",
61+
"@jupyterlab/fileeditor": "^3.6.0",
62+
"@jupyterlab/mainmenu": "^3.6.0",
6363
"@jupyterlab/services": "^6.1.0",
64-
"@jupyterlab/settingregistry": "^3.6.0 || ~4.0.0"
64+
"@jupyterlab/settingregistry": "^3.6.0"
6565
},
6666
"devDependencies": {
6767
"@babel/core": "^7.0.0",
6868
"@babel/preset-env": "^7.0.0",
69-
"@jupyterlab/builder": "^3.6.0 || ~4.0.0",
70-
"@jupyterlab/testutils": "^3.6.0 || ~4.0.0",
69+
"@jupyterlab/builder": "^3.6.0",
70+
"@jupyterlab/testutils": "^3.6.0",
7171
"@types/jest": "^26.0.0",
7272
"@typescript-eslint/eslint-plugin": "^4.8.1",
7373
"@typescript-eslint/parser": "^4.8.1",

src/formatter.ts

+118-27
Original file line numberDiff line numberDiff line change
@@ -268,39 +268,130 @@ export class JupyterlabFileEditorCodeFormatter extends JupyterlabCodeFormatter {
268268
}
269269

270270
formatAction(config: any, formatter: string) {
271+
return this.formatEditor(config, { saving: false }, formatter);
272+
}
273+
274+
public async formatEditor(
275+
config: any,
276+
context: Context,
277+
formatter?: string,
278+
) {
271279
if (this.working) {
272280
return;
273281
}
274-
const editorWidget = this.editorTracker.currentWidget;
275-
this.working = true;
276-
const editor = editorWidget!.content.editor;
277-
const code = editor.model.sharedModel.source;
278-
this.formatCode(
279-
[code],
280-
formatter,
281-
config[formatter],
282-
false,
283-
config.cacheFormatters
284-
)
285-
.then(data => {
286-
if (data.code[0].error) {
287-
void showErrorMessage(
288-
'Jupyterlab Code Formatter Error',
289-
data.code[0].error
282+
try {
283+
this.working = true;
284+
285+
const formattersToUse = await this.getFormattersToUse(config, formatter);
286+
await this.applyFormatters(
287+
formattersToUse,
288+
config,
289+
context
290+
);
291+
} catch (error) {
292+
await showErrorMessage('Jupyterlab Code Formatter Error', error);
293+
}
294+
this.working = false;
295+
}
296+
297+
private getEditorType() {
298+
if (!this.editorTracker.currentWidget) {
299+
return null;
300+
}
301+
302+
const mimeType =
303+
this.editorTracker.currentWidget.content.model!.mimeType;
304+
305+
const mimeTypes = new Map([
306+
['text/x-python', 'python'],
307+
['application/x-rsrc', 'r'],
308+
['application/x-scala', 'scala'],
309+
['application/x-rustsrc', 'rust'],
310+
['application/x-c++src', 'cpp'], // Not sure that this is right, whatever.
311+
// Add more MIME types and corresponding programming languages here
312+
]);
313+
314+
return mimeTypes.get(mimeType);
315+
}
316+
317+
private getDefaultFormatters(config: any): Array<string> {
318+
const editorType = this.getEditorType();
319+
if (editorType) {
320+
const defaultFormatter =
321+
config.preferences.default_formatter[editorType];
322+
if (defaultFormatter instanceof Array) {
323+
return defaultFormatter;
324+
} else if (defaultFormatter !== undefined) {
325+
return [defaultFormatter];
326+
}
327+
}
328+
return [];
329+
}
330+
331+
private async getFormattersToUse(config: any, formatter?: string) {
332+
const defaultFormatters = this.getDefaultFormatters(config);
333+
const formattersToUse =
334+
formatter !== undefined ? [formatter] : defaultFormatters;
335+
336+
if (formattersToUse.length === 0) {
337+
await showErrorMessage(
338+
'Jupyterlab Code Formatter Error',
339+
'Unable to find default formatters to use, please file an issue on GitHub.'
340+
);
341+
}
342+
343+
return formattersToUse;
344+
}
345+
346+
private async applyFormatters(
347+
formattersToUse: string[],
348+
config: any,
349+
context: Context
350+
) {
351+
for (const formatterToUse of formattersToUse) {
352+
if (formatterToUse === 'noop' || formatterToUse === 'skip') {
353+
continue;
354+
}
355+
const showErrors =
356+
!(config.suppressFormatterErrors ?? false) &&
357+
!(
358+
(config.suppressFormatterErrorsIFFAutoFormatOnSave ?? false) &&
359+
context.saving
290360
);
291-
this.working = false;
292-
return;
293-
}
294-
this.editorTracker.currentWidget!.content.editor.model.sharedModel.source =
295-
data.code[0].code;
296-
this.working = false;
297-
})
298-
.catch(error => {
299-
this.working = false;
300-
void showErrorMessage('Jupyterlab Code Formatter Error', error);
301-
});
361+
362+
const editorWidget = this.editorTracker.currentWidget;
363+
this.working = true;
364+
const editor = editorWidget!.content.editor;
365+
const code = editor.model.sharedModel.source;
366+
this.formatCode(
367+
[code],
368+
formatterToUse,
369+
config[formatterToUse],
370+
false,
371+
config.cacheFormatters
372+
)
373+
.then(data => {
374+
if (data.code[0].error) {
375+
if (showErrors) {
376+
void showErrorMessage(
377+
'Jupyterlab Code Formatter Error',
378+
data.code[0].error
379+
);
380+
}
381+
this.working = false;
382+
return;
383+
}
384+
this.editorTracker.currentWidget!.content.editor.model.sharedModel.source =
385+
data.code[0].code;
386+
this.working = false;
387+
})
388+
.catch(error => {
389+
void showErrorMessage('Jupyterlab Code Formatter Error', error);
390+
});
391+
}
302392
}
303393

394+
304395
applicable(formatter: string, currentWidget: Widget) {
305396
const currentEditorWidget = this.editorTracker.currentWidget;
306397
// TODO: Handle showing just the correct formatter for the language later

src/index.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DocumentRegistry } from '@jupyterlab/docregistry';
1+
import { DocumentRegistry, DocumentWidget, DocumentModel } from '@jupyterlab/docregistry';
22
import {
33
INotebookModel,
44
INotebookTracker,
@@ -113,8 +113,33 @@ class JupyterLabCodeFormatter
113113
}
114114
}
115115

116+
private createNewEditor(widget: DocumentWidget, context: DocumentRegistry.IContext<DocumentModel>): IDisposable {
117+
// Connect to save(State) signal, to be able to detect document save event
118+
context.saveState.connect(this.onSaveEditor, this);
119+
// Return an empty disposable, because we don't create any object
120+
return new DisposableDelegate(() => { });
121+
}
122+
123+
private async onSaveEditor(
124+
context: DocumentRegistry.IContext<DocumentModel>,
125+
state: DocumentRegistry.SaveState
126+
) {
127+
if (state === 'started' && this.config.formatOnSave) {
128+
this.fileEditorCodeFormatter.formatEditor(
129+
this.config,
130+
{saving: true},
131+
undefined,
132+
)
133+
}
134+
}
135+
116136
private setupWidgetExtension() {
117137
this.app.docRegistry.addWidgetExtension('Notebook', this);
138+
this.app.docRegistry.addWidgetExtension('editor', {
139+
createNew: (widget: DocumentWidget, context: DocumentRegistry.IContext<DocumentModel>): IDisposable => {
140+
return this.createNewEditor(widget, context);
141+
}
142+
});
118143
}
119144

120145
private setupContextMenu() {

tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"strict": true,
1919
"strictNullChecks": true,
2020
"target": "ES2018",
21-
"types": ["jest"]
21+
"types": ["jest"],
22+
"skipLibCheck": true
2223
},
2324
"include": ["src/*"]
2425
}

0 commit comments

Comments
 (0)