diff --git a/Main.lean b/Main.lean index 1dc5ed6..eef68ef 100644 --- a/Main.lean +++ b/Main.lean @@ -8,6 +8,7 @@ import LeanDoc.Doc.Html import LeanDoc.Output import LeanDoc.Output.Html import LeanDoc.Doc.Concrete +import LeanDoc.Doc.Lsp open LeanDoc Doc Output Html Concrete ToHtml Elab Monad open Lean Elab Term @@ -22,7 +23,7 @@ def vanish : RoleExpander def rev : RoleExpander | _args, stxs => .reverse <$> stxs.mapM elabInline -def html [Monad m] (doc : Part .none) : m Html := Genre.none.toHtml {logError := fun _ => pure ()} () () doc +def html [Monad m] (doc : Doc .none) : m Html := Genre.none.toHtml {logError := fun _ => pure ()} () () doc def main : IO Unit := do IO.println <| Html.asString <| Html.embody <| ← html <| #doc (.none) "My wonderful document" => @@ -59,6 +60,12 @@ As someone said: Block code too ``` +[foo][my link][^foo] + ## Other idea +[my link]: https://example.com + +[^foo]: blah blah blah *BLAH*! + ## Further subsection diff --git a/src/leanblog/LeanDoc/Genre/Blog/Basic.lean b/src/leanblog/LeanDoc/Genre/Blog/Basic.lean index 25364a2..2fc5c8f 100644 --- a/src/leanblog/LeanDoc/Genre/Blog/Basic.lean +++ b/src/leanblog/LeanDoc/Genre/Blog/Basic.lean @@ -97,12 +97,9 @@ namespace Blog structure Post where date : Date authors : List String - content : Part Blog + content : Doc Blog draft : Bool -deriving TypeName - -instance : Inhabited Post where - default := ⟨default, default, default, default⟩ +deriving TypeName, Inhabited partial def TraverseState.freshId (state : Blog.TraverseState) (path : List String) (hint : Lean.Name) : String := Id.run do let mut idStr := mangle (toString hint) diff --git a/src/leanblog/LeanDoc/Genre/Blog/Generate.lean b/src/leanblog/LeanDoc/Genre/Blog/Generate.lean index d8a758d..5cd8437 100644 --- a/src/leanblog/LeanDoc/Genre/Blog/Generate.lean +++ b/src/leanblog/LeanDoc/Genre/Blog/Generate.lean @@ -89,7 +89,7 @@ def inPage (here : Page) (act : GenerateM α) : GenerateM α := withReader (fun c => {c with ctxt.path := c.ctxt.path ++ [here.name]}) act def inPost (here : Post) (act : GenerateM α) : GenerateM α := - withReader (fun c => {c with ctxt.path := c.ctxt.path ++ [c.ctxt.config.postName here.date here.content.titleString ]}) act + withReader (fun c => {c with ctxt.path := c.ctxt.path ++ [c.ctxt.config.postName here.date here.content.content.titleString ]}) act @@ -125,7 +125,7 @@ mutual if post.draft && !(← showDrafts) then continue inPost post <| do let ⟨baseTemplate, modParams⟩ := theme.adHocTemplates (Array.mk (← currentPath)) |>.getD ⟨theme.postTemplate, id⟩ - let output ← Template.renderMany [baseTemplate, theme.primaryTemplate] <| modParams <| ← forPart post.content + let output ← Template.renderMany [baseTemplate, theme.primaryTemplate] <| modParams <| ← forPart post.content.content ensureDir (← currentDir) IO.println s!"Generating post {← currentDir}" IO.FS.withFile ((← currentDir).join "index.html") .write fun h => do @@ -142,7 +142,7 @@ mutual some <$> ps.mapM fun p => theme.archiveEntryTemplate.render (.ofList [("post", ⟨.mk p, #[]⟩)]) else pure none - let pageParams : Template.Params ← forPart txt + let pageParams : Template.Params ← forPart txt.content let ⟨baseTemplate, modParams⟩ := theme.adHocTemplates (Array.mk (← currentPath)) |>.getD ⟨theme.pageTemplate, id⟩ let output ← Template.renderMany [baseTemplate, theme.primaryTemplate] <| modParams <| match postList with @@ -164,7 +164,7 @@ def Site.generate (theme : Theme) (site : Site): GenerateM Unit := do let root ← currentDir ensureDir root let ⟨baseTemplate, modParams⟩ := theme.adHocTemplates (Array.mk (← currentPath)) |>.getD ⟨theme.pageTemplate, id⟩ - let output ← Template.renderMany [baseTemplate, theme.primaryTemplate] <| modParams <| ← forPart site.frontPage + let output ← Template.renderMany [baseTemplate, theme.primaryTemplate] <| modParams <| ← forPart site.frontPage.content let filename := root.join "index.html" IO.FS.withFile filename .write fun h => do h.putStrLn "" diff --git a/src/leanblog/LeanDoc/Genre/Blog/Site.lean b/src/leanblog/LeanDoc/Genre/Blog/Site.lean index de79a78..0a8ae08 100644 --- a/src/leanblog/LeanDoc/Genre/Blog/Site.lean +++ b/src/leanblog/LeanDoc/Genre/Blog/Site.lean @@ -10,7 +10,7 @@ inductive Dir α where deriving Inhabited defmethod Post.traverse1 (post : Post) : Blog.TraverseM Post := - withReader (fun ctxt => {ctxt with path := ctxt.path ++ [ctxt.config.postName post.date post.content.titleString]}) <| do + withReader (fun ctxt => {ctxt with path := ctxt.path ++ [ctxt.config.postName post.date post.content.content.titleString]}) <| do let content' ← Blog.traverse post.content pure {post with content := content'} @@ -20,7 +20,7 @@ def Dir.traverse1 (dir : Dir α) (sub : α → Blog.TraverseM α) : Blog.Travers | .blog posts => .blog <$> posts.mapM Post.traverse1 inductive Page where - | page (name : String) (id : Lean.Name) (text : Part Blog) (contents : Dir Page) + | page (name : String) (id : Lean.Name) (text : Doc Blog) (contents : Dir Page) | static (name : String) (files : System.FilePath) instance : Inhabited Page where @@ -41,7 +41,7 @@ partial def Page.traverse1 (nav : Page) : Blog.TraverseM Page := do | .static .. => pure nav structure Site where - frontPage : Part Blog + frontPage : Doc Blog contents : Dir Page def Site.traverse1 (site : Site) : Blog.TraverseM Site := do @@ -69,7 +69,7 @@ class MonadPath (m : Type → Type u) where export MonadPath (currentPath) def postName [Monad m] [MonadConfig m] (post : Post) : m String := do - pure <| (← currentConfig).postName post.date post.content.titleString + pure <| (← currentConfig).postName post.date post.content.content.titleString def relative [Monad m] [MonadConfig m] [MonadPath m] (target : List String) : m (List String) := do return relativize (← currentPath) target diff --git a/src/leanblog/LeanDoc/Genre/Blog/Theme.lean b/src/leanblog/LeanDoc/Genre/Blog/Theme.lean index 1f250b3..5abd674 100644 --- a/src/leanblog/LeanDoc/Genre/Blog/Theme.lean +++ b/src/leanblog/LeanDoc/Genre/Blog/Theme.lean @@ -31,12 +31,12 @@ def dirLinks : Dir Page → TemplateM (Array Html) | .pages subs => subs.filterMapM fun | .page name _id txt .. => - pure <| some {{
  • {{txt.titleString}}
  • }} + pure <| some {{
  • {{txt.content.titleString}}
  • }} | .static .. => pure none | .blog subs => subs.mapM fun s => do - let url ← mkLink [(← currentConfig).postName s.date s.content.titleString] - return {{
  • {{s.content.titleString}}
  • }} + let url ← mkLink [(← currentConfig).postName s.date s.content.content.titleString] + return {{
  • {{s.content.content.titleString}}
  • }} where mkLink dest := do let dest' ← relative dest @@ -91,7 +91,7 @@ def archiveEntry : Template := do | Date.mk y m d => {{ s!"{y}-{m}-{d}" }} }} " — " - {{post.content.titleString}} + {{post.content.content.titleString}} }}] diff --git a/src/leandoc/LeanDoc/Doc.lean b/src/leandoc/LeanDoc/Doc.lean index 830a991..59f5f46 100644 --- a/src/leandoc/LeanDoc/Doc.lean +++ b/src/leandoc/LeanDoc/Doc.lean @@ -54,6 +54,7 @@ inductive Inline (genre : Genre) : Type where | code (string : String) | linebreak (string : String) | link (content : Array (Inline genre)) (dest : LinkDest) + | footnote (name : String) | image (alt : String) (dest : LinkDest) | concat (content : Array (Inline genre)) | other (container : genre.Inline) (content : Array (Inline genre)) @@ -61,9 +62,19 @@ inductive Inline (genre : Genre) : Type where private def reprArray (r : α → Nat → Format) (arr : Array α) : Format := .bracket "#[" (.joinSep (arr.toList.map (r · max_prec)) ("," ++ .line)) "]" +private def reprList (r : α → Nat → Format) (xs : List α) : Format := + .bracket "[" (.joinSep (xs.map (r · max_prec)) ("," ++ .line)) "]" + +private def reprPair (x : α → Nat → Format) (y : β → Nat → Format) (v : α × β) : Format := + .bracket "(" (x v.fst max_prec ++ ("," ++.line) ++ y v.snd max_prec) ")" + private def reprCtor (c : Name) (args : List Format) : Format := .nest 2 <| .group (.joinSep (.text s!"{c}" :: args) .line) +private def reprHash [BEq α] [Hashable α] (k : α → Nat → Format) (v : β → Nat → Format) (h : Lean.HashMap α β) : Format := + reprCtor ``Lean.HashMap.ofList [reprList (fun x _ => reprPair k v x) h.toList] + + partial def Inline.reprPrec [Repr g.Inline] (inline : Inline g) (prec : Nat) : Std.Format := open Repr Std.Format in let rec go i p := @@ -78,6 +89,7 @@ partial def Inline.reprPrec [Repr g.Inline] (inline : Inline g) (prec : Nat) : S reprArray go content, reprArg dest ] + | .footnote name => reprCtor ``Inline.footnote [reprArg name] | .image content dest => reprCtor ``Inline.image [ reprArg content, reprArg dest @@ -175,6 +187,20 @@ partial def Part.reprPrec [Repr g.Inline] [Repr g.Block] [Repr g.PartMetadata] ( instance [Repr g.Inline] [Repr g.Block] [Repr g.PartMetadata] : Repr (Part g) := ⟨Part.reprPrec⟩ +structure Doc (genre : Genre) where + content : Part genre + linkRefs : Lean.HashMap String String + footnotes : Lean.HashMap String (Array (Inline genre)) +deriving Inhabited + +instance [Repr k] [Repr v] [BEq k] [Hashable k] : Repr (Lean.HashMap k v) where + reprPrec h _n := reprCtor ``Lean.HashMap.ofList [reprArg h.toList] + +def Doc.reprPrec [Repr (Inline g)] [Repr (Part g)] (doc : Doc g) (prec : Nat) : Std.Format := + reprCtor ``Doc.mk [reprArg doc.content, reprArg doc.linkRefs, reprArg doc.footnotes] + +instance [Repr g.Inline] [Repr g.Block] [Repr g.PartMetadata] [Repr (Part g)] : Repr (Doc g) := ⟨Doc.reprPrec⟩ + class Traverse (g : Genre) (m : outParam (Type → Type)) where part [MonadReader g.TraverseContext m] [MonadState g.TraverseState m] : Part g → m Unit block [MonadReader g.TraverseContext m] [MonadState g.TraverseState m] : Block g → m Unit @@ -187,8 +213,8 @@ class Traverse (g : Genre) (m : outParam (Type → Type)) where partial def Genre.traverse (g : Genre) [Traverse g m] [Monad m] [MonadReader g.TraverseContext m] [MonadState g.TraverseState m] - (top : Part g) : m (Part g) := - part top + (top : Doc g) : m (Doc g) := + doc top where inline (i : Doc.Inline g) : m (Doc.Inline g) := do @@ -197,6 +223,7 @@ where | .emph content => .emph <$> content.mapM inline | .bold content => .bold <$> content.mapM inline | .link content ref => (.link · ref) <$> content.mapM inline + | .footnote name => pure <| .footnote name | .image alt ref => pure <| .image alt ref | .concat content => .concat <$> content.mapM inline | .other container content => do @@ -230,3 +257,6 @@ where p := p' let .mk title titleString meta content subParts := p pure <| .mk (← title.mapM inline) titleString meta (← content.mapM block) (← subParts.mapM part) + + doc (d : Doc g) : m (Doc g) := do + pure <| .mk (← part d.content) d.linkRefs (← d.footnotes.foldM (fun fs k v => do pure <| fs.insert k (← v.mapM inline)) .empty) diff --git a/src/leandoc/LeanDoc/Doc/Concrete.lean b/src/leandoc/LeanDoc/Doc/Concrete.lean index a368472..5a041b5 100644 --- a/src/leandoc/LeanDoc/Doc/Concrete.lean +++ b/src/leandoc/LeanDoc/Doc/Concrete.lean @@ -51,14 +51,14 @@ def inlineStr := inStrLit <| textLine elab "inlines!" s:inlineStr : term => open Lean Elab Term in match s.raw with | ` => do - let tms ← DocElabM.run (.init (← `(foo))) <| out.mapM elabInline + let (tms, _) ← DocElabM.run {} (.init (← `(foo))) <| out.mapM elabInline elabTerm (← `(term| #[ $[$tms],* ] )) none | _ => throwUnsupportedSyntax set_option pp.rawOnError true /-- -info: #[Inline.text "Hello, ", Inline.emph #[Inline.bold #[Inline.text "emph"]]] : Array (Inline ?m.10489) +info: #[Inline.text "Hello, ", Inline.emph #[Inline.bold #[Inline.text "emph"]]] : Array (Inline ?m.11031) -/ #guard_msgs in #check inlines!"Hello, _*emph*_" @@ -89,14 +89,22 @@ elab "#docs" "(" genre:term ")" n:ident title:inlineStr ":=" ":::::::" text:docu let ⟨`⟩ := title | dbg_trace "nope {ppSyntax title}" throwUnsupportedSyntax let titleString := inlinesToString (← getEnv) titleParts - let ((), st) ← liftTermElabM <| PartElabM.run (.init titleName) <| do + let ((), st, st') ← liftTermElabM <| PartElabM.run {} (.init titleName) <| do setTitle titleString (← liftDocElabM <| titleParts.mapM elabInline) for b in blocks do partCommand b closePartsUntil 0 endPos pure () - let finished := st.partContext.toPartFrame.close endPos + let finished := st'.partContext.toPartFrame.close endPos pushInfoLeaf <| .ofCustomInfo {stx := (← getRef) , value := Dynamic.mk finished.toTOC} - elabCommand (← `(def $n : Part $genre := $(← finished.toSyntax))) + for r in internalRefs st'.linkDefs st.linkRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + for r in internalRefs st'.footnoteDefs st.footnoteRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + let linkRefs : List (String × String) := st'.linkDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + let footnoteRefs : List (String × Array (TSyntax `term)) := st'.footnoteDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + elabCommand (← `(def $n : Doc $genre := {content := $(← finished.toSyntax), linkRefs := HashMap.ofList $(quote linkRefs), footnotes := HashMap.ofList $(quote footnoteRefs)})) elab "#doc" "(" genre:term ")" title:inlineStr "=>" text:document eof:eoi : term => open Lean Elab Term PartElabM DocElabM in do @@ -107,14 +115,23 @@ elab "#doc" "(" genre:term ")" title:inlineStr "=>" text:document eof:eoi : term let ⟨`⟩ := title | dbg_trace "nope {ppSyntax title}" throwUnsupportedSyntax let titleString := inlinesToString (← getEnv) titleParts - let ((), st) ← PartElabM.run (.init titleName) <| do + let ((), st, st') ← PartElabM.run {} (.init titleName) <| do setTitle titleString (← liftDocElabM <| titleParts.mapM elabInline) for b in blocks do partCommand b closePartsUntil 0 endPos pure () - let finished := st.partContext.toPartFrame.close endPos + let finished := st'.partContext.toPartFrame.close endPos pushInfoLeaf <| .ofCustomInfo {stx := (← getRef) , value := Dynamic.mk finished.toTOC} - elabTerm (← `(($(← finished.toSyntax) : Part $genre))) none + for r in internalRefs st'.linkDefs st.linkRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + for r in internalRefs st'.footnoteDefs st.footnoteRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + + let linkRefs : List (String × String) := st'.linkDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + let footnoteRefs : List (String × Array (TSyntax `term)) := st'.footnoteDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + elabTerm (← `(({content := $(← finished.toSyntax), linkRefs := HashMap.ofList $(quote linkRefs), footnotes := HashMap.ofList $(quote footnoteRefs) : Doc $genre}))) none macro "%doc" moduleName:ident : term => @@ -137,12 +154,21 @@ elab "#doc" "(" genre:term ")" title:inlineStr "=>" text:document eof:eoi : comm let ⟨`⟩ := title | dbg_trace "nope {ppSyntax title}" throwUnsupportedSyntax let titleString := inlinesToString (← getEnv) titleParts - let ((), st) ← liftTermElabM <| PartElabM.run (.init titleName) <| do + let ((), st, st') ← liftTermElabM <| PartElabM.run {} (.init titleName) <| do setTitle titleString (← liftDocElabM <| titleParts.mapM elabInline) for b in blocks do partCommand b closePartsUntil 0 endPos pure () - let finished := st.partContext.toPartFrame.close endPos + let finished := st'.partContext.toPartFrame.close endPos pushInfoLeaf <| .ofCustomInfo {stx := (← getRef) , value := Dynamic.mk finished.toTOC} let docName ← mkIdentFrom title <$> currentDocName - elabCommand (← `(def $docName : Part $genre := $(← finished.toSyntax))) + let linkRefs : List (String × String) := st'.linkDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + let footnoteRefs : List (String × Array (TSyntax `term)) := st'.footnoteDefs.toList.map (fun (k, v) => (k, DocDef.val v)) + for r in internalRefs st'.linkDefs st.linkRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + for r in internalRefs st'.footnoteDefs st.footnoteRefs do + for stx in r.syntax do + pushInfoLeaf <| .ofCustomInfo {stx := stx , value := Dynamic.mk r} + + elabCommand (← `(def $docName : Doc $genre := {content := $(← finished.toSyntax), linkRefs := HashMap.ofList $(quote linkRefs), footnotes := HashMap.ofList $(quote footnoteRefs)})) diff --git a/src/leandoc/LeanDoc/Doc/Elab.lean b/src/leandoc/LeanDoc/Doc/Elab.lean index 4e07393..cd6b84c 100644 --- a/src/leandoc/LeanDoc/Doc/Elab.lean +++ b/src/leandoc/LeanDoc/Doc/Elab.lean @@ -12,6 +12,7 @@ namespace LeanDoc.Doc.Elab open Lean Elab open PartElabM +open DocElabM open LeanDoc.Syntax def throwUnexpected [Monad m] [MonadError m] (stx : Syntax) : m α := @@ -27,7 +28,6 @@ partial def elabInline (inline : Syntax) : DocElabM (TSyntax `term) := for e in exp do try let termStx ← withFreshMacroScope <| e stx - return termStx catch | ex@(.internal id) => @@ -118,9 +118,21 @@ def _root_.LeanDoc.Syntax.link.expand : InlineExpander match dest with | `(link_target| ( $url )) => ``(Inline.link #[$[$(← txt.mapM elabInline)],*] (LinkDest.url $url)) + | `(link_target| [ $ref ]) => do + addLinkRef ref + -- Round-trip through quote to get rid of source locations, preventing unwanted IDE info + ``(Inline.link #[$[$(← txt.mapM elabInline)],*] (LinkDest.ref $(quote ref.getString))) | _ => withRef dest throwUnsupportedSyntax | _ => throwUnsupportedSyntax +@[inline_expander LeanDoc.Syntax.footnote] +def _root_.LeanDoc.Syntax.link.footnote : InlineExpander + | `(inline| [^ $name:str ]) => do + addFootnoteRef name + ``(Inline.footnote $(quote name.getString)) + | _ => throwUnsupportedSyntax + + @[inline_expander LeanDoc.Syntax.image] def _root_.LeanDoc.Syntax.image.expand : InlineExpander | `(inline| image[ $alt:str* ] $dest:link_target) => do @@ -184,13 +196,25 @@ where partial def closePartsUntil (outer : Nat) (endPos : String.Pos) : PartElabM Unit := do let level ← currentLevel if outer ≤ level then - match (← get).partContext.close endPos with + match (← getThe PartElabM.State).partContext.close endPos with | some ctxt' => - modify fun st => {st with partContext := ctxt'} + modifyThe PartElabM.State fun st => {st with partContext := ctxt'} if outer < level then closePartsUntil outer endPos | none => pure () +@[part_command LeanDoc.Syntax.footnote_ref] +partial def _root_.LeanDoc.Syntax.footnote_ref.command : PartCommand + | `(block| [^ $name:str ]: $contents* ) => + addFootnoteDef name =<< contents.mapM (elabInline ·.raw) + | _ => throwUnsupportedSyntax + +@[part_command LeanDoc.Syntax.link_ref] +partial def _root_.LeanDoc.Syntax.link_ref.command : PartCommand + | `(block| [ $name:str ]: $url:str ) => + addLinkDef name url.getString + | stx => dbg_trace "{stx}"; throwUnsupportedSyntax + @[part_command LeanDoc.Syntax.header] partial def _root_.LeanDoc.Syntax.header.command : PartCommand @@ -305,7 +329,7 @@ def _root_.LeanDoc.Syntax.blockquote.expand : BlockExpander @[block_expander LeanDoc.Syntax.codeblock] def _root_.LeanDoc.Syntax.codeblock.expand : BlockExpander - | ` => do + | ` => do let name ← resolveGlobalConstNoOverloadWithInfo nameStx let exp ← codeBlockExpandersFor name -- TODO typed syntax here @@ -321,7 +345,7 @@ def _root_.LeanDoc.Syntax.codeblock.expand : BlockExpander | ex => throw ex dbg_trace "No code block expander for '{nameStx}' ---> '{name}'" throwUnsupportedSyntax - | ` => + | ` => ``(Block.code Option.none #[] $(Syntax.mkNumLit col) $(quote contents)) | _ => throwUnsupportedSyntax diff --git a/src/leandoc/LeanDoc/Doc/Elab/Monad.lean b/src/leandoc/LeanDoc/Doc/Elab/Monad.lean index e749945..2accb8f 100644 --- a/src/leandoc/LeanDoc/Doc/Elab/Monad.lean +++ b/src/leandoc/LeanDoc/Doc/Elab/Monad.lean @@ -136,89 +136,156 @@ def PartContext.close (ctxt : PartContext) (endPos : String.Pos) : Option PartCo def PartContext.push (ctxt : PartContext) (fr : PartFrame) : PartContext := ⟨fr, ctxt.parents.push ctxt.toPartFrame⟩ +/-- References that must be local to the current blob of concrete document syntax -/ +structure DocDef (α : Type) where + defSite : Syntax + val : α + +structure DocUses where + useSites : Array Syntax := {} + +def DocUses.add (uses : DocUses) (loc : Syntax) : DocUses := {uses with useSites := uses.useSites.push loc} + structure PartElabM.State where partContext : PartContext -deriving Repr + linkDefs : HashMap String (DocDef String) := {} + footnoteDefs : HashMap String (DocDef (Array (TSyntax `term))) := {} +structure DocElabM.State where + linkRefs : HashMap String DocUses := {} + footnoteRefs : HashMap String DocUses := {} -def PartElabM.State.init (title : Syntax) (expandedTitle : Option (String × Array (TSyntax `term)) := none) : PartElabM.State where - partContext := {titleSyntax := title, expandedTitle, blocks := #[], priorParts := #[], parents := #[]} +/-- Custom info tree data to save footnote and reflink cross-references -/ +structure DocRefInfo where + defSite : Option Syntax + useSites : Array Syntax +deriving TypeName, Repr -def PartElabM (α : Type) : Type := StateT PartElabM.State TermElabM α +def DocRefInfo.syntax (dri : DocRefInfo) : Array Syntax := + (dri.defSite.map (#[·])|>.getD #[]) ++ dri.useSites -def PartElabM.run (st : PartElabM.State) (act : PartElabM α) : TermElabM (α × PartElabM.State) := - StateT.run act st +def internalRefs (defs : HashMap String (DocDef α)) (refs : HashMap String DocUses) : Array DocRefInfo := Id.run do + let keys : HashSet String := defs.fold (fun soFar k _ => HashSet.insert soFar k) <| refs.fold (fun soFar k _ => soFar.insert k) {} + let mut refInfo := #[] + for k in keys do + refInfo := refInfo.push ⟨defs.find? k |>.map (·.defSite), refs.find? k |>.map (·.useSites) |>.getD #[]⟩ + refInfo +def PartElabM.State.init (title : Syntax) (expandedTitle : Option (String × Array (TSyntax `term)) := none) : PartElabM.State where + partContext := {titleSyntax := title, expandedTitle, blocks := #[], priorParts := #[], parents := #[]} -instance : MonadRef PartElabM := inferInstanceAs <| MonadRef (StateT PartElabM.State TermElabM) +def PartElabM (α : Type) : Type := StateT DocElabM.State (StateT PartElabM.State TermElabM) α -instance : AddErrorMessageContext PartElabM := inferInstanceAs <| AddErrorMessageContext (StateT PartElabM.State TermElabM) +def PartElabM.run (st : DocElabM.State) (st' : PartElabM.State) (act : PartElabM α) : TermElabM (α × DocElabM.State × PartElabM.State) := do + let ((res, st), st') ← act st st' + pure (res, st, st') -instance : MonadQuotation PartElabM := inferInstanceAs <| MonadQuotation (StateT PartElabM.State TermElabM) +instance : MonadRef PartElabM := inferInstanceAs <| MonadRef (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : Monad PartElabM := inferInstanceAs <| Monad (StateT PartElabM.State TermElabM) +instance : AddErrorMessageContext PartElabM := inferInstanceAs <| AddErrorMessageContext (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadLift TermElabM PartElabM := inferInstanceAs <| MonadLift TermElabM (StateT PartElabM.State TermElabM) +instance : MonadQuotation PartElabM := inferInstanceAs <| MonadQuotation (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadExceptOf Exception PartElabM := inferInstanceAs <| MonadExceptOf Exception (StateT PartElabM.State TermElabM) +instance : Monad PartElabM := inferInstanceAs <| Monad (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadState PartElabM.State PartElabM := inferInstanceAs <| MonadState PartElabM.State (StateT PartElabM.State TermElabM) +instance : MonadLift TermElabM PartElabM where + monadLift act := fun st st' => do return ((← act, st), st') -instance : MonadStateOf PartElabM.State PartElabM := inferInstanceAs <| MonadStateOf PartElabM.State (StateT PartElabM.State TermElabM) +instance : MonadExceptOf Exception PartElabM := inferInstanceAs <| MonadExceptOf Exception (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadStateOf PartElabM.State PartElabM := inferInstanceAs <| MonadStateOf PartElabM.State (StateT PartElabM.State TermElabM) +instance : MonadStateOf DocElabM.State PartElabM := inferInstanceAs <| MonadStateOf DocElabM.State (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadFinally PartElabM := inferInstanceAs <| MonadFinally (StateT PartElabM.State TermElabM) +instance : MonadStateOf PartElabM.State PartElabM := inferInstanceAs <| MonadStateOf PartElabM.State (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -instance : MonadRef PartElabM := inferInstanceAs <| MonadRef (StateT PartElabM.State TermElabM) +instance : MonadFinally PartElabM := inferInstanceAs <| MonadFinally (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -def DocElabM (α : Type) : Type := ReaderT PartElabM.State TermElabM α +instance : MonadRef PartElabM := inferInstanceAs <| MonadRef (StateT DocElabM.State (StateT PartElabM.State TermElabM)) -def DocElabM.run (st : PartElabM.State) (act : DocElabM α) : TermElabM α := - ReaderT.run act st +def DocElabM (α : Type) : Type := ReaderT PartElabM.State (StateT DocElabM.State TermElabM) α -instance : Inhabited (DocElabM α) := inferInstanceAs <| Inhabited (ReaderT PartElabM.State TermElabM α) +def DocElabM.run (st : DocElabM.State) (st' : PartElabM.State) (act : DocElabM α) : TermElabM (α × DocElabM.State) := do + StateT.run (act st') st -instance : AddErrorMessageContext DocElabM := inferInstanceAs <| AddErrorMessageContext (ReaderT PartElabM.State TermElabM) +instance : Inhabited (DocElabM α) := ⟨fun _ _ => default⟩ -instance : MonadLift TermElabM DocElabM := inferInstanceAs <| MonadLift TermElabM (ReaderT PartElabM.State TermElabM) +instance : AddErrorMessageContext DocElabM := inferInstanceAs <| AddErrorMessageContext (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadRef DocElabM := inferInstanceAs <| MonadRef (ReaderT PartElabM.State TermElabM) +instance : MonadLift TermElabM DocElabM where + monadLift act := fun _ st' => do return (← act, st') -instance : MonadQuotation DocElabM := inferInstanceAs <| MonadQuotation (ReaderT PartElabM.State TermElabM) +instance : MonadRef DocElabM := inferInstanceAs <| MonadRef (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : Monad DocElabM := inferInstanceAs <| Monad (ReaderT PartElabM.State TermElabM) +instance : MonadQuotation DocElabM := inferInstanceAs <| MonadQuotation (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadLift TermElabM DocElabM := inferInstanceAs <| MonadLift TermElabM (ReaderT PartElabM.State TermElabM) +instance : Monad DocElabM := inferInstanceAs <| Monad (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadExceptOf Exception DocElabM := inferInstanceAs <| MonadExceptOf Exception (ReaderT PartElabM.State TermElabM) +instance : MonadExceptOf Exception DocElabM := inferInstanceAs <| MonadExceptOf Exception (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadReader PartElabM.State DocElabM := inferInstanceAs <| MonadReader PartElabM.State (ReaderT PartElabM.State TermElabM) +instance : MonadReader PartElabM.State DocElabM := inferInstanceAs <| MonadReader PartElabM.State (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadReaderOf PartElabM.State DocElabM := inferInstanceAs <| MonadReaderOf PartElabM.State (ReaderT PartElabM.State TermElabM) +instance : MonadReaderOf PartElabM.State DocElabM := inferInstanceAs <| MonadReaderOf PartElabM.State (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadReaderOf PartElabM.State DocElabM := inferInstanceAs <| MonadReaderOf PartElabM.State (ReaderT PartElabM.State TermElabM) +instance : MonadStateOf DocElabM.State DocElabM := inferInstanceAs <| MonadStateOf DocElabM.State (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadFinally DocElabM := inferInstanceAs <| MonadFinally (ReaderT PartElabM.State TermElabM) +instance : MonadFinally DocElabM := inferInstanceAs <| MonadFinally (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) -instance : MonadRecDepth DocElabM := inferInstanceAs <| MonadRecDepth (ReaderT PartElabM.State TermElabM) +instance : MonadRecDepth DocElabM where + withRecDepth n act := fun st st' => MonadRecDepth.withRecDepth n (act st st') + getRecDepth := fun _ st' => do return (← MonadRecDepth.getRecDepth, st') + getMaxRecDepth := fun _ st' => do return (← MonadRecDepth.getMaxRecDepth, st') -def PartElabM.liftDocElabM (act : DocElabM α) : PartElabM α := do act.run (← get) +def PartElabM.liftDocElabM (act : DocElabM α) : PartElabM α := do + let (out, st') ← act.run (← getThe DocElabM.State) (← getThe PartElabM.State) + set st' + pure out instance : MonadLift DocElabM PartElabM := ⟨PartElabM.liftDocElabM⟩ -def PartElabM.currentLevel : PartElabM Nat := do return (← get).partContext.level +def PartElabM.currentLevel : PartElabM Nat := do return (← getThe State).partContext.level -def PartElabM.setTitle (titlePreview : String) (titleInlines : Array (TSyntax `term)) : PartElabM Unit := modify fun st => +def PartElabM.setTitle (titlePreview : String) (titleInlines : Array (TSyntax `term)) : PartElabM Unit := modifyThe State fun st => {st with partContext.expandedTitle := some (titlePreview, titleInlines)} -def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := modify fun st => +def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := modifyThe State fun st => {st with partContext.blocks := st.partContext.blocks.push block} -def PartElabM.push (fr : PartFrame) : PartElabM Unit := modify fun st => {st with partContext := st.partContext.push fr} +def PartElabM.addLinkDef (refName : TSyntax `str) (url : String) : PartElabM Unit := do + let strName := refName.getString + match (← getThe State).linkDefs.find? strName with + | none => + modifyThe State fun st => {st with linkDefs := st.linkDefs.insert strName ⟨refName, url⟩} + | some ⟨_, url'⟩ => + throwErrorAt refName "Already defined as '{url'}'" + +def DocElabM.addLinkRef (refName : TSyntax `str) : DocElabM Unit := do + let strName := refName.getString + match (← getThe State).linkRefs.find? strName with + | none => + modifyThe State fun st => {st with linkRefs := st.linkRefs.insert strName ⟨#[refName]⟩} + | some ⟨uses⟩ => + modifyThe State fun st => {st with linkRefs := st.linkRefs.insert strName ⟨uses.push refName⟩} + +def PartElabM.addFootnoteDef (refName : TSyntax `str) (content : Array (TSyntax `term)) : PartElabM Unit := do + let strName := refName.getString + match (← getThe State).footnoteDefs.find? strName with + | none => + modifyThe State fun st => {st with footnoteDefs := st.footnoteDefs.insert strName ⟨refName, content⟩} + | some ⟨_, url'⟩ => + throwErrorAt refName "Already defined as '{url'}'" + +def DocElabM.addFootnoteRef (refName : TSyntax `str) : DocElabM Unit := do + let strName := refName.getString + match (← getThe State).footnoteRefs.find? strName with + | none => + modifyThe State fun st => {st with footnoteRefs := st.footnoteRefs.insert strName ⟨#[refName]⟩} + | some ⟨uses⟩ => + modifyThe State fun st => {st with footnoteRefs := st.footnoteRefs.insert strName ⟨uses.push refName⟩} + + +def PartElabM.push (fr : PartFrame) : PartElabM Unit := modifyThe State fun st => {st with partContext := st.partContext.push fr} def PartElabM.debug (msg : String) : PartElabM Unit := do - let st ← get + let st ← getThe State dbg_trace "DEBUG: {msg}" dbg_trace " partContext: {repr st.partContext}" dbg_trace "" @@ -229,8 +296,6 @@ structure DocListInfo where bullets : Array Syntax deriving Repr, TypeName - - abbrev InlineExpander := Syntax → DocElabM (TSyntax `term) initialize inlineExpanderAttr : KeyedDeclsAttribute InlineExpander ← diff --git a/src/leandoc/LeanDoc/Doc/Html.lean b/src/leandoc/LeanDoc/Doc/Html.lean index 34963f5..11478e2 100644 --- a/src/leandoc/LeanDoc/Doc/Html.lean +++ b/src/leandoc/LeanDoc/Doc/Html.lean @@ -15,13 +15,15 @@ namespace LeanDoc.Doc.Html open LeanDoc Output Doc Html -structure Options (m : Type → Type) where +structure Options (g : Genre) (m : Type → Type) where /-- The level of the top-level headers -/ headerLevel : Nat := 1 logError : String → m Unit + refLinks : Lean.HashMap String String := {} + footnotes : Lean.HashMap String (Array (Inline g)) := {} -abbrev HtmlT (genre : Genre) (m : Type → Type) (α : Type) : Type := - ReaderT (Options m × genre.TraverseContext × genre.TraverseState) m α +abbrev HtmlT (genre : Genre) (m : Type → Type) : Type → Type := + ReaderT (Options genre m × genre.TraverseContext × genre.TraverseState) m instance [Functor m] : Functor (HtmlT genre m) where map f act := fun ρ => Functor.map f (act ρ) @@ -30,17 +32,17 @@ instance [Monad m] : Monad (HtmlT genre m) where pure x := fun _ => pure x bind act k := fun ρ => (act ρ >>= fun x => k x ρ) -instance [Pure m] : MonadReader (Options m × genre.TraverseContext × genre.TraverseState) (HtmlT genre m) where +instance [Pure m] : MonadReader (Options genre m × genre.TraverseContext × genre.TraverseState) (HtmlT genre m) where read := fun ρ => pure ρ -instance : MonadWithReader (Options m × genre.TraverseContext × genre.TraverseState) (HtmlT genre m) where +instance : MonadWithReader (Options genre m × genre.TraverseContext × genre.TraverseState) (HtmlT genre m) where withReader f act := fun ρ => act (f ρ) -def HtmlT.options [Monad m] : HtmlT genre m (Options m) := do +def HtmlT.options [Monad m] : HtmlT genre m (Options genre m) := do let (opts, _, _) ← read pure opts -def HtmlT.withOptions (opts : Options m → Options m) (act : HtmlT g m α) : HtmlT g m α := +def HtmlT.withOptions (opts : Options g m → Options g m) (act : HtmlT g m α) : HtmlT g m α := withReader (fun (x, y, z) => (opts x, y, z)) act def HtmlT.context [Monad m] : HtmlT genre m genre.TraverseContext := do @@ -68,16 +70,30 @@ class GenreHtml (genre : Genre) (m : Type → Type) where section open ToHtml -defmethod LinkDest.str : LinkDest → String - | .url addr => addr - | .ref TODO => TODO +defmethod LinkDest.str [Monad m] : LinkDest → HtmlT g m String + | .url addr => pure addr + | .ref name => do + let ({refLinks := refs, ..}, _, _) ← read + if let some url := refs.find? name then + pure url + else + logError s!"Unknown reference '{name}'" + pure "" partial def Inline.toHtml [Monad m] [GenreHtml g m] : Inline g → HtmlT g m Html | .text str => pure <| .text str | .link content dest => do - pure {{ {{← content.mapM toHtml}} }} + pure {{ {{← content.mapM toHtml}} }} | .image alt dest => do - pure {{ {{alt}}/ }} + pure {{ {{alt}}/ }} + | .footnote name => do + let ({footnotes := footnotes, ..}, _, _) ← read + if let some txt := footnotes.find? name then + pure {{
    "["{{name}}"]"{{← txt.mapM toHtml}}
    }} + else + let message := s!"Unknown footnote '{name}' - options were {footnotes.toList.map (·.fst)}" + logError message + pure {{{{message}}}} | .linebreak _str => pure .empty | .emph content => do pure {{ {{← content.mapM toHtml }} }} @@ -137,12 +153,15 @@ partial def Part.toHtml [Monad m] [GenreHtml g m] (p : Part g) : HtmlT g m Html instance [Monad m] [GenreHtml g m] : ToHtml g m (Part g) where toHtml := Part.toHtml +instance [Monad m] [GenreHtml g m] : ToHtml g m (Doc g) where + toHtml (d : Doc g) := withReader (fun o => {o with fst.refLinks := d.linkRefs, fst.footnotes := d.footnotes}) (Part.toHtml d.content) + instance : GenreHtml .none m where part _ m := nomatch m block _ x := nomatch x inline _ x := nomatch x -defmethod Genre.toHtml (g : Genre) [ToHtml g m α] (options : Options m) (context : g.TraverseContext) (state : g.TraverseState) (x : α) : m Html := +defmethod Genre.toHtml (g : Genre) [ToHtml g m α] (options : Options g m) (context : g.TraverseContext) (state : g.TraverseState) (x : α) : m Html := ToHtml.toHtml x (options, context, state) open LeanDoc.Examples diff --git a/src/leandoc/LeanDoc/Doc/Lsp.lean b/src/leandoc/LeanDoc/Doc/Lsp.lean index e4cbfba..d4d8a73 100644 --- a/src/leandoc/LeanDoc/Doc/Lsp.lean +++ b/src/leandoc/LeanDoc/Doc/Lsp.lean @@ -11,7 +11,7 @@ import Std.CodeAction.Basic import LeanDoc.Doc.Elab import LeanDoc.Syntax -open LeanDoc.Doc.Elab (DocListInfo TOC) +open LeanDoc.Doc.Elab (DocListInfo DocRefInfo TOC) open Lean @@ -96,7 +96,39 @@ partial instance : FromJson Lean.Lsp.DocumentSymbolResult where pure ⟨syms⟩ open Lean Server Lsp RequestM in -def handleHl (params : DocumentHighlightParams) (prev : RequestTask DocumentHighlightResult) : RequestM (RequestTask DocumentHighlightResult) := do +def handleDef (params : TextDocumentPositionParams) (prev : RequestTask (Array LocationLink)) : RequestM (RequestTask (Array LocationLink)) := do + let doc ← readDoc + let text := doc.meta.text + let pos := text.lspPosToUtf8Pos params.position + bindWaitFindSnap doc (·.endPos + ' ' >= pos) (notFoundX := pure prev) fun snap => do + withFallbackResponse prev <| pure <| Task.spawn <| fun () => do + let nodes := snap.infoTree.deepestNodes fun _ctxt info _arr => + match info with + | .ofCustomInfo ⟨stx, data⟩ => + if stx.containsPos pos then + data.get? DocRefInfo + else none + | _ => none + if nodes.isEmpty then prev.get + else + let mut locs := #[] + for node in nodes do + match node with + | ⟨some defSite, _⟩ => + let mut origin : Option Range := none + for stx in node.syntax do + if let some ⟨head, tail⟩ := stx.getRange? then + if pos ≥ head && pos ≤ tail then + origin := stx.lspRange text + break + let some target := defSite.lspRange text + | continue + locs := locs.push {originSelectionRange? := origin, targetRange := target, targetUri := params.textDocument.uri, targetSelectionRange := target} + | _ => continue + pure locs + +open Lean Server Lsp RequestM in +def handleRefs (params : ReferenceParams) (prev : RequestTask (Array Location)) : RequestM (RequestTask (Array Location)) := do let doc ← readDoc let text := doc.meta.text let pos := text.lspPosToUtf8Pos params.position @@ -106,17 +138,48 @@ def handleHl (params : DocumentHighlightParams) (prev : RequestTask DocumentHigh match info with | .ofCustomInfo ⟨stx, data⟩ => if stx.containsPos pos then - data.get? DocListInfo + data.get? DocRefInfo + else none + | _ => none + if nodes.isEmpty then prev.get + else + let mut locs := #[] + for node in nodes do + for stx in node.syntax do + if let some range := stx.lspRange text then + locs := locs.push {uri := params.textDocument.uri, range := range} + pure locs + +open Lean Server Lsp RequestM in +def handleHl (params : DocumentHighlightParams) (prev : RequestTask DocumentHighlightResult) : RequestM (RequestTask DocumentHighlightResult) := do + let doc ← readDoc + let text := doc.meta.text + let pos := text.lspPosToUtf8Pos params.position + bindWaitFindSnap doc (·.endPos + ' ' >= pos) (notFoundX := pure prev) fun snap => do + withFallbackResponse prev <| pure <| Task.spawn <| fun () => do + let nodes : List (_ ⊕ _) := snap.infoTree.deepestNodes fun _ctxt info _arr => + match info with + | .ofCustomInfo ⟨stx, data⟩ => + if stx.containsPos pos then + (.inl <$> data.get? DocListInfo) <|> (.inr <$> data.get? DocRefInfo) else none | _ => none if nodes.isEmpty then prev.get else let mut hls := #[] for node in nodes do - let ⟨stxs⟩ := node - for s in stxs do - if let some r := s.lspRange text then - hls := hls.push ⟨r, none⟩ + match node with + | .inl ⟨stxs⟩ => + for s in stxs do + if let some r := s.lspRange text then + hls := hls.push ⟨r, none⟩ + | .inr ⟨defSite, useSites⟩ => + if let some s := defSite then + if let some r := s.lspRange text then + hls := hls.push ⟨r, none⟩ + for s in useSites do + if let some r := s.lspRange text then + hls := hls.push ⟨r, none⟩ pure hls open Lean Lsp @@ -361,6 +424,8 @@ def renumberLists : CodeActionProvider := fun params snap => do open Lean Server Lsp in initialize + chainLspRequestHandler "textDocument/definition" TextDocumentPositionParams (Array LocationLink) handleDef + -- chainLspRequestHandler "textDocument/references" ReferenceParams (Array Location) handleRefs -- TODO make this work - right now it goes through the watchdog so we can't chain it chainLspRequestHandler "textDocument/documentHighlight" DocumentHighlightParams DocumentHighlightResult handleHl chainLspRequestHandler "textDocument/documentSymbol" DocumentSymbolParams DocumentSymbolResult handleSyms chainLspRequestHandler "textDocument/semanticTokens/full" SemanticTokensParams SemanticTokens handleTokensFull diff --git a/src/leandoc/LeanDoc/Examples.lean b/src/leandoc/LeanDoc/Examples.lean index 95369c1..75f2cc1 100644 --- a/src/leandoc/LeanDoc/Examples.lean +++ b/src/leandoc/LeanDoc/Examples.lean @@ -16,7 +16,12 @@ set_option pp.rawOnError true ::::::: ::::::: -/-- info: LeanDoc.Doc.Part.mk #[LeanDoc.Doc.Inline.text "Nothing"] "Nothing" none #[] #[] -/ +/-- +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk #[LeanDoc.Doc.Inline.text "Nothing"] "Nothing" none #[] #[] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] +-/ #guard_msgs in #eval noDoc @@ -30,12 +35,15 @@ Hello, I'm a paragraph. Yes I am! /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "My title here"] - "My title here" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Hello, I'm a paragraph. Yes I am!"]] - #[] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "My title here"] + "My title here" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Hello, I'm a paragraph. Yes I am!"]] + #[] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval a @@ -48,13 +56,17 @@ info: LeanDoc.Doc.Part.mk ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "My title here"] - "My title here" - none - #[LeanDoc.Doc.Block.ul - #[{ indent := 0, contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Just a list with one item"]] }]] - #[] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "My title here"] + "My title here" + none + #[LeanDoc.Doc.Block.ul + #[{ indent := 0, + contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Just a list with one item"]] }]] + #[] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval a' @@ -71,17 +83,20 @@ a paragraph ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "My title here"] - "My title here" - none - #[] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1"] - "Section 1" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "a paragraph"]] - #[]] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "My title here"] + "My title here" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "a paragraph"]] + #[]] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval b @@ -106,30 +121,33 @@ More text: ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "My title here"] - "My title here" - none - #[] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1"] - "Section 1" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "a paragraph"]] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1.1"] - "Section 1.1" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "More text:"], - LeanDoc.Doc.Block.ul - #[{ indent := 0, contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "and a list"]] }, - { indent := 0, - contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "with two"], - LeanDoc.Doc.Block.ul - #[{ indent := 1, - contents := #[LeanDoc.Doc.Block.para - #[LeanDoc.Doc.Inline.text "and nested"]] }]] }]] - #[]]] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "My title here"] + "My title here" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "a paragraph"]] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1.1"] + "Section 1.1" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "More text:"], + LeanDoc.Doc.Block.ul + #[{ indent := 0, contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "and a list"]] }, + { indent := 0, + contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "with two"], + LeanDoc.Doc.Block.ul + #[{ indent := 1, + contents := #[LeanDoc.Doc.Block.para + #[LeanDoc.Doc.Inline.text "and nested"]] }]] }]] + #[]]] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval c @@ -149,23 +167,26 @@ Also, 2 > 3. ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "More writing"] - "More writing" - none - #[] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1"] - "Section 1" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Here's a quote;"], - LeanDoc.Doc.Block.blockquote - #[(LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "I like quotes"]), - (LeanDoc.Doc.Block.ul - #[{ indent := 2, - contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Also with lists in them"]] }])], - LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Also, 2 > 3."]] - #[]] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "More writing"] + "More writing" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Here's a quote;"], + LeanDoc.Doc.Block.blockquote + #[(LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "I like quotes"]), + (LeanDoc.Doc.Block.ul + #[{ indent := 2, + contents := #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Also with lists in them"]] }])], + LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Also, 2 > 3."]] + #[]] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval d @@ -185,18 +206,21 @@ Here's some code ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "More writing"] - "More writing" - none - #[] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1"] - "Section 1" - none - #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Here's some code"], - LeanDoc.Doc.Block.code none #[] 0 "(define (zero f z) z)\n(define (succ n) (lambda (f x) (f (n f z))))\n"] - #[]] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "More writing"] + "More writing" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para #[LeanDoc.Doc.Inline.text "Here's some code"], + LeanDoc.Doc.Block.code none #[] 0 "(define (zero f z) z)\n(define (succ n) (lambda (f x) (f (n f z))))\n"] + #[]] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval e @@ -216,19 +240,88 @@ Here's some `code`! ::::::: /-- -info: LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "More code writing"] - "More code writing" - none - #[] - #[LeanDoc.Doc.Part.mk - #[LeanDoc.Doc.Inline.text "Section 1"] - "Section 1" - none - #[LeanDoc.Doc.Block.para - #[LeanDoc.Doc.Inline.text "Here's some ", LeanDoc.Doc.Inline.code "code", LeanDoc.Doc.Inline.text "!"], - LeanDoc.Doc.Block.code none #[] 0 "(define (zero f z) z)\n(define (succ n) (lambda (f x) (f (n f z))))\n"] - #[]] +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "More code writing"] + "More code writing" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para + #[LeanDoc.Doc.Inline.text "Here's some ", LeanDoc.Doc.Inline.code "code", LeanDoc.Doc.Inline.text "!"], + LeanDoc.Doc.Block.code none #[] 0 "(define (zero f z) z)\n(define (succ n) (lambda (f x) (f (n f z))))\n"] + #[]] + Lean.HashMap.ofList [] + Lean.HashMap.ofList [] -/ #guard_msgs in #eval f + +#docs (.none) g "Ref link before" := +::::::: + +# Section 1 + +[to here]: http://example.com + +Here's [a link][to here]! + +::::::: + +/-- +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Ref link before"] + "Ref link before" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para + #[LeanDoc.Doc.Inline.text "Here's ", + LeanDoc.Doc.Inline.link #[(LeanDoc.Doc.Inline.text "a link")] (LeanDoc.Doc.LinkDest.ref "to here"), + LeanDoc.Doc.Inline.text "!"]] + #[]] + Lean.HashMap.ofList [("to here", "http://example.com")] + Lean.HashMap.ofList [] +-/ +#guard_msgs in + #eval g + +#docs (.none) g' "Ref link after" := +::::::: + +# Section 1 + +Here's [a link][to here]! + +[to here]: http://example.com + +::::::: + +/-- +info: LeanDoc.Doc.Doc.mk + LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Ref link after"] + "Ref link after" + none + #[] + #[LeanDoc.Doc.Part.mk + #[LeanDoc.Doc.Inline.text "Section 1"] + "Section 1" + none + #[LeanDoc.Doc.Block.para + #[LeanDoc.Doc.Inline.text "Here's ", + LeanDoc.Doc.Inline.link #[(LeanDoc.Doc.Inline.text "a link")] (LeanDoc.Doc.LinkDest.ref "to here"), + LeanDoc.Doc.Inline.text "!"]] + #[]] + Lean.HashMap.ofList [("to here", "http://example.com")] + Lean.HashMap.ofList [] +-/ +#guard_msgs in + #eval g' diff --git a/src/leandoc/LeanDoc/Parser.lean b/src/leandoc/LeanDoc/Parser.lean index 83b3a9d..6bedd19 100644 --- a/src/leandoc/LeanDoc/Parser.lean +++ b/src/leandoc/LeanDoc/Parser.lean @@ -820,11 +820,17 @@ mutual partial def link (ctxt : InlineCtxt) := nodeFn ``link <| - (atomicFn (notInLink ctxt >> strFn "[" )) >> + (atomicFn (notInLink ctxt >> strFn "[" >> notFollowedByFn (chFn '^') "'^'" )) >> many1Fn (inline {ctxt with inLink := true}) >> strFn "]" >> linkTarget + partial def footnote (ctxt : InlineCtxt) := + nodeFn ``footnote <| + (atomicFn (notInLink ctxt >> strFn "[^" )) >> + nodeFn `str (asStringFn (quoted := true) (many1Fn (satisfyEscFn (· != ']')))) >> + strFn "]" + partial def linkTarget := ref <|> url where notUrlEnd := satisfyEscFn (· ∉ ")\n".toList) >> takeUntilEscFn (· ∈ ")\n".toList) @@ -857,7 +863,7 @@ mutual s.pushSyntax fakeClose - partial def delimitedInline (ctxt : InlineCtxt) : ParserFn := emph ctxt <|> bold ctxt <|> code <|> role ctxt <|> image <|> link ctxt + partial def delimitedInline (ctxt : InlineCtxt) : ParserFn := emph ctxt <|> bold ctxt <|> code <|> role ctxt <|> image <|> link ctxt <|> footnote ctxt partial def inline (ctxt : InlineCtxt) : ParserFn := text <|> linebreak ctxt <|> delimitedInline ctxt @@ -1184,6 +1190,22 @@ All input consumed. #guard_msgs in #eval role {} |>.test! "{hello world:=gaia}[there *is* _a meaning!_ ]" +/-- +info: Success! Final stack: + (LeanDoc.Syntax.footnote "[^" (str "\"1\"") "]") +All input consumed. +-/ +#guard_msgs in +#eval footnote {} |>.test! "[^1]" + +/-- +info: Success! Final stack: + (LeanDoc.Syntax.footnote "[^" (str "\"1\"") "]") +All input consumed. +-/ +#guard_msgs in +#eval inline {} |>.test! "[^1]" + structure InList where indentation : Nat type : OrderedListType ⊕ UnorderedListType @@ -1526,9 +1548,22 @@ mutual eatSpaces := takeWhileFn (· == ' ') intro := guardMinColumn (ctxt.minIndent) >> withCurrentColumn fun c => atomicFn (chFn '{') >> withCurrentColumn fun c' => nameAndArgs (some c') >> nameArgWhitespace (some c) >> chFn '}' + partial def linkRef (c : BlockCtxt) : ParserFn := + nodeFn ``link_ref <| + atomicFn (ignoreFn (bol >> eatSpaces >> guardMinColumn c.minIndent) >> chFn '[' >> nodeFn strLitKind (asStringFn (quoted := true) (satisfyEscFn nameStart >> manyFn (satisfyEscFn (· != ']')))) >> strFn "]:") >> + eatSpaces >> + nodeFn strLitKind (asStringFn (quoted := true) (takeWhileFn (· != '\n'))) >> + ignoreFn (satisfyFn (· == '\n') "newline" <|> eoiFn) + where nameStart c := c != ']' && c != '^' + + partial def footnoteRef (c : BlockCtxt) : ParserFn := + nodeFn ``footnote_ref <| + atomicFn (ignoreFn (bol >> eatSpaces >> guardMinColumn c.minIndent) >> strFn "[^" >> nodeFn strLitKind (asStringFn (quoted := true) (many1Fn (satisfyEscFn (· != ']')))) >> strFn "]:") >> + eatSpaces >> + notFollowedByFn blockOpener "block opener" >> guardMinColumn c.minIndent >> textLine partial def block (c : BlockCtxt) : ParserFn := - block_role c <|> unorderedList c <|> orderedList c <|> definitionList c <|> header c <|> codeBlock c <|> directive c <|> blockquote c <|> para c + block_role c <|> unorderedList c <|> orderedList c <|> definitionList c <|> header c <|> codeBlock c <|> directive c <|> blockquote c <|> linkRef c <|> footnoteRef c <|> para c partial def blocks (c : BlockCtxt) : ParserFn := sepByFn true (block c) (ignoreFn (manyFn blankLine)) @@ -1725,7 +1760,7 @@ All input consumed. #eval blocks {} |>.test! "* foo\n * bar\n* more outer" /-- -info: Failure: unexpected end of input; expected ![, [ or expected beginning of line at ⟨2, 15⟩ +info: Failure: unexpected end of input; expected ![, [, [^ or expected beginning of line at ⟨2, 15⟩ Final stack: [(LeanDoc.Syntax.dl [(LeanDoc.Syntax.desc @@ -1737,7 +1772,7 @@ Final stack: (str "\"Let's say more!\""))] "=>" [(LeanDoc.Syntax.para - [(LeanDoc.Syntax.link )]) + [(LeanDoc.Syntax.footnote )]) ])])] Remaining: "" -/ @@ -2273,7 +2308,7 @@ All input consumed. #guard_msgs in #eval block {} |>.test! "{\n test\n arg}\nHere's a modified paragraph." /-- -info: Failure: '{'; expected ![, *, +, - or [ +info: Failure: '{'; expected ![, *, +, -, [ or [^ Final stack: (LeanDoc.Syntax.block_role "{" @@ -2281,7 +2316,7 @@ Final stack: [(arg.anon `arg)] "}" (LeanDoc.Syntax.para - [(LeanDoc.Syntax.link )])) + [(LeanDoc.Syntax.footnote )])) Remaining: "\n\nHere's a modified paragraph." -/ #guard_msgs in @@ -2400,3 +2435,91 @@ All input consumed. -/ #guard_msgs in #eval blocks {} |>.test! "[\\[link A\\]](https://example.com) [\\[link B\\]](https://more.example.com)" + + +/-- +info: Success! Final stack: + [(LeanDoc.Syntax.para + [(LeanDoc.Syntax.link + "[" + [(LeanDoc.Syntax.text (str "\"My link\""))] + "]" + (LeanDoc.Syntax.ref + "[" + (str "\"lean\"") + "]"))]) + (LeanDoc.Syntax.link_ref + "[" + (str "\"lean\"") + "]:" + (str "\"https://lean-lang.org\""))] +All input consumed. +-/ +#guard_msgs in + #eval blocks {} |>.test! "[My link][lean]\n\n[lean]: https://lean-lang.org" + +/-- +info: Failure: expected ( or [ +Final stack: + [(LeanDoc.Syntax.para + [(LeanDoc.Syntax.link + "[" + [(LeanDoc.Syntax.text (str "\"My link\""))] + "]" + (LeanDoc.Syntax.ref + "[" + (str "\"lean\"") + "]")) + (LeanDoc.Syntax.linebreak "\n") + (LeanDoc.Syntax.link + "[" + [(LeanDoc.Syntax.text (str "\"lean\""))] + "]" + (LeanDoc.Syntax.url ))])] +Remaining: ": https://lean-lang.org" +-/ +-- NB this is correct - blank lines are required here +#guard_msgs in + #eval blocks {} |>.test! "[My link][lean]\n[lean]: https://lean-lang.org" + +/-- +info: Success! Final stack: + [(LeanDoc.Syntax.para + [(LeanDoc.Syntax.link + "[" + [(LeanDoc.Syntax.text (str "\"My link\""))] + "]" + (LeanDoc.Syntax.ref + "[" + (str "\"lean\"") + "]"))]) + (LeanDoc.Syntax.link_ref + "[" + (str "\"lean\"") + "]:" + (str "\"https://lean-lang.org\"")) + (LeanDoc.Syntax.para + [(LeanDoc.Syntax.text (str "\"hello\""))])] +All input consumed. +-/ +#guard_msgs in + #eval blocks {} |>.test! "[My link][lean]\n\n[lean]: https://lean-lang.org\nhello" + +/-- +info: Success! Final stack: + [(LeanDoc.Syntax.para + [(LeanDoc.Syntax.text (str "\"Blah blah\"")) + (LeanDoc.Syntax.footnote + "[^" + (str "\"1\"") + "]")]) + (LeanDoc.Syntax.footnote_ref + "[^" + (str "\"1\"") + "]:" + [(LeanDoc.Syntax.text + (str "\"More can be said\""))])] +All input consumed. +-/ +#guard_msgs in + #eval blocks {} |>.test! "Blah blah[^1]\n\n[^1]: More can be said" diff --git a/src/leandoc/LeanDoc/Syntax.lean b/src/leandoc/LeanDoc/Syntax.lean index 4e65eff..e61a39c 100644 --- a/src/leandoc/LeanDoc/Syntax.lean +++ b/src/leandoc/LeanDoc/Syntax.lean @@ -31,6 +31,8 @@ syntax (name:=bold) "*{" inline* "}" : inline syntax (name:=link) "link[" inline* "]" link_target : inline /-- Image -/ syntax (name:=image) "image[" str* "]" link_target : inline +/-- A footnote use -/ +syntax (name:=footnote) "[^" str "]" : inline /-- Line break -/ syntax (name:=linebreak) "line!" : inline /-- Literal characters-/ @@ -56,23 +58,13 @@ syntax (name:=ol) "ol{" num list_item* "}" : block syntax (name:=codeblock) str : block /-- Quotation -/ syntax (name:=blockquote) str : block +/-- A link reference definition -/ +syntax (name:=link_ref) "[" str "]:" str : block +/-- A footnote definition -/ +syntax (name:=footnote_ref) "[^" str "]:" inline* : block /-- Custom directive -/ syntax (name:=directive) "directive{" ident argument* "}" "[" block* "]": block /-- A header -/ syntax (name:=header) inline* : block syntax (name:=block_role) "role{" ident argument* "}" "[" block "]" : block - - -open LeanDoc.SyntaxUtils Lean Elab Term Std in -macro "#seeStx" tm:term : command => - `(#eval (ppSyntax <$> $tm : TermElabM Std.Format) ) - -#seeStx `(inline| "foo") -#seeStx `(inline| _{ "foo" "bar" }) -#seeStx `(inline| *{ "foo" "bar" }) -#seeStx `(inline| link[_{"FPiL" line! "book"}]("https://...")) -#seeStx `(inline| code{ "foo" }) - - -end LeanDoc.Syntax