packages feed

tilia-0.0.1.0: src/Tilia/Render/Layout.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Runs of things: statements, bindings, equations, list elements.
module Tilia.Render.Layout
  ( -- * Blocks
    Bracing (..),
    items,
    itemsSepBy,

    -- * Blank lines
    keepBlanks,

    -- * Positions in a run
    Place (..),
    places,
  )
where

import Tilia.Doc.Combinators
import Tilia.Span

----------------------------------------------------------------------------
-- Blocks

-- | May a block put braces around itself when it is laid out on one line?
--
-- It may not when something outside it is already doing so: nested braces
-- would be correct but unreadable, and more to the point the outer block
-- has already made the items unambiguous.
data Bracing
  = MayBrace
  | NoBrace
  deriving (Eq, Show)

-- | A block: one item per line when broken, semicolons when flat.
items :: Bracing -> [Doc] -> Doc
items = itemsSepBy False

-- | 'items', with control over whether the broken form carries semicolons
-- too.
--
-- It has to when the block is standing in for something that would
-- otherwise be read as continuing: an or-pattern inside an as-pattern, for
-- instance, where a bare line break would let the next alternative be taken
-- for a new argument.
itemsSepBy ::
  -- | Semicolons in the broken layout as well?
  Bool ->
  Bracing ->
  [Doc] ->
  Doc
itemsSepBy semisWhenBroken bracing xs = variant flatForm brokenForm
  where
    flatForm = case (bracing, xs) of
      (MayBrace, []) -> txt "{}"
      (NoBrace, []) -> mempty
      (MayBrace, _) -> txt "{" <> space <> joined <> space <> txt "}"
      (NoBrace, _) -> joined
    joined = sepBy (semi <> space) xs
    brokenForm =
      sepBy (includeWhen semisWhenBroken semi <> hardBreak) xs

----------------------------------------------------------------------------
-- Blank lines

-- | Put back the empty lines the author left between items.
keepBlanks ::
  -- | Was there an empty line between two items?
  (Maybe Span -> Maybe Span -> Bool) ->
  -- | Where each item was, and what it prints as
  [(Maybe Span, Doc)] ->
  [Doc]
keepBlanks blank xs = zipWith gap (Nothing : map fst xs) xs
  where
    gap previous (here, d) = includeWhen (blank previous here) hardBreak <> d

----------------------------------------------------------------------------
-- Positions in a run

-- | Where an item sits among its siblings.
data Place
  = Only
  | First
  | Middle
  | Last
  deriving (Eq, Show)

-- | Label each item of a list with its position.
places :: [a] -> [(Place, a)]
places [] = []
places [x] = [(Only, x)]
places (x : xs) = (First, x) : go xs
  where
    go [] = []
    go [y] = [(Last, y)]
    go (y : ys) = (Middle, y) : go ys