on master help sign in
optimize span generation. Use string-qq for tests.

Recovers most of the performance lost with 4.7.0.0.

commit4f3cd6c906f7709c53cb27241cda8139d73a11ea authorCorey O'Connor <coreyoconnor@gmail.com> dateThu 14 Jul 2011, 18:23 (15 years ago) parenta57c0b5
9 files changed+220−166
src/Graphics/Vty/Debug.hs+8−8 src/Graphics/Vty/Span.hs+150−89 src/Graphics/Vty/Terminal/Generic.hs+21−22 test/Verify.hs−5 test/interactive_terminal_test.hs+28−28 test/verify_debug_terminal.hs+4−4 test/verify_eval_terminfo_caps.hs+5−6 test/verify_span_ops.hs+2−2 vty.cabal+2−2
src/Graphics/Vty/Debug.hs+8−8
@@ -10,20 +10,20 @@ import Graphics.Vty.Debug.Image
1010import Graphics.Vty.Span
1111import Graphics.Vty.DisplayRegion
1212
13−import Data.Array
13+import qualified Data.Vector as Vector
1414import Data.Word
1515
16−row_ops_effected_columns :: SpanOpSequence -> [Word]
16+row_ops_effected_columns :: DisplayOps -> [Word]
1717row_ops_effected_columns spans
18− = map span_ops_effected_columns (elems $ row_ops spans)
18+ = Vector.toList $ Vector.map span_ops_effected_columns $ display_ops spans
1919
20−all_spans_have_width :: SpanOpSequence -> Word -> Bool
20+all_spans_have_width :: DisplayOps -> Word -> Bool
2121all_spans_have_width spans expected
22− = all (== expected) ( map span_ops_effected_columns (elems $ row_ops spans) )
22+ = all (== expected) $ Vector.toList $ Vector.map span_ops_effected_columns $ display_ops spans
2323
24−span_ops_effected_rows :: SpanOpSequence -> Word
25−span_ops_effected_rows (SpanOpSequence _ the_row_ops)
26− = toEnum $ length (filter (not . null) (elems the_row_ops))
24+span_ops_effected_rows :: DisplayOps -> Word
25+span_ops_effected_rows (DisplayOps _ the_row_ops)
26+ = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList the_row_ops))
2727
2828type SpanConstructLog = [SpanConstructEvent]
2929data SpanConstructEvent = SpanSetAttr Attr
src/Graphics/Vty/Span.hs+150−89
@@ -18,18 +18,19 @@ import Codec.Binary.UTF8.String ( encode )
1818import Control.Monad ( forM_ )
1919import Control.Monad.ST.Strict
2020
21−import Data.Array.ST
21+import Data.Vector (Vector)
22+import qualified Data.Vector as Vector hiding ( take, replicate )
23+import Data.Vector.Mutable ( MVector(..))
24+import qualified Data.Vector.Mutable as Vector
25+
2226import qualified Data.ByteString as B
2327import qualified Data.ByteString.Internal as BInt
2428import qualified Data.Foldable as Foldable
25−import Data.List
2629import qualified Data.String.UTF8 as UTF8
2730import Data.Word
2831
2932import Foreign.Storable ( pokeByteOff )
3033
31−import GHC.Arr
32−
3334{- | A picture is translated into a sequences of state changes and character spans.
3435 - State changes are currently limited to new attribute values. The attribute is applied to all
3536 - following spans. Including spans of the next row. The nth element of the sequence represents the
@@ -37,39 +38,45 @@ import GHC.Arr
3738 -
3839 - A span op sequence will be defined for all rows and columns (and no more) of the region provided
3940 - with the picture to spans_for_pic.
40− -
4141 -
4242 - todo: Partition attribute changes into multiple categories according to the serialized
4343 - representation of the various attributes.
4444 -}
45−data SpanOpSequence = SpanOpSequence
46− { effected_region :: DisplayRegion
47− , row_ops :: RowOps
48− }
4945
50−type RowOps = Array Word SpanOps
51−type SpanOps = [SpanOp]
46+data DisplayOps = DisplayOps
47+ { effected_region :: DisplayRegion
48+ , display_ops :: RowOps
49+ }
50+
51+-- vector of span operation vectors. One per row of the screen.
52+type RowOps = Vector SpanOps
53+
54+type MRowOps s = MVector s SpanOps
5255
53−instance Show SpanOpSequence where
54− show (SpanOpSequence _ the_row_ops)
55− = concat $ ["{ "] ++ map (\ops -> show ops ++ "; " ) (elems the_row_ops) ++ [" }"]
56+-- vector of span operations. executed in succession
57+type SpanOps = Vector SpanOp
58+
59+type MSpanOps s = MVector s SpanOp
60+
61+instance Show DisplayOps where
62+ show (DisplayOps _ the_row_ops)
63+ = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"
5664
5765instance Show SpanOp where
5866 show (AttributeChange attr) = show attr
5967 show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
6068
61−span_ops_columns :: SpanOpSequence -> Word
69+span_ops_columns :: DisplayOps -> Word
6270span_ops_columns ops = region_width $ effected_region ops
6371
64−span_ops_rows :: SpanOpSequence -> Word
72+span_ops_rows :: DisplayOps -> Word
6573span_ops_rows ops = region_height $ effected_region ops
6674
67−span_ops_effected_columns :: [SpanOp] -> Word
68−span_ops_effected_columns in_ops = span_ops_effected_columns' 0 in_ops
75+span_ops_effected_columns :: SpanOps -> Word
76+span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
6977 where
70− span_ops_effected_columns' t [] = t
71− span_ops_effected_columns' t (TextSpan w _ _ : r) = span_ops_effected_columns' (t + w) r
72− span_ops_effected_columns' t (_ : r) = span_ops_effected_columns' t r
78+ span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
79+ span_ops_effected_columns' t _ = t
7380
7481-- |
7582--
@@ -84,54 +91,70 @@ data SpanOp =
8491 | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
8592 deriving Eq
8693
94+-- used to determine the width of a span operation , if it has one.
8795span_op_has_width :: SpanOp -> Maybe (Word, Word)
8896span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
8997span_op_has_width _ = Nothing
9098
99+-- returns the number of columns to the character at the given position in the span op
91100columns_to_char_offset :: Word -> SpanOp -> Word
92101columns_to_char_offset cx (TextSpan _ _ utf8_str) =
93102 let str = UTF8.toString utf8_str
94− in toEnum $ sum $ map wcwidth $ take (fromEnum cx) str
103+ in toEnum $! sum $! map wcwidth $! take (fromEnum cx) str
95104columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
96105
97106-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
98107-- specified region.
99−spans_for_pic :: Picture -> DisplayRegion -> SpanOpSequence
100−spans_for_pic pic r = SpanOpSequence r $ runSTArray (build_spans pic r)
108+spans_for_pic :: Picture -> DisplayRegion -> DisplayOps
109+spans_for_pic pic r = DisplayOps r $ Vector.create (build_spans pic r)
101110
102−build_spans :: Picture -> DisplayRegion -> ST s (STArray s Word [SpanOp])
111+build_spans :: Picture -> DisplayRegion -> ST s (MRowOps s)
103112build_spans pic region = do
104− -- m for mutable
105− mrow_ops <- newSTArray (0, region_height region - 1) []
106− -- I think this can be better optimized if the build produced span operations in display order.
113+ -- m for mutable! ;-)
114+ mrow_ops <- Vector.replicate (fromEnum $ region_height region) Vector.empty
115+ -- XXX: I think building the span operations in display order would provide better performance.
107116 -- However, I got stuck trying to implement an algorithm that did this. This will be considered
108117 -- as a possible future optimization.
109118 --
110− -- A depth first traversal of the image is performed.
111− -- ordered according to the column range defined by the image from least to greatest.
112− -- The output row ops will at least have the region of the image specified. Iterate over all
113− -- output rows and output background fills for all unspecified columns.
119+ -- A depth first traversal of the image is performed. ordered according to the column range
120+ -- defined by the image from least to greatest. The output row ops will at least have the
121+ -- region of the image specified. Iterate over all output rows and output background fills for
122+ -- all unspecified columns.
123+ --
124+ -- The images are made into span operations from left to right. It's possible that this could
125+ -- easily be made to assure top to bottom output as well.
114126 if region_height region > 0
115127 then do
116128 -- The ops builder recursively descends the image and outputs span ops that would
117129 -- display that image. The number of columns remaining in this row before exceeding the
118− -- bounds is also provided. This is used to clip the span ops produced.
119− _ <- ops_for_row mrow_ops (pic_background pic) region (pic_image pic) (0,0) 0 (region_width region)
130+ -- bounds is also provided. This is used to clip the span ops produced to the display.
131+ -- The skip dimensions provided do....???
132+ _ <- row_ops_for_image mrow_ops
133+ (pic_image pic)
134+ (pic_background pic)
135+ region
136+ (0,0)
137+ 0
138+ (region_width region)
120139 -- Fill in any unspecified columns with the background pattern.
121− forM_ [0 .. region_height region - 1] $ \row -> do
122− end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
140+ forM_ [0 .. (fromEnum $ region_height region - 1)] $! \row -> do
141+ end_x <- Vector.read mrow_ops row >>= return . span_ops_effected_columns
123142 if end_x < region_width region
124143 then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
125144 else return ()
126145 else return ()
127146 return mrow_ops
128147
129−type MRowOps s = STArray s Word SpanOps
130−
131−ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> (Word, Word) -> Word -> Word -> ST s (Word, Word)
132−ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_columns
148+row_ops_for_image :: MRowOps s -> Image -> Background -> DisplayRegion -> (Word, Word) -> Int -> Word -> ST s (Word, Word)
149+row_ops_for_image mrow_ops -- the image to output the ops to
150+ image -- the image to rasterize in column order to mrow_ops
151+ bg -- the background fill
152+ region -- ???
153+ skip_dim@(skip_row,skip_col) -- the number of rows
154+ y -- ???
155+ remaining_columns -- ???
133156 | remaining_columns == 0 = return skip_dim
134− | y >= region_height region = return skip_dim
157+ | y >= fromEnum (region_height region) = return skip_dim
135158 | otherwise = case image of
136159 EmptyImage -> return skip_dim
137160 -- The width provided is the number of columns this text span will occupy when displayed.
@@ -143,22 +166,34 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
143166 else do
144167 skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns
145168 return (skip_row, skip_col')
146− VertJoin t b _ _ -> do
147− (skip_row',skip_col') <- ops_for_row mrow_ops bg region t skip_dim y remaining_columns
148− (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region b (skip_row', skip_col) (y + image_height t - (skip_row - skip_row')) remaining_columns
169+ VertJoin top_image bottom_image _ _ -> do
170+ (skip_row',skip_col') <- row_ops_for_image mrow_ops
171+ top_image
172+ bg
173+ region
174+ skip_dim
175+ y
176+ remaining_columns
177+ (skip_row'',skip_col'') <- row_ops_for_image mrow_ops
178+ bottom_image
179+ bg
180+ region
181+ (skip_row', skip_col)
182+ (y + (fromEnum $! image_height top_image) - (fromEnum $! skip_row - skip_row'))
183+ remaining_columns
149184 return (skip_row'', min skip_col' skip_col'')
150185 HorizJoin l r _ _ -> do
151− (skip_row',skip_col') <- ops_for_row mrow_ops bg region l skip_dim y remaining_columns
186+ (skip_row',skip_col') <- row_ops_for_image mrow_ops l bg region skip_dim y remaining_columns
152187 -- Don't output the right part unless there is at least a single column left after
153188 -- outputting the left part.
154189 if image_width l - (skip_col - skip_col') > remaining_columns
155190 then return (skip_row,skip_col')
156191 else do
157− (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region r (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col'))
192+ (skip_row'',skip_col'') <- row_ops_for_image mrow_ops r bg region (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col'))
158193 return (min skip_row' skip_row'', skip_col'')
159194 BGFill width height -> do
160− let min_height = if y + height > region_height region
161− then region_height region - y
195+ let min_height = if y + (fromEnum height) > (fromEnum $! region_height region)
196+ then region_height region - (toEnum y)
162197 else height
163198 min_width = if width > remaining_columns
164199 then remaining_columns
@@ -169,7 +204,7 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
169204 actual_width = if skip_col > min_width
170205 then 0
171206 else min_width - skip_col
172− forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
207+ forM_ [y .. y + fromEnum actual_height - 1] $! \y' -> snoc_bg_fill mrow_ops bg actual_width y'
173208 let skip_row' = if actual_height > skip_row
174209 then 0
175210 else skip_row - min_height
@@ -181,25 +216,25 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
181216 if dx < 0
182217 -- Translation left
183218 -- Extract the delta and add it to skip_col.
184− then ops_for_row mrow_ops bg region (translate (0, dy) i) (skip_row, skip_col + dw) y remaining_columns
219+ then row_ops_for_image mrow_ops (translate (0, dy) i) bg region (skip_row, skip_col + dw) y remaining_columns
185220 -- Translation right
186221 else if dy < 0
187222 -- Translation up
188223 -- Extract the delta and add it to skip_row.
189− then ops_for_row mrow_ops bg region (translate (dx, 0) i) (skip_row + dh, skip_col) y remaining_columns
224+ then row_ops_for_image mrow_ops (translate (dx, 0) i) bg region (skip_row + dh, skip_col) y remaining_columns
190225 -- Translation down
191226 -- Pad the start of lines and above the image with a
192227 -- background_fill image.
193− else ops_for_row mrow_ops bg region (background_fill ow dh <-> (background_fill dw ih <|> i)) skip_dim y remaining_columns
228+ else row_ops_for_image mrow_ops (background_fill ow dh <-> (background_fill dw ih <|> i)) bg region skip_dim y remaining_columns
194229 where
195230 dw = toEnum $ abs dx
196231 dh = toEnum $ abs dy
197232 ow = image_width image
198233 ih = image_height i
199234 ImageCrop (max_w,max_h) i ->
200− if y >= max_h
235+ if y >= fromEnum max_h
201236 then return skip_dim
202− else ops_for_row mrow_ops bg region i skip_dim y (min remaining_columns max_w)
237+ else row_ops_for_image mrow_ops i bg region skip_dim y (min remaining_columns max_w)
203238 ImagePad (min_w,min_h) i -> do
204239 let hpad = if image_width i < min_w
205240 then background_fill (min_w - image_width i) (image_height i)
@@ -207,41 +242,68 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
207242 let vpad = if image_height i < min_h
208243 then background_fill (image_width i) (min_h - image_height i)
209244 else empty_image
210− ops_for_row mrow_ops bg region ((i <|> hpad) <-> vpad) skip_dim y remaining_columns
245+ row_ops_for_image mrow_ops ((i <|> hpad) <-> vpad) bg region skip_dim y remaining_columns
211246
212−snoc_text_span :: Attr
213− -> DisplayString
214− -> MRowOps s
215− -> Word
216− -> Word
217− -> Word
247+snoc_text_span :: Attr -- the display attributes of the text span
248+ -> DisplayString -- the text to output
249+ -> MRowOps s -- the display operations to add to
250+ -> Word -- the number of display columns in the text span to
251+ -- skip before outputting
252+ -> Int -- the row of the display operations to add to
253+ -> Word -- the number of columns from the next column to be
254+ -- defined to the end of the display for the row.
218255 -> ST s Word
219−snoc_text_span a text_str mrow_ops skip_col y remaining_columns = do
256+snoc_text_span a text_str mrow_ops columns_to_skip y remaining_columns = do
220257 snoc_op mrow_ops y $ AttributeChange a
221− let (ow', dw', cw', txt) = Foldable.foldl'
222− build_text_span
223− ( 0, 0, 0, B.empty )
224− text_str
225− snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
226− return $ skip_col - dw'
258+ -- At most a text span will consist of remaining_columns characters
259+ -- we keep track of the position of the next character.
260+ let max_len :: Int = fromEnum remaining_columns
261+ mspan_chars <- Vector.new max_len
262+ ( used_display_columns, display_columns_skipped, used_char_count )
263+ <- Foldable.foldlM (build_text_span mspan_chars) ( 0, 0, 0 ) text_str
264+ -- once all characters have been output to mspan_chars we grab the used head
265+ out_text <- Vector.unsafeFreeze $! Vector.take used_char_count mspan_chars
266+ -- convert to UTF8 bytestring.
267+ -- This could be made faster. Hopefully the optimizer does a fair job at fusing the fold
268+ -- contained in fromString with the unfold in toList. No biggy right now then.
269+ snoc_op mrow_ops y $! TextSpan used_display_columns (toEnum used_char_count)
270+ $! UTF8.fromString
271+ $! Vector.toList out_text
272+ return $ columns_to_skip - display_columns_skipped
227273 where
228− build_text_span (ow', dw', char_count', b0) (c,w) = {-# SCC "build_text_span" #-}
229− if dw' == skip_col
230− then if ow' == remaining_columns
231− then ( ow', dw', char_count', b0 )
232− else if (w + ow') > remaining_columns
233− then ( remaining_columns, dw', char_count' + ooverflow, B.append b0 oelipses )
234− else ( ow' + w, dw', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
235− else if (w + dw') > skip_col
236− then ( doverflow, skip_col, doverflow, B.append b0 delipses )
237− else ( ow', w + dw', char_count', b0 )
238− where
239− doverflow = skip_col - dw'
240− delipses = B.pack $ encode $ genericReplicate doverflow '…'
241− ooverflow = remaining_columns - ow'
242− oelipses = B.pack $ encode $ genericReplicate ooverflow '…'
274+ build_text_span mspan_chars (used_display_columns, display_columns_skipped, used_char_count)
275+ (out_char, char_display_width) = {-# SCC "build_text_span" #-}
276+ -- Only valid if the maximum width of a character is 2 display columns.
277+ -- XXX: Optimize into a skip pass then clipped fill pass
278+ if display_columns_skipped == columns_to_skip
279+ then if used_display_columns == remaining_columns
280+ then return $! ( used_display_columns, display_columns_skipped, used_char_count )
281+ else if ( used_display_columns + char_display_width ) > remaining_columns
282+ then do
283+ Vector.write mspan_chars used_char_count '…'
284+ return $! ( used_display_columns
285+ , display_columns_skipped
286+ , used_char_count + 1
287+ )
288+ else do
289+ Vector.write mspan_chars used_char_count out_char
290+ return $! ( used_display_columns + char_display_width
291+ , display_columns_skipped
292+ , used_char_count + 1
293+ )
294+ else if (display_columns_skipped + char_display_width) > columns_to_skip
295+ then do
296+ Vector.write mspan_chars used_char_count '…'
297+ return $! ( used_display_columns + 1
298+ , columns_to_skip
299+ , used_char_count + 1
300+ )
301+ else return $ ( used_display_columns
302+ , display_columns_skipped + char_display_width
303+ , used_char_count
304+ )
243305
244−snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
306+snoc_bg_fill :: MRowOps s -> Background -> Word -> Int -> ST s ()
245307snoc_bg_fill _row_ops _bg 0 _row
246308 = return ()
247309snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
@@ -264,10 +326,9 @@ snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
264326 $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
265327 snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
266328
267−snoc_op :: MRowOps s -> Word -> SpanOp -> ST s ()
329+snoc_op :: MRowOps s -> Int -> SpanOp -> ST s ()
268330snoc_op !mrow_ops !row !op = do
269− ops <- readSTArray mrow_ops row
270− let ops' = ops ++ [op]
271− writeSTArray mrow_ops row ops'
272− return ()
331+ ops <- Vector.read mrow_ops row
332+ let ops' = Vector.snoc ops op
333+ Vector.write mrow_ops row ops'
273334
src/Graphics/Vty/Terminal/Generic.hs+21−22
@@ -21,11 +21,10 @@ import Graphics.Vty.DisplayAttributes
2121import Control.Monad ( liftM )
2222import Control.Monad.Trans
2323
24−import Data.Array
2524import qualified Data.ByteString.Internal as BSCore
26−import Data.Foldable
2725import Data.IORef
2826import Data.String.UTF8 hiding ( foldl )
27+import qualified Data.Vector as Vector
2928
3029import System.IO
3130
@@ -110,7 +109,7 @@ display_context t b = do
110109 display_terminal_instance t b (\ d -> DisplayHandle d t s)
111110
112111data DisplayState = DisplayState
113− { previous_output_ref :: IORef (Maybe SpanOpSequence)
112+ { previous_output_ref :: IORef (Maybe DisplayOps)
114113 }
115114
116115initial_display_state :: MonadIO m => m DisplayState
@@ -220,8 +219,8 @@ output_picture (DisplayHandle d t s) pic = do
220219 -> if effected_region previous_ops /= effected_region ops
221220 then return $ replicate ( fromEnum $ region_height $ effected_region ops )
222221 True
223− else return $ zipWith (/=) ( elems $ row_ops previous_ops )
224− ( elems $ row_ops ops )
222+ else return $ zipWith (/=) ( Vector.toList $ display_ops previous_ops )
223+ ( Vector.toList $ display_ops ops )
225224
226225 -- determine the number of bytes required to completely serialize the output ops.
227226 let total = hide_cursor_required_bytes d
@@ -254,9 +253,9 @@ output_picture (DisplayHandle d t s) pic = do
254253 liftIO $ writeIORef (previous_output_ref s) (Just ops)
255254 return ()
256255
257−required_bytes :: DisplayTerminal d => d -> FixedAttr -> [Bool] -> SpanOpSequence -> Word
256+required_bytes :: DisplayTerminal d => d -> FixedAttr -> [Bool] -> DisplayOps -> Word
258257required_bytes d in_fattr diffs ops =
259− let (_, n, _, _) = foldl' required_bytes' (0, 0, in_fattr, diffs) ( row_ops ops )
258+ let (_, n, _, _) = Vector.foldl' required_bytes' (0, 0, in_fattr, diffs) ( display_ops ops )
260259 in n
261260 where
262261 required_bytes' (y, current_sum, fattr, True : diffs') span_ops
@@ -272,11 +271,11 @@ span_ops_required_bytes d y in_fattr span_ops =
272271 -- The first operation is to set the cursor to the start of the row
273272 let header_required_bytes = move_cursor_required_bytes d 0 y
274273 -- then the span ops are serialized in the order specified
275− in foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes d fattr op
276− in (c + current_sum, fattr')
277− )
278− (header_required_bytes, in_fattr)
279− span_ops
274+ in Vector.foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes d fattr op
275+ in (c + current_sum, fattr')
276+ )
277+ (header_required_bytes, in_fattr)
278+ span_ops
280279
281280span_op_required_bytes :: DisplayTerminal d => d -> FixedAttr -> SpanOp -> (Word, FixedAttr)
282281span_op_required_bytes d fattr (AttributeChange attr) =
@@ -292,12 +291,12 @@ serialize_output_ops :: ( MonadIO m, DisplayTerminal d )
292291 -> OutputBuffer
293292 -> FixedAttr
294293 -> [Bool]
295− -> SpanOpSequence
294+ -> DisplayOps
296295 -> m OutputBuffer
297296serialize_output_ops d start_ptr in_fattr diffs ops = do
298− (_, end_ptr, _, _) <- foldlM serialize_output_ops'
297+ (_, end_ptr, _, _) <- Vector.foldM' serialize_output_ops'
299298 ( 0, start_ptr, in_fattr, diffs )
300− ( row_ops ops )
299+ ( display_ops ops )
301300 return end_ptr
302301 where
303302 serialize_output_ops' ( y, out_ptr, fattr, True : diffs' ) span_ops
@@ -319,9 +318,9 @@ serialize_span_ops d y out_ptr in_fattr span_ops = do
319318 -- The first operation is to set the cursor to the start of the row
320319 out_ptr' <- serialize_move_cursor d 0 y out_ptr
321320 -- then the span ops are serialized in the order specified
322− foldlM ( \(out_ptr'', fattr) op -> serialize_span_op d op out_ptr'' fattr )
323− (out_ptr', in_fattr)
324− span_ops
321+ Vector.foldM ( \(out_ptr'', fattr) op -> serialize_span_op d op out_ptr'' fattr )
322+ (out_ptr', in_fattr)
323+ span_ops
325324
326325serialize_span_op :: ( MonadIO m, DisplayTerminal d )
327326 => d
@@ -357,16 +356,16 @@ data CursorOutputMap = CursorOutputMap
357356 { char_to_output_pos :: (Word, Word) -> (Word, Word)
358357 }
359358
360−cursor_output_map :: SpanOpSequence -> Cursor -> CursorOutputMap
359+cursor_output_map :: DisplayOps -> Cursor -> CursorOutputMap
361360cursor_output_map span_ops _cursor = CursorOutputMap
362361 { char_to_output_pos = \(cx, cy) -> (cursor_column_offset span_ops cx cy, cy)
363362 }
364363
365−cursor_column_offset :: SpanOpSequence -> Word -> Word -> Word
364+cursor_column_offset :: DisplayOps -> Word -> Word -> Word
366365cursor_column_offset span_ops cx cy =
367− let cursor_row_ops = row_ops span_ops ! cy
366+ let cursor_row_ops = Vector.unsafeIndex (display_ops span_ops) (fromEnum cy)
368367 (out_offset, _, _)
369− = foldl' ( \(d, current_cx, done) op ->
368+ = Vector.foldl' ( \(d, current_cx, done) op ->
370369 if done then (d, current_cx, done) else case span_op_has_width op of
371370 Nothing -> (d, current_cx, False)
372371 Just (cw, ow) -> case compare cx (current_cx + cw) of
test/Verify.hs−5
@@ -24,7 +24,6 @@ import Test.QuickCheck.Monadic ( monadicIO )
2424
2525import qualified Codec.Binary.UTF8.String as UTF8
2626
27−import Control.Applicative
2827import Control.Monad.State.Strict
2928
3029import Data.IORef
@@ -37,10 +36,6 @@ import System.Random
3736
3837type Test = StateT TestState IO
3938
40−instance Applicative Test where
41− pure = return
42− ( <*> ) = ap
43−
4439data TestState = TestState
4540 { results_ref :: IORef [QC.Result]
4641 }
test/interactive_terminal_test.hs+28−28
@@ -13,10 +13,10 @@ import Control.Monad
1313import Data.List ( lookup )
1414import Data.Maybe ( isJust, fromJust )
1515import Data.Monoid
16+import Data.String.QQ
1617import Data.Word
1718
1819import Foreign.Marshal.Array
19−import HereDoc
2020
2121import qualified System.Environment as Env
2222
@@ -28,14 +28,14 @@ main = do
2828output_file_path = "test_results.list"
2929
3030print_intro = do
31− putStr $ [$heredoc|
31+ putStr $ [s|
3232This is an interactive verification program for the terminal input and output
3333support of the VTY library. This will ask a series of questions about what you
3434see on screen. The goal is to verify that VTY's output and input support
3535performs as expected with your terminal.
3636
3737This program produces a file named
38− |] ++ output_file_path ++ [$heredoc|
38+ |] ++ output_file_path ++ [s|
3939in the current directory that contains the results for each test assertion. This
4040can be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve
4141support for your terminal. No personal information is contained in the report.
@@ -186,7 +186,7 @@ reserve_output_test = Test
186186 release_terminal t
187187 return ()
188188 , print_summary = do
189− putStr $ [$heredoc|
189+ putStr $ [s|
190190Once return is pressed:
191191 0. The screen will be cleared.
192192 1. Four lines of text should be visible.
@@ -197,7 +197,7 @@ After return is pressed for the second time this test then:
197197 * The cursor is visible.
198198|]
199199 , confirm_results = do
200− putStr $ [$heredoc|
200+ putStr $ [s|
201201Did the test output match the description?
202202|]
203203 default_success_confirm_results
@@ -308,14 +308,14 @@ display_bounds_test_3 = Test
308308 }
309309
310310display_bounds_test_summary has_cursor = do
311− putStr $ [$heredoc|
311+ putStr $ [s|
312312Once return is pressed:
313313 0. The screen will be cleared.
314314|]
315315 if has_cursor
316316 then putStr " 1. The cursor will be visible."
317317 else putStr " 1. The cursor will NOT be visible."
318− putStr [$heredoc|
318+ putStr [s|
319319 2. The border of the display will be outlined in Xs.
320320 So if - and | represented the edge of the terminal window:
321321 |-------------|
@@ -323,12 +323,12 @@ Once return is pressed:
323323 |X X||]
324324
325325 if has_cursor
326− then putStr $ [$heredoc|
326+ then putStr $ [s|
327327 |XXXXXXXXXXXXC| |]
328− else putStr $ [$heredoc|
328+ else putStr $ [s|
329329 |XXXXXXXXXXXXX| |]
330330
331− putStr $ [$heredoc|
331+ putStr $ [s|
332332 |-------------|
333333
334334 ( Where C is the final position of the cursor. There may be an X drawn
@@ -341,7 +341,7 @@ After return is pressed for the second time:
341341|]
342342
343343generic_output_match_confirm = do
344− putStr $ [$heredoc|
344+ putStr $ [s|
345345Did the test output match the description?
346346|]
347347 default_success_confirm_results
@@ -423,7 +423,7 @@ unicode_single_width_1 = Test
423423 , confirm_results = generic_output_match_confirm
424424 }
425425
426−unicode_single_width_summary = putStr [$heredoc|
426+unicode_single_width_summary = putStr [s|
427427Once return is pressed:
428428 0. The screen will be cleared.
429429 1. The cursor will be hidden.
@@ -505,7 +505,7 @@ unicode_double_width_1 = Test
505505 , confirm_results = generic_output_match_confirm
506506 }
507507
508−unicode_double_width_summary = putStr [$heredoc|
508+unicode_double_width_summary = putStr [s|
509509Once return is pressed:
510510 0. The screen will be cleared.
511511 1. The cursor will be hidden.
@@ -558,7 +558,7 @@ attributes_test_0 = Test
558558 release_terminal t
559559 return ()
560560 , print_summary = do
561− putStr $ [$heredoc|
561+ putStr $ [s|
562562Once return is pressed:
563563 0. The screen will be cleared.
564564 1. The cursor will be hidden.
@@ -579,7 +579,7 @@ After return is pressed for the second time:
579579 1. The cursor should be visible.
580580|]
581581 , confirm_results = do
582− putStr $ [$heredoc|
582+ putStr $ [s|
583583Did the test output match the description?
584584|]
585585 default_success_confirm_results
@@ -604,7 +604,7 @@ attributes_test_1 = Test
604604 release_terminal t
605605 return ()
606606 , print_summary = do
607− putStr $ [$heredoc|
607+ putStr $ [s|
608608Once return is pressed:
609609 0. The screen will be cleared.
610610 1. The cursor will be hidden.
@@ -634,7 +634,7 @@ After return is pressed for the second time:
634634 1. The cursor should be visible.
635635|]
636636 , confirm_results = do
637− putStr $ [$heredoc|
637+ putStr $ [s|
638638Did the test output match the description?
639639|]
640640 default_success_confirm_results
@@ -661,7 +661,7 @@ attributes_test_2 = Test
661661 release_terminal t
662662 return ()
663663 , print_summary = do
664− putStr $ [$heredoc|
664+ putStr $ [s|
665665Once return is pressed:
666666 0. The screen will be cleared.
667667 1. The cursor will be hidden.
@@ -693,7 +693,7 @@ After return is pressed for the second time:
693693 1. The cursor should be visible.
694694|]
695695 , confirm_results = do
696− putStr $ [$heredoc|
696+ putStr $ [s|
697697Did the test output match the description?
698698|]
699699 default_success_confirm_results
@@ -720,7 +720,7 @@ attributes_test_3 = Test
720720 release_terminal t
721721 return ()
722722 , print_summary = do
723− putStr $ [$heredoc|
723+ putStr $ [s|
724724Once return is pressed:
725725 0. The screen will be cleared.
726726 1. The cursor will be hidden.
@@ -760,7 +760,7 @@ After return is pressed for the second time:
760760 1. The cursor should be visible.
761761|]
762762 , confirm_results = do
763− putStr $ [$heredoc|
763+ putStr $ [s|
764764Did the test output match the description?
765765|]
766766 default_success_confirm_results
@@ -796,7 +796,7 @@ attributes_test_4 = Test
796796 release_terminal t
797797 return ()
798798 , print_summary = do
799− putStr $ [$heredoc|
799+ putStr $ [s|
800800Once return is pressed:
801801 0. The screen will be cleared.
802802 1. The cursor will be hidden.
@@ -823,7 +823,7 @@ After return is pressed for the second time:
823823 1. The cursor should be visible.
824824|]
825825 , confirm_results = do
826− putStr $ [$heredoc|
826+ putStr $ [s|
827827Did the test output match the description?
828828|]
829829 default_success_confirm_results
@@ -847,7 +847,7 @@ attributes_test_5 = Test
847847 release_terminal t
848848 return ()
849849 , print_summary = do
850− putStr $ [$heredoc|
850+ putStr $ [s|
851851Once return is pressed:
852852 0. The screen will be cleared.
853853 1. The cursor will be hidden.
@@ -861,7 +861,7 @@ After return is pressed for the second time:
861861 1. The cursor should be visible.
862862|]
863863 , confirm_results = do
864− putStr $ [$heredoc|
864+ putStr $ [s|
865865Did the test output match the description?
866866|]
867867 default_success_confirm_results
@@ -879,7 +879,7 @@ inline_test_0 = Test
879879 putStrLn "line 3."
880880 release_terminal t
881881 return ()
882− , print_summary = putStr $ [$heredoc|
882+ , print_summary = putStr $ [s|
883883lines are in order.
884884The second line "line 2" should have a red background and the text underline.
885885The third line "line 3" should be drawn in the same style as the first line.
@@ -900,7 +900,7 @@ inline_test_1 = Test
900900 putStrLn "Not styled."
901901 release_terminal t
902902 return ()
903− , print_summary = putStr $ [$heredoc|
903+ , print_summary = putStr $ [s|
904904|]
905905
906906 , confirm_results = generic_output_match_confirm
@@ -918,7 +918,7 @@ inline_test_2 = Test
918918 putStr "Not styled.\n"
919919 release_terminal t
920920 return ()
921− , print_summary = putStr $ [$heredoc|
921+ , print_summary = putStr $ [s|
922922|]
923923
924924 , confirm_results = generic_output_match_confirm
test/verify_debug_terminal.hs+4−4
@@ -50,7 +50,7 @@ single_T_row (DebugWindow w h) = liftIOResult $ do
5050 ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
5151 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
5252 if out_bytes /= expected_bytes
53− then return $ failed $ result { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
53+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
5454 else return succeeded
5555
5656many_T_rows :: DebugWindow -> Property
@@ -67,7 +67,7 @@ many_T_rows (DebugWindow w h) = liftIOResult $ do
6767 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
6868 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
6969 if out_bytes /= expected_bytes
70− then return $ failed $ result { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
70+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
7171 else return succeeded
7272
7373many_T_rows_cropped_width :: DebugWindow -> Property
@@ -84,7 +84,7 @@ many_T_rows_cropped_width (DebugWindow w h) = liftIOResult $ do
8484 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
8585 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
8686 if out_bytes /= expected_bytes
87− then return $ failed $ result { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
87+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
8888 else return succeeded
8989
9090many_T_rows_cropped_height :: DebugWindow -> Property
@@ -101,7 +101,7 @@ many_T_rows_cropped_height (DebugWindow w h) = liftIOResult $ do
101101 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
102102 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
103103 if out_bytes /= expected_bytes
104− then return $ failed $ result { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
104+ then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
105105 else return succeeded
106106
107107main = run_test $ do
test/verify_eval_terminfo_caps.hs+5−6
@@ -104,8 +104,7 @@ main = do
104104 parse_result <- parse_cap_expression cap_def
105105 let test_name = "\teval cap " ++ cap_name ++ " -> " ++ show cap_def
106106 _ <- case parse_result of
107− Left error -> verify test_name ( liftResult $ failed
108− $ result { reason = "prase error " ++ show error } )
107+ Left error -> verify test_name ( failed { reason = "prase error " ++ show error } )
109108 Right !cap_expr -> verify test_name ( verify_eval_cap eval_buffer cap_expr )
110109 return ()
111110 Nothing -> do
@@ -124,11 +123,11 @@ verify_eval_cap eval_buffer expr !junk_int = do
124123 end_ptr <- serialize_cap_expression expr input_values start_ptr
125124 case end_ptr `minusPtr` start_ptr of
126125 count | count < 0 ->
127− return $ failed $ result { reason = "End pointer before start pointer." }
126+ return $ failed { reason = "End pointer before start pointer." }
128127 | toEnum count > byte_count ->
129− return $ failed $ result { reason = "End pointer past end of buffer by "
130− ++ show (toEnum count - byte_count)
131− }
128+ return $ failed { reason = "End pointer past end of buffer by "
129+ ++ show (toEnum count - byte_count)
130+ }
132131 | otherwise ->
133132 return succeeded
134133
test/verify_span_ops.hs+2−2
@@ -11,7 +11,7 @@ import Graphics.Vty.Debug
1111
1212import Verify
1313
14−import Data.Array
14+import qualified Data.Vector as Vector
1515
1616unit_image_and_zero_window_0 :: UnitImage -> EmptyWindow -> Bool
1717unit_image_and_zero_window_0 (UnitImage _ i) (EmptyWindow w) =
@@ -139,7 +139,7 @@ span_ops_actually_fill_columns (DefaultPic pic win _) =
139139first_span_op_sets_attr :: DefaultPic -> Bool
140140first_span_op_sets_attr DefaultPic { default_pic = pic, default_win = win } =
141141 let spans = spans_for_pic pic (region_for_window win)
142− in all ( is_attr_span_op . head ) ( elems $ row_ops spans )
142+ in all ( is_attr_span_op . Vector.head ) ( Vector.toList $ display_ops spans )
143143
144144single_attr_single_span_stack_op_coverage :: SingleAttrSingleSpanStack -> Result
145145single_attr_single_span_stack_op_coverage stack =
vty.cabal+2−2
@@ -1,5 +1,5 @@
11Name: vty
2−Version: 4.7.0.1
2+Version: 4.7.0.3
33License: BSD3
44License-file: LICENSE
55Author: Stefan O'Rear, Corey O'Connor
@@ -33,7 +33,7 @@ Build-Depends: terminfo >= 0.3 && < 0.4
3333Build-Depends: utf8-string >= 0.3 && < 0.4
3434Build-Depends: mtl >= 1.1.1.0 && < 2.1
3535Build-Depends: ghc-prim, parallel >= 2.2 && < 3.2, deepseq >= 1.1 && < 1.2
36−Build-Depends: array
36+Build-Depends: vector >= 0.7
3737Build-Depends: parsec >= 2 && < 4
3838Build-Type: Simple
3939Data-Files: README, TODO