on master help sign in
Merge branch 'master' of github.com:coreyoconnor/vty
commit3da7ed057ef9d4da0fa42efd210931d646546f7a authorCorey O'Connor <coreyoconnor@gmail.com> dateFri 26 Jul 2013, 21:29 (13 years ago) parenta1036dfef226f9
21 files changed+430−353
CHANGELOG+20−7 README.md+1 src/Data/Terminfo/Eval.hs+1−1 src/Data/Terminfo/Parse.hs+14−3 src/Graphics/Vty.hs+2−2 src/Graphics/Vty/Image/Internal.hs+1−1 src/Graphics/Vty/Picture.hs+12−6 src/Graphics/Vty/PictureToSpans.hs+302−2 src/Graphics/Vty/Span.hs+19−287 src/Graphics/Vty/Terminal/Interface.hs+13 src/Graphics/Vty/Terminal/Mock.hs+5 src/Graphics/Vty/Terminal/TerminfoBased.hs+5−1 test/Verify.hs+1 test/Verify/Data/Terminfo/Parse.hs−6 test/Verify/Graphics/Vty/Image.hs+3−3 test/VerifySpanOps.hs+3−2 test/VerifyTerminal.hs+12−12 test/benchmark.hs+2−2 test/current_benchmark_results.txt+13−17 test/interactive_terminal_test vty.cabal+1−1
CHANGELOG+20−7
@@ -1,12 +1,25 @@
115.0.0
2− * API changes:
3− * removed the typeclass based terminal and display context interface in favor of a data
4− structure of properties interface.
2+ * removed the typeclass based terminal and display context interface in favor of a data
3+ structure of properties interface.
4+ * The default picture for an image now uses the "clear" background. This background fills
5+ background spans with spaces or just ends the line.
6+ * Previously the background defaulted to the space character. This causes issues copying
7+ text from a text editor. The text would end up with many extra spaces at the end of the
8+ line.
9+ * Layer support
10+ * Each layer is an image.
11+ * The layers for a picture are a list of images.
12+ * The first image is the top-most layer.
13+ * The final rendering is as if the bottom layer (last image) was rendered, then the next
14+ (second to last image) etc.
15+ * The transparent areas for a layer are the background image components.
16+ * If the background is clear there is no background layer.
17+ * If there is a background character then the bottom layer is the background layer.
518 * compatibility improvements:
6− * terminfo based terminals with no cursor support are now silently accepted. The cursor
7− visibility changes in the Picture will have no effect.
8− * alternate (setf/setb) color maps supported. Though colors beyond the first 8 are just a
9− guess.
19+ * terminfo based terminals with no cursor support are silently accepted. The cursor
20+ visibility changes in the Picture will have no effect.
21+ * alternate (setf/setb) color maps supported. Though colors beyond the first 8 are just a
22+ guess.
1023
11244.7.0.0
1225 * API changes:
README.md+1
@@ -63,6 +63,7 @@ Profiling appears to cause issues with coverage when enabled. To evaluate covera
6363follows:
6464
6565~~~
66+rm -rf dist
6667cabal configure --enable-tests --enable-library-coverage \
6768 --disable-library-profiling \
6869 --disable-executable-profiling
src/Data/Terminfo/Eval.hs+1−1
@@ -162,7 +162,7 @@ serialize_cap_ops :: OutputBuffer -> CapOps -> EvalT IO OutputBuffer
162162serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
163163
164164serialize_cap_op :: OutputBuffer -> CapOp -> EvalT IO OutputBuffer
165−serialize_cap_op !out_ptr ( Bytes !offset !byte_count !next_offset ) = do
165+serialize_cap_op !out_ptr (Bytes !offset !byte_count !next_offset) = do
166166 !cap <- get >>= return . eval_expression
167167 let ( !start_ptr, _ ) = cap_bytes cap
168168 !src_ptr = start_ptr `plusPtr` offset
src/Data/Terminfo/Parse.hs+14−3
@@ -21,6 +21,8 @@ import Foreign.C.Types
2121import Foreign.Marshal.Array
2222import Foreign.Ptr
2323
24+import Numeric (showHex)
25+
2426import Text.ParserCombinators.Parsec
2527
2628type CapBytes = ( Ptr Word8, CSize )
@@ -31,7 +33,16 @@ data CapExpression = CapExpression
3133 , source_string :: !String
3234 , param_count :: !Word
3335 , param_ops :: !ParamOps
34− }
36+ } deriving (Eq)
37+
38+instance Show CapExpression where
39+ show c
40+ = "CapExpression { " ++ show (cap_ops c) ++ " }"
41+ ++ " <- [" ++ hex_dump ( map ( toEnum . fromEnum ) $! source_string c ) ++ "]"
42+ ++ " <= " ++ show (source_string c)
43+ where
44+ hex_dump :: [Word8] -> String
45+ hex_dump = foldr (\b s -> showHex b s) ""
3546
3647instance NFData CapExpression where
3748 rnf (CapExpression ops !_bytes !str !c !p_ops)
@@ -55,7 +66,7 @@ data CapOp =
5566 | BitwiseOr | BitwiseXOr | BitwiseAnd
5667 | ArithPlus | ArithMinus
5768 | CompareEq | CompareLt | CompareGt
58− deriving ( Show )
69+ deriving (Show, Eq)
5970
6071instance NFData CapOp where
6172 rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset
@@ -76,7 +87,7 @@ instance NFData CapOp where
7687type ParamOps = [ParamOp]
7788data ParamOp =
7889 IncFirstTwo
79− deriving ( Show )
90+ deriving (Show, Eq)
8091
8192instance NFData ParamOp where
8293 rnf IncFirstTwo = ()
src/Graphics/Vty.hs+2−2
@@ -80,8 +80,8 @@ mkVty = mkVtyEscDelay 0
8080-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
8181-- \todo move input init into terminal interface
8282mkVtyEscDelay :: Int -> IO Vty
83−mkVtyEscDelay escDelay = do
84− term_info <- Terminfo.setupTermFromEnv
83+mkVtyEscDelay escDelay = do
84+ term_info <- Terminfo.setupTermFromEnv
8585 t <- current_terminal
8686 reserve_display t
8787 (kvar, endi) <- initTermInput escDelay term_info
src/Graphics/Vty/Image/Internal.hs+1−1
@@ -33,7 +33,7 @@ data Image =
3333 }
3434 -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
3535 -- required to be between two images of equal height. The horiz_join constructor adds background
36− -- filles to the provided images that assure this is true for the HorizJoin value produced.
36+ -- fills to the provided images that assure this is true for the HorizJoin value produced.
3737 | HorizJoin
3838 { part_left :: Image
3939 , part_right :: Image
src/Graphics/Vty/Picture.hs+12−6
@@ -54,7 +54,7 @@ pic_for_image :: Image -> Picture
5454pic_for_image i = Picture
5555 { pic_cursor = NoCursor
5656 , pic_layers = [i]
57− , pic_background = Background ' ' current_attr
57+ , pic_background = ClearBackground
5858 }
5959
6060-- | A picture can be configured either to not show the cursor or show the cursor at the specified
@@ -80,17 +80,23 @@ instance NFData Cursor where
8080--
8181-- \todo The current attribute is always set to the default attributes at the start of updating the
8282-- screen to a picture.
83−--
84−-- \todo The background character *must* occupy a single column and no more.
85−--
86−-- \todo background char should be optional
87−data Background = Background
83+data Background
84+ = Background
8885 { background_char :: Char
8986 , background_attr :: Attr
9087 }
88+ -- | The background is:
89+ --
90+ -- * the space character if there are remaining non-skip ops
91+ -- * (?) nothing if there are no remaining non-skip ops.
92+ --
93+ -- Might require the terminal interface to do a line clear?
94+ -- Or should the nothing case be switched with crlf?
95+ | ClearBackground
9196
9297instance NFData Background where
9398 rnf (Background c a) = c `seq` a `seq` ()
99+ rnf ClearBackground = ()
94100
95101-- | Compatibility with applications that do not use more than a single layer.
96102pic_image :: Picture -> Image
src/Graphics/Vty/PictureToSpans.hs+302−2
@@ -1,4 +1,304 @@
1−-- Copyright 2009-2010 Corey O'Connor
2−module Graphics.Vty.PictureToSpans
1+-- Copyright Corey O'Connor<coreyoconnor@gmail.com>
2+{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE TemplateHaskell #-}
4+{-# LANGUAGE BangPatterns #-}
5+{-# LANGUAGE ExistentialQuantification #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE NamedFieldPuns #-}
8+module Graphics.Vty.PictureToSpans where
9+
10+import Graphics.Vty.DisplayRegion
11+import Graphics.Vty.Image
12+import Graphics.Vty.Image.Internal
13+import Graphics.Vty.Picture
14+import Graphics.Vty.Span
15+
16+import Control.Applicative
17+import Control.Lens hiding ( op )
18+import Control.Monad.Reader
19+import Control.Monad.State.Strict hiding ( state )
20+import Control.Monad.ST.Strict hiding ( unsafeIOToST )
21+
22+import qualified Data.Vector as Vector hiding ( take, replicate )
23+import Data.Vector.Mutable ( MVector(..))
24+import qualified Data.Vector.Mutable as MVector
25+
26+import qualified Data.Text as T
27+import qualified Data.Text.Encoding as T
28+import qualified Data.Text.Lazy as TL
29+
30+type MRowOps s = MVector s SpanOps
31+
32+type MSpanOps s = MVector s SpanOp
33+
34+-- transform plus clip. More or less.
35+data BlitState = BlitState
36+ -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
37+ { _column_offset :: Int
38+ , _row_offset :: Int
39+ -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
40+ , _skip_columns :: Int
41+ -- >= 0 and < image_height
42+ , _skip_rows :: Int
43+ -- includes consideration of skip_columns. In display space.
44+ -- The number of columns from the next column to be defined to the end of the display for the
45+ -- row.
46+ , _remaining_columns :: Int
47+ -- includes consideration of skip_rows. In display space.
48+ , _remaining_rows :: Int
49+ }
50+
51+makeLenses ''BlitState
52+
53+data BlitEnv s = BlitEnv
54+ { _region :: DisplayRegion
55+ , _mrow_ops :: MRowOps s
56+ }
57+
58+makeLenses ''BlitEnv
59+
60+type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
61+
62+-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
63+-- specified region.
64+spans_for_pic :: Picture -> DisplayRegion -> DisplayOps
65+spans_for_pic pic r = DisplayOps r $ Vector.create (combined_spans_for_layers pic r)
66+
67+-- | Produces the span ops for each layer then combines them.
68+--
69+-- TODO: a fold over a builder function. start with span ops that are a bg fill of the entire
70+-- region.
71+combined_spans_for_layers :: Picture -> DisplayRegion -> ST s (MRowOps s)
72+combined_spans_for_layers pic r
73+ | region_width r == 0 || region_height r == 0 = MVector.new 0
74+ | otherwise = do
75+ layer_ops <- mapM (\layer -> build_spans layer r) (pic_layers pic)
76+ case layer_ops of
77+ [] -> fail "empty picture"
78+ [ops] -> substitute_skips (pic_background pic) ops
79+ _ -> fail "TODO: picture with more than one layer not supported"
80+
81+substitute_skips :: Background -> MRowOps s -> ST s (MRowOps s)
82+substitute_skips ClearBackground ops = do
83+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
84+ row_ops <- MVector.read ops row
85+ -- the image operations assure that background fills are combined.
86+ -- clipping a background fill does not split the background fill.
87+ -- merging of image layers can split a skip, but only by the insertion of a non skip.
88+ -- all this combines to mean we can check the last operation and remove it if it's a skip
89+ -- todo: or does it?
90+ let row_ops' = case Vector.last row_ops of
91+ Skip w -> Vector.init row_ops `Vector.snoc` RowEnd w
92+ _ -> row_ops
93+ -- now all the skips can be replaced by replications of ' ' of the required width.
94+ let row_ops'' = swap_skips_for_single_column_char_span ' ' current_attr row_ops'
95+ MVector.write ops row row_ops''
96+ return ops
97+substitute_skips (Background {background_char, background_attr}) ops = do
98+ -- At this point we decide if the background character is single column or not.
99+ -- obviously, single column is easier.
100+ case safe_wcwidth background_char of
101+ w | w == 0 -> fail $ "invalid background character " ++ show background_char
102+ | w == 1 -> do
103+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
104+ row_ops <- MVector.read ops row
105+ let row_ops' = swap_skips_for_single_column_char_span background_char background_attr row_ops
106+ MVector.write ops row row_ops'
107+ | otherwise -> do
108+ forM_ [0 .. MVector.length ops - 1] $ \row -> do
109+ row_ops <- MVector.read ops row
110+ let row_ops' = swap_skips_for_char_span w background_char background_attr row_ops
111+ MVector.write ops row row_ops'
112+ return ops
113+
114+swap_skips_for_single_column_char_span :: Char -> Attr -> SpanOps -> SpanOps
115+swap_skips_for_single_column_char_span c a = Vector.concatMap f
116+ where f (Skip ow) = let txt = T.pack $ replicate ow c
117+ in Vector.cons (AttributeChange a)
118+ (Vector.singleton $ TextSpan ow ow (T.encodeUtf8 txt))
119+ f v = Vector.singleton v
120+
121+swap_skips_for_char_span :: Int -> Char -> Attr -> SpanOps -> SpanOps
122+swap_skips_for_char_span w c a = Vector.concatMap f
3123 where
124+ f (Skip ow) = let txt_0_cw = ow `div` w
125+ txt_0 = T.pack $ replicate txt_0_cw c
126+ txt_1_cw = ow `mod` w
127+ txt_1 = T.pack $ replicate txt_1_cw '…'
128+ cw = txt_0_cw + txt_1_cw
129+ txt = txt_0 `T.append` txt_1
130+ in Vector.cons (AttributeChange a)
131+ (Vector.singleton $ TextSpan ow cw $ T.encodeUtf8 txt)
132+ f v = Vector.singleton v
133+
134+-- | Builds a vector of row operations that will output the given picture to the terminal.
135+--
136+-- Crops to the given display region.
137+--
138+-- TODO: I'm pretty sure there is an algorithm that does not require a mutable buffer.
139+build_spans :: Image -> DisplayRegion -> ST s (MRowOps s)
140+build_spans image out_region = do
141+ -- First we create a mutable vector for each rows output operations.
142+ out_ops <- MVector.replicate (region_height out_region) Vector.empty
143+ -- \todo I think building the span operations in display order would provide better performance.
144+ -- However, I got stuck trying to implement an algorithm that did this. This will be considered
145+ -- as a possible future optimization.
146+ --
147+ -- A depth first traversal of the image is performed. ordered according to the column range
148+ -- defined by the image from least to greatest. The output row ops will at least have the
149+ -- region of the image specified. Iterate over all output rows and output background fills for
150+ -- all unspecified columns.
151+ --
152+ -- The images are made into span operations from left to right. It's possible that this could
153+ -- easily be made to assure top to bottom output as well.
154+ when (region_height out_region > 0 && region_width out_region > 0) $ do
155+ -- The ops builder recursively descends the image and outputs span ops that would
156+ -- display that image. The number of columns remaining in this row before exceeding the
157+ -- bounds is also provided. This is used to clip the span ops produced to the display.
158+ let full_build = do
159+ start_image_build image
160+ -- Fill in any unspecified columns with a skip.
161+ forM_ [0 .. (region_height out_region - 1)] (add_row_completion out_region)
162+ init_env = BlitEnv out_region out_ops
163+ init_state = BlitState 0 0 0 0 (region_width out_region) (region_height out_region)
164+ _ <- runStateT (runReaderT full_build init_env) init_state
165+ return ()
166+ return out_ops
167+
168+-- | Add the operations required to build a given image to the current set of row operations
169+-- returns the number of columns and rows contributed to the output.
170+start_image_build :: Image -> BlitM s ()
171+start_image_build image = do
172+ out_of_bounds <- is_out_of_bounds <$> get
173+ when (not out_of_bounds) $ add_maybe_clipped image
174+
175+is_out_of_bounds :: BlitState -> Bool
176+is_out_of_bounds s
177+ | s ^. remaining_columns <= 0 = True
178+ | s ^. remaining_rows <= 0 = True
179+ | otherwise = False
180+
181+-- TODO: prove this cannot be called in an out of bounds case.
182+add_maybe_clipped :: forall s . Image -> BlitM s ()
183+add_maybe_clipped EmptyImage = return ()
184+add_maybe_clipped (HorizText a text_str ow _cw) = do
185+ -- TODO: assumption that text spans are only 1 row high.
186+ s <- use skip_rows
187+ when (s < 1) $ do
188+ use row_offset >>= snoc_op (AttributeChange a)
189+ left_clip <- use skip_columns
190+ right_clip <- use remaining_columns
191+ let left_clipped = left_clip > 0
192+ right_clipped = (ow - left_clip) > right_clip
193+ if left_clipped || right_clipped
194+ then let text_str' = clip_text text_str left_clip right_clip
195+ in add_unclipped_text text_str'
196+ else add_unclipped_text text_str
197+add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
198+ add_maybe_clipped_join "vert_join" skip_rows remaining_rows row_offset
199+ (image_height top_image)
200+ top_image
201+ bottom_image
202+ oh
203+add_maybe_clipped (HorizJoin left_image right_image ow _oh) = do
204+ add_maybe_clipped_join "horiz_join" skip_columns remaining_columns column_offset
205+ (image_width left_image)
206+ left_image
207+ right_image
208+ ow
209+add_maybe_clipped BGFill {output_width, output_height} = do
210+ s <- get
211+ let output_width' = min (output_width - s^.skip_columns) (s^.remaining_columns)
212+ output_height' = min (output_height - s^.skip_rows ) (s^.remaining_rows)
213+ y <- use row_offset
214+ forM_ [y..y+output_height'-1] $ snoc_op (Skip output_width')
215+add_maybe_clipped CropRight {cropped_image, output_width} = do
216+ remaining_columns .= output_width
217+ add_maybe_clipped cropped_image
218+add_maybe_clipped CropLeft {cropped_image, left_skip} = do
219+ skip_columns += left_skip
220+ add_maybe_clipped cropped_image
221+add_maybe_clipped CropBottom {cropped_image, output_height} = do
222+ remaining_rows .= output_height
223+ add_maybe_clipped cropped_image
224+add_maybe_clipped CropTop {cropped_image, top_skip} = do
225+ skip_rows += top_skip
226+ add_maybe_clipped cropped_image
227+
228+add_maybe_clipped_join :: forall s . String
229+ -> Lens BlitState BlitState Int Int
230+ -> Lens BlitState BlitState Int Int
231+ -> Lens BlitState BlitState Int Int
232+ -> Int
233+ -> Image
234+ -> Image
235+ -> Int
236+ -> BlitM s ()
237+add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
238+ state <- get
239+ when (state^.remaining == 0) $ fail $ name ++ " with remaining == 0"
240+ case state^.skip of
241+ s | s >= size -> fail $ name ++ " on fully clipped"
242+ -- TODO: check if clipped in other dim. if not use add_unclipped
243+ | s == 0 -> case state^.remaining of
244+ r | r > i0_dim -> do
245+ add_maybe_clipped i0
246+ put $ state & offset +~ i0_dim & remaining -~ i0_dim
247+ add_maybe_clipped i1
248+ | otherwise -> add_maybe_clipped i0
249+ | s >= i0_dim -> do
250+ put $ state & skip -~ i0_dim
251+ add_maybe_clipped i1
252+ | otherwise -> case i0_dim - s of
253+ i0_dim' | state^.remaining <= i0_dim' -> add_maybe_clipped i0
254+ | otherwise -> do
255+ add_maybe_clipped i0
256+ put $ state & offset +~ i0_dim' & remaining -~ i0_dim'
257+ add_maybe_clipped i1
258+
259+-- TODO: store a skip list in HorizText(?)
260+-- TODO: represent display strings containing chars that are not 1 column chars as a separate
261+-- display string value?
262+-- TODO: assumes max column width is 2
263+clip_text :: DisplayText -> Int -> Int -> DisplayText
264+clip_text txt left_skip right_clip =
265+ -- CPS would clarify this I think
266+ let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
267+ txt' = if pad_prefix then TL.cons '…' (TL.drop (to_drop+1) txt) else TL.drop to_drop txt
268+ (to_take,pad_suffix) = clip_for_char_width right_clip txt' 0
269+ txt'' = TL.append (TL.take to_take txt') (if pad_suffix then TL.singleton '…' else TL.empty)
270+ clip_for_char_width 0 _ n = (n, False)
271+ clip_for_char_width w t n
272+ | w < cw = (n, True)
273+ | w == cw = (n+1, False)
274+ | w > cw = clip_for_char_width (w - cw) (TL.tail t) (n + 1)
275+ where cw = safe_wcwidth (TL.head t)
276+ clip_for_char_width _ _ _ = error "clip_for_char_width applied to undefined"
277+ in txt''
278+
279+add_unclipped_text :: DisplayText -> BlitM s ()
280+add_unclipped_text txt = do
281+ let op = TextSpan used_display_columns
282+ (fromIntegral $ TL.length txt)
283+ (T.encodeUtf8 $ TL.toStrict txt)
284+ used_display_columns = wcswidth $ TL.unpack txt
285+ use row_offset >>= snoc_op op
286+
287+add_row_completion :: DisplayRegion -> Int -> BlitM s ()
288+add_row_completion display_region row = do
289+ all_row_ops <- view mrow_ops
290+ row_ops <- lift $ lift $ MVector.read all_row_ops row
291+ let end_x = span_ops_effected_columns row_ops
292+ when (end_x < region_width display_region) $ do
293+ let ow = region_width display_region - end_x
294+ snoc_op (Skip ow) row
295+
296+-- | snocs the operation to the operations for the given row.
297+snoc_op :: SpanOp -> Int -> BlitM s ()
298+snoc_op !op !row = do
299+ the_mrow_ops <- view mrow_ops
300+ lift $ lift $ do
301+ ops <- MVector.read the_mrow_ops row
302+ let ops' = Vector.snoc ops op
303+ MVector.write the_mrow_ops row ops'
4304
src/Graphics/Vty/Span.hs+19−287
@@ -1,8 +1,5 @@
1−{-# LANGUAGE RankNTypes #-}
21-- Copyright Corey O'Connor
3−{-# LANGUAGE TemplateHaskell #-}
42{-# LANGUAGE BangPatterns #-}
5−{-# LANGUAGE ExistentialQuantification #-}
63{-# LANGUAGE NamedFieldPuns #-}
74{-# LANGUAGE GADTs #-}
85{-# LANGUAGE ScopedTypeVariables #-}
@@ -20,39 +17,33 @@
2017module Graphics.Vty.Span
2118 where
2219
23−import Graphics.Vty.Image
24−import Graphics.Vty.Image.Internal
25−import Graphics.Vty.Picture
2620import Graphics.Vty.DisplayRegion
27−
28−import Control.Applicative
29−import Control.Lens hiding ( op )
30−import Control.Monad.Reader
31−import Control.Monad.State.Strict hiding ( state )
32−import Control.Monad.ST.Strict hiding ( unsafeIOToST )
21+import Graphics.Vty.Image
3322
3423import Data.Vector (Vector)
3524import qualified Data.Vector as Vector hiding ( take, replicate )
36−import Data.Vector.Mutable ( MVector(..))
37−import qualified Data.Vector.Mutable as Vector
3825
3926import qualified Data.ByteString as BS
4027import qualified Data.Text as T
4128import qualified Data.Text.Encoding as T
42−import qualified Data.Text.Lazy as TL
4329
4430-- | This represents an operation on the terminal. Either an attribute change or the output of a
4531-- text string.
46−--
47−-- todo: This type may need to be restructured to increase sharing in the bytestring
48−--
49−-- todo: Make foldable
5032data SpanOp =
5133 AttributeChange !Attr
5234 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
5335 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
5436 -- TextSpan [output width in columns] [number of characters] [data]
5537 | TextSpan !Int !Int BS.ByteString
38+ -- | Skips the given number of columns
39+ -- A skip is transparent.... maybe? I am not sure how attribute changes interact.
40+ -- todo: separate from this type.
41+ | Skip !Int
42+ -- | Marks the end of a row. specifies how many columns are remaining. These columns will not be
43+ -- explicitly overwritten with the span ops. The terminal is require to assure the remaining
44+ -- columns are clear.
45+ -- todo: separate from this type.
46+ | RowEnd !Int
5647 deriving Eq
5748
5849-- | vector of span operations. executed in succession. This represents the operations required to
@@ -69,46 +60,15 @@ data DisplayOps = DisplayOps
6960-- | vector of span operation vectors. One per row of the screen.
7061type RowOps = Vector SpanOps
7162
72−type MRowOps s = MVector s SpanOps
73−
74−type MSpanOps s = MVector s SpanOp
75−
7663instance Show DisplayOps where
7764 show (DisplayOps _ the_row_ops)
7865 = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"
7966
8067instance Show SpanOp where
8168 show (AttributeChange attr) = show attr
82− show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
83−
84−-- transform plus clip. More or less.
85−data BlitState = BlitState
86− -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
87− { _column_offset :: Int
88− , _row_offset :: Int
89− -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
90− , _skip_columns :: Int
91− -- >= 0 and < image_height
92− , _skip_rows :: Int
93− -- includes consideration of skip_columns. In display space.
94− -- The number of columns from the next column to be defined to the end of the display for the
95− -- row.
96− , _remaining_columns :: Int
97− -- includes consideration of skip_rows. In display space.
98− , _remaining_rows :: Int
99− }
100−
101−makeLenses ''BlitState
102−
103−data BlitEnv s = BlitEnv
104− { _bg :: Background
105− , _region :: DisplayRegion
106− , _mrow_ops :: MRowOps s
107− }
108−
109−makeLenses ''BlitEnv
110−
111−type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
69+ show (TextSpan ow cw _) = "TextSpan(" ++ show ow ++ ", " ++ show cw ++ ")"
70+ show (Skip ow) = "Skip(" ++ show ow ++ ")"
71+ show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
11272
11373-- | Number of columns the DisplayOps are defined for
11474span_ops_columns :: DisplayOps -> Int
@@ -123,11 +83,15 @@ span_ops_effected_columns :: SpanOps -> Int
12383span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
12484 where
12585 span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
86+ span_ops_effected_columns' t (Skip w) = t + w
87+ span_ops_effected_columns' t (RowEnd w) = t + w
12688 span_ops_effected_columns' t _ = t
12789
12890-- | The width of a single SpanOp in columns
12991span_op_has_width :: SpanOp -> Maybe (Int, Int)
13092span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
93+span_op_has_width (Skip ow) = Just (ow,ow)
94+span_op_has_width (RowEnd ow) = Just (ow,ow)
13195span_op_has_width _ = Nothing
13296
13397-- | returns the number of columns to the character at the given position in the span op
@@ -135,239 +99,7 @@ columns_to_char_offset :: Int -> SpanOp -> Int
13599columns_to_char_offset cx (TextSpan _ _ utf8_str) =
136100 let str = T.unpack (T.decodeUtf8 utf8_str)
137101 in wcswidth (take cx str)
102+columns_to_char_offset cx (Skip _) = cx
103+columns_to_char_offset cx (RowEnd _) = cx
138104columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
139105
140−-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
141−-- specified region.
142−spans_for_pic :: Picture -> DisplayRegion -> DisplayOps
143−spans_for_pic pic r = DisplayOps r $ Vector.create (combined_spans_for_layers pic r)
144−
145−-- | Produces the span ops for each layer then combines them.
146−--
147−-- TODO: a fold over a builder function. start with span ops that are a bg fill of the entire
148−-- region.
149−combined_spans_for_layers :: Picture -> DisplayRegion -> ST s (MRowOps s)
150−combined_spans_for_layers pic r = do
151− layer_ops <- mapM (\layer -> build_spans layer r (pic_background pic)) (pic_layers pic)
152− case layer_ops of
153− [] -> fail "empty picture"
154− [ops] -> return ops
155− _ -> fail "TODO: picture with more than one layer not supported"
156−
157−-- | Builds a vector of row operations that will output the given picture to the terminal.
158−--
159−-- Crops to the given display region.
160−--
161−-- TODO: I'm pretty sure there is an algorithm that does not require a mutable buffer.
162−build_spans :: Image -> DisplayRegion -> Background -> ST s (MRowOps s)
163−build_spans image out_region background = do
164− -- First we create a mutable vector for each rows output operations.
165− out_ops <- Vector.replicate (region_height out_region) Vector.empty
166− -- \todo I think building the span operations in display order would provide better performance.
167− -- However, I got stuck trying to implement an algorithm that did this. This will be considered
168− -- as a possible future optimization.
169− --
170− -- A depth first traversal of the image is performed. ordered according to the column range
171− -- defined by the image from least to greatest. The output row ops will at least have the
172− -- region of the image specified. Iterate over all output rows and output background fills for
173− -- all unspecified columns.
174− --
175− -- The images are made into span operations from left to right. It's possible that this could
176− -- easily be made to assure top to bottom output as well.
177− when (region_height out_region > 0 && region_width out_region > 0) $ do
178− -- The ops builder recursively descends the image and outputs span ops that would
179− -- display that image. The number of columns remaining in this row before exceeding the
180− -- bounds is also provided. This is used to clip the span ops produced to the display.
181− let full_build = do
182− start_image_build image
183− -- Fill in any unspecified columns with the background pattern.
184− forM_ [0 .. (region_height out_region - 1)] (add_row_completion out_region)
185− init_env = BlitEnv background out_region out_ops
186− init_state = BlitState 0 0 0 0 (region_width out_region) (region_height out_region)
187− _ <- runStateT (runReaderT full_build init_env) init_state
188− return ()
189− return out_ops
190−
191−-- | Add the operations required to build a given image to the current set of row operations
192−-- returns the number of columns and rows contributed to the output.
193−start_image_build :: Image -> BlitM s ()
194−start_image_build image = do
195− out_of_bounds <- is_out_of_bounds <$> get
196− when (not out_of_bounds) $ add_maybe_clipped image
197−
198−is_out_of_bounds :: BlitState -> Bool
199−is_out_of_bounds s
200− | s ^. remaining_columns <= 0 = True
201− | s ^. remaining_rows <= 0 = True
202− | otherwise = False
203−
204−-- TODO: prove skip_columns, skip_rows == 0
205−-- TODO: prove remaining_columns >= image_width
206−-- TODO: prove remaining_rows >= image_height
207−add_unclipped :: Image -> BlitM s ()
208−add_unclipped EmptyImage = return ()
209−add_unclipped (HorizText a text_str _ow _cw) = do
210− use row_offset >>= snoc_op (AttributeChange a)
211− add_unclipped_text text_str
212−add_unclipped (HorizJoin part_left part_right _ow _oh) = do
213− -- TODO: push into env and use
214− y <- use row_offset
215− add_unclipped part_left
216− row_offset .= y
217− add_unclipped part_right
218−add_unclipped (VertJoin part_top part_bottom _ow _oh) = do
219− y <- use row_offset
220− add_unclipped part_top
221− row_offset .= y + image_height part_top
222− add_unclipped part_bottom
223− -- TODO: assumes background character is 1 column
224−add_unclipped (BGFill ow oh) = do
225− Background c a <- view bg
226− y <- use row_offset
227− let op = TextSpan ow ow (T.encodeUtf8 $ T.replicate (fromIntegral ow) (T.singleton c))
228− forM_ [y..y+oh-1] $ \row -> do
229− snoc_op (AttributeChange a) row
230− snoc_op op row
231−-- TODO: we know it's clipped actually, the equations that are exposed that introduce a
232−-- Crop all assure the image exceeds the crop in the relevant direction.
233−add_unclipped CropRight {cropped_image, output_width} = do
234− remaining_columns .= output_width
235− add_maybe_clipped cropped_image
236−add_unclipped CropLeft {cropped_image, left_skip} = do
237− skip_columns .= left_skip
238− add_maybe_clipped cropped_image
239−add_unclipped CropBottom {cropped_image, output_height} = do
240− remaining_rows .= output_height
241− add_maybe_clipped cropped_image
242−add_unclipped CropTop {cropped_image, top_skip} = do
243− skip_rows .= top_skip
244− add_maybe_clipped cropped_image
245−
246−-- TODO: prove this cannot be called in an out of bounds case.
247−add_maybe_clipped :: forall s . Image -> BlitM s ()
248−add_maybe_clipped EmptyImage = return ()
249−add_maybe_clipped (HorizText a text_str ow _cw) = do
250− -- TODO: assumption that text spans are only 1 row high.
251− s <- use skip_rows
252− when (s < 1) $ do
253− use row_offset >>= snoc_op (AttributeChange a)
254− left_clip <- use skip_columns
255− right_clip <- use remaining_columns
256− let left_clipped = left_clip > 0
257− right_clipped = (ow - left_clip) > right_clip
258− if left_clipped || right_clipped
259− then let text_str' = clip_text text_str left_clip right_clip
260− in add_unclipped_text text_str'
261− else add_unclipped_text text_str
262−add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
263− add_maybe_clipped_join "vert_join" skip_rows remaining_rows row_offset
264− (image_height top_image)
265− top_image
266− bottom_image
267− oh
268−add_maybe_clipped (HorizJoin left_image right_image ow _oh) = do
269− add_maybe_clipped_join "horiz_join" skip_columns remaining_columns column_offset
270− (image_width left_image)
271− left_image
272− right_image
273− ow
274−add_maybe_clipped BGFill {output_width, output_height} = do
275− s <- get
276− let output_width' = min (output_width - s^.skip_columns) (s^.remaining_columns)
277− output_height' = min (output_height - s^.skip_rows ) (s^.remaining_rows)
278− add_unclipped (BGFill output_width' output_height')
279−add_maybe_clipped CropRight {cropped_image, output_width} = do
280− remaining_columns .= output_width
281− add_maybe_clipped cropped_image
282−add_maybe_clipped CropLeft {cropped_image, left_skip} = do
283− skip_columns += left_skip
284− add_maybe_clipped cropped_image
285−add_maybe_clipped CropBottom {cropped_image, output_height} = do
286− remaining_rows .= output_height
287− add_maybe_clipped cropped_image
288−add_maybe_clipped CropTop {cropped_image, top_skip} = do
289− skip_rows += top_skip
290− add_maybe_clipped cropped_image
291−
292−add_maybe_clipped_join :: forall s . String
293− -> Lens BlitState BlitState Int Int
294− -> Lens BlitState BlitState Int Int
295− -> Lens BlitState BlitState Int Int
296− -> Int
297− -> Image
298− -> Image
299− -> Int
300− -> BlitM s ()
301−add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
302− state <- get
303− when (state^.remaining == 0) $ fail $ name ++ " with remaining == 0"
304− case state^.skip of
305− s | s >= size -> fail $ name ++ " on fully clipped"
306− -- TODO: check if clipped in other dim. if not use add_unclipped
307− | s == 0 -> case state^.remaining of
308− r | r > i0_dim -> do
309− add_maybe_clipped i0
310− put $ state & offset +~ i0_dim & remaining -~ i0_dim
311− add_maybe_clipped i1
312− | otherwise -> add_maybe_clipped i0
313− | s >= i0_dim -> do
314− put $ state & skip -~ i0_dim
315− add_maybe_clipped i1
316− | otherwise -> case i0_dim - s of
317− i0_dim' | state^.remaining <= i0_dim' -> add_maybe_clipped i0
318− | otherwise -> do
319− add_maybe_clipped i0
320− put $ state & offset +~ i0_dim' & remaining -~ i0_dim'
321− add_maybe_clipped i1
322−
323−-- TODO: store a skip list in HorizText(?)
324−-- TODO: represent display strings containing chars that are not 1 column chars as a separate
325−-- display string value?
326−-- TODO: assumes max column width is 2
327−clip_text :: DisplayText -> Int -> Int -> DisplayText
328−clip_text txt left_skip right_clip =
329− -- CPS would clarify this I think
330− let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
331− txt' = if pad_prefix then TL.cons '…' (TL.drop (to_drop+1) txt) else TL.drop to_drop txt
332− (to_take,pad_suffix) = clip_for_char_width right_clip txt' 0
333− txt'' = TL.append (TL.take to_take txt') (if pad_suffix then TL.singleton '…' else TL.empty)
334− clip_for_char_width 0 _ n = (n, False)
335− clip_for_char_width w t n
336− | w < cw = (n, True)
337− | w == cw = (n+1, False)
338− | w > cw = clip_for_char_width (w - cw) (TL.tail t) (n + 1)
339− where cw = safe_wcwidth (TL.head t)
340− clip_for_char_width _ _ _ = error "clip_for_char_width applied to undefined"
341− in txt''
342−
343−add_unclipped_text :: DisplayText -> BlitM s ()
344−add_unclipped_text txt = do
345− let op = TextSpan used_display_columns
346− (fromIntegral $ TL.length txt)
347− (T.encodeUtf8 $ TL.toStrict txt)
348− used_display_columns = wcswidth $ TL.unpack txt
349− use row_offset >>= snoc_op op
350−
351−-- todo: If there is no background pattern defined then skip to next line.
352−-- todo: assumes background character is 1 column
353−add_row_completion :: DisplayRegion -> Int -> BlitM s ()
354−add_row_completion display_region row = do
355− all_row_ops <- view mrow_ops
356− Background c a <- view bg
357− row_ops <- lift $ lift $ Vector.read all_row_ops row
358− let end_x = span_ops_effected_columns row_ops
359− when (end_x < region_width display_region) $ do
360− let ow = region_width display_region - end_x
361− op = TextSpan ow ow (T.encodeUtf8 $ T.replicate (fromIntegral ow) (T.singleton c))
362− snoc_op (AttributeChange a) row
363− snoc_op op row
364−
365−-- | snocs the operation to the operations for the given row.
366−snoc_op :: SpanOp -> Int -> BlitM s ()
367−snoc_op !op !row = do
368− the_mrow_ops <- view mrow_ops
369− lift $ lift $ do
370− ops <- Vector.read the_mrow_ops row
371− let ops' = Vector.snoc ops op
372− Vector.write the_mrow_ops row ops'
373−
src/Graphics/Vty/Terminal/Interface.hs+13
@@ -14,6 +14,7 @@ module Graphics.Vty.Terminal.Interface ( module Graphics.Vty.Terminal.Interface
1414import Data.Marshalling
1515
1616import Graphics.Vty.Picture
17+import Graphics.Vty.PictureToSpans
1718import Graphics.Vty.Span
1819import Graphics.Vty.DisplayRegion
1920
@@ -87,6 +88,8 @@ display_context t r = liftIO $ do
8788 , serialize_set_attr = serialize_set_attr self
8889 , default_attr_required_bytes = default_attr_required_bytes self
8990 , serialize_default_attr = serialize_default_attr self
91+ , row_end_required_bytes = row_end_required_bytes self
92+ , serialize_row_end = serialize_row_end self
9093 , inline_hack = return ()
9194 }
9295 mfix (mk_display_context t . def_context)
@@ -119,6 +122,8 @@ data DisplayContext = DisplayContext
119122 -- | Reset the display attributes to the default display attributes
120123 , default_attr_required_bytes :: Int
121124 , serialize_default_attr :: OutputBuffer -> IO OutputBuffer
125+ , row_end_required_bytes :: Int
126+ , serialize_row_end :: OutputBuffer -> IO OutputBuffer
122127 -- | See Graphics.Vty.Terminal.XTermColor.inline_hack
123128 , inline_hack :: IO ()
124129 }
@@ -234,6 +239,8 @@ span_op_required_bytes dc fattr (AttributeChange attr) =
234239 fattr' = fix_display_attr fattr attr'
235240 in (c, fattr')
236241span_op_required_bytes _dc fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)
242+span_op_required_bytes _dc _fattr (Skip _) = error "span_op_required_bytes for Skip."
243+span_op_required_bytes dc fattr (RowEnd _) = (row_end_required_bytes dc, fattr)
237244
238245serialize_output_ops :: DisplayContext
239246 -> OutputBuffer
@@ -269,6 +276,8 @@ serialize_span_ops dc y out_ptr in_fattr span_ops = do
269276 (out_ptr', in_fattr)
270277 span_ops
271278
279+-- |
280+-- TODO: move this into the terminal implementation?
272281serialize_span_op :: DisplayContext
273282 -> SpanOp
274283 -> OutputBuffer
@@ -283,6 +292,10 @@ serialize_span_op dc (AttributeChange attr) out_ptr fattr = do
283292serialize_span_op _dc (TextSpan _ _ str) out_ptr fattr = do
284293 out_ptr' <- serialize_utf8_text str out_ptr
285294 return (out_ptr', fattr)
295+serialize_span_op _dc (Skip _) _out_ptr _fattr = error "serialize_span_op for Skip"
296+serialize_span_op dc (RowEnd _) out_ptr fattr = do
297+ out_ptr' <- serialize_row_end dc out_ptr
298+ return (out_ptr', fattr)
286299
287300send_to_terminal :: Terminal -> Int -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()
288301send_to_terminal t c f = allocaBytes (fromEnum c) $ \start_ptr -> do
src/Graphics/Vty/Terminal/Mock.hs+5
@@ -79,6 +79,11 @@ mock_terminal r = liftIO $ do
7979 , serialize_default_attr = \ptr -> do
8080 poke ptr (toEnum $ fromEnum 'D')
8181 return $ ptr `plusPtr` 1
82+ -- row end is always visualized as the single character 'E'
83+ , row_end_required_bytes = 1
84+ , serialize_row_end = \ptr -> do
85+ poke ptr (toEnum $ fromEnum 'E')
86+ return $ ptr `plusPtr` 1
8287 }
8388 }
8489
src/Graphics/Vty/Terminal/TerminfoBased.hs+5−1
@@ -47,6 +47,7 @@ data TerminfoCaps = TerminfoCaps
4747 , set_back_color :: CapExpression
4848 , set_default_attr :: CapExpression
4949 , clear_screen :: CapExpression
50+ , clear_eol :: CapExpression
5051 , display_attr_caps :: DisplayAttrCaps
5152 }
5253
@@ -109,6 +110,7 @@ reserve_terminal in_ID out_handle = liftIO $ mfix $ \self -> do
109110 <*> pure set_back_cap
110111 <*> require_cap ti "sgr0"
111112 <*> require_cap ti "clear"
113+ <*> require_cap ti "el"
112114 <*> current_display_attr_caps ti
113115 let send_cap s = send_cap_to_terminal self (s terminfo_caps)
114116 maybe_send_cap s = when (isJust $ s terminfo_caps) . send_cap (fromJust . s)
@@ -212,7 +214,9 @@ mk_terminfo_display_context terminfo_caps self = do
212214 , attr_required_bytes = \_prev_attr _req_attr _diffs -> assumed_attr_required_bytes
213215 , serialize_set_attr = terminfo_serialize_set_attr self terminfo_caps
214216 , default_attr_required_bytes = cap_expression_required_bytes (set_default_attr terminfo_caps) []
215− , serialize_default_attr = \out_ptr -> serialize_cap_expression (set_default_attr terminfo_caps) [] out_ptr
217+ , serialize_default_attr = serialize_cap_expression (set_default_attr terminfo_caps) []
218+ , row_end_required_bytes = cap_expression_required_bytes (clear_eol terminfo_caps) []
219+ , serialize_row_end = serialize_cap_expression (clear_eol terminfo_caps) []
216220 }
217221
218222-- | Instead of evaluating all the rules related to setting display attributes twice (once in
test/Verify.hs+1
@@ -59,6 +59,7 @@ verify test_name p = Test $ TestInstance
5959 qc_result <- quickCheckResult p
6060 case qc_result of
6161 QC.Success {..} -> return $ Finished TS.Pass
62+ QC.Failure {output} -> return $ Finished $ TS.Fail output
6263 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
6364 , tags = []
6465 , options = []
test/Verify/Data/Terminfo/Parse.hs−6
@@ -12,12 +12,6 @@ import Data.Word
1212
1313import Numeric
1414
15−instance Show CapExpression where
16− show c
17− = "CapExpression { " ++ show (cap_ops c) ++ " }"
18− ++ " <- [" ++ hex_dump ( map ( toEnum . fromEnum ) $! source_string c ) ++ "]"
19− ++ " <= " ++ show (source_string c)
20−
2115hex_dump :: [Word8] -> String
2216hex_dump bytes = foldr (\b s -> showHex b s) "" bytes
2317
test/Verify/Graphics/Vty/Image.hs+3−3
@@ -80,9 +80,9 @@ instance Arbitrary SingleAttrSingleSpanStack where
8080 arbitrary = do
8181 image_list <- Verify.resize 128 (listOf1 arbitrary)
8282 let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
83− return $ SingleAttrSingleSpanStack
84− image
85− image_list
83+ return $ SingleAttrSingleSpanStack
84+ image
85+ image_list
8686 ( maximum $ map expected_columns image_list )
8787 ( toEnum $ length image_list )
8888
test/VerifySpanOps.hs+3−2
@@ -2,12 +2,13 @@
22{-# LANGUAGE NamedFieldPuns #-}
33module VerifySpanOps where
44
5−import Verify.Graphics.Vty.Picture
5+import Verify.Graphics.Vty.DisplayRegion
66import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Picture
78import Verify.Graphics.Vty.Span
8−import Verify.Graphics.Vty.DisplayRegion
99
1010import Graphics.Vty.Debug
11+import Graphics.Vty.PictureToSpans
1112
1213import Verify
1314
test/VerifyTerminal.hs+12−12
@@ -18,6 +18,14 @@ import qualified Data.String.UTF8 as UTF8
1818
1919import System.IO
2020
21+compare_bytes out_bytes expected_bytes =
22+ if out_bytes /= expected_bytes
23+ then return $ failed { reason = "bytes\n" ++ show out_bytes
24+ ++ "\nare not the expected bytes\n"
25+ ++ show expected_bytes
26+ }
27+ else return succeeded
28+
2129unit_image_unit_bounds :: UnitImage -> Property
2230unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
2331 (_,t) <- mock_terminal (DisplayRegion 1 1)
@@ -49,9 +57,7 @@ single_T_row (MockWindow w h) = liftIOResult $ do
4957 -- character
5058 ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
5159 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
52− if out_bytes /= expected_bytes
53− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
54− else return succeeded
60+ compare_bytes out_bytes expected_bytes
5561
5662many_T_rows :: MockWindow -> Property
5763many_T_rows (MockWindow w h) = liftIOResult $ do
@@ -66,9 +72,7 @@ many_T_rows (MockWindow w h) = liftIOResult $ do
6672 -- attribute change. 'A', followed by w 'T's
6773 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
6874 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
69− if out_bytes /= expected_bytes
70− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
71− else return succeeded
75+ compare_bytes out_bytes expected_bytes
7276
7377many_T_rows_cropped_width :: MockWindow -> Property
7478many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
@@ -83,9 +87,7 @@ many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
8387 -- attribute change. 'A', followed by w 'T's
8488 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
8589 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
86− if out_bytes /= expected_bytes
87− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
88− else return succeeded
90+ compare_bytes out_bytes expected_bytes
8991
9092many_T_rows_cropped_height :: MockWindow -> Property
9193many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
@@ -100,9 +102,7 @@ many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
100102 -- attribute change. 'A', followed by w count 'T's
101103 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
102104 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
103− if out_bytes /= expected_bytes
104− then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }
105− else return succeeded
105+ compare_bytes out_bytes expected_bytes
106106
107107tests :: IO [Test]
108108tests = return [ verify "unit_image_unit_bounds" unit_image_unit_bounds
test/benchmark.hs+2−2
@@ -32,10 +32,10 @@ main = do
3232 ["-h" ] -> help
3333 _ -> do
3434 let args' = if args /= []
35− then args
35+ then map (drop 2) args
3636 else map fst benches
3737 -- drop the dash-dash "--"
38− results <- forM args' $ \(_ : _ : b_name) -> do
38+ results <- forM args' $ \b_name -> do
3939 case lookup b_name benches of
4040 Just b -> bench b_name b
4141 Nothing -> fail $ "No benchmark named " ++ b_name
test/current_benchmark_results.txt+13−17
@@ -1,20 +1,16 @@
1−pre-release 5.0
1+no unclipped opt 5.0
2+on toast
23[("no-diff-opt-0",147,8),("render-char-0",332,18),("render-char-1",277,16),("vertical-scroll-0",1503,61),("image-fuzz-0",46,3)]
34[("no-diff-opt-0",137,11),("render-char-0",470,18),("render-char-1",254,18),("vertical-scroll-0",1240,56),("image-fuzz-0",46,3)]
4−release 4.7
5−starting no-diff-opt-0
6−user time: 226
7−system time: 13
8−starting render-char-0
9−user time: 411
10−system time: 22
11−starting render-char-1
12−user time: 386
13−system time: 21
14−starting vertical-scroll-0
15−user time: 1930
16−system time: 96
17−starting image-fuzz-0
18−user time: 1225
19−system time: 52
5+
6+on ooyala, no screen, iTerm:
7+
8+ * 5.0 initial:
9+ * [("no-diff-opt-0",24,3),("render-char-0",102,8),("render-char-1",41,4),("vertical-scroll-0",226,24),("image-fuzz-0",17,1)]
10+
11+ * post background span replacement:
12+ * [("no-diff-opt-0",26,3),("render-char-0",109,8),("render-char-1",44,5),("vertical-scroll-0",244,26),("image-fuzz-0",17,2)]
13+ * [("no-diff-opt-0",25,4),("render-char-0",110,7),("render-char-1",43,6),("vertical-scroll-0",239,24),("image-fuzz-0",17,2)]
14+
15+release 4.7 on toast
2016[("no-diff-opt-0",226,13),("render-char-0",411,22),("render-char-1",386,21),("vertical-scroll-0",1930,96),("image-fuzz-0",1225,52)]
test/interactive_terminal_testnew file

Binary file not shown.

vty.cabal+1−1
@@ -58,6 +58,7 @@ library
5858 Graphics.Vty.Error
5959 Graphics.Vty.Image
6060 Graphics.Vty.Inline
61+ Graphics.Vty.Inline.Unsafe
6162 Graphics.Vty.LLInput
6263 Graphics.Vty.Picture
6364 Graphics.Vty.Terminal
@@ -79,7 +80,6 @@ library
7980 Graphics.Vty.Terminal.XTermColor
8081 Graphics.Vty.Terminal.TerminfoBased
8182
82− other-modules: Graphics.Vty.Inline.Hack
8383
8484 c-sources: cbits/gwinsz.c
8585 cbits/set_term_timing.c