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