cleanup and scrolling

pull/118/head
Jon Eugster 3 years ago
parent 805b0b94c1
commit d0316b734a

@ -64,7 +64,7 @@ config.autoClosingPairs = config.autoClosingPairs.map(
monaco.languages.setLanguageConfiguration('lean4cmd', config); monaco.languages.setLanguageConfiguration('lean4cmd', config);
/** The input field */ /** The input field */
export function CommandLine() { export function CommandLine({proofPanelRef}: {proofPanelRef: React.MutableRefObject<HTMLDivElement>}) {
/** Reference to the hidden multi-line editor */ /** Reference to the hidden multi-line editor */
const editor = React.useContext(MonacoEditorContext) const editor = React.useContext(MonacoEditorContext)
@ -74,7 +74,7 @@ export function CommandLine() {
const [oneLineEditor, setOneLineEditor] = useState<monaco.editor.IStandaloneCodeEditor>(null) const [oneLineEditor, setOneLineEditor] = useState<monaco.editor.IStandaloneCodeEditor>(null)
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)
const { commandLineMode, commandLineInput, setCommandLineInput } = React.useContext(InputModeContext) const {commandLineInput, setCommandLineInput} = React.useContext(InputModeContext)
const inputRef = useRef() const inputRef = useRef()
@ -87,7 +87,7 @@ export function CommandLine() {
/** Load all goals an messages of the current proof (line-by-line) and save /** Load all goals an messages of the current proof (line-by-line) and save
* the retrieved information into context (`ProofContext`) * the retrieved information into context (`ProofContext`)
*/ */
const loadAllGoals = React.useCallback(() => { const loadAllGoals = React.useCallback((proofPanelRef) => {
let goalCalls = [] let goalCalls = []
let msgCalls = [] let msgCalls = []
@ -174,6 +174,8 @@ export function CommandLine() {
}) })
// Save the proof to the context // Save the proof to the context
setProof(tmpProof) setProof(tmpProof)
console.debug('updated proof')
proofPanelRef.current?.lastElementChild?.scrollIntoView()
}) })
}) })
}, [commandLineInput, editor]) }, [commandLineInput, editor])
@ -207,12 +209,15 @@ export function CommandLine() {
useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => { useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => {
if (params.uri == editor.getModel().uri.toString()) { if (params.uri == editor.getModel().uri.toString()) {
setProcessing(false) setProcessing(false)
loadAllGoals(proofPanelRef)
if (!hasErrors(params.diagnostics)) { if (!hasErrors(params.diagnostics)) {
setCommandLineInput("") setCommandLineInput("")
editor.setPosition(editor.getModel().getFullModelRange().getEndPosition()) editor.setPosition(editor.getModel().getFullModelRange().getEndPosition())
} }
} }
loadAllGoals() // TODO: instead of loading all goals every time, we could only load the last one // TODO: This is the wrong place apparently. Where do wee need to load them?
// TODO: instead of loading all goals every time, we could only load the last one
// loadAllGoals()
}, []); }, []);
useEffect(() => { useEffect(() => {

@ -135,6 +135,12 @@
.commandline-interface .content { .commandline-interface .content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
scroll-behavior: smooth;
}
/* TODO this is in the wrong file */
.chat {
scroll-behavior: smooth;
} }
.commandline-interface .content .tmp-pusher { .commandline-interface .content .tmp-pusher {

@ -31,36 +31,35 @@ import { InputModeContext, MonacoEditorContext, ProofContext, ProofStep } from '
import { CommandLine, hasErrors, hasInteractiveErrors } from './command_line'; import { CommandLine, hasErrors, hasInteractiveErrors } from './command_line';
import { InteractiveDiagnostic } from '@leanprover/infoview/*'; import { InteractiveDiagnostic } from '@leanprover/infoview/*';
import { Button } from '../button'; import { Button } from '../button';
import { CircularProgress } from '@mui/material';
/** Wrapper for the two editors. It is important that the `div` with `codeViewRef` is /** Wrapper for the two editors. It is important that the `div` with `codeViewRef` is
* always present, or the monaco editor cannot start. * always present, or the monaco editor cannot start.
*/ */
export function DualEditor({level, codeviewRef, levelId, worldId, commandLineMode}) { export function DualEditor({level, codeviewRef, levelId, worldId}) {
const ec = React.useContext(EditorContext) const ec = React.useContext(EditorContext)
// const { commandLineMode, setCommandLineMode } = React.useContext(InputModeContext) const {commandLineMode} = React.useContext(InputModeContext)
// return <div className={hidden ? 'hidden' : ''}>
// <ExerciseStatement data={data} />
// <div className={`statement ${commandLineMode ? 'hidden' : ''}`}><code>{data?.descrFormat}</code></div>
// <div ref={codeviewRef} className={'codeview'}></div>
// {ec && <Main key={`${worldId}/${levelId}`} world={worldId} level={levelId} />}
// </div>
// }
return <> return <>
<div className={commandLineMode ? ' hidden' : ''}> <div className={commandLineMode ? 'hidden' : ''}>
<ExerciseStatement data={level?.data} /> <ExerciseStatement data={level?.data} />
<div ref={codeviewRef} className={'codeview'}></div> <div ref={codeviewRef} className={'codeview'}></div>
</div> </div>
{ec && <DualEditorMain worldId={worldId} levelId={levelId} level={level} commandLineMode={commandLineMode}/>} {ec ?
<DualEditorMain worldId={worldId} levelId={levelId} level={level} /> :
// TODO: Style this if relevant.
<>
<p>Editor is starting up...</p>
<CircularProgress />
</>
}
</> </>
} }
/* The part of the two editors that can needs the editor connection first */ /** The part of the two editors that needs the editor connection first */
function DualEditorMain({worldId, levelId, level, commandLineMode}) { function DualEditorMain({worldId, levelId, level}) {
const ec = React.useContext(EditorContext) const ec = React.useContext(EditorContext)
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {commandLineMode} = React.useContext(InputModeContext)
/* Set up updates to the global infoview state on editor events. */ /* Set up updates to the global infoview state on editor events. */
const config = useEventResult(ec.events.changedInfoviewConfig) ?? defaultInfoviewConfig; const config = useEventResult(ec.events.changedInfoviewConfig) ?? defaultInfoviewConfig;
@ -69,8 +68,8 @@ function DualEditorMain({worldId, levelId, level, commandLineMode}) {
'$/lean/fileProgress', '$/lean/fileProgress',
new Map<DocumentUri, LeanFileProgressProcessingInfo[]>(), new Map<DocumentUri, LeanFileProgressProcessingInfo[]>(),
async (params: LeanFileProgressParams) => (allProgress) => { async (params: LeanFileProgressParams) => (allProgress) => {
const newProgress = new Map(allProgress); const newProgress = new Map(allProgress);
return newProgress.set(params.textDocument.uri, params.processing); return newProgress.set(params.textDocument.uri, params.processing);
}, []) }, [])
const serverVersion = useEventResult(ec.events.serverRestarted, result => new ServerVersion(result.serverInfo?.version ?? '')) const serverVersion = useEventResult(ec.events.serverRestarted, result => new ServerVersion(result.serverInfo?.version ?? ''))
@ -80,11 +79,6 @@ function DualEditorMain({worldId, levelId, level, commandLineMode}) {
<WithRpcSessions> <WithRpcSessions>
<WithLspDiagnosticsContext> <WithLspDiagnosticsContext>
<ProgressContext.Provider value={allProgress}> <ProgressContext.Provider value={allProgress}>
{/* We need the editor to always there and hidden because
the command line edits its content */}
{ // TODO: Is there any possibility that the editor connection takes a while
// and we should show a circular progress here?
}
{commandLineMode ? {commandLineMode ?
<CommandLineInterface world={worldId} level={levelId} data={level?.data}/> <CommandLineInterface world={worldId} level={levelId} data={level?.data}/>
: :
@ -98,11 +92,12 @@ function DualEditorMain({worldId, levelId, level, commandLineMode}) {
</> </>
} }
// The mathematical formulation of the statement, supporting e.g. Latex /** The mathematical formulation of the statement, supporting e.g. Latex
// It takes three forms, depending on the precence of name and description: * It takes three forms, depending on the precence of name and description:
// - Theorem xyz: description * - Theorem xyz: description
// - Theorem xyz * - Theorem xyz
// - Exercises: description * - Exercises: description
*/
function ExerciseStatement({data}) { function ExerciseStatement({data}) {
if (!data?.descrText) { return <></> } if (!data?.descrText) { return <></> }
return <div className="exercise-statement"><Markdown> return <div className="exercise-statement"><Markdown>
@ -276,24 +271,29 @@ export function CommandLineInterface(props: {world: string, level: number, data:
const ec = React.useContext(EditorContext) const ec = React.useContext(EditorContext)
const editor = React.useContext(MonacoEditorContext) const editor = React.useContext(MonacoEditorContext)
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
const {proof} = React.useContext(ProofContext) const {proof} = React.useContext(ProofContext)
const proofPanelRef = React.useRef<HTMLDivElement>(null)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
// Mark level as completed when server gives notification // Mark level as completed when server gives notification
useServerNotificationEffect( useServerNotificationEffect(
'$/game/completed', '$/game/completed',
(params: any) => { (params: any) => {
if (ec.events.changedCursorLocation.current && if (ec.events.changedCursorLocation.current &&
ec.events.changedCursorLocation.current.uri === params.uri) { ec.events.changedCursorLocation.current.uri === params.uri) {
dispatch(levelCompleted({game: gameId, world: props.world, level: props.level})) dispatch(levelCompleted({game: gameId, world: props.world, level: props.level}))
} }
}, }, []
[] )
);
// React.useEffect(() => {
// console.debug('updated proof')
// // proofPanelRef.current?.lastElementChild?.scrollIntoView() //scrollTo(0,0)
// }, [proof])
/** Delete all proof lines starting from a given line. /** Delete all proof lines starting from a given line.
* Note that the first line (i.e. deleting everything) is `1`! * Note that the first line (i.e. deleting everything) is `1`!
@ -343,53 +343,47 @@ export function CommandLineInterface(props: {world: string, level: number, data:
// NB: the cursor may temporarily become `undefined` when a file is closed. In this case // NB: the cursor may temporarily become `undefined` when a file is closed. In this case
// it's important not to reconstruct the `WithBlah` wrappers below since they contain state // it's important not to reconstruct the `WithBlah` wrappers below since they contain state
// that we want to persist. // that we want to persist.
let ret
if (!serverVersion) {
ret = <p>Waiting for Lean server to start...</p>
} else if (serverStoppedResult){
ret = <div><p>{serverStoppedResult.message}</p><p className="error">{serverStoppedResult.reason}</p></div>
} else {
//className="infoview vscode-light"
ret = <div className="commandline-interface">
{/* {completed && <div className="level-completed">Level completed! 🎉</div>} */}
<div className="content">
<ExerciseStatement data={props.data} />
{/* <Infos /> */}
<div className="tmp-pusher"></div>
{proof.map((step, i) => {
if (i == proof.length - 1 && hasInteractiveErrors(step.errors)) {
// if the last command contains an error, we should not display it
// as it will be overwritten by the next entered command
return <div>
<Errors errors={step.errors} commandLineMode={true}/>
</div>
}
else {
return <>
<div className="step">
<Command command={step.command} deleteProof={deleteProof(i)}/>
<Errors errors={step.errors} commandLineMode={true}/>
<GoalsTab proofStep={step}/>
</div>
{/* Show a message that there are no goals left */}
{!step.goals.length && (
<div className="message information">
{completed ?
<p>Level completed! 🎉</p> :
<p>
<b>no goals left</b><br />
<i>This probably means you solved the level with warnings</i>
</p>
}
</div>
)}
</>
}
})}
</div>
<CommandLine />
</div>
}
return ret if (!serverVersion) {return <p>Waiting for Lean server to start...</p>}
if (serverStoppedResult) {return <div>
<p>{serverStoppedResult.message}</p>
<p className="error">{serverStoppedResult.reason}</p>
</div>}
return <div className="commandline-interface">
<div className="content" ref={proofPanelRef}>
<ExerciseStatement data={props.data} />
<div className="tmp-pusher"></div>
{proof.length ? proof.map((step, i) => {
if (i == proof.length - 1 && hasInteractiveErrors(step.errors)) {
// if the last command contains an error, we only display the errors but not the
// entered command as it is still present in the command line.
return <div>
<Errors errors={step.errors} commandLineMode={true}/>
</div>
} else {
return <>
<div className="step">
<Command command={step.command} deleteProof={deleteProof(i)}/>
<Errors errors={step.errors} commandLineMode={true}/>
<GoalsTab proofStep={step}/>
</div>
{/* Show a message that there are no goals left */}
{!step.goals.length && (
<div className="message information">
{completed ?
<p>Level completed! 🎉</p> :
<p>
<b>no goals left</b><br />
<i>This probably means you solved the level with warnings</i>
</p>
}
</div>
)}
</>
}
}) : <CircularProgress />}
</div>
<CommandLine proofPanelRef={proofPanelRef}/>
</div>
} }

@ -58,7 +58,7 @@ function Level() {
function PlayableLevel({worldId, levelId}) { function PlayableLevel({worldId, levelId}) {
const codeviewRef = useRef<HTMLDivElement>(null) const codeviewRef = useRef<HTMLDivElement>(null)
const chatPanelRef = useRef<HTMLDivElement>(null) const chatRef = useRef<HTMLDivElement>(null)
const gameId = React.useContext(GameIdContext) const gameId = React.useContext(GameIdContext)
@ -77,13 +77,20 @@ function PlayableLevel({worldId, levelId}) {
const theme = useTheme(); const theme = useTheme();
useEffect(() => { useEffect(() => {
// Scroll to top when loading a new level // // Scroll to top when loading a new level
// TODO: Thats the wrong behaviour probably // // TODO: Thats the wrong behaviour probably
chatPanelRef.current!.scrollTo(0,0) // chatRef.current!.scrollTo(0,0)
// Reset command line input when loading a new level // Reset command line input when loading a new level
setCommandLineInput("") setCommandLineInput("")
}, [levelId]) }, [levelId])
useEffect(() => {
// TODO: For some reason this is always called twice
console.debug('scroll chat')
chatRef.current!.lastElementChild?.scrollIntoView() //scrollTo(0,0)
}, [proof])
React.useEffect(() => { React.useEffect(() => {
if (!commandLineMode) { if (!commandLineMode) {
// Delete last input attempt from command line // Delete last input attempt from command line
@ -187,8 +194,8 @@ function PlayableLevel({worldId, levelId}) {
<ProofContext.Provider value={{proof, setProof}}> <ProofContext.Provider value={{proof, setProof}}>
<LevelAppBar isLoading={level.isLoading} levelTitle={levelTitle} worldId={worldId} levelId={levelId} /> <LevelAppBar isLoading={level.isLoading} levelTitle={levelTitle} worldId={worldId} levelId={levelId} />
<Split minSize={0} snapOffset={200} sizes={[25, 50, 25]} className={`app-content level ${level.isLoading ? 'hidden' : ''}`}> <Split minSize={0} snapOffset={200} sizes={[25, 50, 25]} className={`app-content level ${level.isLoading ? 'hidden' : ''}`}>
<div ref={chatPanelRef} className="chat-panel"> <div className="chat-panel">
<div className="chat"> <div ref={chatRef} className="chat">
{level?.data?.introduction && {level?.data?.introduction &&
<div className="message information"> <div className="message information">
<Markdown>{level?.data?.introduction}</Markdown> <Markdown>{level?.data?.introduction}</Markdown>
@ -225,7 +232,7 @@ function PlayableLevel({worldId, levelId}) {
<EditorContext.Provider value={editorConnection}> <EditorContext.Provider value={editorConnection}>
<MonacoEditorContext.Provider value={editor}> <MonacoEditorContext.Provider value={editor}>
<div className="exercise"> <div className="exercise">
<DualEditor level={level} codeviewRef={codeviewRef} levelId={levelId} worldId={worldId} commandLineMode={commandLineMode} /> <DualEditor level={level} codeviewRef={codeviewRef} levelId={levelId} worldId={worldId} />
</div> </div>
</MonacoEditorContext.Provider> </MonacoEditorContext.Provider>
</EditorContext.Provider> </EditorContext.Provider>

11126
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -27,6 +27,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-markdown": "^8.0.4", "react-markdown": "^8.0.4",
"react-native": "^0.72.3",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.5.0", "react-router-dom": "^6.5.0",
"react-split": "^2.0.14", "react-split": "^2.0.14",

Loading…
Cancel
Save