diff --git a/src/ScriptEditor/ui/Options.ts b/src/ScriptEditor/ui/Options.ts index d7e43b83d..bab7d3917 100644 --- a/src/ScriptEditor/ui/Options.ts +++ b/src/ScriptEditor/ui/Options.ts @@ -16,4 +16,5 @@ export interface Options { wordWrap: WordWrapOptions; cursorStyle: CursorStyle; cursorBlinking: CursorBlinking; + beautifyOnSave: boolean; } diff --git a/src/ScriptEditor/ui/OptionsModal.tsx b/src/ScriptEditor/ui/OptionsModal.tsx index 27db47897..cc48253b6 100644 --- a/src/ScriptEditor/ui/OptionsModal.tsx +++ b/src/ScriptEditor/ui/OptionsModal.tsx @@ -142,6 +142,14 @@ export function OptionsModal(props: OptionsModalProps): ReactElement { ))} + +
+ Run Beautify on Save: + props.onOptionChange("beautifyOnSave", e.target.checked)} + checked={props.options.beautifyOnSave} + /> +
); } diff --git a/src/ScriptEditor/ui/ScriptEditorContext.tsx b/src/ScriptEditor/ui/ScriptEditorContext.tsx index 92a290e5d..cc4c0f92a 100644 --- a/src/ScriptEditor/ui/ScriptEditorContext.tsx +++ b/src/ScriptEditor/ui/ScriptEditorContext.tsx @@ -91,6 +91,7 @@ export function ScriptEditorContextProvider({ children }: { children: React.Reac wordWrap: Settings.MonacoWordWrap, cursorStyle: Settings.MonacoCursorStyle, cursorBlinking: Settings.MonacoCursorBlinking, + beautifyOnSave: Settings.MonacoBeautifyOnSave, }); function saveOptions(options: Options) { @@ -105,6 +106,7 @@ export function ScriptEditorContextProvider({ children }: { children: React.Reac Settings.MonacoCursorStyle = options.cursorStyle; Settings.MonacoCursorBlinking = options.cursorBlinking; Settings.MonacoWordWrap = options.wordWrap; + Settings.MonacoBeautifyOnSave = options.beautifyOnSave; } return ( diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 18a59a348..9c6560443 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -176,15 +176,26 @@ function Root(props: IProps): React.ReactElement { reloadModelOfCurrentScript(); } - const { showRAMError, updateRAM, startUpdatingRAM, finishUpdatingRAM } = useScriptEditorContext(); + const { options, showRAMError, updateRAM, startUpdatingRAM, finishUpdatingRAM } = useScriptEditorContext(); let decorations: monaco.editor.IEditorDecorationsCollection | undefined; - const save = useCallback(() => { + const beautify = useCallback(async (): Promise => { + const action = editorRef.current?.getAction("editor.action.formatDocument"); + if (action == null) { + return; + } + return action.run().catch((error) => console.error(error)); + }, []); + + const save = useCallback(async () => { if (currentScript === null) { console.error("currentScript is null when it shouldn't be. Unable to save script"); return; } + + const preSave = options.beautifyOnSave ? beautify : () => Promise.resolve(); + // this is duplicate code with saving later. if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalEditScript) { //Make sure filename + code properly follow tutorial @@ -200,6 +211,7 @@ function Root(props: IProps): React.ReactElement { } //Save the script + await preSave(); saveScript(currentScript); Router.toPage(Page.Terminal); @@ -207,11 +219,12 @@ function Root(props: IProps): React.ReactElement { return; } + await preSave(); saveScript(currentScript); rerender(); - }, [rerender]); + }, [rerender, options.beautifyOnSave, beautify]); - const run = useCallback(() => { + const run = useCallback(async () => { if (currentScript === null) { return; } @@ -227,7 +240,7 @@ function Root(props: IProps): React.ReactElement { } // Always save before doing anything else. - save(); + await save(); const result = createRunningScriptInstance(server, currentScript.path, null, 1, []); if (!result.success) { @@ -238,7 +251,7 @@ function Root(props: IProps): React.ReactElement { }, [save]); useEffect(() => { - function keydown(event: KeyboardEvent): void { + async function keydown(event: KeyboardEvent) { if (Settings.DisableHotkeys) { return; } @@ -246,7 +259,7 @@ function Root(props: IProps): React.ReactElement { if (keyBindingTypes.has(ScriptEditorAction.Save)) { event.preventDefault(); event.stopPropagation(); - save(); + await save(); } if (keyBindingTypes.has(ScriptEditorAction.GoToTerminal)) { event.preventDefault(); @@ -254,11 +267,14 @@ function Root(props: IProps): React.ReactElement { } if (keyBindingTypes.has(ScriptEditorAction.Run)) { event.preventDefault(); - run(); + await run(); } } - document.addEventListener("keydown", keydown); - return () => document.removeEventListener("keydown", keydown); + const listener = (event: KeyboardEvent) => { + keydown(event).catch((error) => console.error(error)); + }; + document.addEventListener("keydown", listener); + return () => document.removeEventListener("keydown", listener); }, [save, run]); function infLoop(ast: AST, code: string): void { @@ -598,7 +614,7 @@ function Root(props: IProps): React.ReactElement { {statusBarRef.current} - + {!currentScript && } diff --git a/src/ScriptEditor/ui/Toolbar.tsx b/src/ScriptEditor/ui/Toolbar.tsx index 36a49e95f..bc7dac49c 100644 --- a/src/ScriptEditor/ui/Toolbar.tsx +++ b/src/ScriptEditor/ui/Toolbar.tsx @@ -31,21 +31,15 @@ type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; interface IProps { editor: IStandaloneCodeEditor | null; - onSave: () => void; - onRun: () => void; + onSave: () => Promise; + onRun: () => Promise; + onBeautify: () => Promise; } -export function Toolbar({ editor, onSave, onRun }: IProps) { +export function Toolbar({ editor, onSave, onRun, onBeautify }: IProps) { const [ramInfoOpen, { on: openRAMInfo, off: closeRAMInfo }] = useBoolean(false); const [optionsOpen, { on: openOptions, off: closeOptions }] = useBoolean(false); - function beautify(): void { - editor - ?.getAction("editor.action.formatDocument") - ?.run() - .catch((error) => console.error(error)); - } - const { ram, ramEntries, isUpdatingRAM, options, saveOptions } = useScriptEditorContext(); const onOptionChange: OptionsModalProps["onOptionChange"] = (option, value) => { @@ -68,12 +62,24 @@ export function Toolbar({ editor, onSave, onRun }: IProps) { - + - + - diff --git a/src/ScriptEditor/ui/useVimEditor.tsx b/src/ScriptEditor/ui/useVimEditor.tsx index 27f8d4303..0df27d21e 100644 --- a/src/ScriptEditor/ui/useVimEditor.tsx +++ b/src/ScriptEditor/ui/useVimEditor.tsx @@ -13,7 +13,7 @@ interface IProps { editor: IStandaloneCodeEditor | null; onOpenNextTab: (step: number) => void; onOpenPreviousTab: (step: number) => void; - onSave: () => void; + onSave: () => Promise; } export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, onSave }: IProps) { @@ -32,7 +32,7 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on setVimEditor(MonacoVim.initVimMode(editor, statusBarRef, StatusBar, rerender)); MonacoVim.VimMode.Vim.defineEx("write", "w", function () { // your own implementation on what you want to do when :w is pressed - actionsRef.current.save(); + actionsRef.current.save().catch((error) => console.error(error)); }); MonacoVim.VimMode.Vim.defineEx("quit", "q", function () { Router.toPage(Page.Terminal); @@ -43,8 +43,12 @@ export function useVimEditor({ editor, vim, onOpenNextTab, onOpenPreviousTab, on MonacoVim.VimMode.Vim.mapCommand("@", "", "", null, { context: "normal" }); const saveNQuit = (): void => { - actionsRef.current.save(); - Router.toPage(Page.Terminal); + actionsRef.current + .save() + .then(() => { + Router.toPage(Page.Terminal); + }) + .catch((error) => console.error(error)); }; // "wqriteandquit" & "xriteandquit" are not typos, prefix must be found in full string MonacoVim.VimMode.Vim.defineEx("wqriteandquit", "wq", saveNQuit); diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 8f1cd4924..e76a68e6b 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -176,6 +176,8 @@ export const Settings = { MonacoDefaultToVim: false, /** Word wrap setting for Script Editor. */ MonacoWordWrap: "off" as WordWrapOptions, + /** Whether to run Beautify code formatter on save */ + MonacoBeautifyOnSave: false, /** Control the cursor style*/ MonacoCursorStyle: "line" as CursorStyle, /** Control the cursor animation style */