diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 5886c4769..43f8f3fde 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -24,6 +24,10 @@ import { initTheme, activateTheme, theme } from './themes.mjs'; import { sliderPlugin, updateSliderWidgets } from './slider.mjs'; import { widgetPlugin, updateWidgets } from './widget.mjs'; import { persistentAtom } from '@nanostores/persistent'; +import * as prettier from 'prettier/standalone'; +import typescriptPlugin from 'prettier/plugins/typescript'; +import estreePlugin from "prettier/plugins/estree"; +import { EditorSelection } from '@codemirror/state'; const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), @@ -102,6 +106,43 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo preventDefault: true, run: () => onStop?.(), }, + { + key: 'Ctrl-\\', + preventDefault: true, + run: async (editorView) => { + const currentState = editorView.state.doc.toString(); + + // Prettier insists on consistent quotes, but in Strudel double quotes are interpreted + // as patterns and single quotes are for everything else, so a consistent setting won't work. + // It's a great formatter though, so as a workaround it works to put "// prettier-ignore" comments + // before the single quoted stuff to preserve it, but this is a pain to make users to, so an extra + // hack is to take the preformatted code, insert these comments behind the scenes, format, then + // remove them before setting the editor state. + const preFormat = currentState.split('\n').map( + line => line.match(/\'.*\'/) != null ? "// prettier-ignore\n" + line : line + ).join('\n'); + console.log(preFormat); + const formattedState = (await prettier.format( + preFormat, + { + parser: 'typescript', + plugins: [typescriptPlugin, estreePlugin], + semi: false, + } + )) + .replace(/.*\/\/ prettier-ignore.*\n/g, ''); + + editorView.dispatch({ + changes: { from: 0, to: editorView.state.doc.length, insert: formattedState}, + selection: EditorSelection.single( + // keep cursor close to the original position, but also keep it within the bounds + // of the formatted document + Math.min(editorView.state.selection.main.to, formattedState.length) + ), + scrollIntoView: true + }); + } + } /* { key: 'Ctrl-Shift-.', run: () => (onPanic ? onPanic() : onStop?.()), diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 5ef5e73b0..5c22c1d3d 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -49,7 +49,8 @@ "@strudel/transpiler": "workspace:*", "@uiw/codemirror-themes": "^4.21.21", "@uiw/codemirror-themes-all": "^4.21.21", - "nanostores": "^0.9.5" + "nanostores": "^0.9.5", + "prettier": "^3.3.3" }, "devDependencies": { "vite": "^5.0.10" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a18f06e73..ebcf7a8c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -212,6 +212,9 @@ importers: nanostores: specifier: ^0.9.5 version: 0.9.5 + prettier: + specifier: ^3.3.3 + version: 3.3.3 devDependencies: vite: specifier: ^5.0.10 @@ -7740,6 +7743,7 @@ packages: workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained workbox-navigation-preload@7.0.0: resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==}