unison/pretty.u
Paul Chiusano e3e59320f4 WIP on pure Unison doc rendering
Rejiggered Doc type to have special forms separate from the doc structure, and filled in pretty-printer implementation. This allows the bulk of doc rendering to happen in pure Unison.
2021-03-04 13:42:12 -06:00

159 lines
5.1 KiB
Plaintext

unique[ecf75311027ef922e3e50d358b1d7bc93c84035e] type Width
-- | The number of columns.
= SingleLine Nat
-- | The number of columns in the first, last, and longest lines.
| MultiLine Nat Nat Nat
Width.empty = SingleLine 0
Width.append = cases
SingleLine c, SingleLine c2 -> SingleLine (c + c2)
SingleLine c, MultiLine fc lc mc ->
fc' = c + fc
MultiLine fc' lc (max fc' mc)
MultiLine fc lc mc, SingleLine c ->
lc' = lc + c
MultiLine fc lc' (max lc' mc)
MultiLine fc lc mc, MultiLine fc2 lc2 mc2 ->
MultiLine fc lc2 (max mc (max mc2 (lc + fc2)))
Width.maxColumn : Width -> Nat
Width.maxColumn = cases
SingleLine c -> c
MultiLine _ _ c -> c
Width.toNat : Width -> Nat
Width.toNat = cases
SingleLine c -> c
MultiLine _ c _ -> c
Width.fromChar : Char -> Width
Width.fromChar = cases
?\n -> MultiLine 0 0 0
_ -> SingleLine 1
Width.snocChar : Width -> Char -> Width
Width.snocChar w ch = match ch with
?\n -> append w (fromChar ch)
_ -> match w with
SingleLine c -> SingleLine (c + 1)
MultiLine fc lc mc ->
lc' = lc + 1
MultiLine fc lc' (max lc' mc)
Width.fromText : Text -> Width
Width.fromText txt =
foldLeft snocChar empty (Text.toCharList txt)
-- Surgically modify the given width to pretend it has not exceeded availableWidth.
Width.forceFit : Nat -> Width -> Width
Width.forceFit availableWidth = cases
SingleLine c -> SingleLine (min c (drop availableWidth 1))
MultiLine fc lc mc -> MultiLine fc lc (min mc (drop availableWidth 1))
Width.fits : Nat -> Width -> Width -> Boolean
Width.fits availableWidth w1 w2 =
toNat (append w1 w2) <= availableWidth
Width.forceFits : Nat -> Width -> Width -> Boolean
Width.forceFits availableWidth w1 w2 =
toNat (append (forceFit availableWidth w1) w2) <= availableWidth
unique[d7b2ced8c08b2c6e54050d1f5acedef3395f293d] type Pretty.Annotated w txt
= Empty
-- | A group adds a level of breaking. Layout tries not to break a group
-- unless needed to fit in available width. Breaking is done "outside in".
--
-- (a | b) <> (c | d) will try (a <> c), then (b <> d)
--
-- (a | b) <> group (c | d) will try (a <> c), then (b <> c), then (b <> d)
| Group w (Pretty.Annotated w txt)
| Lit w txt
| Wrap w [Pretty.Annotated w txt]
| OrElse w (Pretty.Annotated w txt) (Pretty.Annotated w txt)
| Append w [Pretty.Annotated w txt]
type Pretty txt = Pretty (Pretty.Annotated () txt)
Pretty.get = cases Pretty p -> p
Pretty.annotate : (txt -> Width) -> Pretty txt -> Pretty.Annotated Width txt
Pretty.annotate w p =
go = cases
Empty -> Empty
Group _ p ->
p' = go p
Group (preferredWidth p') p'
Lit _ txt -> Lit (w txt) txt
Wrap _ ps ->
ps' = List.map go ps
preferred = foldLeft (acc p -> append acc (preferredWidth p)) empty ps'
Wrap preferred ps'
OrElse _ p1 p2 ->
p1' = go p1
p2' = go p2
OrElse (append (preferredWidth p1') (preferredWidth p2')) p1' p2'
Append _ ps ->
ps' = List.map go ps
preferred = foldLeft (acc p -> append acc (preferredWidth p)) empty ps'
Append preferred ps'
go (Pretty.get p)
Pretty.preferredWidth : Pretty.Annotated Width s -> Width
Pretty.preferredWidth = cases
Empty -> Width.empty
Group w _ -> w
Lit w _ -> w
Wrap w _ -> w
OrElse w _ _ -> w
Append w _ -> w
List.for! : [a] -> (a ->{g} ()) ->{g} ()
List.for! as f = match as with
[] -> ()
a +: as -> f a
for! as f
Pretty.layout : (txt -> Width) -> Nat -> Pretty txt ->{Stream txt} ()
Pretty.layout width available p = layout' available (annotate width p)
Pretty.layout' : Nat -> Pretty.Annotated Width txt ->{Stream txt} ()
Pretty.layout' availableWidth p =
-- `go currentWidth rem`, where `currentWidth` is the width of
-- elements to the left of the current position, and the list is
-- elements we need to append. Elements wrapped in Left in this
-- list are scheduled to be "broken"; elements wrapped in Right
-- we will attempt to fit inline, and break only if necessary.
go : Width
-> [Either (Pretty.Annotated Width txt) (Pretty.Annotated Width txt)]
-> {Stream txt} ()
go = cases
_, [] -> ()
cur, p +: rest -> match p with
Right p -> -- `p` might fit, let's try it!
if forceFits availableWidth cur (preferredWidth p) then
flow p
go (append cur (preferredWidth p)) rest
else
go cur (Left p +: rest) -- nope, switch to breaking mode
Left p -> match p with -- `p` requires breaking
Empty -> go cur rest
Append _ ps -> go cur (List.map Left ps ++ rest)
Group _ p -> go cur (Right p +: rest)
-- Note: literals can't be broken further so they're
-- added to output unconditionally
Lit preferred l -> emit l; go (append cur preferred) rest
OrElse _ _ p -> go cur (Right p +: rest)
Wrap _ ps -> go cur (List.map Right ps ++ rest)
flow p = match p with
Append _ ps -> for! ps flow
Empty -> ()
Group _ p -> flow p
Lit _ s -> emit s
OrElse _ p _ -> flow p
Wrap _ ps -> for! ps flow
go Width.empty [Right p]