11{-# LANGUAGE DeriveFunctor #-}
2+ {-# LANGUAGE FlexibleContexts #-}
23{-# LANGUAGE FlexibleInstances #-}
34{-# LANGUAGE RecordWildCards #-}
5+ {-# LANGUAGE TupleSections #-}
6+ {-# LANGUAGE TypeFamilies #-}
7+ {-# LANGUAGE ViewPatterns #-}
48
59module Text.Layout.Table.Cell where
610
7- import Control.Monad (join )
11+ import Data.Bifunctor (Bifunctor (.. ))
12+ import Data.Kind (Type )
813import qualified Data.Text as T
914
1015import Text.Layout.Table.Primitives.AlignInfo
@@ -48,10 +53,20 @@ dropBoth l r = adjustCell (negate $ truncateNegative l) (negate $ truncateNegati
4853
4954instance Applicative CellView where
5055 pure x = CellView x 0 0
51- ( CellView f l r) <*> ( CellView x l' r') = CellView (f x) (l + l') (r + r')
56+ CellView f l r <*> CellView x l' r' = CellView (f x) (l + l') (r + r')
5257
5358instance Monad CellView where
54- (CellView x l r) >>= f = let CellView y l' r' = f x in CellView y (l + l') (r + r')
59+ CellView x l r >>= f = let CellView y l' r' = f x in CellView y (l + l') (r + r')
60+
61+ instance Semigroup a => Semigroup (CellView a ) where
62+ CellView a l r <> CellView b l' r' = CellView (a <> b) (l + l') (r + r')
63+
64+ instance Monoid a => Monoid (CellView a ) where
65+ mempty = pure mempty
66+
67+ -- | Build the contents of a 'CellView' and add padding.
68+ buildCellView :: StringBuilder b => (a -> b ) -> CellView a -> b
69+ buildCellView build (CellView a l r) = spacesB l <> build a <> spacesB r
5570
5671-- | The total amount of adjustment in 'CellView'.
5772totalAdjustment :: CellView a -> Int
@@ -64,33 +79,40 @@ redistributeAdjustment l r a = CellView (baseCell a) lAdjustment rAdjustment
6479 lAdjustment = (totalAdjustment a * l) `div` (l + r)
6580 rAdjustment = totalAdjustment a - lAdjustment
6681
82+
6783-- | Types that can be measured for visible characters, define a sub-string
6884-- operation and turned into a 'StringBuilder'.
69- class Cell a where
85+ class Monoid (DropAction a ) => Cell a where
86+ -- | Describes the action necessary to drop width elements.
87+ type DropAction a :: Type
88+
7089 -- | Returns the length of the visible characters as displayed on the
7190 -- output medium.
7291 visibleLength :: a -> Int
7392
93+ -- | Determine the final visible length after requesting to drop width
94+ -- units, along with the action needed to accomplish that.
95+ dropLengthUnits :: Int -> Int -> a -> (Int , DropAction a )
96+
7497 -- | Measure the preceding and following characters for a position where
7598 -- the predicate matches.
7699 measureAlignment :: (Char -> Bool ) -> a -> AlignInfo
77100
101+ -- | Evaluate a 'DropAction' and build the result.
102+ dropAction :: StringBuilder b => DropAction a -> a -> b
103+
78104 -- | Insert the contents into a 'StringBuilder'.
79105 buildCell :: StringBuilder b => a -> b
80- buildCell = buildCellView . pure
81106
82- -- | Insert the contents into a 'StringBuilder', padding or trimming as
83- -- necessary.
84- --
85- -- The 'Cell' instance of 'CellView a' means that this can usually be
86- -- substituted with 'buildCell', and is only needed for defining the
87- -- instance.
88- buildCellView :: StringBuilder b => CellView a -> b
89-
90- {-# MINIMAL visibleLength, measureAlignment, buildCellView #-}
107+ {-# MINIMAL visibleLength, dropLengthUnits, measureAlignment, dropAction, buildCell #-}
91108
92109instance Cell a => Cell (CellView a ) where
110+ type DropAction (CellView a ) = CellView (DropAction a )
93111 visibleLength (CellView a l r) = visibleLength a + l + r
112+ dropLengthUnits l r (CellView a l' r') =
113+ -- Asking to drop more than the padding which exists: adjust the amount dropped
114+ -- Asking to drop less than the padding: just reduce the padding
115+ second (adjustCell (max 0 $ l' - l) (max 0 $ r' - r)) $ dropLengthUnits (max 0 $ l - l') (max 0 $ r - r') a
94116 measureAlignment f (CellView a l r) = case mMatchRemaining of
95117 -- No match
96118 Nothing -> AlignInfo (truncateNegative $ matchAt + l + r) Nothing
@@ -100,91 +122,89 @@ instance Cell a => Cell (CellView a) where
100122 Just matchRemaining -> AlignInfo (matchAt + l) (Just $ matchRemaining + r)
101123 where
102124 AlignInfo matchAt mMatchRemaining = measureAlignment f a
103- buildCell = buildCellView
104- buildCellView = buildCellView . join
125+
126+ dropAction action a = buildCellView id $ dropAction <$> action <*> a
127+ buildCell (CellView a l r) =
128+ case (compare l 0 , compare r 0 ) of
129+ (GT , GT ) -> spacesB l <> buildCell a <> spacesB r
130+ (GT , LT ) -> spacesB l <> dropAction (snd (dropLengthUnits 0 (negate r) a)) a
131+ (GT , EQ ) -> spacesB l <> buildCell a
132+ (LT , GT ) -> dropAction (snd (dropLengthUnits (negate l) 0 a)) a <> spacesB r
133+ (LT , LT ) -> dropAction (snd (dropLengthUnits (negate l) (negate r) a)) a
134+ (LT , EQ ) -> dropAction (snd (dropLengthUnits (negate l) 0 a)) a
135+ (EQ , GT ) -> buildCell a <> spacesB r
136+ (EQ , LT ) -> dropAction (snd (dropLengthUnits 0 (negate r) a)) a
137+ (EQ , EQ ) -> buildCell a
105138
106139instance Cell a => Cell (Maybe a ) where
140+ type DropAction (Maybe a ) = DropAction a
107141 visibleLength = maybe 0 visibleLength
142+ dropLengthUnits l r (Just a) = dropLengthUnits l r a
143+ dropLengthUnits _ _ Nothing = (0 , mempty )
108144 measureAlignment p = maybe mempty (measureAlignment p)
145+
146+ dropAction action = maybe mempty (dropAction action)
109147 buildCell = maybe mempty buildCell
110- buildCellView (CellView a l r) = maybe (spacesB $ l + r) (buildCellView . adjustCell l r) a
111148
112149instance (Cell a , Cell b ) => Cell (Either a b ) where
150+ type DropAction (Either a b ) = (DropAction a , DropAction b )
113151 visibleLength = either visibleLength visibleLength
152+ dropLengthUnits l r (Left a) = second (,mempty ) $ dropLengthUnits l r a
153+ dropLengthUnits l r (Right a) = second (mempty ,) $ dropLengthUnits l r a
114154 measureAlignment p = either (measureAlignment p) (measureAlignment p)
155+
156+ dropAction = uncurry either . bimap dropAction dropAction
115157 buildCell = either buildCell buildCell
116- buildCellView (CellView a l r) = either go go a
117- where
118- go x = buildCellView $ CellView x l r
158+
159+ -- | How to drop width units from many common types.
160+ data DefaultDropAction
161+ = DropAll
162+ | Drop Int Int
163+ deriving (Show )
164+
165+ instance Semigroup DefaultDropAction where
166+ DropAll <> _ = DropAll
167+ _ <> DropAll = DropAll
168+ Drop l r <> Drop l' r' = Drop (l + l') (r + r')
169+
170+ instance Monoid DefaultDropAction where
171+ mempty = Drop 0 0
172+
173+ -- | Construct a drop specification when every unit has width exactly one.
174+ --
175+ -- This can be used for 'dropLengthUnits' in most cases.
176+ defaultDropLengthUnits :: Cell a => Int -> Int -> a -> (Int , DefaultDropAction )
177+ defaultDropLengthUnits (truncateNegative -> l) (truncateNegative -> r) a
178+ | l + r >= n = (0 , DropAll )
179+ | otherwise = (n - l - r, Drop l r)
180+ where
181+ n = visibleLength a
119182
120183instance Cell String where
184+ type DropAction String = DefaultDropAction
121185 visibleLength = length
186+ dropLengthUnits = defaultDropLengthUnits
122187 measureAlignment p xs = case break p xs of
123188 (ls, rs) -> AlignInfo (length ls) $ case rs of
124189 [] -> Nothing
125190 _ : rs' -> Just $ length rs'
126191
192+ dropAction DropAll _ = mempty
193+ dropAction (Drop l r) a = stringB . drop l $ zipWith const a (drop r a)
127194 buildCell = stringB
128- buildCellView = buildCellViewLRHelper stringB drop (\ n s -> zipWith const s $ drop n s)
129195
130196instance Cell T. Text where
197+ type DropAction T. Text = DefaultDropAction
131198 visibleLength = T. length
199+ dropLengthUnits = defaultDropLengthUnits
132200 measureAlignment p xs = case T. break p xs of
133201 (ls, rs) -> AlignInfo (T. length ls) $ if T. null rs
134202 then Nothing
135203 else Just $ T. length rs - 1
136204
205+ dropAction DropAll = const mempty
206+ dropAction (Drop l r) = textB . T. drop l . T. dropEnd r
137207 buildCell = textB
138- buildCellView = buildCellViewLRHelper textB T. drop T. dropEnd
139-
140- -- | Construct 'buildCellView' from a builder function, a function for
141- -- trimming from the left, and a function for trimming from the right.
142- --
143- -- Used to define instances of 'Cell'.
144- buildCellViewLRHelper :: StringBuilder b
145- => (a -> b ) -- ^ Builder function for 'a'.
146- -> (Int -> a -> a ) -- ^ Function for trimming on the left.
147- -> (Int -> a -> a ) -- ^ Function for trimming on the right.
148- -> CellView a
149- -> b
150- buildCellViewLRHelper build trimL trimR =
151- buildCellViewHelper build (\ i -> build . trimL i) (\ i -> build . trimR i) (\ l r -> build . trimL l . trimR r)
152-
153- -- | Construct 'buildCellView' from a builder function, and a function for
154- -- trimming from the left and right simultaneously.
155- --
156- -- Used to define instanced of 'Cell'.
157- buildCellViewBothHelper
158- :: StringBuilder b
159- => (a -> b ) -- ^ Builder function for 'a'.
160- -> (Int -> Int -> a -> a ) -- ^ Function for trimming on the left and right simultaneously.
161- -> CellView a
162- -> b
163- buildCellViewBothHelper build trimBoth =
164- buildCellViewHelper build (\ i -> build . trimBoth i 0 ) (\ i -> build . trimBoth 0 i) (\ l r -> build . trimBoth l r)
165-
166- -- | Construct 'buildCellView' from builder functions and trimming functions.
167- --
168- -- Used to define instances of 'Cell'.
169- buildCellViewHelper
170- :: StringBuilder b
171- => (a -> b ) -- ^ Builder function for 'a'.
172- -> (Int -> a -> b ) -- ^ Function for trimming on the left.
173- -> (Int -> a -> b ) -- ^ Function for trimming on the right.
174- -> (Int -> Int -> a -> b ) -- ^ Function for trimming on the left and right simultaneously.
175- -> CellView a
176- -> b
177- buildCellViewHelper build trimL trimR trimBoth (CellView a l r) =
178- case (compare l 0 , compare r 0 ) of
179- (GT , GT ) -> spacesB l <> build a <> spacesB r
180- (GT , LT ) -> spacesB l <> trimR (negate r) a
181- (GT , EQ ) -> spacesB l <> build a
182- (LT , GT ) -> trimL (negate l) a <> spacesB r
183- (LT , LT ) -> trimBoth (negate l) (negate r) a
184- (LT , EQ ) -> trimL (negate l) a
185- (EQ , GT ) -> build a <> spacesB r
186- (EQ , LT ) -> trimR (negate r) a
187- (EQ , EQ ) -> build a
188208
189209-- | Creates a 'StringBuilder' with the amount of missing spaces.
190210remSpacesB
@@ -477,9 +497,9 @@ buildCellMod
477497 -> CellMod c
478498 -> s
479499buildCellMod cutMark CellMod {.. } =
480- -- 'buildCellView' takes care of padding and trimming.
500+ -- 'buildCell takes care of padding and trimming.
481501 applyMarkOrEmpty applyLeftMark leftCutMarkLenCM
482- <> buildCellView (CellView baseCellCM leftAdjustmentCM rightAdjustmentCM)
502+ <> buildCell (CellView baseCellCM leftAdjustmentCM rightAdjustmentCM)
483503 <> applyMarkOrEmpty applyRightMark rightCutMarkLenCM
484504 where
485505 applyMarkOrEmpty applyMark k = if k > 0 then applyMark k else mempty
0 commit comments