@@ -137,7 +138,7 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
{userWidgets.map(widget =>
{widget.name}
- goal) : []}
+
)}
@@ -166,6 +167,7 @@ interface InfoDisplayProps {
pos: DocumentPosition,
status: InfoStatus,
messages: InteractiveDiagnostic[],
+ proof?: ProofState,
goals?: InteractiveGoals,
termGoal?: InteractiveTermGoal,
error?: string,
@@ -175,7 +177,7 @@ interface InfoDisplayProps {
}
/** Displays goal state and messages. Can be paused. */
-function InfoDisplay(props0: ProofStateProps & InfoDisplayProps & InfoPinnable) {
+function InfoDisplay(props0: InfoDisplayProps & InfoPinnable) {
// Used to update the paused state *just once* if it is paused,
// but a display update is triggered
const [shouldRefresh, setShouldRefresh] = React.useState(false)
@@ -214,7 +216,7 @@ function InfoDisplay(props0: ProofStateProps & InfoDisplayProps & InfoPinnable)
{/* */}
{/* */}
-
+
{/* */}
@@ -290,6 +292,8 @@ function InfoAux(props: InfoProps) {
// with e.g. a new `pos`.
type InfoRequestResult = Omit
const [state, triggerUpdateCore] = useAsyncWithTrigger(() => new Promise((resolve, reject) => {
+
+ const proofReq = rpcSess.call('Game.getProofState', DocumentPosition.toTdpp(pos))
const goalsReq = rpcSess.call('Game.getInteractiveGoals', DocumentPosition.toTdpp(pos))
const termGoalReq = getInteractiveTermGoal(rpcSess, DocumentPosition.toTdpp(pos))
const widgetsReq = Widget_getWidgets(rpcSess, pos).catch(discardMethodNotFound)
@@ -308,6 +312,7 @@ function InfoAux(props: InfoProps) {
pos,
status: 'updating',
messages: lspDiagsHere.map(lspDiagToInteractive),
+ proof: undefined,
goals: undefined,
termGoal: undefined,
error: undefined,
@@ -318,11 +323,12 @@ function InfoAux(props: InfoProps) {
// NB: it is important to await await reqs at once, otherwise
// if both throw then one exception becomes unhandled.
- Promise.all([goalsReq, termGoalReq, widgetsReq, messagesReq]).then(
- ([goals, termGoal, userWidgets, messages]) => resolve({
+ Promise.all([proofReq, goalsReq, termGoalReq, widgetsReq, messagesReq]).then(
+ ([proof, goals, termGoal, userWidgets, messages]) => resolve({
pos,
status: 'ready',
messages,
+ proof : proof as any,
goals: goals as any,
termGoal,
error: undefined,
@@ -353,6 +359,7 @@ function InfoAux(props: InfoProps) {
pos,
status: 'error',
messages: lspDiagsHere.map(lspDiagToInteractive),
+ proof: undefined,
goals: undefined,
termGoal: undefined,
error: `Error fetching goals: ${errorString}`,
@@ -389,6 +396,7 @@ function InfoAux(props: InfoProps) {
pos,
status: 'updating',
messages: [],
+ proof: undefined,
goals: undefined,
termGoal: undefined,
error: undefined,
diff --git a/client/src/components/infoview/main.tsx b/client/src/components/infoview/main.tsx
index 956d2f6..3e768fa 100644
--- a/client/src/components/infoview/main.tsx
+++ b/client/src/components/infoview/main.tsx
@@ -26,15 +26,16 @@ import Markdown from '../markdown';
import { Infos } from './infos';
import { AllMessages, Errors, WithLspDiagnosticsContext } from './messages';
-import { Goal } from './goals';
-import { DeletedChatContext, InputModeContext, PreferencesContext, MonacoEditorContext, ProofContext, ProofStep, SelectionContext, WorldLevelIdContext } from './context';
-import { Typewriter, hasErrors, hasInteractiveErrors } from './typewriter';
+import { Goal, isLastStepWithErrors, lastStepHasErrors, loadGoals } from './goals';
+import { DeletedChatContext, InputModeContext, PreferencesContext, MonacoEditorContext, ProofContext, SelectionContext, WorldLevelIdContext } from './context';
+import { Typewriter, getInteractiveDiagsAt, hasErrors, hasInteractiveErrors } from './typewriter';
import { InteractiveDiagnostic } from '@leanprover/infoview/*';
import { Button } from '../button';
import { CircularProgress } from '@mui/material';
-import { GameHint } from './rpc_api';
+import { GameHint, InteractiveGoalsWithHints, ProofState } from './rpc_api';
import { store } from '../../state/store';
-import { Hints, filterHints } from '../hints';
+import { Hints, MoreHelpButton, filterHints } from '../hints';
+import { DocumentPosition } from '../../../../node_modules/lean4-infoview/src/infoview/util';
/** Wrapper for the two editors. It is important that the `div` with `codeViewRef` is
* always present, or the monaco editor cannot start.
@@ -61,36 +62,35 @@ function DualEditorMain({ worldId, levelId, level, worldSize }: { worldId: strin
const gameId = React.useContext(GameIdContext)
const { typewriterMode } = React.useContext(InputModeContext)
- // Mark level as completed when server gives notification
+ const {proof, setProof} = React.useContext(ProofContext)
+
const dispatch = useAppDispatch()
- useServerNotificationEffect(
- '$/game/completed',
- (params: any) => {
- if (ec.events.changedCursorLocation.current &&
- ec.events.changedCursorLocation.current.uri === params.uri) {
- dispatch(levelCompleted({ game: gameId, world: worldId, level: levelId }))
-
- // On completion, add the names of all new items to the local storage
- let newTiles = [
- ...level?.tactics,
- ...level?.lemmas,
- ...level?.definitions
- ].filter((tile) => tile.new).map((tile) => tile.name)
-
- // Add the proven statement to the local storage as well.
- if (level?.statementName != null) {
- newTiles.push(level?.statementName)
- }
- let inv: string[] = selectInventory(gameId)(store.getState())
+ React.useEffect(() => {
+ if (proof.completed) {
+ dispatch(levelCompleted({ game: gameId, world: worldId, level: levelId }))
+
+ // On completion, add the names of all new items to the local storage
+ let newTiles = [
+ ...level?.tactics,
+ ...level?.lemmas,
+ ...level?.definitions
+ ].filter((tile) => tile.new).map((tile) => tile.name)
+
+ // Add the proven statement to the local storage as well.
+ if (level?.statementName != null) {
+ newTiles.push(level?.statementName)
+ }
- // add new items and remove duplicates
- let newInv = [...inv, ...newTiles].filter((item, i, array) => array.indexOf(item) == i)
+ let inv: string[] = selectInventory(gameId)(store.getState())
- dispatch(changedInventory({ game: gameId, inventory: newInv }))
- }
- }, [level]
- )
+ // add new items and remove duplicates
+ let newInv = [...inv, ...newTiles].filter((item, i, array) => array.indexOf(item) == i)
+
+ dispatch(changedInventory({ game: gameId, inventory: newInv }))
+
+ }
+ }, [proof, level])
/* Set up updates to the global infoview state on editor events. */
const config = useEventResult(ec.events.changedInfoviewConfig) ?? defaultInfoviewConfig;
@@ -154,7 +154,7 @@ export function Main(props: { world: string, level: number, data: LevelInfo}) {
const gameId = React.useContext(GameIdContext)
const {worldId, levelId} = React.useContext(WorldLevelIdContext)
- const completed = useAppSelector(selectCompleted(gameId, props.world, props.level))
+ const { proof, setProof } = React.useContext(ProofContext)
console.debug(`template: ${props.data?.template}`)
@@ -206,7 +206,7 @@ export function Main(props: { world: string, level: number, data: LevelInfo}) {
ret =
{serverStoppedResult.message}
{serverStoppedResult.reason}
} else {
ret =
- {completed &&
Level completed! 🎉
}
+ {proof.completed &&
Level completed! 🎉
}
}
@@ -223,15 +223,24 @@ const goalFilter = {
}
/** The display of a single entered lean command */
-function Command({ command, deleteProof }: { command: string, deleteProof: any }) {
+function Command({ proof, i, deleteProof }: { proof: ProofState, i: number, deleteProof: any }) {
// The first step will always have an empty command
- if (!command) { return <>> }
- return
-
{command}
-
-
+ if (!proof?.steps[i]?.command) { return <>> }
+
+ if (isLastStepWithErrors(proof, i)) {
+ // If the last step has errors, we display the command in a different style
+ // indicating that it will be removed on the next try.
+ return
+ Failed command: {proof.steps[i].command}
+
+ } else {
+ return
+
{proof.steps[i].command}
+
+
+ }
}
// const MessageView = React.memo(({uri, diag}: MessageViewProps) => {
@@ -286,10 +295,14 @@ function Command({ command, deleteProof }: { command: string, deleteProof: any }
// }, fastIsEqual)
/** The tabs of goals that lean ahs after the command of this step has been processed */
-function GoalsTabs({ proofStep, last, onClick, onGoalChange=(n)=>{}}: { proofStep: ProofStep, last : boolean, onClick? : any, onGoalChange?: (n?: number) => void }) {
+function GoalsTabs({ proofStep, last, onClick, onGoalChange=(n)=>{}}: { proofStep: InteractiveGoalsWithHints, last : boolean, onClick? : any, onGoalChange?: (n?: number) => void }) {
const [selectedGoal, setSelectedGoal] = React.useState(0)
+ if (proofStep.goals.length == 0) {
+ return <>>
+ }
+
return
{proofStep.goals.map((goal, i) => (
@@ -300,7 +313,7 @@ function GoalsTabs({ proofStep, last, onClick, onGoalChange=(n)=>{}}: { proofSte
))}
-
+
}
@@ -350,12 +363,11 @@ export function TypewriterInterface({props}) {
const [loadingProgress, setLoadingProgress] = React.useState(0)
const { setDeletedChat, showHelp, setShowHelp } = React.useContext(DeletedChatContext)
const {mobile} = React.useContext(PreferencesContext)
- const { proof } = React.useContext(ProofContext)
+ const { proof, setProof } = React.useContext(ProofContext)
const { setTypewriterInput } = React.useContext(InputModeContext)
const { selectedStep, setSelectedStep } = React.useContext(SelectionContext)
const proofPanelRef = React.useRef(null)
- const completed = useAppSelector(selectCompleted(gameId, props.world, props.level))
// const config = useEventResult(ec.events.changedInfoviewConfig) ?? defaultInfoviewConfig;
// const curUri = useEventResult(ec.events.changedCursorLocation, loc => loc?.uri);
@@ -367,9 +379,11 @@ export function TypewriterInterface({props}) {
function deleteProof(line: number) {
return (ev) => {
let deletedChat: Array = []
- filterHints(proof).slice(line).map((hintsAtStep, i) => {
+ proof.steps.slice(line).map((step, i) => {
+ let filteredHints = filterHints(step.goals[0]?.hints, proof?.steps[i-1]?.goals[0]?.hints)
+
// Only add these hidden hints to the deletion stack which were visible
- deletedChat = [...deletedChat, ...hintsAtStep.filter(hint => (!hint.hidden || showHelp.has(line + i)))]
+ deletedChat = [...deletedChat, ...filteredHints.filter(hint => (!hint.hidden || showHelp.has(line + i)))]
})
setDeletedChat(deletedChat)
@@ -382,7 +396,9 @@ export function TypewriterInterface({props}) {
forceMoveMarkers: false
}])
setSelectedStep(undefined)
- setTypewriterInput(proof[line].command)
+ setTypewriterInput(proof.steps[line].command)
+ // Reload proof on deleting
+ loadGoals(rpcSess, uri, setProof)
ev.stopPropagation()
}
}
@@ -402,7 +418,7 @@ export function TypewriterInterface({props}) {
// Scroll to the end of the proof if it is updated.
React.useEffect(() => {
- if (proof?.length > 1) {
+ if (proof.steps.length > 1) {
proofPanelRef.current?.lastElementChild?.scrollIntoView() //scrollTo(0,0)
} else {
proofPanelRef.current?.scrollTo(0,0)
@@ -423,38 +439,8 @@ export function TypewriterInterface({props}) {
}
}, [selectedStep])
- // TODO: This about hidden hints is all copied from `level.tsx`. Can we move that into `hints.tsx`?
-
- // If the last step has errors, we want to treat it as if it is part of the second-to-last step
- let k = proof.length - 1
- let withErr = hasInteractiveErrors(proof[k]?.errors) ? 1 : 0
-
- const activateHiddenHints = (ev) => {
- // If the last step (`k`) has errors, we want the hidden hints from the
- // second-to-last step to be affected
- if (!(proof.length)) {return}
-
- // state must not be mutated, therefore we need to clone the set
- let tmp = new Set(showHelp)
- if (tmp.has(k - withErr)) {
- tmp.delete(k - withErr)
- } else {
- tmp.add(k - withErr)
- }
- setShowHelp(tmp)
- console.debug(`help: ${Array.from(tmp.values())}`)
- }
-
- function hasHiddenHints(i : number): boolean {
- let step = proof[i]
-
- // For example if the proof isn't loaded yet
- if(!step) {return false}
-
- return step.hints.some((hint) => hint.hidden)
- }
-
- let lastStepErrors = proof.length ? hasInteractiveErrors(proof[proof.length - 1].errors) : false
+ // TODO: superfluous, can be replaced with `withErr` from above
+ let lastStepErrors = proof.steps.length ? hasInteractiveErrors(getInteractiveDiagsAt(proof, proof.steps.length)) : false
useServerNotificationEffect("$/game/loading", (params : any) => {
@@ -474,20 +460,22 @@ export function TypewriterInterface({props}) {
- {proof.length ?
+ {proof.steps.length ?
<>
- {proof.map((step, i) => {
- if (i == proof.length - 1 && lastStepErrors) {
- // 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.
- // TODO: Should not use index as key.
- return
-
-
- } else {
+ {proof.steps.map((step, i) => {
+ let filteredHints = filterHints(step.goals[0]?.hints, proof?.steps[i-1]?.goals[0]?.hints)
+
+ // if (i == proof.steps.length - 1 && hasInteractiveErrors(step.diags)) {
+ // // 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.
+ // // TODO: Should not use index as key.
+ // return
}
diff --git a/client/src/components/infoview/rpc_api.ts b/client/src/components/infoview/rpc_api.ts
index 3833c61..c499e93 100644
--- a/client/src/components/infoview/rpc_api.ts
+++ b/client/src/components/infoview/rpc_api.ts
@@ -3,46 +3,80 @@
*
* This file is based on `vscode-lean4/vscode-lean4/src/rpcApi.ts`
*/
-import { ContextInfo, FVarId, CodeWithInfos, MVarId } from '@leanprover/infoview-api';
-
-export interface GameHint {
- text: string;
- hidden: boolean;
-}
+import type { Range } from 'vscode-languageserver-protocol';
+import type { ContextInfo, FVarId, CodeWithInfos, MVarId } from '@leanprover/infoview-api';
+import { InteractiveDiagnostic, TermInfo } from '@leanprover/infoview/*';
+import type { Diagnostic } from 'vscode-languageserver-protocol';
export interface InteractiveHypothesisBundle {
/** The pretty names of the variables in the bundle. Anonymous names are rendered
* as `"[anonymous]"` whereas inaccessible ones have a `✝` appended at the end.
* Use `InteractiveHypothesisBundle_nonAnonymousNames` to filter anonymouse ones out. */
names: string[];
- /** Present since server version 1.1.2. */
fvarIds?: FVarId[];
type: CodeWithInfos;
val?: CodeWithInfos;
isInstance?: boolean;
isType?: boolean;
- isAssumption?: boolean;
isInserted?: boolean;
isRemoved?: boolean;
+ isAssumption?: boolean;
}
export interface InteractiveGoalCore {
hyps: InteractiveHypothesisBundle[];
type: CodeWithInfos;
- /** Present since server version 1.1.2. */
ctx?: ContextInfo;
}
export interface InteractiveGoal extends InteractiveGoalCore {
userName?: string;
goalPrefix?: string;
- /** Present since server version 1.1.2. */
mvarId?: MVarId;
isInserted?: boolean;
isRemoved?: boolean;
+}
+
+export interface InteractiveGoals extends InteractiveGoalCore {
+ goals: InteractiveGoals[];
+}
+
+export interface InteractiveTermGoal extends InteractiveGoalCore {
+ range?: Range;
+ term?: TermInfo;
+}
+
+export interface GameHint {
+ text: string;
+ hidden: boolean;
+}
+
+export interface InteractiveGoalWithHints {
+ goal: InteractiveGoal;
hints: GameHint[];
}
-export interface InteractiveGoals {
- goals: InteractiveGoal[];
+export interface InteractiveGoalsWithHints {
+ goals: InteractiveGoalWithHints[];
+ command: string;
+ diags: InteractiveDiagnostic[];
+}
+
+/**
+ * The proof state as it is received from the server.
+ * Per proof step of the tactic proof, there is one `InteractiveGoalWithHints[]`.
+ */
+export interface ProofState {
+ /** The proof steps. step 0 is the state at the beginning of the proof. step one
+ * contains the goal after the first line has been evaluated.
+ *
+ * In particular `step[i]` is the proof step at the beginning of line `i` in vscode.
+ */
+ steps: InteractiveGoalsWithHints[];
+ /** The remaining diagnostics that are not in the steps. Usually this should only
+ * be the "unsolved goals" message, I believe.
+ */
+ diagnostics : InteractiveDiagnostic[];
+ completed : Boolean;
+ completedWithWarnings : Boolean;
}
diff --git a/client/src/components/infoview/typewriter.tsx b/client/src/components/infoview/typewriter.tsx
index 347a021..e457af0 100644
--- a/client/src/components/infoview/typewriter.tsx
+++ b/client/src/components/infoview/typewriter.tsx
@@ -5,7 +5,7 @@ import { faWandMagicSparkles } from '@fortawesome/free-solid-svg-icons'
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'
import { Registry } from 'monaco-textmate' // peer dependency
import { wireTmGrammars } from 'monaco-editor-textmate'
-import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol';
+import { DiagnosticSeverity, PublishDiagnosticsParams, DocumentUri } from 'vscode-languageserver-protocol';
import { useServerNotificationEffect } from '../../../../node_modules/lean4-infoview/src/infoview/util';
import { AbbreviationRewriter } from 'lean4web/client/src/editor/abbreviation/rewriter/AbbreviationRewriter';
import { AbbreviationProvider } from 'lean4web/client/src/editor/abbreviation/AbbreviationProvider';
@@ -13,13 +13,21 @@ import * as leanSyntax from 'lean4web/client/src/syntaxes/lean.json'
import * as leanMarkdownSyntax from 'lean4web/client/src/syntaxes/lean-markdown.json'
import * as codeblockSyntax from 'lean4web/client/src/syntaxes/codeblock.json'
import languageConfig from 'lean4/language-configuration.json';
-import { InteractiveDiagnostic, getInteractiveDiagnostics } from '@leanprover/infoview-api';
+import { InteractiveDiagnostic, RpcSessionAtPos, getInteractiveDiagnostics } from '@leanprover/infoview-api';
import { Diagnostic } from 'vscode-languageserver-types';
import { DocumentPosition } from '../../../../node_modules/lean4-infoview/src/infoview/util';
import { RpcContext } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions';
-import { DeletedChatContext, InputModeContext, MonacoEditorContext, ProofContext, ProofStep } from './context'
-import { goalsToString } from './goals'
-import { GameHint, InteractiveGoals } from './rpc_api'
+import { DeletedChatContext, InputModeContext, MonacoEditorContext, ProofContext } from './context'
+import { goalsToString, lastStepHasErrors, loadGoals } from './goals'
+import { GameHint, ProofState } from './rpc_api'
+
+export interface GameDiagnosticsParams {
+ uri: DocumentUri;
+ diagnostics: Diagnostic[];
+}
+
+
+
/* We register a new language `leancmd` that looks like lean4, but does not use the lsp server. */
@@ -64,7 +72,7 @@ config.autoClosingPairs = config.autoClosingPairs.map(
monaco.languages.setLanguageConfiguration('lean4cmd', config);
/** The input field */
-export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boolean}) {
+export function Typewriter({disabled}: {disabled?: boolean}) {
/** Reference to the hidden multi-line editor */
const editor = React.useContext(MonacoEditorContext)
@@ -89,98 +97,98 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
/** Load all goals an messages of the current proof (line-by-line) and save
* the retrieved information into context (`ProofContext`)
*/
- const loadAllGoals = React.useCallback(() => {
-
- let goalCalls = []
- let msgCalls = []
-
- // For each line of code ask the server for the goals and the messages on this line
- for (let i = 0; i < model.getLineCount(); i++) {
- goalCalls.push(
- rpcSess.call('Game.getInteractiveGoals', DocumentPosition.toTdpp({line: i, character: 0, uri: uri}))
- )
- msgCalls.push(
- getInteractiveDiagnostics(rpcSess, {start: i, end: i+1}).catch((error) => {console.debug("promise broken")})
- )
- }
-
- // Wait for all these requests to be processed before saving the results
- Promise.all(goalCalls).then((steps : InteractiveGoals[]) => {
- Promise.all(msgCalls).then((diagnostics : [InteractiveDiagnostic[]]) => {
- let tmpProof : ProofStep[] = []
-
- let goalCount = 0
-
- steps.map((goals, i) => {
- // The first step has an empty command and therefore also no error messages
- // Usually there is a newline at the end of the editors content, so we need to
- // display diagnostics from potentally two lines in the last step.
- let messages = i ? (i == steps.length - 1 ? diagnostics.slice(i-1).flat() : diagnostics[i-1]) : []
-
- // Filter out the 'unsolved goals' message
- messages = messages.filter((msg) => {
- return !("append" in msg.message &&
- "text" in msg.message.append[0] &&
- msg.message.append[0].text === "unsolved goals")
- })
-
- if (typeof goals == 'undefined') {
- tmpProof.push({
- command: i ? model.getLineContent(i) : '',
- goals: [],
- hints: [],
- errors: messages
- } as ProofStep)
- console.debug('goals is undefined')
- return
- }
-
- // If the number of goals reduce, show a message
- if (goals.goals.length && goalCount > goals.goals.length) {
- messages.unshift({
- range: {
- start: {
- line: i-1,
- character: 0,
- },
- end: {
- line: i-1,
- character: 0,
- }},
- severity: DiagnosticSeverity.Information,
- message: {
- text: 'intermediate goal solved 🎉'
- }
- })
- }
- goalCount = goals.goals.length
-
- // with no goals there will be no hints.
- let hints : GameHint[] = goals.goals.length ? goals.goals[0].hints : []
-
- console.debug(`Command (${i}): `, i ? model.getLineContent(i) : '')
- console.debug(`Goals: (${i}): `, goalsToString(goals)) //
- console.debug(`Hints: (${i}): `, hints)
- console.debug(`Errors: (${i}): `, messages)
-
- tmpProof.push({
- // the command of the line above. Note that `getLineContent` starts counting
- // at `1` instead of `zero`. The first ProofStep will have an empty command.
- command: i ? model.getLineContent(i) : '',
- // TODO: store correct data
- goals: goals.goals,
- // only need the hints of the active goals in chat
- hints: hints,
- // errors and messages from the server
- errors: messages
- } as ProofStep)
-
- })
- // Save the proof to the context
- setProof(tmpProof)
- }).catch((error) => {console.debug("promise broken")})
- }).catch((error) => {console.debug("promise broken")})
- }, [editor, rpcSess, uri, model])
+ // const loadAllGoals = React.useCallback(() => {
+
+ // let goalCalls = []
+ // let msgCalls = []
+
+ // // For each line of code ask the server for the goals and the messages on this line
+ // for (let i = 0; i < model.getLineCount(); i++) {
+ // goalCalls.push(
+ // rpcSess.call('Game.getInteractiveGoals', DocumentPosition.toTdpp({line: i, character: 0, uri: uri}))
+ // )
+ // msgCalls.push(
+ // getInteractiveDiagnostics(rpcSess, {start: i, end: i+1}).catch((error) => {console.debug("promise broken")})
+ // )
+ // }
+
+ // // Wait for all these requests to be processed before saving the results
+ // Promise.all(goalCalls).then((steps : InteractiveGoalsWithHints[]) => {
+ // Promise.all(msgCalls).then((diagnostics : [InteractiveDiagnostic[]]) => {
+ // let tmpProof : ProofStep[] = []
+
+ // let goalCount = 0
+
+ // steps.map((goals, i) => {
+ // // The first step has an empty command and therefore also no error messages
+ // // Usually there is a newline at the end of the editors content, so we need to
+ // // display diagnostics from potentally two lines in the last step.
+ // let messages = i ? (i == steps.length - 1 ? diagnostics.slice(i-1).flat() : diagnostics[i-1]) : []
+
+ // // Filter out the 'unsolved goals' message
+ // messages = messages.filter((msg) => {
+ // return !("append" in msg.message &&
+ // "text" in msg.message.append[0] &&
+ // msg.message.append[0].text === "unsolved goals")
+ // })
+
+ // if (typeof goals == 'undefined') {
+ // tmpProof.push({
+ // command: i ? model.getLineContent(i) : '',
+ // goals: [],
+ // hints: [],
+ // errors: messages
+ // } as ProofStep)
+ // console.debug('goals is undefined')
+ // return
+ // }
+
+ // // If the number of goals reduce, show a message
+ // if (goals.length && goalCount > goals.length) {
+ // messages.unshift({
+ // range: {
+ // start: {
+ // line: i-1,
+ // character: 0,
+ // },
+ // end: {
+ // line: i-1,
+ // character: 0,
+ // }},
+ // severity: DiagnosticSeverity.Information,
+ // message: {
+ // text: 'intermediate goal solved 🎉'
+ // }
+ // })
+ // }
+ // goalCount = goals.length
+
+ // // with no goals there will be no hints.
+ // let hints : GameHint[] = goals.length ? goals[0].hints : []
+
+ // console.debug(`Command (${i}): `, i ? model.getLineContent(i) : '')
+ // console.debug(`Goals: (${i}): `, goalsToString(goals)) //
+ // console.debug(`Hints: (${i}): `, hints)
+ // console.debug(`Errors: (${i}): `, messages)
+
+ // tmpProof.push({
+ // // the command of the line above. Note that `getLineContent` starts counting
+ // // at `1` instead of `zero`. The first ProofStep will have an empty command.
+ // command: i ? model.getLineContent(i) : '',
+ // // TODO: store correct data
+ // goals: goals.map(g => g.goal),
+ // // only need the hints of the active goals in chat
+ // hints: hints,
+ // // errors and messages from the server
+ // errors: messages
+ // } as ProofStep)
+
+ // })
+ // // Save the proof to the context
+ // setProof(tmpProof)
+ // }).catch((error) => {console.debug("promise broken")})
+ // }).catch((error) => {console.debug("promise broken")})
+ // }, [editor, rpcSess, uri, model])
// Run the command
const runCommand = React.useCallback(() => {
@@ -201,6 +209,8 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
forceMoveMarkers: false
}])
setTypewriterInput('')
+ // Load proof after executing edits
+ loadGoals(rpcSess, uri, setProof)
}
editor.setPosition(pos)
@@ -212,9 +222,15 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
}
}, [typewriterInput])
+ /* Load proof on start/switching to typewriter */
+ useEffect(() => {
+ loadGoals(rpcSess, uri, setProof)
+ }, [])
+
+ /** If the last step has an error, add the command to the typewriter. */
useEffect(() => {
- if (proof.length && hasInteractiveErrors(proof[proof.length - 1].errors)) {
- setTypewriterInput(proof[proof.length - 1].command)
+ if (lastStepHasErrors(proof)) {
+ setTypewriterInput(proof.steps[proof.steps.length - 1].command)
}
}, [proof])
@@ -222,7 +238,9 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => {
if (params.uri == uri) {
setProcessing(false)
- loadAllGoals()
+ //loadGoals(rpcSess, uri, setProof)
+
+ // TODO: loadAllGoals()
if (!hasErrors(params.diagnostics)) {
//setTypewriterInput("")
editor.setPosition(editor.getModel().getFullModelRange().getEndPosition())
@@ -236,6 +254,15 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
// loadAllGoals()
}, [uri]);
+ // React when answer from the server comes back
+ useServerNotificationEffect('$/game/publishDiagnostics', (params: GameDiagnosticsParams) => {
+ console.log('Received game diagnostics')
+ console.log(`diag. uri : ${params.uri}`)
+ console.log(params.diagnostics)
+
+ }, [uri]);
+
+
useEffect(() => {
const myEditor = monaco.editor.create(inputRef.current!, {
value: typewriterInput,
@@ -306,7 +333,8 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
useEffect(() => {
console.debug(`time to update: ${uri} \n ${rpcSess}`)
console.debug(rpcSess)
- loadAllGoals()
+ // console.debug('LOAD ALL GOALS')
+ // TODO: loadAllGoals()
}, [rpcSess])
/** Process the entered command */
@@ -315,7 +343,8 @@ export function Typewriter({hidden, disabled}: {hidden?: boolean, disabled?: boo
runCommand()
}
- return
+ // do not display if the proof is completed (with potential warnings still present)
+ return