diff --git a/examples/package-manual/PackageManual.lean b/examples/package-manual/PackageManual.lean index d7858f748..14f14110b 100644 --- a/examples/package-manual/PackageManual.lean +++ b/examples/package-manual/PackageManual.lean @@ -30,6 +30,8 @@ set_option verso.exampleProject "examples/documented-package" -- `(module := ...)` argument to most elements that show code. set_option verso.exampleModule "Zippers" +open Verso.Code.External + #doc (Manual) "Zippers: A Documentation Example" => %%% authors := ["David Thrane Christiansen"] diff --git a/src/verso-manual/VersoManual.lean b/src/verso-manual/VersoManual.lean index 492e0fbbb..3ec46cfe0 100644 --- a/src/verso-manual/VersoManual.lean +++ b/src/verso-manual/VersoManual.lean @@ -154,6 +154,11 @@ block_extension Block.paragraph where some <| fun _ go _ _ content => do pure <| {{
{{← content.mapM go}}
}} +/-- +Indicates that all the block-level elements contained within the directive are a single logical +paragraph. In HTML output, they are rendered with less space between them, and LaTeX renders them as +a single paragraph (e.g. without extraneous indentation). +-/ @[directive_expander paragraph] def paragraph : DirectiveExpander | #[], stxs => do diff --git a/src/verso/Verso/Code/External.lean b/src/verso/Verso/Code/External.lean index 45315d360..72479b88e 100644 --- a/src/verso/Verso/Code/External.lean +++ b/src/verso/Verso/Code/External.lean @@ -348,14 +348,7 @@ def codeBlockSuggestion (newBlock : String) : Suggestion := let sugg := newBlock.splitOn "\n" |>.map (Std.Format.text ·) |> Std.Format.nil.joinSuffix {suggestion := newBlock, messageData? := sugg} -/-- -Includes the contents of the specified example module. - -Requires that the genre have an `ExternalCode` instance. --/ -@[code_block_expander module] -def module : CodeBlockExpander - | args, code => withTraceNode `Elab.Verso (fun _ => pure m!"module") <| do +def moduleContentBlock (args : Array Arg) (code : StrLit) : DocElabM (Array Term) := do let cfg@{ module := moduleName, anchor?, showProofStates := _, defSite := _ } ← parseThe CodeContext args withAnchored moduleName anchor? fun hl => do logInfos hl @@ -376,35 +369,59 @@ def module : CodeBlockExpander logErrorAt code <| m!"Mismatched code:{indentD mismatch}" ++ h pure #[← ``(leanBlock $(quote hl) $(quote cfg.toCodeConfig))] -macro_rules - | `(block|```anchor $arg:arg_val $args* | $s ```) => - `(block|```module anchor:=$arg $args* | $s ```) - | `(block|```%$tok anchor $args* | $_s ```) => - Macro.throwErrorAt (mkNullNode (#[tok] ++ args)) "Expected a positional identifier as first argument" /-- Includes the contents of the specified example module. Requires that the genre have an `ExternalCode` instance. -/ -@[role_expander module] -def moduleInline : RoleExpander - | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleInline") <| do - let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args - let code? ← oneCodeStr? inls +@[code_block_expander module] +def module : CodeBlockExpander + | args, code => withTraceNode `Elab.Verso (fun _ => pure m!"module") <| do + moduleContentBlock args code - withAnchored moduleName anchor? fun hl => do - logInfos hl - if let some code := code? then - let _ ← ExpectString.expectString "code" code (hl.toString.trim) +/-- +Includes the contents of the specified anchored example. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander anchor] +def anchor : CodeBlockExpander + | args, code => withTraceNode `Elab.Verso (fun _ => pure m!"anchor") <| do + if let some (Arg.anon a) := args[0]? then + moduleContentBlock (#[.named .missing (mkIdent `anchor) a] ++ args.drop 1) code + else + throwError "Expected a positional argument first (the anchor name)" - pure #[← ``(leanInline $(quote hl) $(quote cfg.toCodeConfig))] +def moduleInline (args : Array Arg) (inls : TSyntaxArray `inline) : DocElabM (Array Term) := do + let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args + let code? ← oneCodeStr? inls -macro_rules - | `(inline|role{ anchor $arg:arg_val $args* } [%$t1 $s ]%$t2) => - `(inline|role{ module anchor:=$arg $args* } [%$t1 $s ]%$t2) - | `(inline|role{%$tok anchor $args* } [ $_s ]) => - Macro.throwErrorAt (mkNullNode (#[tok] ++ args)) "Expected a positional identifier as first argument" + withAnchored moduleName anchor? fun hl => do + logInfos hl + if let some code := code? then + let _ ← ExpectString.expectString "code" code (hl.toString.trim) + + pure #[← ``(leanInline $(quote hl) $(quote cfg.toCodeConfig))] + + +/-- +Includes the contents of the specified anchor. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[role_expander module] +def moduleInlineRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleInlineRole") <| do + moduleInline args inls + +@[role_expander anchor] +def anchorInlineRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"anchorInlineRole") <| do + if let some (Arg.anon a) := args[0]? then + moduleInline (#[.named .missing (mkIdent `anchor) a] ++ args.drop 1) inls + else + throwError "Expected a positional argument first (the anchor name)" private partial def allTokens (hl : Highlighted) : HashSet String := match hl with @@ -431,6 +448,28 @@ where mkHover (sig : String) (doc? : Option String) : String := s!"\n\n----------\n\n{d}" else "" +def moduleNameInline (args : Array Arg) (inls : TSyntaxArray `inline) : DocElabM (Array Term) := do + let cfg@{module := moduleName, anchor?, show?, showProofStates := _, defSite := _} ← parseThe NameContext args + let name ← oneCodeStr inls + let nameStr := name.getString + + withAnchored moduleName anchor? fun hl => do + if let some tok@⟨k, _txt⟩ := hl.matchingName? nameStr then + + let tok := show?.map (⟨k, ·.getId.toString⟩) |>.getD tok + if let some h := tokenHover tok then + Hover.addCustomHover name (.markdown h) + return #[← ``(leanInline $(quote (Highlighted.token tok)) $(quote cfg.toCodeConfig))] + else + -- TODO test thresholds/sorting + let ss := smartSuggestions (allNames hl |>.toArray) nameStr + let h ← + if ss.isEmpty then pure m!"" + else hintAt name "Use a known name" ss + logErrorAt name m!"'{nameStr}' not found in:{indentD <| ExpectString.abbreviateString (maxLength := 100) hl.toString}{h}" + + return #[← ``(sorryAx _ true)] + /-- Quotes the first instance of the given name from the module. @@ -438,32 +477,21 @@ Requires that the genre have an `ExternalCode` instance. -/ @[role_expander moduleName] def moduleName : RoleExpander - | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleName") <| do - let cfg@{module := moduleName, anchor?, show?, showProofStates := _, defSite := _} ← parseThe NameContext args - let name ← oneCodeStr inls - let nameStr := name.getString - - withAnchored moduleName anchor? fun hl => do - if let some tok@⟨k, _txt⟩ := hl.matchingName? nameStr then - - let tok := show?.map (⟨k, ·.getId.toString⟩) |>.getD tok - if let some h := tokenHover tok then - Hover.addCustomHover name (.markdown h) - return #[← ``(leanInline $(quote (Highlighted.token tok)) $(quote cfg.toCodeConfig))] - else - -- TODO test thresholds/sorting - let ss := smartSuggestions (allNames hl |>.toArray) nameStr - let h ← - if ss.isEmpty then pure m!"" - else hintAt name "Use a known name" ss - logErrorAt name m!"'{nameStr}' not found in:{indentD <| ExpectString.abbreviateString (maxLength := 100) hl.toString}{h}" - - return #[← ``(sorryAx _ true)] + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleName") <| moduleNameInline args inls +/-- +Quotes the first instance of the given name from the anchor. -macro_rules - | `(inline|role{%$rs anchorName $a:arg_val $arg* }%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleName $arg* anchor:=$a }%$re [%$s $str* ]%$e) +Requires that the genre have an `ExternalCode` instance. +-/ +@[role_expander anchorName] +def anchorName : RoleExpander + | args, inls => + withTraceNode `Elab.Verso (fun _ => pure m!"moduleName") <| + if let some (Arg.anon a) := args[0]? then + moduleNameInline (#[.named .missing (mkIdent `anchor) a] ++ args.drop 1) inls + else + throwError "Expected a positional argument first (the anchor name)" private def suggestTerms (hl : Highlighted) (input : String) : Array String := Id.run do @@ -487,6 +515,33 @@ private def suggestTerms (hl : Highlighted) (input : String) : Array String := I lines ++ (smartSuggestions out.toArray input (threshold := (max ·.length ·.length)) (count := 15)) +def moduleTermInline (args : Array Arg) (inls : TSyntaxArray `inline) : DocElabM (Array Term) := do + let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args + let term ← oneCodeStr inls + + withAnchored moduleName anchor? fun hl => do + if term.getString.trim.isEmpty then + let suggs := suggestTerms hl term.getString + let h ← hintAt term "Use one of these" suggs + let expectedString := ExpectString.abbreviateString (maxLength := 100) <| hl.toString + let mut msg := m!"No expected term provided.\n" + msg := msg ++ m!"in:{indentD <| m!"\n".joinSep <| (m!"{·}") <$> expectedString.splitOn "\n"}" + msg := msg ++ h + logErrorAt term msg + return #[← ``(sorryAx _ true)] + else if let some e := hl.matchingExpr? term.getString then + logInfos e + return #[← ``(leanInline $(quote e) $(quote cfg.toCodeConfig))] + else + let suggs := suggestTerms hl term.getString + let h ← hintAt term "Use one of these" suggs + let expectedString := ExpectString.abbreviateString (maxLength := 100) <| hl.toString + let mut msg := m!"Not found: `{term.getString}`\n" + msg := msg ++ m!"in:{indentD <| m!"\n".joinSep <| (m!"{·}") <$> expectedString.splitOn "\n"}" + msg := msg ++ h + logErrorAt term msg + return #[← ``(sorryAx _ true)] + /-- Quotes the first term that matches the provided string. @@ -494,72 +549,64 @@ Requires that the genre have an `ExternalCode` instance. -/ @[role_expander moduleTerm] def moduleTerm : RoleExpander - | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleTerm") <| do - let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args - let term ← oneCodeStr inls + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleTerm") <| moduleTermInline args inls - withAnchored moduleName anchor? fun hl => do - if term.getString.trim.isEmpty then - let suggs := suggestTerms hl term.getString - let h ← hintAt term "Use one of these" suggs - let expectedString := ExpectString.abbreviateString (maxLength := 100) <| hl.toString - let mut msg := m!"No expected term provided.\n" - msg := msg ++ m!"in:{indentD <| m!"\n".joinSep <| (m!"{·}") <$> expectedString.splitOn "\n"}" - msg := msg ++ h - logErrorAt term msg - return #[← ``(sorryAx _ true)] - else if let some e := hl.matchingExpr? term.getString then - logInfos e - return #[← ``(leanInline $(quote e) $(quote cfg.toCodeConfig))] - else - let suggs := suggestTerms hl term.getString - let h ← hintAt term "Use one of these" suggs - let expectedString := ExpectString.abbreviateString (maxLength := 100) <| hl.toString - let mut msg := m!"Not found: `{term.getString}`\n" - msg := msg ++ m!"in:{indentD <| m!"\n".joinSep <| (m!"{·}") <$> expectedString.splitOn "\n"}" - msg := msg ++ h - logErrorAt term msg - return #[← ``(sorryAx _ true)] - -macro_rules - | `(inline|role{%$rs anchorTerm $a:arg_val $arg* }%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleTerm $arg* anchor:=$a }%$re [%$s $str* ]%$e) +/-- +Quotes the first term in the given anchor that matches the provided string. -@[code_block_expander moduleTerm, inherit_doc moduleTerm] -def moduleTermBlock : CodeBlockExpander - | args, term => withTraceNode `Elab.Verso (fun _ => pure m!"moduleTerm") <| do - let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args +Requires that the genre have an `ExternalCode` instance. +-/ +@[role_expander anchorTerm] +def anchorTerm : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"anchorTerm") <| + if let some (Arg.anon a) := args[0]? then + moduleTermInline (#[.named .missing (mkIdent `anchor) a] ++ args.drop 1) inls + else + throwError "Expected a positional argument first (the anchor name)" - withAnchored moduleName anchor? fun hl => do - let str := term.getString.trim - if str.isEmpty then - let ref ← getRef - let suggs := suggestTerms hl str - let suggs ← suggs.filterMapM fun s => - editCodeBlock ref s - let h ← - if suggs.size > 0 then - hint m!"Use one of these" suggs - else pure m!"" - let wanted := String.trim <| ExpectString.abbreviateString (maxLength := 100) <| hl.toString - let wanted := wanted.splitOn "\n" |>.map (Std.Format.text ·) |> Std.Format.nil.joinSuffix - logErrorAt term (m!"No sub-term of the following was specified: {indentD <| wanted}" ++ h) - return #[← ``(sorryAx _ true)] - if let some e := hl.matchingExpr? str then - logInfos e - return #[← ``(leanBlock $(quote e) $(quote cfg.toCodeConfig))] - else - let ref ← getRef - let suggs := suggestTerms hl str - let suggs ← suggs.filterMapM fun s => - editCodeBlock ref s - let h ← hint "Use one of these" suggs - logErrorAt term (m!"Not found: '{str}' in: {indentD <| ExpectString.abbreviateString (maxLength := 100) <| hl.toString}" ++ h) - return #[← ``(sorryAx _ true)] -macro_rules - | `(block|```anchorTerm $a:arg_val $args* | $s ```) => - `(block|```moduleTerm anchor := $a:arg_val $args* | $s ```) +def moduleTermBlock (args : Array Arg) (term : StrLit) : DocElabM (Array Term) := do + let cfg@{module := moduleName, anchor?, showProofStates := _, defSite := _} ← parseThe CodeContext args + + withAnchored moduleName anchor? fun hl => do + let str := term.getString.trim + if str.isEmpty then + let ref ← getRef + let suggs := suggestTerms hl str + let suggs ← suggs.filterMapM fun s => + editCodeBlock ref s + let h ← + if suggs.size > 0 then + hint m!"Use one of these" suggs + else pure m!"" + let wanted := String.trim <| ExpectString.abbreviateString (maxLength := 100) <| hl.toString + let wanted := wanted.splitOn "\n" |>.map (Std.Format.text ·) |> Std.Format.nil.joinSuffix + logErrorAt term (m!"No sub-term of the following was specified: {indentD <| wanted}" ++ h) + return #[← ``(sorryAx _ true)] + if let some e := hl.matchingExpr? str then + logInfos e + return #[← ``(leanBlock $(quote e) $(quote cfg.toCodeConfig))] + else + let ref ← getRef + let suggs := suggestTerms hl str + let suggs ← suggs.filterMapM fun s => + editCodeBlock ref s + let h ← hint "Use one of these" suggs + logErrorAt term (m!"Not found: '{str}' in: {indentD <| ExpectString.abbreviateString (maxLength := 100) <| hl.toString}" ++ h) + return #[← ``(sorryAx _ true)] + + +@[code_block_expander moduleTerm, inherit_doc moduleTerm] +def moduleTermBlockExp : CodeBlockExpander + | args, term => withTraceNode `Elab.Verso (fun _ => pure m!"moduleTerm") <| moduleTermBlock args term + +@[code_block_expander anchorTerm, inherit_doc anchorTerm] +def anchorTermBlockExp : CodeBlockExpander + | args, term => withTraceNode `Elab.Verso (fun _ => pure m!"moduleTerm") <| + if let some (Arg.anon a) := args[0]? then + moduleTermBlock (#[.named .missing (mkIdent `anchor) a] ++ args.drop 1) term + else + throwError "Expected a positional argument first (the anchor name)" private def severityName {m} [Monad m] [MonadEnv m] [MonadResolveName m] : MessageSeverity → m String | .error => unresolveNameGlobal ``MessageSeverity.error <&> (·.toString) @@ -583,6 +630,54 @@ private partial def findTrace? (header : String) : MessageContents Highlighted if msg.toString == header then pure t else chs.findSome? (findTrace? header) +def outputBlock (args : Array Arg) (str : StrLit) : DocElabM (Array Term) := do + let {module := moduleName, anchor?, severity, expandTraces, onlyTrace, showProofStates := _, defSite := _} ← parseThe MessageContext args + + withAnchored moduleName anchor? fun hl => do + let infos : Array _ := allInfo hl + + let mut candidates : Array Highlighted.Message := #[] + + for (msg, _) in infos do + let msg ← + if let some tr := onlyTrace then + if let some msg' := findTrace? tr msg.contents then + pure {msg with contents := msg'} + else continue + else pure <| msg + candidates := candidates.push msg + if messagesMatch (msg.toString (expandTraces := expandTraces)) str.getString then + if msg.severity == .ofSeverity severity.1 then + return #[← ``(leanOutputBlock $(quote msg) (expandTraces := $(quote expandTraces)))] + else + let wanted ← severityName msg.severity.toSeverity + throwError "Mismatched severity. Expected '{repr severity.1}', got '{wanted}'.{← severityHint wanted severity.2}" + + let suggs : Array Suggestion := candidates.map fun msg => { + suggestion := withNl (msg.toString (expandTraces := expandTraces)), + preInfo? := some s!"{sevStr msg.severity.toSeverity}: " + } + + let ref ← getRef + + let mut err : MessageData := "Expected" + + err := err ++ (m!"\nor".joinSep <| candidates.toList.map fun msg => indentD (msg.toString (expandTraces := expandTraces)) ++ "\n") + + if str.getString.trim.isEmpty then + err := err ++ "but nothing was provided." + else + err := err ++ m!"but got:{indentD str.getString.trim}" + if suggs.size = 1 then + err := err ++ (← hintAt str "Use this:\n" suggs) + else if suggs.size > 1 then + err := err ++ (← hintAt str "Use one of these:\n" suggs) + + logErrorAt ref err + + return #[← ``(sorryAx _ true)] + + /-- Displays output from the example module. @@ -590,14 +685,86 @@ Requires that the genre have an `ExternalCode` instance. -/ @[code_block_expander moduleOut] def moduleOut : CodeBlockExpander - | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOut") <| do - let {module := moduleName, anchor?, severity, expandTraces, onlyTrace, showProofStates := _, defSite := _} ← parseThe MessageContext args + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOut") <| outputBlock args str - withAnchored moduleName anchor? fun hl => do - let infos : Array _ := allInfo hl +/-- +Displays information output from the example module. - let mut candidates : Array Highlighted.Message := #[] +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander moduleInfo] +def moduleInfo : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"moduleInfo") <| + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.information] ++ args) str + +/-- +Displays error output from the example module. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander moduleError] +def moduleError : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"moduleError") <| + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.error] ++ args) str + +/-- +Displays warning output from the example module. +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander moduleWarning] +def moduleWarning : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"moduleWarning") <| + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.warning] ++ args) str + +/-- +Displays information output from the example module's anchor. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander anchorInfo] +def anchorInfo : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"anchorInfo") <| do + if let some (Arg.anon a) := args[0]? then + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.information, .named .missing (mkIdent `anchor) a] ++ args.drop 1) str + else + throwError "Expected a positional argument first (the anchor name)" + +/-- +Displays error output from the example module's anchor. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander anchorError] +def anchorError : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"anchorError") <| do + if let some (Arg.anon a) := args[0]? then + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.error, .named .missing (mkIdent `anchor) a] ++ args.drop 1) str + else + throwError "Expected a positional argument first (the anchor name)" +/-- +Displays warning output from the example module's anchor. + +Requires that the genre have an `ExternalCode` instance. +-/ +@[code_block_expander anchorWarning] +def anchorWarning : CodeBlockExpander + | args, str => withTraceNode `Elab.Verso (fun _ => pure m!"anchorWarning") <| do + if let some (Arg.anon a) := args[0]? then + outputBlock (#[.anon <| .name <| mkIdent ``MessageSeverity.warning, .named .missing (mkIdent `anchor) a] ++ args.drop 1) str + else + throwError "Expected a positional argument first (the anchor name)" + + +def moduleOutInline (args : Array Arg) (inls : TSyntaxArray `inline) : DocElabM (Array Term) := do + let str? ← oneCodeStr? inls + + let {module := moduleName, anchor?, expandTraces, onlyTrace, severity, showProofStates := _, defSite := _} ← parseThe MessageContext args + + withAnchored moduleName anchor? fun hl => do + let infos := allInfo hl + if let some str := str? then + let mut candidates : Array Highlighted.Message := #[] for (msg, _) in infos do let msg ← if let some tr := onlyTrace then @@ -606,113 +773,48 @@ def moduleOut : CodeBlockExpander else continue else pure <| msg candidates := candidates.push msg + if messagesMatch (msg.toString (expandTraces := expandTraces)) str.getString then if msg.severity == .ofSeverity severity.1 then - return #[← ``(leanOutputBlock $(quote msg) (expandTraces := $(quote expandTraces)))] + return #[← ``(leanOutputInline $(quote msg) true (expandTraces := $(quote expandTraces)))] else - let wanted ← severityName msg.severity.toSeverity + let wanted ← severityName msg.severity.toSeverity throwError "Mismatched severity. Expected '{repr severity.1}', got '{wanted}'.{← severityHint wanted severity.2}" + let ref := + if let `(inline|role{ $_ $_* }[ $x ]) := (← getRef) then x.raw else str + let suggs : Array Suggestion := candidates.map fun msg => { - suggestion := withNl (msg.toString (expandTraces := expandTraces)), - preInfo? := some s!"{sevStr msg.severity.toSeverity}: " + suggestion := quoteCode (msg.toString (expandTraces := expandTraces)).trim, + preInfo? := s!"{sevStr msg.severity.toSeverity}: " } - - let ref ← getRef - - let mut err : MessageData := "Expected" - - err := err ++ (m!"\nor".joinSep <| candidates.toList.map fun msg => indentD (msg.toString (expandTraces := expandTraces)) ++ "\n") - - if str.getString.trim.isEmpty then - err := err ++ "but nothing was provided." - else - err := err ++ m!"but got:{indentD str.getString.trim}" - if suggs.size = 1 then - err := err ++ (← hintAt str "Use this:\n" suggs) - else if suggs.size > 1 then - err := err ++ (← hintAt str "Use one of these:\n" suggs) - - logErrorAt ref err - - return #[← ``(sorryAx _ true)] - -macro_rules - | `(block|```moduleInfo $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.information $arg* | $s ```) - | `(block|```moduleError $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.error $arg* | $s ```) - | `(block|```moduleWarning $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.warning $arg* | $s ```) - | `(block|```anchorInfo $a:arg_val $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.information anchor:=$a $arg* | $s ```) - | `(block|```anchorError $a:arg_val $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.error anchor:=$a $arg* | $s ```) - | `(block|```anchorWarning $a:arg_val $arg* | $s ```) => - `(block|```moduleOut MessageSeverity.warning anchor:=$a $arg* | $s ```) - -@[role_expander moduleOut, inherit_doc moduleOut] -def moduleOutRole : RoleExpander - | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOutRole") <| do - let str? ← oneCodeStr? inls - - let {module := moduleName, anchor?, expandTraces, onlyTrace, severity, showProofStates := _, defSite := _} ← parseThe MessageContext args - - - withAnchored moduleName anchor? fun hl => do - let infos := allInfo hl - if let some str := str? then - let mut candidates : Array Highlighted.Message := #[] + let h ← + if suggs.isEmpty then pure m!"" + else hintAt ref "Use one of these." suggs + + let err := + m!"Expected one of:{indentD (m!"\n".joinSep <| candidates.toList.map (·.toString (expandTraces := expandTraces)))}" ++ + m!"\nbut got:{indentD str.getString}\n" ++ h + logErrorAt str err + else + let candidates := infos.filterMap fun (msg, _) => + if let some tr := onlyTrace then + if let some msg' := findTrace? tr msg.contents then + pure {msg with contents := msg'} + else none + else pure <| msg + let err := m!"Expected one of:{indentD (m!"\n".joinSep <| candidates.toList.map (·.toString (expandTraces := expandTraces)))}" + Lean.logError m!"No expected term provided. {err}" + if let `(inline|role{$_ $_*} [%$tok1 $contents* ]%$tok2) := (← getRef) then + let stx := + if tok1.getHeadInfo matches .original .. && tok2.getHeadInfo matches .original .. then + mkNullNode #[tok1, tok2] + else mkNullNode contents for (msg, _) in infos do - let msg ← - if let some tr := onlyTrace then - if let some msg' := findTrace? tr msg.contents then - pure {msg with contents := msg'} - else continue - else pure <| msg - candidates := candidates.push msg - - if messagesMatch (msg.toString (expandTraces := expandTraces)) str.getString then - if msg.severity == .ofSeverity severity.1 then - return #[← ``(leanOutputInline $(quote msg) true (expandTraces := $(quote expandTraces)))] - else - let wanted ← severityName msg.severity.toSeverity - throwError "Mismatched severity. Expected '{repr severity.1}', got '{wanted}'.{← severityHint wanted severity.2}" - - let ref := - if let `(inline|role{ $_ $_* }[ $x ]) := (← getRef) then x.raw else str - - let suggs : Array Suggestion := candidates.map fun msg => { - suggestion := quoteCode (msg.toString (expandTraces := expandTraces)).trim, - preInfo? := s!"{sevStr msg.severity.toSeverity}: " - } - let h ← - if suggs.isEmpty then pure m!"" - else hintAt ref "Use one of these." suggs + let str := msg.toString + Suggestion.saveSuggestion stx (quoteCode <| ExpectString.abbreviateString str.trim) (quoteCode str.trim) - let err := - m!"Expected one of:{indentD (m!"\n".joinSep <| candidates.toList.map (·.toString (expandTraces := expandTraces)))}" ++ - m!"\nbut got:{indentD str.getString}\n" ++ h - logErrorAt str err - else - let candidates := infos.filterMap fun (msg, _) => - if let some tr := onlyTrace then - if let some msg' := findTrace? tr msg.contents then - pure {msg with contents := msg'} - else none - else pure <| msg - let err := m!"Expected one of:{indentD (m!"\n".joinSep <| candidates.toList.map (·.toString (expandTraces := expandTraces)))}" - Lean.logError m!"No expected term provided. {err}" - if let `(inline|role{$_ $_*} [%$tok1 $contents* ]%$tok2) := (← getRef) then - let stx := - if tok1.getHeadInfo matches .original .. && tok2.getHeadInfo matches .original .. then - mkNullNode #[tok1, tok2] - else mkNullNode contents - for (msg, _) in infos do - let str := msg.toString - Suggestion.saveSuggestion stx (quoteCode <| ExpectString.abbreviateString str.trim) (quoteCode str.trim) - - return #[← ``(sorryAx _ true)] + return #[← ``(sorryAx _ true)] where quoteCode (str : String) : String := Id.run do @@ -732,19 +834,46 @@ where let delim := String.mk (List.replicate n '`') return delim ++ str ++ delim -macro_rules - | `(inline|role{%$rs moduleInfo $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.information $arg*}%$re [%$s $str* ]%$e) - | `(inline|role{%$rs moduleError $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.error $arg*}%$re [%$s $str* ]%$e) - | `(inline|role{%$rs moduleWarning $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.warning $arg*}%$re [%$s $str* ]%$e) - | `(inline|role{%$rs anchorInfo $a:arg_val $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.information anchor:=$a $arg*}%$re [%$s $str* ]%$e) - | `(inline|role{%$rs anchorError $a:arg_val $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.error anchor:=$a $arg*}%$re [%$s $str* ]%$e) - | `(inline|role{%$rs anchorWarning $a:arg_val $arg*}%$re [%$s $str* ]%$e) => - `(inline|role{%$rs moduleOut MessageSeverity.warning anchor:=$a $arg*}%$re [%$s $str* ]%$e) + +@[role_expander moduleOut, inherit_doc moduleOut] +def moduleOutRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOutRole") <| moduleOutInline args inls + +@[role_expander moduleInfo, inherit_doc moduleInfo] +def moduleOutInfoRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOutInfoRole") <| + moduleOutInline (#[.anon <| .name <| mkIdent ``MessageSeverity.information] ++ args) inls + +@[role_expander moduleError, inherit_doc moduleError] +def moduleOutErrorRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOutErrorRole") <| + moduleOutInline (#[.anon <| .name <| mkIdent ``MessageSeverity.error] ++ args) inls + +@[role_expander moduleWarning, inherit_doc moduleWarning] +def moduleOutWarningRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"moduleOutWarningRole") <| + moduleOutInline (#[.anon <| .name <| mkIdent ``MessageSeverity.warning] ++ args) inls + +def anchorOutAsRole (severity : Name) (args : Array Arg) (inls : TSyntaxArray `inline) : DocElabM (Array Term) := + if let some (Arg.anon a) := args[0]? then + moduleOutInline (#[.anon <| .name <| mkIdent severity, .named .missing (mkIdent `anchor) a] ++ args.drop 1) inls + else + throwError "Expected a positional argument first (the anchor name)" + +@[role_expander anchorInfo, inherit_doc anchorInfo] +def anchorOutInfoRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"anchorOutInfoRole") <| + anchorOutAsRole ``MessageSeverity.information args inls + +@[role_expander anchorWarning, inherit_doc anchorWarning] +def anchorOutWarningRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"anchorOutWarningRole") <| + anchorOutAsRole ``MessageSeverity.warning args inls + +@[role_expander anchorError, inherit_doc anchorError] +def anchorOutErrorRole : RoleExpander + | args, inls => withTraceNode `Elab.Verso (fun _ => pure m!"anchorOutErrorRole") <| + anchorOutAsRole ``MessageSeverity.error args inls /-- Explicitly indicates that a code element is intended to be literal character values without any diff --git a/src/verso/Verso/Doc/Elab.lean b/src/verso/Verso/Doc/Elab.lean index 373502da8..b715fe969 100644 --- a/src/verso/Verso/Doc/Elab.lean +++ b/src/verso/Verso/Doc/Elab.lean @@ -347,6 +347,12 @@ def includeSection : PartCommand | `(block|block_role{include}) => throwError "Expected an argument" | `(block|block_role{include $arg1 $arg2 $arg3 $args*}) => throwErrorAt arg2 "Expected one or two arguments" | stx@`(block|block_role{include $args* }) => do + Hover.addCustomHover stx + r#"Includes another document at this point in the document. + +* `{include NAME}`: Includes the document as a child part. +* `{include N NAME}`: Includes the document at header level `N`, as if its header had `N` header indicators (`#`) before it. +"# match (← parseArgs args) with | #[.anon (.name x)] => let name ← resolved x