From 4bcaf979cad604306faea2741e9d9e26e7fe5a8d Mon Sep 17 00:00:00 2001 From: Nathan Wang Date: Mon, 18 Dec 2023 21:31:32 -0500 Subject: [PATCH] get code running working --- pages/[id].tsx | 16 ++++---- src/atoms/workspace.ts | 41 +++++++++++++++++++ .../CodeInterface/CodeInterface.tsx | 23 ++++++++++- .../JudgeInterface/JudgeInterface.tsx | 11 +++-- src/components/NavBar/FileMenu.tsx | 30 +++++++++++--- src/components/Workspace/Workspace.tsx | 5 +++ .../CodemirrorEditor/CodemirrorEditor.tsx | 1 + .../MonacoEditor/monaco-editor-types.ts | 9 +++- 8 files changed, 118 insertions(+), 18 deletions(-) diff --git a/pages/[id].tsx b/pages/[id].tsx index 4f2ad21a..7ecb0fff 100644 --- a/pages/[id].tsx +++ b/pages/[id].tsx @@ -8,9 +8,11 @@ import { submitToJudge } from '../src/scripts/judge'; import { useAtom, useAtomValue } from 'jotai'; import { useUpdateAtom } from 'jotai/utils'; import { + inputEditorValueAtom, inputMonacoEditorAtom, layoutEditorsAtom, loadingAtom, + mainEditorValueAtom, mainMonacoEditorAtom, } from '../src/atoms/workspace'; import { @@ -46,8 +48,8 @@ function EditorPage() { const isDesktop = useMediaQuery('(min-width: 1024px)', true); const layoutEditors = useUpdateAtom(layoutEditorsAtom); const [mobileActiveTab, setMobileActiveTab] = useAtom(mobileActiveTabAtom); - const inputEditor = useAtomValue(inputMonacoEditorAtom); - const mainMonacoEditor = useAtomValue(mainMonacoEditorAtom); + const getMainEditorValue = useAtomValue(mainEditorValueAtom); + const getInputEditorValue = useAtomValue(inputEditorValueAtom); const [judgeResults, setJudgeResults] = useJudgeResults(); useUserFileConnection(); @@ -101,7 +103,7 @@ function EditorPage() { expectedOutput?: string, prefix?: string ) => { - if (!mainMonacoEditor) { + if (!getMainEditorValue) { // editor is still loading return; } @@ -109,7 +111,7 @@ function EditorPage() { setIsRunning(true); setResultAt(inputTabIndex, null); - const code = mainMonacoEditor.getValue(); + const code = getMainEditorValue(); fetchJudge(code, input) .then(async resp => { const data: JudgeResult = await resp.json(); @@ -138,7 +140,7 @@ function EditorPage() { }; const runAllSamples = async () => { - if (!problem || !mainMonacoEditor) { + if (!problem || !getMainEditorValue) { // editor is still loading return; } @@ -147,7 +149,7 @@ function EditorPage() { setIsRunning(true); setResultAt(1, null); - const code = mainMonacoEditor.getValue(); + const code = getMainEditorValue(); try { const promises = []; for (let index = 0; index < samples.length; ++index) { @@ -219,7 +221,7 @@ function EditorPage() { }; if (inputTab === 'input') { - if (inputEditor) runWithInput(inputEditor.getValue()); + if (getInputEditorValue) runWithInput(getInputEditorValue()); } else if (inputTab === 'judge') { runAllSamples(); } else { diff --git a/src/atoms/workspace.ts b/src/atoms/workspace.ts index ebe57669..b9c3ed21 100644 --- a/src/atoms/workspace.ts +++ b/src/atoms/workspace.ts @@ -1,3 +1,4 @@ +import { EditorView } from '@uiw/react-codemirror'; import { atom } from 'jotai'; import type * as monaco from 'monaco-editor'; @@ -10,8 +11,48 @@ export const inputMonacoEditorAtom = atom(null); export const outputMonacoEditorAtom = atom(null); + +export const mainCodemirrorEditorAtom = atom(null); +export const inputCodemirrorEditorAtom = atom(null); + export const layoutEditorsAtom = atom(null, (get, _set, _arg) => { get(mainMonacoEditorAtom)?.layout(); get(inputMonacoEditorAtom)?.layout(); get(outputMonacoEditorAtom)?.layout(); }); + +// returns a function that can be called to get the value of the editor +export const mainEditorValueAtom = atom(get => { + const monacoEditor = get(mainMonacoEditorAtom); + const codemirrorEditor = get(mainCodemirrorEditorAtom); + if (monacoEditor && codemirrorEditor) { + console.error('Both main editors are defined, this should not happen!'); + } + if (!monacoEditor && !codemirrorEditor) { + return null; + } + return () => { + if (monacoEditor) { + return monacoEditor.getValue(); + } + return codemirrorEditor!.state.doc.toString(); + }; +}); + +// returns a function that can be called to get the value of the editor +export const inputEditorValueAtom = atom(get => { + const monacoEditor = get(inputMonacoEditorAtom); + const codemirrorEditor = get(inputCodemirrorEditorAtom); + if (monacoEditor && codemirrorEditor) { + console.error('Both input editors are defined, this should not happen!'); + } + if (!monacoEditor && !codemirrorEditor) { + return null; + } + return () => { + if (monacoEditor) { + return monacoEditor.getValue(); + } + return codemirrorEditor!.state.doc.toString(); + }; +}); diff --git a/src/components/CodeInterface/CodeInterface.tsx b/src/components/CodeInterface/CodeInterface.tsx index baaf4bd9..96b7a6b4 100644 --- a/src/components/CodeInterface/CodeInterface.tsx +++ b/src/components/CodeInterface/CodeInterface.tsx @@ -2,12 +2,16 @@ import defaultCode from '../../scripts/defaultCode'; import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import { useAtom } from 'jotai'; -import { mainMonacoEditorAtom } from '../../atoms/workspace'; +import { + mainCodemirrorEditorAtom, + mainMonacoEditorAtom, +} from '../../atoms/workspace'; import { LazyRealtimeEditor } from '../RealtimeEditor/LazyRealtimeEditor'; import type * as monaco from 'monaco-editor'; import { useEditorContext } from '../../context/EditorContext'; import useUserPermission from '../../hooks/useUserPermission'; import { useUserContext } from '../../context/UserContext'; +import { EditorView } from '@uiw/react-codemirror'; export const CodeInterface = ({ className, @@ -20,8 +24,15 @@ export const CodeInterface = ({ const readOnly = !(permission === 'OWNER' || permission === 'READ_WRITE'); const [editor, setEditor] = useState(null); + const [codemirrorEditor, setCodemirrorEditor] = useState( + null + ); const [, setMainMonacoEditor] = useAtom(mainMonacoEditorAtom); + const [, setMainCodemirrorEditor] = useAtom(mainCodemirrorEditorAtom); + // I think we need these useEffect()s here because otherwise when the component + // is unmounted, the mainMonacoEditorAtom/mainCodemirrorEditorAtom will still + // be set useEffect(() => { if (editor) { setMainMonacoEditor(editor); @@ -31,6 +42,15 @@ export const CodeInterface = ({ } }, [editor, setMainMonacoEditor]); + useEffect(() => { + if (codemirrorEditor) { + setMainCodemirrorEditor(codemirrorEditor); + return () => { + setMainCodemirrorEditor(null); + }; + } + }, [codemirrorEditor, setCodemirrorEditor]); + const { tabSize, lightMode } = useUserContext().userData; return ( @@ -68,6 +88,7 @@ export const CodeInterface = ({ e.focus(); }, 0); }} + onCodemirrorMount={(view, state) => setCodemirrorEditor(view)} defaultValue={defaultCode[lang]} yjsDocumentId={`${fileData.id}.${lang}`} useEditorWithVim={true} diff --git a/src/components/JudgeInterface/JudgeInterface.tsx b/src/components/JudgeInterface/JudgeInterface.tsx index 37711e59..dcb4a981 100644 --- a/src/components/JudgeInterface/JudgeInterface.tsx +++ b/src/components/JudgeInterface/JudgeInterface.tsx @@ -1,6 +1,9 @@ import { useAtomValue } from 'jotai/utils'; import React from 'react'; -import { mainMonacoEditorAtom } from '../../atoms/workspace'; +import { + mainEditorValueAtom, + mainMonacoEditorAtom, +} from '../../atoms/workspace'; import USACOResults from './USACOResults'; import { ProblemData, StatusData } from '../Workspace/Workspace'; import SubmitButton from './SubmitButton'; @@ -48,11 +51,11 @@ export default function JudgeInterface({ setStatusData: React.Dispatch>; handleRunCode: () => void; }): JSX.Element { - const mainMonacoEditor = useAtomValue(mainMonacoEditorAtom); + const getMainEditorValue = useAtomValue(mainEditorValueAtom); const lang = useEditorContext().fileData.settings.language; const handleSubmit = async () => { - if (!mainMonacoEditor || !lang) { + if (!getMainEditorValue || !lang) { alert('Error: Page still loading?'); return; } @@ -64,7 +67,7 @@ export default function JudgeInterface({ const data = { problemID: problem.id, language: { cpp: 'c++17', java: 'java', py: 'python3' }[lang], - base64Code: encode(mainMonacoEditor.getValue()), + base64Code: encode(getMainEditorValue()), }; const resp = await fetch(`${judgePrefix}/submit`, { diff --git a/src/components/NavBar/FileMenu.tsx b/src/components/NavBar/FileMenu.tsx index 1da48a48..14b20b9f 100644 --- a/src/components/NavBar/FileMenu.tsx +++ b/src/components/NavBar/FileMenu.tsx @@ -12,7 +12,11 @@ import classNames from 'classnames'; import ReactDOM from 'react-dom'; import { usePopper } from 'react-popper'; -import { mainMonacoEditorAtom } from '../../atoms/workspace'; +import { + mainCodemirrorEditorAtom, + mainEditorValueAtom, + mainMonacoEditorAtom, +} from '../../atoms/workspace'; import { useAtomValue } from 'jotai'; import { useEditorContext } from '../../context/EditorContext'; import defaultCode from '../../scripts/defaultCode'; @@ -22,7 +26,9 @@ import useUserPermission from '../../hooks/useUserPermission'; export const FileMenu = (props: { onOpenSettings: Function }): JSX.Element => { const { fileData } = useEditorContext(); + const getMainEditorValue = useAtomValue(mainEditorValueAtom); const mainMonacoEditor = useAtomValue(mainMonacoEditorAtom); + const mainCodemirrorEditor = useAtomValue(mainCodemirrorEditorAtom); const permission = useUserPermission(); const [referenceElement, setReferenceElement] = useState( @@ -41,12 +47,12 @@ export const FileMenu = (props: { onOpenSettings: Function }): JSX.Element => { /* ======= BEGIN DROPDOWN ACTIONS ======= */ const handleDownloadFile = () => { - if (!mainMonacoEditor) { + if (!getMainEditorValue) { alert("Editor hasn't loaded yet. Please wait."); return; } - const code = mainMonacoEditor.getValue(); + const code = getMainEditorValue(); const fileNames = { cpp: `${fileData.settings.workspaceName}.cpp`, @@ -58,12 +64,26 @@ export const FileMenu = (props: { onOpenSettings: Function }): JSX.Element => { }; const handleInsertFileTemplate = () => { - if (!mainMonacoEditor) { + if (!mainMonacoEditor && !mainCodemirrorEditor) { alert("Editor hasn't loaded yet, please wait"); return; } if (confirm('Reset current file? Any changes you made will be lost.')) { - mainMonacoEditor.setValue(defaultCode[fileData.settings.language]); + if (mainMonacoEditor) + mainMonacoEditor.setValue(defaultCode[fileData.settings.language]); + else if (mainCodemirrorEditor) { + mainCodemirrorEditor.dispatch({ + changes: { + from: 0, + to: mainCodemirrorEditor.state.doc.length, + insert: defaultCode[fileData.settings.language], + }, + }); + } else { + console.error( + "?? shouldn't happen, both monaco and codemirror editors are not defined" + ); + } } }; diff --git a/src/components/Workspace/Workspace.tsx b/src/components/Workspace/Workspace.tsx index db13ad93..df970b58 100644 --- a/src/components/Workspace/Workspace.tsx +++ b/src/components/Workspace/Workspace.tsx @@ -9,6 +9,7 @@ import { layoutEditorsAtom, inputMonacoEditorAtom, outputMonacoEditorAtom, + inputCodemirrorEditorAtom, } from '../../atoms/workspace'; import { mobileActiveTabAtom, @@ -82,6 +83,7 @@ export default function Workspace({ const [inputTab, setInputTab] = useAtom(inputTabAtom); const showSidebar = useAtomValue(showSidebarAtom); const setInputEditor = useUpdateAtom(inputMonacoEditorAtom); + const setCodemirrorInputEditor = useUpdateAtom(inputCodemirrorEditorAtom); const setOutputEditor = useUpdateAtom(outputMonacoEditorAtom); const [problem, setProblem] = useAtom(problemAtom); @@ -175,6 +177,9 @@ export default function Workspace({ e.layout(); }, 0); }} + onCodemirrorMount={(view, state) => + setCodemirrorInputEditor(view) + } defaultValue="" yjsDocumentId={`${fileData.id}.input`} /> diff --git a/src/components/editor/CodemirrorEditor/CodemirrorEditor.tsx b/src/components/editor/CodemirrorEditor/CodemirrorEditor.tsx index 374cb896..ef8a57e9 100644 --- a/src/components/editor/CodemirrorEditor/CodemirrorEditor.tsx +++ b/src/components/editor/CodemirrorEditor/CodemirrorEditor.tsx @@ -58,6 +58,7 @@ export const CodemirrorEditor = (props: EditorProps): JSX.Element => { style={{ fontSize: '13px' }} readOnly={props.options?.readOnly ?? false} extensions={extensions} + onCreateEditor={props.onCodemirrorMount} /> ); }; diff --git a/src/components/editor/MonacoEditor/monaco-editor-types.ts b/src/components/editor/MonacoEditor/monaco-editor-types.ts index d7df58d5..734cb20e 100644 --- a/src/components/editor/MonacoEditor/monaco-editor-types.ts +++ b/src/components/editor/MonacoEditor/monaco-editor-types.ts @@ -1,4 +1,5 @@ import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import type { ReactCodeMirrorProps } from '@uiw/react-codemirror'; type Theme = 'vs-dark' | 'light'; export type OnMount = ( @@ -114,6 +115,12 @@ export interface EditorProps { onBeforeDispose?: Function; + /** + * Only called for Codemirror editor. + * Same signature as onCreateEditor?(view: EditorView, state: EditorState): void; + */ + onCodemirrorMount?: ReactCodeMirrorProps['onCreateEditor']; + vim?: boolean; lspEnabled?: boolean; @@ -121,7 +128,7 @@ export interface EditorProps { /** * If provided, the code editor should create a yjs binding with the given information */ - yjsInfo: { + yjsInfo?: { yjsText: any; yjsAwareness: any; } | null;