show hints

pull/43/head
Alexander Bentkamp 3 years ago
parent 8175d32a3c
commit b7cc5aaf57

@ -6,20 +6,21 @@ import { InteractiveGoal, InteractiveGoals, InteractiveHypothesisBundle, Interac
import { WithTooltipOnHover } from '../../../../node_modules/lean4-infoview/src/infoview/tooltips'; import { WithTooltipOnHover } from '../../../../node_modules/lean4-infoview/src/infoview/tooltips';
import { EditorContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { EditorContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts';
import { Locations, LocationsContext, SelectableLocation } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation'; import { Locations, LocationsContext, SelectableLocation } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation';
import { GameInteractiveGoal, GameInteractiveGoals } from './rpcApi';
/** Returns true if `h` is inaccessible according to Lean's default name rendering. */ /** Returns true if `h` is inaccessible according to Lean's default name rendering. */
function isInaccessibleName(h: string): boolean { function isInaccessibleName(h: string): boolean {
return h.indexOf('✝') >= 0; return h.indexOf('✝') >= 0;
} }
function goalToString(g: InteractiveGoal): string { function goalToString(g: GameInteractiveGoal): string {
let ret = '' let ret = ''
if (g.userName) { if (g.goal.userName) {
ret += `case ${g.userName}\n` ret += `case ${g.goal.userName}\n`
} }
for (const h of g.hyps) { for (const h of g.goal.hyps) {
const names = InteractiveHypothesisBundle_nonAnonymousNames(h).join(' ') const names = InteractiveHypothesisBundle_nonAnonymousNames(h).join(' ')
ret += `${names} : ${TaggedText_stripTags(h.type)}` ret += `${names} : ${TaggedText_stripTags(h.type)}`
if (h.val) { if (h.val) {
@ -28,12 +29,12 @@ function goalToString(g: InteractiveGoal): string {
ret += '\n' ret += '\n'
} }
ret += `${TaggedText_stripTags(g.type)}` ret += `${TaggedText_stripTags(g.goal.type)}`
return ret return ret
} }
export function goalsToString(goals: InteractiveGoals): string { export function goalsToString(goals: GameInteractiveGoals): string {
return goals.goals.map(goalToString).join('\n\n') return goals.goals.map(goalToString).join('\n\n')
} }
@ -111,7 +112,7 @@ function Hyp({ hyp: h, mvarId }: HypProps) {
} }
interface GoalProps { interface GoalProps {
goal: InteractiveGoal goal: GameInteractiveGoal
filter: GoalFilterState filter: GoalFilterState
} }
@ -121,44 +122,49 @@ interface GoalProps {
export const Goal = React.memo((props: GoalProps) => { export const Goal = React.memo((props: GoalProps) => {
const { goal, filter } = props const { goal, filter } = props
const prefix = goal.goalPrefix ?? 'Prove: ' const prefix = goal.goal.goalPrefix ?? 'Prove: '
const filteredList = getFilteredHypotheses(goal.hyps, filter); const filteredList = getFilteredHypotheses(goal.goal.hyps, filter);
const hyps = filter.reverse ? filteredList.slice().reverse() : filteredList; const hyps = filter.reverse ? filteredList.slice().reverse() : filteredList;
const locs = React.useContext(LocationsContext) const locs = React.useContext(LocationsContext)
const goalLocs = React.useMemo(() => const goalLocs = React.useMemo(() =>
locs && goal.mvarId ? locs && goal.goal.mvarId ?
{ ...locs, subexprTemplate: { mvarId: goal.mvarId, loc: { target: '' }}} : { ...locs, subexprTemplate: { mvarId: goal.goal.mvarId, loc: { target: '' }}} :
undefined, undefined,
[locs, goal.mvarId]) [locs, goal.goal.mvarId])
const goalLi = <div key={'goal'}> const goalLi = <div key={'goal'}>
<strong className="goal-vdash">Prove: </strong> <strong className="goal-vdash">Prove: </strong>
<LocationsContext.Provider value={goalLocs}> <LocationsContext.Provider value={goalLocs}>
<InteractiveCode fmt={goal.type} /> <InteractiveCode fmt={goal.goal.type} />
</LocationsContext.Provider> </LocationsContext.Provider>
</div> </div>
let cn = 'font-code tl pre-wrap bl bw1 pl1 b--transparent ' let cn = 'font-code tl pre-wrap bl bw1 pl1 b--transparent '
if (props.goal.isInserted) cn += 'b--inserted ' if (props.goal.goal.isInserted) cn += 'b--inserted '
if (props.goal.isRemoved) cn += 'b--removed ' if (props.goal.goal.isRemoved) cn += 'b--removed '
if (goal.userName) { // TODO: make this prettier
const hints = goal.messages.map((m) => <div>{m.message}</div>)
if (goal.goal.userName) {
return <details open className={cn}> return <details open className={cn}>
<summary className='mv1 pointer'> <summary className='mv1 pointer'>
<strong className="goal-case">case </strong>{goal.userName} <strong className="goal-case">case </strong>{goal.goal.userName}
</summary> </summary>
{filter.reverse && goalLi} {filter.reverse && goalLi}
{hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.mvarId} key={i} />)} {hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.goal.mvarId} key={i} />)}
{!filter.reverse && goalLi} {!filter.reverse && goalLi}
{hints}
</details> </details>
} else return <div className={cn}> } else return <div className={cn}>
{filter.reverse && goalLi} {filter.reverse && goalLi}
{hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.mvarId} key={i} />)} {hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.goal.mvarId} key={i} />)}
{!filter.reverse && goalLi} {!filter.reverse && goalLi}
{hints}
</div> </div>
}) })
interface GoalsProps { interface GoalsProps {
goals: InteractiveGoals goals: GameInteractiveGoals
filter: GoalFilterState filter: GoalFilterState
} }
@ -179,7 +185,7 @@ interface FilteredGoalsProps {
* When this is `undefined`, the component will not appear at all but will remember its state * When this is `undefined`, the component will not appear at all but will remember its state
* by virtue of still being mounted in the React tree. When it does appear again, the filter * by virtue of still being mounted in the React tree. When it does appear again, the filter
* settings and collapsed state will be as before. */ * settings and collapsed state will be as before. */
goals?: InteractiveGoals goals?: GameInteractiveGoals
} }
/** /**

@ -11,6 +11,7 @@ import { lspDiagToInteractive, MessagesList } from './messages';
import { getInteractiveGoals, getInteractiveTermGoal, InteractiveDiagnostic, import { getInteractiveGoals, getInteractiveTermGoal, InteractiveDiagnostic,
InteractiveGoals, UserWidgetInstance, Widget_getWidgets, RpcSessionAtPos, isRpcError, InteractiveGoals, UserWidgetInstance, Widget_getWidgets, RpcSessionAtPos, isRpcError,
RpcErrorCode, getInteractiveDiagnostics, InteractiveTermGoal } from '@leanprover/infoview-api'; RpcErrorCode, getInteractiveDiagnostics, InteractiveTermGoal } from '@leanprover/infoview-api';
import { GameInteractiveGoal, GameInteractiveGoals } from './rpcApi';
import { PanelWidgetDisplay } from '../../../../node_modules/lean4-infoview/src/infoview/userWidget' import { PanelWidgetDisplay } from '../../../../node_modules/lean4-infoview/src/infoview/userWidget'
import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions';
import { GoalsLocation, Locations, LocationsContext } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation'; import { GoalsLocation, Locations, LocationsContext } from '../../../../node_modules/lean4-infoview/src/infoview/goalLocation';
@ -76,7 +77,7 @@ const InfoStatusBar = React.memo((props: InfoStatusBarProps) => {
interface InfoDisplayContentProps extends PausableProps { interface InfoDisplayContentProps extends PausableProps {
pos: DocumentPosition; pos: DocumentPosition;
messages: InteractiveDiagnostic[]; messages: InteractiveDiagnostic[];
goals?: InteractiveGoals; goals?: GameInteractiveGoals;
termGoal?: InteractiveTermGoal; termGoal?: InteractiveTermGoal;
error?: string; error?: string;
userWidgets: UserWidgetInstance[]; userWidgets: UserWidgetInstance[];
@ -120,11 +121,11 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
{goals && <Goals filter={{ reverse: false, showType: true, showInstance: true, showHiddenAssumption: true, showLetValue: true }} key='goals' goals={goals} />} {goals && <Goals filter={{ reverse: false, showType: true, showInstance: true, showHiddenAssumption: true, showLetValue: true }} key='goals' goals={goals} />}
</LocationsContext.Provider> </LocationsContext.Provider>
<FilteredGoals headerChildren='Expected type' key='term-goal' <FilteredGoals headerChildren='Expected type' key='term-goal'
goals={termGoal !== undefined ? {goals: [termGoal]} : undefined} /> goals={termGoal !== undefined ? {goals: [{goal:termGoal, messages: []}]} : undefined} />
{userWidgets.map(widget => {userWidgets.map(widget =>
<details key={`widget::${widget.id}::${widget.range?.toString()}`} open> <details key={`widget::${widget.id}::${widget.range?.toString()}`} open>
<summary className='mv2 pointer'>{widget.name}</summary> <summary className='mv2 pointer'>{widget.name}</summary>
<PanelWidgetDisplay pos={pos} goals={goals ? goals.goals : []} termGoal={termGoal} <PanelWidgetDisplay pos={pos} goals={goals ? goals.goals.map (goal => goal.goal) : []} termGoal={termGoal}
selectedLocations={selectedLocs} widget={widget}/> selectedLocations={selectedLocs} widget={widget}/>
</details> </details>
)} )}
@ -152,7 +153,7 @@ interface InfoDisplayProps {
pos: DocumentPosition; pos: DocumentPosition;
status: InfoStatus; status: InfoStatus;
messages: InteractiveDiagnostic[]; messages: InteractiveDiagnostic[];
goals?: InteractiveGoals; goals?: GameInteractiveGoals;
termGoal?: InteractiveTermGoal; termGoal?: InteractiveTermGoal;
error?: string; error?: string;
userWidgets: UserWidgetInstance[]; userWidgets: UserWidgetInstance[];

@ -0,0 +1,15 @@
import { InteractiveGoals, InteractiveGoal } from '@leanprover/infoview-api';
export interface GameMessage {
message: string;
spoiler: boolean;
}
export interface GameInteractiveGoal {
goal: InteractiveGoal;
messages: GameMessage[];
}
export interface GameInteractiveGoals {
goals: GameInteractiveGoal[];
}

@ -100,7 +100,7 @@ def matchDecls (declMvars : Array Expr) (declFvars : Array Expr) : MetaM Bool :=
open Meta in open Meta in
/-- Find all messages whose trigger matches the current goal -/ /-- Find all messages whose trigger matches the current goal -/
def findMessages (goal : MVarId) (doc : FileWorker.EditableDocument) (hLog : IO.FS.Stream) : MetaM (Array GameMessage) := do def findMessages (goal : MVarId) (doc : FileWorker.EditableDocument) : MetaM (Array GameMessage) := do
goal.withContext do goal.withContext do
let level ← getLevelByFileName doc.meta.mkInputContext.fileName let level ← getLevelByFileName doc.meta.mkInputContext.fileName
let messages ← level.messages.filterMapM fun message => do let messages ← level.messages.filterMapM fun message => do
@ -109,7 +109,6 @@ def findMessages (goal : MVarId) (doc : FileWorker.EditableDocument) (hLog : IO.
if ← isDefEq messageGoal (← inferType $ mkMVar goal) -- TODO: also check assumptions if ← isDefEq messageGoal (← inferType $ mkMVar goal) -- TODO: also check assumptions
then then
let lctx ← getLCtx -- Local context of the `goal` let lctx ← getLCtx -- Local context of the `goal`
hLog.putStr s!"{← declMvars.mapM inferType} =?= {← lctx.getFVars.mapM inferType}"
if ← matchDecls declMvars lctx.getFVars if ← matchDecls declMvars lctx.getFVars
then then
return some { message := message.message, spoiler := message.spoiler } return some { message := message.message, spoiler := message.spoiler }
@ -117,8 +116,22 @@ def findMessages (goal : MVarId) (doc : FileWorker.EditableDocument) (hLog : IO.
else return none else return none
return messages return messages
structure GameInteractiveGoal where
goal : InteractiveGoal
messages: Array GameMessage
deriving RpcEncodable
structure GameInteractiveGoals where
goals : Array GameInteractiveGoal
deriving RpcEncodable
def GameInteractiveGoals.append (l r : GameInteractiveGoals) : GameInteractiveGoals where
goals := l.goals ++ r.goals
instance : Append GameInteractiveGoals := ⟨GameInteractiveGoals.append⟩
open RequestM in open RequestM in
def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Option Widget.InteractiveGoals)) := do def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Option GameInteractiveGoals)) := do
let doc ← readDoc let doc ← readDoc
let text := doc.meta.text let text := doc.meta.text
let hoverPos := text.lspPosToUtf8Pos p.position let hoverPos := text.lspPosToUtf8Pos p.position
@ -127,29 +140,26 @@ def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Optio
withWaitFindSnap doc (fun s => ¬ (s.infoTree.goalsAt? doc.meta.text hoverPos).isEmpty) withWaitFindSnap doc (fun s => ¬ (s.infoTree.goalsAt? doc.meta.text hoverPos).isEmpty)
(notFoundX := return none) fun snap => do (notFoundX := return none) fun snap => do
if let rs@(_ :: _) := snap.infoTree.goalsAt? doc.meta.text hoverPos then if let rs@(_ :: _) := snap.infoTree.goalsAt? doc.meta.text hoverPos then
let goals : List Widget.InteractiveGoals ← rs.mapM fun { ctxInfo := ci, tacticInfo := ti, useAfter := useAfter, .. } => do let goals : List GameInteractiveGoals ← rs.mapM fun { ctxInfo := ci, tacticInfo := ti, useAfter := useAfter, .. } => do
let ciAfter := { ci with mctx := ti.mctxAfter } let ciAfter := { ci with mctx := ti.mctxAfter }
let ci := if useAfter then ciAfter else { ci with mctx := ti.mctxBefore } let ci := if useAfter then ciAfter else { ci with mctx := ti.mctxBefore }
-- compute the interactive goals -- compute the interactive goals
let goals ← ci.runMetaM {} (do let goals ← ci.runMetaM {} do
let goals := List.toArray <| if useAfter then ti.goalsAfter else ti.goalsBefore return List.toArray <| if useAfter then ti.goalsAfter else ti.goalsBefore
let goals ← goals.mapM Widget.goalToInteractive let goals ← ci.runMetaM {} do
return {goals} goals.mapM fun goal => do
) let messages ← findMessages goal doc
return {goal := ← Widget.goalToInteractive goal, messages}
-- compute the goal diff -- compute the goal diff
let goals ← ciAfter.runMetaM {} (do -- let goals ← ciAfter.runMetaM {} (do
try -- try
Widget.diffInteractiveGoals useAfter ti goals -- Widget.diffInteractiveGoals useAfter ti goals
catch _ => -- catch _ =>
-- fail silently, since this is just a bonus feature -- -- fail silently, since this is just a bonus feature
return goals -- return goals
) -- )
-- TODO: add hints return {goals}
-- let goals ← ci.runMetaM {} $ goals.mapM fun goal => do return some <| goals.foldl (· ++ ·) ⟨#[]⟩
-- let messages ← findMessages goal doc hLog
-- return ← goal.toGameGoal messages
return goals
return some <| goals.foldl (· ++ ·) ∅
else else
return none return none
@ -157,7 +167,7 @@ builtin_initialize
registerBuiltinRpcProcedure registerBuiltinRpcProcedure
`Game.getInteractiveGoals `Game.getInteractiveGoals
Lsp.PlainGoalParams Lsp.PlainGoalParams
(Option InteractiveGoals) (Option GameInteractiveGoals)
getInteractiveGoals getInteractiveGoals
structure Diagnostic where structure Diagnostic where

Loading…
Cancel
Save