custom interactive goal data types

pull/43/head
Alexander Bentkamp 3 years ago
parent 09aae16693
commit ca366d4bde

@ -2,11 +2,11 @@
import * as React from 'react' import * as React from 'react'
import { InteractiveCode } from '../../../../node_modules/lean4-infoview/src/infoview/interactiveCode' import { InteractiveCode } from '../../../../node_modules/lean4-infoview/src/infoview/interactiveCode'
import { InteractiveGoal, InteractiveGoals, InteractiveHypothesisBundle, InteractiveHypothesisBundle_nonAnonymousNames, MVarId, TaggedText_stripTags } from '@leanprover/infoview-api' import { InteractiveHypothesisBundle, InteractiveHypothesisBundle_nonAnonymousNames, MVarId, TaggedText_stripTags } from '@leanprover/infoview-api'
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'; import { InteractiveGoal, InteractiveGoals } from './rpcApi';
import { Hints } from './hints'; import { Hints } from './hints';
/** 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. */
@ -14,14 +14,14 @@ function isInaccessibleName(h: string): boolean {
return h.indexOf('✝') >= 0; return h.indexOf('✝') >= 0;
} }
function goalToString(g: GameInteractiveGoal): string { function goalToString(g: InteractiveGoal): string {
let ret = '' let ret = ''
if (g.goal.userName) { if (g.userName) {
ret += `case ${g.goal.userName}\n` ret += `case ${g.userName}\n`
} }
for (const h of g.goal.hyps) { for (const h of g.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) {
@ -30,12 +30,12 @@ function goalToString(g: GameInteractiveGoal): string {
ret += '\n' ret += '\n'
} }
ret += `${TaggedText_stripTags(g.goal.type)}` ret += `${TaggedText_stripTags(g.type)}`
return ret return ret
} }
export function goalsToString(goals: GameInteractiveGoals): string { export function goalsToString(goals: InteractiveGoals): string {
return goals.goals.map(goalToString).join('\n\n') return goals.goals.map(goalToString).join('\n\n')
} }
@ -113,7 +113,7 @@ function Hyp({ hyp: h, mvarId }: HypProps) {
} }
interface GoalProps { interface GoalProps {
goal: GameInteractiveGoal goal: InteractiveGoal
filter: GoalFilterState filter: GoalFilterState
} }
@ -123,48 +123,48 @@ 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.goal.goalPrefix ?? 'Prove: ' const prefix = goal.goalPrefix ?? 'Prove: '
const filteredList = getFilteredHypotheses(goal.goal.hyps, filter); const filteredList = getFilteredHypotheses(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.goal.mvarId ? locs && goal.mvarId ?
{ ...locs, subexprTemplate: { mvarId: goal.goal.mvarId, loc: { target: '' }}} : { ...locs, subexprTemplate: { mvarId: goal.mvarId, loc: { target: '' }}} :
undefined, undefined,
[locs, goal.goal.mvarId]) [locs, 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.goal.type} /> <InteractiveCode fmt={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.goal.isInserted) cn += 'b--inserted ' if (props.goal.isInserted) cn += 'b--inserted '
if (props.goal.goal.isRemoved) cn += 'b--removed ' if (props.goal.isRemoved) cn += 'b--removed '
const hints = <Hints hints={goal.hints} /> const hints = <Hints hints={goal.hints} />
if (goal.goal.userName) { if (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.goal.userName} <strong className="goal-case">case </strong>{goal.userName}
</summary> </summary>
{filter.reverse && goalLi} {filter.reverse && goalLi}
{hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.goal.mvarId} key={i} />)} {hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.mvarId} key={i} />)}
{!filter.reverse && goalLi} {!filter.reverse && goalLi}
{hints} {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.goal.mvarId} key={i} />)} {hyps.map((h, i) => <Hyp hyp={h} mvarId={goal.mvarId} key={i} />)}
{!filter.reverse && goalLi} {!filter.reverse && goalLi}
{hints} {hints}
</div> </div>
}) })
interface GoalsProps { interface GoalsProps {
goals: GameInteractiveGoals goals: InteractiveGoals
filter: GoalFilterState filter: GoalFilterState
} }
@ -185,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?: GameInteractiveGoals goals?: InteractiveGoals
} }
/** /**

@ -8,10 +8,10 @@ import { basename, DocumentPosition, RangeHelpers, useEvent, usePausableState, d
mapRpcError, useAsyncWithTrigger, PausableProps } from '../../../../node_modules/lean4-infoview/src/infoview/util'; mapRpcError, useAsyncWithTrigger, PausableProps } from '../../../../node_modules/lean4-infoview/src/infoview/util';
import { ConfigContext, EditorContext, LspDiagnosticsContext, ProgressContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { ConfigContext, EditorContext, LspDiagnosticsContext, ProgressContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts';
import { lspDiagToInteractive, MessagesList } from './messages'; import { lspDiagToInteractive, MessagesList } from './messages';
import { getInteractiveGoals, getInteractiveTermGoal, InteractiveDiagnostic, import { getInteractiveTermGoal, InteractiveDiagnostic,
InteractiveGoals, UserWidgetInstance, Widget_getWidgets, RpcSessionAtPos, isRpcError, 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 { InteractiveGoal, InteractiveGoals } 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';
@ -77,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?: GameInteractiveGoals; goals?: InteractiveGoals;
termGoal?: InteractiveTermGoal; termGoal?: InteractiveTermGoal;
error?: string; error?: string;
userWidgets: UserWidgetInstance[]; userWidgets: UserWidgetInstance[];
@ -121,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: [{goal:termGoal, hints: []}]} : undefined} /> goals={termGoal !== undefined ? {goals: [termGoal as any]} : 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.map (goal => goal.goal) : []} termGoal={termGoal} <PanelWidgetDisplay pos={pos} goals={goals ? goals.goals.map (goal => goal) : []} termGoal={termGoal}
selectedLocations={selectedLocs} widget={widget}/> selectedLocations={selectedLocs} widget={widget}/>
</details> </details>
)} )}
@ -153,7 +153,7 @@ interface InfoDisplayProps {
pos: DocumentPosition; pos: DocumentPosition;
status: InfoStatus; status: InfoStatus;
messages: InteractiveDiagnostic[]; messages: InteractiveDiagnostic[];
goals?: GameInteractiveGoals; goals?: InteractiveGoals;
termGoal?: InteractiveTermGoal; termGoal?: InteractiveTermGoal;
error?: string; error?: string;
userWidgets: UserWidgetInstance[]; userWidgets: UserWidgetInstance[];

@ -1,15 +1,27 @@
import { InteractiveGoals, InteractiveGoal } from '@leanprover/infoview-api'; import { ContextInfo, InteractiveHypothesisBundle, CodeWithInfos, MVarId } from '@leanprover/infoview-api';
export interface GameHint { export interface GameHint {
text: string; text: string;
hidden: boolean; hidden: boolean;
} }
export interface GameInteractiveGoal { export interface InteractiveGoalCore {
goal: InteractiveGoal; 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;
hints: GameHint[]; hints: GameHint[];
} }
export interface GameInteractiveGoals { export interface InteractiveGoals {
goals: GameInteractiveGoal[]; goals: InteractiveGoal[];
} }

@ -0,0 +1,197 @@
/- This file is mostly copied from `Lean/Widget/InteractiveGoal.lean`. -/
import Lean.Widget.InteractiveGoal
/-! Functionality related to tactic-mode and term-mode goals with embedded `CodeWithInfos`. -/
namespace GameServer
open Lean Lean.Widget Lean.Server
structure GameHint where
text : String
hidden : Bool
deriving FromJson, ToJson
/-- In the infoview, if multiple hypotheses `h₁`, `h₂` have the same type `α`, they are rendered
as `h₁ h₂ : α`. We call this a 'hypothesis bundle'. We use `none` instead of `some false` for
booleans to save space in the json encoding. -/
structure InteractiveHypothesisBundle where
/-- The user-friendly name for each hypothesis. -/
names : Array Name
/-- The ids for each variable. Should have the same length as `names`. -/
fvarIds : Array FVarId
type : CodeWithInfos
/-- The value, in the case the hypothesis is a `let`-binder. -/
val? : Option CodeWithInfos := none
/-- The hypothesis is a typeclass instance. -/
isInstance? : Option Bool := none
/-- The hypothesis is a type. -/
isType? : Option Bool := none
/-- If true, the hypothesis was not present on the previous tactic state.
Only present in tactic-mode goals. -/
isInserted? : Option Bool := none
/-- If true, the hypothesis will be removed in the next tactic state.
Only present in tactic-mode goals. -/
isRemoved? : Option Bool := none
deriving Inhabited, RpcEncodable
/-- The shared parts of interactive term-mode and tactic-mode goals. -/
structure InteractiveGoalCore where
hyps : Array InteractiveHypothesisBundle
/-- The target type. -/
type : CodeWithInfos
/-- Metavariable context that the goal is well-typed in. -/
ctx : WithRpcRef Elab.ContextInfo
/-- An interactive tactic-mode goal. -/
structure InteractiveGoal extends InteractiveGoalCore where
/-- The name `foo` in `case foo`, if any. -/
userName? : Option String
/-- The symbol to display before the target type. Usually `⊢ ` but `conv` goals use ` `
and it could be extended. -/
goalPrefix : String
/-- Identifies the goal (ie with the unique name of the MVar that it is a goal for.) -/
mvarId : MVarId
/-- If true, the goal was not present on the previous tactic state. -/
isInserted? : Option Bool := none
/-- If true, the goal will be removed on the next tactic state. -/
isRemoved? : Option Bool := none
hints : Array GameHint := #[]
deriving RpcEncodable
/-- An interactive term-mode goal. -/
structure InteractiveTermGoal extends InteractiveGoalCore where
/-- Syntactic range of the term. -/
range : Lsp.Range
/-- Information about the term whose type is the term-mode goal. -/
term : WithRpcRef Elab.TermInfo
deriving RpcEncodable
def InteractiveGoalCore.pretty (g : InteractiveGoalCore) (userName? : Option String)
(goalPrefix : String) : Format := Id.run do
let indent := 2 -- Use option
let mut ret := match userName? with
| some userName => f!"case {userName}"
| none => Format.nil
for hyp in g.hyps do
ret := addLine ret
let names := hyp.names
|>.toList
|>.filter (not ∘ Name.isAnonymous)
|>.map toString
|> " ".intercalate
match names with
| "" =>
ret := ret ++ Format.group f!":{Format.nest indent (Format.line ++ hyp.type.stripTags)}"
| _ =>
match hyp.val? with
| some val =>
ret := ret ++ Format.group f!"{names} : {hyp.type.stripTags} :={Format.nest indent (Format.line ++ val.stripTags)}"
| none =>
ret := ret ++ Format.group f!"{names} :{Format.nest indent (Format.line ++ hyp.type.stripTags)}"
ret := addLine ret
ret ++ f!"{goalPrefix}{Format.nest indent g.type.stripTags}"
where
addLine (fmt : Format) : Format :=
if fmt.isNil then fmt else fmt ++ Format.line
def InteractiveGoal.pretty (g : InteractiveGoal) : Format :=
g.toInteractiveGoalCore.pretty g.userName? g.goalPrefix
def InteractiveTermGoal.pretty (g : InteractiveTermGoal) : Format :=
g.toInteractiveGoalCore.pretty none "⊢ "
structure InteractiveGoals where
goals : Array InteractiveGoal
deriving RpcEncodable
def InteractiveGoals.append (l r : InteractiveGoals) : InteractiveGoals where
goals := l.goals ++ r.goals
instance : Append InteractiveGoals := ⟨InteractiveGoals.append⟩
instance : EmptyCollection InteractiveGoals := ⟨{goals := #[]}⟩
open Meta in
/-- Extend an array of hypothesis bundles with another bundle. -/
def addInteractiveHypothesisBundle (hyps : Array InteractiveHypothesisBundle)
(ids : Array (Name × FVarId)) (type : Expr) (value? : Option Expr := none) :
MetaM (Array InteractiveHypothesisBundle) := do
if ids.size == 0 then
throwError "Can only add a nonzero number of ids as an InteractiveHypothesisBundle."
let fvarIds := ids.map Prod.snd
let names := ids.map Prod.fst
return hyps.push {
names
fvarIds
type := (← ppExprTagged type)
val? := (← value?.mapM ppExprTagged)
isInstance? := if (← isClass? type).isSome then true else none
isType? := if (← instantiateMVars type).isSort then true else none
}
open Meta in
variable [MonadControlT MetaM n] [Monad n] [MonadError n] [MonadOptions n] [MonadMCtx n] in
def withGoalCtx (goal : MVarId) (action : LocalContext → MetavarDecl → n α) : n α := do
let mctx ← getMCtx
let some mvarDecl := mctx.findDecl? goal
| throwError "unknown goal {goal.name}"
let lctx := mvarDecl.lctx |>.sanitizeNames.run' {options := (← getOptions)}
withLCtx lctx mvarDecl.localInstances (action lctx mvarDecl)
open Meta in
/-- A variant of `Meta.ppGoal` which preserves subexpression information for interactivity. -/
def goalToInteractive (mvarId : MVarId) (hints : Array GameHint): MetaM InteractiveGoal := do
let ppAuxDecls := pp.auxDecls.get (← getOptions)
let ppImplDetailHyps := pp.implementationDetailHyps.get (← getOptions)
let showLetValues := pp.showLetValues.get (← getOptions)
withGoalCtx mvarId fun lctx mvarDecl => do
let pushPending (ids : Array (Name × FVarId)) (type? : Option Expr) (hyps : Array InteractiveHypothesisBundle)
: MetaM (Array InteractiveHypothesisBundle) :=
if ids.isEmpty then
pure hyps
else
match type? with
| none => pure hyps
| some type => addInteractiveHypothesisBundle hyps ids type
let mut varNames : Array (Name × FVarId) := #[]
let mut prevType? : Option Expr := none
let mut hyps : Array InteractiveHypothesisBundle := #[]
for localDecl in lctx do
if !ppAuxDecls && localDecl.isAuxDecl || !ppImplDetailHyps && localDecl.isImplementationDetail then
continue
else
match localDecl with
| LocalDecl.cdecl _index fvarId varName type _ _ =>
let varName := varName.simpMacroScopes
let type ← instantiateMVars type
if prevType? == none || prevType? == some type then
varNames := varNames.push (varName, fvarId)
else
hyps ← pushPending varNames prevType? hyps
varNames := #[(varName, fvarId)]
prevType? := some type
| LocalDecl.ldecl _index fvarId varName type val _ _ => do
let varName := varName.simpMacroScopes
hyps ← pushPending varNames prevType? hyps
let type ← instantiateMVars type
let val? ← if showLetValues then pure (some (← instantiateMVars val)) else pure none
hyps ← addInteractiveHypothesisBundle hyps #[(varName, fvarId)] type val?
varNames := #[]
prevType? := none
hyps ← pushPending varNames prevType? hyps
let goalTp ← instantiateMVars mvarDecl.type
let goalFmt ← ppExprTagged goalTp
let userName? := match mvarDecl.userName with
| Name.anonymous => none
| name => some <| toString name.eraseMacroScopes
return {
hyps
type := goalFmt
ctx := ⟨← Elab.ContextInfo.save⟩
userName?
goalPrefix := getGoalPrefix mvarDecl
mvarId
hints
}
end GameServer

@ -1,5 +1,6 @@
import Lean import Lean
import GameServer.EnvExtensions import GameServer.EnvExtensions
import GameServer.InteractiveGoal
open Lean open Lean
open Server open Server
@ -10,11 +11,6 @@ open Meta
/-! ## GameGoal -/ /-! ## GameGoal -/
structure GameHint where
text : String
hidden : Bool
deriving FromJson, ToJson
namespace GameServer namespace GameServer
-- TODO: Find a better way to pass on the file name? -- TODO: Find a better way to pass on the file name?
@ -72,22 +68,8 @@ def findHints (goal : MVarId) (doc : FileWorker.EditableDocument) : MetaM (Array
else return none else return none
return hints return hints
structure GameInteractiveGoal where
goal : InteractiveGoal
hints: Array GameHint
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 GameInteractiveGoals)) := do def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Option InteractiveGoals)) := 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
@ -96,7 +78,7 @@ 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 GameInteractiveGoals ← rs.mapM fun { ctxInfo := ci, tacticInfo := ti, useAfter := useAfter, .. } => do let goals : List InteractiveGoals ← 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
@ -105,7 +87,7 @@ def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Optio
let goals ← ci.runMetaM {} do let goals ← ci.runMetaM {} do
goals.mapM fun goal => do goals.mapM fun goal => do
let hints ← findHints goal doc let hints ← findHints goal doc
return {goal := Widget.goalToInteractive goal, hints} return ← goalToInteractive goal hints
-- compute the goal diff -- compute the goal diff
-- let goals ← ciAfter.runMetaM {} (do -- let goals ← ciAfter.runMetaM {} (do
-- try -- try
@ -123,7 +105,7 @@ builtin_initialize
registerBuiltinRpcProcedure registerBuiltinRpcProcedure
`Game.getInteractiveGoals `Game.getInteractiveGoals
Lsp.PlainGoalParams Lsp.PlainGoalParams
(Option GameInteractiveGoals) (Option InteractiveGoals)
getInteractiveGoals getInteractiveGoals
end GameServer end GameServer

Loading…
Cancel
Save