packages feed

trifecta-0.5: Text/Trifecta/Render.hs

module Text.Trifecta.Render
  ( Rendering(..)
  , rendering
  , render
  , addSymbol
  , addFixit
  , effect
  , drawCover
  , drawCaret
  ) where

import Data.Ix (inRange)
import Data.ByteString hiding (groupBy)
import qualified Data.ByteString.UTF8 as UTF8 
import Data.List (groupBy)
import Data.Function (on)
import Data.IntMap as IM
import Text.Trifecta.Delta
import Text.Trifecta.Bytes
import Text.Trifecta.Caret
import Text.PrettyPrint.Leijen.Extras hiding (column)
import Control.Monad.State
import Prelude as P

type Effect e = Doc e -> Doc e
type EffectId = Int

data Rendering e = Rendering 
  { rDelta   :: !Delta
  , rLine    :: String
  , rFresh   :: !EffectId
  , rEffects :: !(IntMap (Effect e))
  , rSymbols :: !(IntMap (EffectId, Char))
  , rFixits  :: !(IntMap (EffectId, Char))
  }

instance HasDelta (Rendering e) where
  delta = rDelta

rendering :: (Doc e -> Doc e) -> Delta -> ByteString -> Rendering e
rendering bold d bs = Rendering d (expand bs) 1 (IM.fromList [(0,id),(1,bold)]) IM.empty IM.empty where
  expand :: ByteString -> String
  expand = go 0 . UTF8.toString where
    go n ('\t':xs) = let t = 8 - mod n 8 in P.replicate t ' ' ++ go (n + t) xs
    go _ ('\n':_)  = []
    go n (x:xs)    = x : go (n + 1) xs
    go _ []        = []

effect :: Effect e -> State (Rendering e) EffectId
effect f = do
   s <- get
   let eff = rFresh s
   put s { rFresh = eff + 1, rEffects = IM.insert eff f (rEffects s) }
   return eff

drawCaret :: EffectId -> Caret -> State (Rendering e) ()
drawCaret eff (Caret p _)  = modify img where
  img r | near p r  = addSymbol (column p) eff "^" r
        | otherwise = r

drawCover :: EffectId -> Cover -> State (Rendering e) ()
drawCover eff (Cover (Caret s _) e) = modify img where
  img r | nl && nh  = addSymbol (column l) eff (P.replicate (column h - column l + 1) '~') r
        | nl        = addSymbol (column l) eff (P.replicate (cols     - column l) '~' ++ ">") r
        | nh        = addSymbol 0 eff ('<' : P.replicate (column l) '~') r
        | otherwise = r
    where 
      l = argmin bytes s e 
      h = argmax bytes s e
      nl = near l r
      nh = near h r
      cols = P.length (rLine r)

addSymbol, addFixit :: Int -> EffectId -> String -> Rendering e -> Rendering e
addSymbol n eff xs0 r = r { rSymbols = interval n eff xs0 (rSymbols r) }
addFixit n eff xs0 r = r { rSymbols = interval n eff xs0 (rSymbols r) }

render :: Rendering e -> Doc e
render r = columns go where
  go cols = dots $ align $ vsep img
    where (dots, lh@(lo, hi)) = window (cols - 10) r
          -- line1, line2, line3 :: Doc e
          line1 = string $ P.take (hi - lo) $ P.drop lo (rLine r)
          line2 = cluster (rEffects r) $ scan (rSymbols r)
          line3 = cluster (rEffects r) $ scan (rFixits r)
          hasFixits = P.any (inRange lh) $ IM.keys (rFixits r)
          img | hasFixits = [line1, line2, line3] 
              | otherwise = [line1, line2]

          scan :: IntMap (EffectId, Char) -> [(EffectId, Char)]
          scan m =   findWithDefault (0,'<') lo m :
                   [ findWithDefault (0,' ') i m | i <- [lo + 1 .. hi - 1]] ++
                   [ findWithDefault (0,'>') hi m ]

          -- cluster :: IntMap (Doc e -> Doc e) -> [(EffectId, Char)] -> Doc e
          cluster m xs = hcat [ findWithDefault id (fst (P.head g)) m $ string (P.map snd g) 
                              | g <- groupBy ((==) `on` fst) xs 
                              ]

window :: Int -> Rendering e -> (Doc e -> Doc e, (Int, Int))
window w r 
  | fcs <= w2 = (id ,                       (0,  hi))
  | otherwise = ((bold (text ("...")) <+>), (mn, hi))
  where 
    fcs = column r
    mn = fcs - w2
    mx = fcs + w2
    hi = min mx w
    w2 = div w 2
    bold = rEffects r IM.! 1

interval :: Int -> EffectId -> String -> IntMap (EffectId, Char) -> IntMap (EffectId, Char)
interval _ _   []     = id
interval k eff (x:xs) = interval (k + 1) eff xs . insert k (eff,x)

argmin :: Ord b => (a -> b) -> a -> a -> a
argmin f a b | f a <= f b = a
             | otherwise  = b

argmax :: Ord b => (a -> b) -> a -> a -> a
argmax f a b | f a > f b = a
             | otherwise = b