Skip to content

Commit 4abb984

Browse files
feat: inline element for Lean commands (#698)
* Refactors a useful helper to make it more broadly accessible. * Adds a `leanCommand` role to the inline Lean code infrastructure.
1 parent 862ba94 commit 4abb984

File tree

3 files changed

+89
-35
lines changed

3 files changed

+89
-35
lines changed

src/verso-manual/VersoManual/InlineLean.lean

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,12 @@ private def toHighlightedLeanInline (shouldShow : Bool) (hls : Highlighted) (str
213213
``(Inline.other (Verso.Genre.Manual.InlineLean.Inline.lean $(← quoteHighlightViaSerialization hls)) #[Inline.code $(quote str.getString)])
214214

215215

216-
/--
217-
Elaborates the provided Lean command in the context of the current Verso module.
218-
-/
219-
@[code_block]
220-
def lean : CodeBlockExpanderOf LeanBlockConfig
221-
| config, str => withoutAsync <| do
222-
216+
def elabCommands (config : LeanBlockConfig) (str : StrLit)
217+
(toHighlightedLeanContent : (shouldShow : Bool) → (hls : Highlighted) → (str: StrLit) → DocElabM Term)
218+
(minCommands : Option Nat := none)
219+
(maxCommands : Option Nat := none) :
220+
DocElabM Term :=
221+
withoutAsync <| do
223222
PointOfInterest.save (← getRef) ((config.name.map (·.toString)).getD (abbrevFirstLine 20 str.getString))
224223
(kind := Lsp.SymbolKind.file)
225224
(detail? := some ("Lean code" ++ config.outlineMeta))
@@ -247,14 +246,24 @@ def lean : CodeBlockExpanderOf LeanBlockConfig
247246
let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages
248247
cmds := cmds.push cmd
249248
pstate := ps'
250-
cmdState := {cmdState with messages := messages}
249+
cmdState := { cmdState with messages := messages }
251250

252251

253252
cmdState ← withInfoTreeContext (mkInfoTree := pure ∘ InfoTree.node (.ofCommandInfo {elaborator := `Manual.Meta.lean, stx := cmd})) <|
254253
runCommand (Command.elabCommand cmd) cmd cctx cmdState
255254

256255
if Parser.isTerminalCommand cmd then break
257256

257+
let nonTerm := cmds.filter (! Parser.isTerminalCommand ·)
258+
if let some maxCmds := maxCommands then
259+
if h : nonTerm.size > maxCmds then
260+
logErrorAt nonTerm.back m!"Expected at most {maxCmds} commands, but got {nonTerm.size} commands."
261+
262+
if let some minCmds := minCommands then
263+
if h : nonTerm.size < minCmds then
264+
let blame := nonTerm[0]? |>.getD (← getRef)
265+
logErrorAt blame m!"Expected at least {minCmds} commands, but got {nonTerm.size} commands."
266+
258267
let origEnv ← getEnv
259268
try
260269
setEnv cmdState.env
@@ -271,7 +280,7 @@ def lean : CodeBlockExpanderOf LeanBlockConfig
271280
hls := hls ++ (← highlightIncludingUnparsed cmd nonSilentMsgs cmdState.infoState.trees (startPos? := lastPos))
272281
lastPos := (cmd.getTrailingTailPos?).getD lastPos
273282

274-
toHighlightedLeanBlock config.show hls str
283+
toHighlightedLeanContent config.show hls str
275284
finally
276285
if !config.keep then
277286
setEnv origEnv
@@ -308,6 +317,22 @@ where
308317
| .error _ => pure cmdState
309318
| .ok ((), cmdState) => pure cmdState
310319

320+
/--
321+
Elaborates the provided Lean command in the context of the current Verso module.
322+
-/
323+
@[code_block]
324+
def lean : CodeBlockExpanderOf LeanBlockConfig
325+
| config, str => elabCommands config str toHighlightedLeanBlock
326+
327+
@[role]
328+
def leanCommand : RoleExpanderOf LeanBlockConfig
329+
| config, inls => do
330+
if let some str ← oneCodeStr? inls then
331+
elabCommands config str toHighlightedLeanInline (minCommands := some 1) (maxCommands := some 1)
332+
else
333+
`(sorry)
334+
335+
311336
/--
312337
Elaborates the provided Lean term in the context of the current Verso module.
313338
-/

src/verso/Verso/Code/External.lean

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public import Verso.Doc
88
import Verso.Doc.ArgParse
99
import Verso.Doc.Elab
1010
public import Verso.Doc.Elab.Monad
11+
public meta import Verso.Doc.Helpers
1112
public meta import Verso.Code.External.Code
1213
import Verso.Code.External.Code
1314
public import Verso.Code.External.Config
@@ -62,32 +63,7 @@ public class ExternalCode (genre : Genre) where
6263

6364
open ExternalCode
6465

65-
private meta def oneCodeStr [Monad m] [MonadError m] (inlines : Array (TSyntax `inline)) : m StrLit := do
66-
let #[code] := inlines
67-
| (if inlines.size == 0 then (throwError ·) else (throwErrorAt (mkNullNode inlines) ·)) "Expected one code element"
68-
let `(inline|code($code)) := code
69-
| throwErrorAt code "Expected a code element"
70-
return code
71-
72-
private meta def oneCodeStr? [Monad m] [MonadError m] [MonadLog m] [AddMessageContext m] [MonadOptions m]
73-
(inlines : Array (TSyntax `inline)) : m (Option StrLit) := do
74-
let #[code] := inlines
75-
| if inlines.size == 0 then
76-
Lean.logError "Expected a code element"
77-
else
78-
logErrorAt (mkNullNode inlines) "Expected one code element"
79-
return none
80-
let `(inline|code($code)) := code
81-
| logErrorAt code "Expected a code element"
82-
return none
83-
return some code
84-
85-
86-
private meta def oneCodeName [Monad m] [MonadError m] (inlines : Array (TSyntax `inline)) : m Ident := do
87-
let code ← oneCodeStr inlines
88-
let str := code.getString
89-
let name := if str.contains '.' then str.toName else Name.str .anonymous str
90-
return mkIdentFrom code name
66+
9167

9268
section
9369

src/verso/Verso/Doc/Helpers.lean

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Author: David Thrane Christiansen
5+
-/
6+
module
7+
import Lean.DocString.Syntax
8+
public import Lean.Exception
9+
public import Lean.Log
10+
11+
set_option doc.verso true
12+
13+
open Lean Doc.Syntax
14+
15+
namespace Verso.Doc
16+
17+
/--
18+
If {name}`inlines` contains exactly one code inline, its contents are returned. Throws an error
19+
otherwise.
20+
-/
21+
public def oneCodeStr [Monad m] [MonadError m] (inlines : Array (TSyntax `inline)) : m StrLit := do
22+
let #[code] := inlines
23+
| (if inlines.size == 0 then (throwError ·) else (throwErrorAt (mkNullNode inlines) ·)) "Expected one code element"
24+
let `(inline|code($code:str)) := code
25+
| throwErrorAt code "Expected a code element"
26+
return code
27+
28+
/--
29+
If {name}`inlines` contains exactly one code inline, its contents are returned. Otherwise, an error
30+
is logged and {name}`none` is returned.
31+
-/
32+
public def oneCodeStr? [Monad m] [MonadError m] [MonadLog m] [AddMessageContext m] [MonadOptions m]
33+
(inlines : Array (TSyntax `inline)) : m (Option StrLit) := do
34+
let #[code] := inlines
35+
| if inlines.size == 0 then
36+
Lean.logError "Expected a code element"
37+
else
38+
logErrorAt (mkNullNode inlines) "Expected one code element"
39+
return none
40+
let `(inline|code($code)) := code
41+
| logErrorAt code "Expected a code element"
42+
return none
43+
return some code
44+
45+
/--
46+
If {name}`inlines` contains exactly one Lean name, it is returned with its source location as an
47+
identifier. Otherwise, an error is thrown.
48+
-/
49+
public def oneCodeName [Monad m] [MonadError m] (inlines : Array (TSyntax `inline)) : m Ident := do
50+
let code ← oneCodeStr inlines
51+
let str := code.getString
52+
let name := if str.contains '.' then str.toName else Name.str .anonymous str
53+
return mkIdentFrom code name

0 commit comments

Comments
 (0)