on master help sign in
Merge branch 'master' of github.com:coreyoconnor/vty
3da7ed0Corey O'Connor
CHANGELOG+20−7
@@ -1,12 +1,25 @@
15.0.0
15.0.0
2− * API changes:
3− * removed the typeclass based terminal and display context interface in favor of a data
2+ * removed the typeclass based terminal and display context interface in favor of a data
4− structure of properties interface.
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.
5 * compatibility improvements:
18 * compatibility improvements:
6− * terminfo based terminals with no cursor support are now silently accepted. The cursor
19+ * terminfo based terminals with no cursor support are silently accepted. The cursor
7− visibility changes in the Picture will have no effect.
20+ 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
21+ * alternate (setf/setb) color maps supported. Though colors beyond the first 8 are just a
9− guess.
22+ guess.
10
23
114.7.0.0
244.7.0.0
12 * API changes:
25 * API changes:
README.md+1
@@ -63,6 +63,7 @@ Profiling appears to cause issues with coverage when enabled. To evaluate covera
63follows:
63follows:
64
64
65~~~
65~~~
66+rm -rf dist
66cabal configure --enable-tests --enable-library-coverage \
67cabal configure --enable-tests --enable-library-coverage \
67 --disable-library-profiling \
68 --disable-library-profiling \
68 --disable-executable-profiling
69 --disable-executable-profiling
src/Data/Terminfo/Eval.hs+1−1
@@ -162,7 +162,7 @@ serialize_cap_ops :: OutputBuffer -> CapOps -> EvalT IO OutputBuffer
162serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
162serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops
163
163
164serialize_cap_op :: OutputBuffer -> CapOp -> EvalT IO OutputBuffer
164serialize_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
166 !cap <- get >>= return . eval_expression
166 !cap <- get >>= return . eval_expression
167 let ( !start_ptr, _ ) = cap_bytes cap
167 let ( !start_ptr, _ ) = cap_bytes cap
168 !src_ptr = start_ptr `plusPtr` offset
168 !src_ptr = start_ptr `plusPtr` offset
src/Data/Terminfo/Parse.hs+14−3
@@ -21,6 +21,8 @@ import Foreign.C.Types
21import Foreign.Marshal.Array
21import Foreign.Marshal.Array
22import Foreign.Ptr
22import Foreign.Ptr
23
23
24+import Numeric (showHex)
25+
24import Text.ParserCombinators.Parsec
26import Text.ParserCombinators.Parsec
25
27
26type CapBytes = ( Ptr Word8, CSize )
28type CapBytes = ( Ptr Word8, CSize )
@@ -31,7 +33,16 @@ data CapExpression = CapExpression
31 , source_string :: !String
33 , source_string :: !String
32 , param_count :: !Word
34 , param_count :: !Word
33 , param_ops :: !ParamOps
35 , 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) ""
35
46
36instance NFData CapExpression where
47instance NFData CapExpression where
37 rnf (CapExpression ops !_bytes !str !c !p_ops)
48 rnf (CapExpression ops !_bytes !str !c !p_ops)
@@ -55,7 +66,7 @@ data CapOp =
55 | BitwiseOr | BitwiseXOr | BitwiseAnd
66 | BitwiseOr | BitwiseXOr | BitwiseAnd
56 | ArithPlus | ArithMinus
67 | ArithPlus | ArithMinus
57 | CompareEq | CompareLt | CompareGt
68 | CompareEq | CompareLt | CompareGt
58− deriving ( Show )
69+ deriving (Show, Eq)
59
70
60instance NFData CapOp where
71instance NFData CapOp where
61 rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset
72 rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset
@@ -76,7 +87,7 @@ instance NFData CapOp where
76type ParamOps = [ParamOp]
87type ParamOps = [ParamOp]
77data ParamOp =
88data ParamOp =
78 IncFirstTwo
89 IncFirstTwo
79− deriving ( Show )
90+ deriving (Show, Eq)
80
91
81instance NFData ParamOp where
92instance NFData ParamOp where
82 rnf IncFirstTwo = ()
93 rnf IncFirstTwo = ()
src/Graphics/Vty.hs+2−2
@@ -80,8 +80,8 @@ mkVty = mkVtyEscDelay 0
80-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
80-- following reading ESC from the terminal before considering the ESC key press as a discrete event.
81-- \todo move input init into terminal interface
81-- \todo move input init into terminal interface
82mkVtyEscDelay :: Int -> IO Vty
82mkVtyEscDelay :: Int -> IO Vty
83−mkVtyEscDelay escDelay = do
83+mkVtyEscDelay escDelay = do
84− term_info <- Terminfo.setupTermFromEnv
84+ term_info <- Terminfo.setupTermFromEnv
85 t <- current_terminal
85 t <- current_terminal
86 reserve_display t
86 reserve_display t
87 (kvar, endi) <- initTermInput escDelay term_info
87 (kvar, endi) <- initTermInput escDelay term_info
src/Graphics/Vty/Image/Internal.hs+1−1
@@ -33,7 +33,7 @@ data Image =
33 }
33 }
34 -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
34 -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
35 -- required to be between two images of equal height. The horiz_join constructor adds background
35 -- 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.
37 | HorizJoin
37 | HorizJoin
38 { part_left :: Image
38 { part_left :: Image
39 , part_right :: Image
39 , part_right :: Image
src/Graphics/Vty/Picture.hs+12−6
@@ -54,7 +54,7 @@ pic_for_image :: Image -> Picture
54pic_for_image i = Picture
54pic_for_image i = Picture
55 { pic_cursor = NoCursor
55 { pic_cursor = NoCursor
56 , pic_layers = [i]
56 , pic_layers = [i]
57− , pic_background = Background ' ' current_attr
57+ , pic_background = ClearBackground
58 }
58 }
59
59
60-- | A picture can be configured either to not show the cursor or show the cursor at the specified
60-- | 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
80--
80--
81-- \todo The current attribute is always set to the default attributes at the start of updating the
81-- \todo The current attribute is always set to the default attributes at the start of updating the
82-- screen to a picture.
82-- 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
88 { background_char :: Char
85 { background_char :: Char
89 , background_attr :: Attr
86 , background_attr :: Attr
90 }
87 }
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
91
96
92instance NFData Background where
97instance NFData Background where
93 rnf (Background c a) = c `seq` a `seq` ()
98 rnf (Background c a) = c `seq` a `seq` ()
99+ rnf ClearBackground = ()
94
100
95-- | Compatibility with applications that do not use more than a single layer.
101-- | Compatibility with applications that do not use more than a single layer.
96pic_image :: Picture -> Image
102pic_image :: Picture -> Image
src/Graphics/Vty/PictureToSpans.hs+302−2
@@ -1,4 +1,304 @@
1−-- Copyright 2009-2010 Corey O'Connor
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 #-}
2−module Graphics.Vty.PictureToSpans
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
3 where
123 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'
4
304
src/Graphics/Vty/Span.hs+19−287
@@ -1,8 +1,5 @@
1−{-# LANGUAGE RankNTypes #-}
2-- Copyright Corey O'Connor
1-- Copyright Corey O'Connor
3−{-# LANGUAGE TemplateHaskell #-}
4{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE BangPatterns #-}
5−{-# LANGUAGE ExistentialQuantification #-}
6{-# LANGUAGE NamedFieldPuns #-}
3{-# LANGUAGE NamedFieldPuns #-}
7{-# LANGUAGE GADTs #-}
4{-# LANGUAGE GADTs #-}
8{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
@@ -20,39 +17,33 @@
20module Graphics.Vty.Span
17module Graphics.Vty.Span
21 where
18 where
22
19
23−import Graphics.Vty.Image
24−import Graphics.Vty.Image.Internal
25−import Graphics.Vty.Picture
26import Graphics.Vty.DisplayRegion
20import Graphics.Vty.DisplayRegion
27−
21+import Graphics.Vty.Image
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 )
33
22
34import Data.Vector (Vector)
23import Data.Vector (Vector)
35import qualified Data.Vector as Vector hiding ( take, replicate )
24import qualified Data.Vector as Vector hiding ( take, replicate )
36−import Data.Vector.Mutable ( MVector(..))
37−import qualified Data.Vector.Mutable as Vector
38
25
39import qualified Data.ByteString as BS
26import qualified Data.ByteString as BS
40import qualified Data.Text as T
27import qualified Data.Text as T
41import qualified Data.Text.Encoding as T
28import qualified Data.Text.Encoding as T
42−import qualified Data.Text.Lazy as TL
43
29
44-- | This represents an operation on the terminal. Either an attribute change or the output of a
30-- | This represents an operation on the terminal. Either an attribute change or the output of a
45-- text string.
31-- text string.
46−--
47−-- todo: This type may need to be restructured to increase sharing in the bytestring
48−--
49−-- todo: Make foldable
50data SpanOp =
32data SpanOp =
51 AttributeChange !Attr
33 AttributeChange !Attr
52 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
34 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
53 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
35 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
54 -- TextSpan [output width in columns] [number of characters] [data]
36 -- TextSpan [output width in columns] [number of characters] [data]
55 | TextSpan !Int !Int BS.ByteString
37 | 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
56 deriving Eq
47 deriving Eq
57
48
58-- | vector of span operations. executed in succession. This represents the operations required to
49-- | vector of span operations. executed in succession. This represents the operations required to
@@ -69,46 +60,15 @@ data DisplayOps = DisplayOps
69-- | vector of span operation vectors. One per row of the screen.
60-- | vector of span operation vectors. One per row of the screen.
70type RowOps = Vector SpanOps
61type RowOps = Vector SpanOps
71
62
72−type MRowOps s = MVector s SpanOps
73−
74−type MSpanOps s = MVector s SpanOp
75−
76instance Show DisplayOps where
63instance Show DisplayOps where
77 show (DisplayOps _ the_row_ops)
64 show (DisplayOps _ the_row_ops)
78 = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"
65 = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"
79
66
80instance Show SpanOp where
67instance Show SpanOp where
81 show (AttributeChange attr) = show attr
68 show (AttributeChange attr) = show attr
82− show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
69+ show (TextSpan ow cw _) = "TextSpan(" ++ show ow ++ ", " ++ show cw ++ ")"
83−
70+ show (Skip ow) = "Skip(" ++ show ow ++ ")"
84−-- transform plus clip. More or less.
71+ show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"
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
112
72
113-- | Number of columns the DisplayOps are defined for
73-- | Number of columns the DisplayOps are defined for
114span_ops_columns :: DisplayOps -> Int
74span_ops_columns :: DisplayOps -> Int
@@ -123,11 +83,15 @@ span_ops_effected_columns :: SpanOps -> Int
123span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
83span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
124 where
84 where
125 span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
85 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
126 span_ops_effected_columns' t _ = t
88 span_ops_effected_columns' t _ = t
127
89
128-- | The width of a single SpanOp in columns
90-- | The width of a single SpanOp in columns
129span_op_has_width :: SpanOp -> Maybe (Int, Int)
91span_op_has_width :: SpanOp -> Maybe (Int, Int)
130span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
92span_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)
131span_op_has_width _ = Nothing
95span_op_has_width _ = Nothing
132
96
133-- | returns the number of columns to the character at the given position in the span op
97-- | 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
135columns_to_char_offset cx (TextSpan _ _ utf8_str) =
99columns_to_char_offset cx (TextSpan _ _ utf8_str) =
136 let str = T.unpack (T.decodeUtf8 utf8_str)
100 let str = T.unpack (T.decodeUtf8 utf8_str)
137 in wcswidth (take cx str)
101 in wcswidth (take cx str)
102+columns_to_char_offset cx (Skip _) = cx
103+columns_to_char_offset cx (RowEnd _) = cx
138columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
104columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
139
105
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
14import Data.Marshalling
14import Data.Marshalling
15
15
16import Graphics.Vty.Picture
16import Graphics.Vty.Picture
17+import Graphics.Vty.PictureToSpans
17import Graphics.Vty.Span
18import Graphics.Vty.Span
18import Graphics.Vty.DisplayRegion
19import Graphics.Vty.DisplayRegion
19
20
@@ -87,6 +88,8 @@ display_context t r = liftIO $ do
87 , serialize_set_attr = serialize_set_attr self
88 , serialize_set_attr = serialize_set_attr self
88 , default_attr_required_bytes = default_attr_required_bytes self
89 , default_attr_required_bytes = default_attr_required_bytes self
89 , serialize_default_attr = serialize_default_attr self
90 , 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
90 , inline_hack = return ()
93 , inline_hack = return ()
91 }
94 }
92 mfix (mk_display_context t . def_context)
95 mfix (mk_display_context t . def_context)
@@ -119,6 +122,8 @@ data DisplayContext = DisplayContext
119 -- | Reset the display attributes to the default display attributes
122 -- | Reset the display attributes to the default display attributes
120 , default_attr_required_bytes :: Int
123 , default_attr_required_bytes :: Int
121 , serialize_default_attr :: OutputBuffer -> IO OutputBuffer
124 , serialize_default_attr :: OutputBuffer -> IO OutputBuffer
125+ , row_end_required_bytes :: Int
126+ , serialize_row_end :: OutputBuffer -> IO OutputBuffer
122 -- | See Graphics.Vty.Terminal.XTermColor.inline_hack
127 -- | See Graphics.Vty.Terminal.XTermColor.inline_hack
123 , inline_hack :: IO ()
128 , inline_hack :: IO ()
124 }
129 }
@@ -234,6 +239,8 @@ span_op_required_bytes dc fattr (AttributeChange attr) =
234 fattr' = fix_display_attr fattr attr'
239 fattr' = fix_display_attr fattr attr'
235 in (c, fattr')
240 in (c, fattr')
236span_op_required_bytes _dc fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)
241span_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)
237
244
238serialize_output_ops :: DisplayContext
245serialize_output_ops :: DisplayContext
239 -> OutputBuffer
246 -> OutputBuffer
@@ -269,6 +276,8 @@ serialize_span_ops dc y out_ptr in_fattr span_ops = do
269 (out_ptr', in_fattr)
276 (out_ptr', in_fattr)
270 span_ops
277 span_ops
271
278
279+-- |
280+-- TODO: move this into the terminal implementation?
272serialize_span_op :: DisplayContext
281serialize_span_op :: DisplayContext
273 -> SpanOp
282 -> SpanOp
274 -> OutputBuffer
283 -> OutputBuffer
@@ -283,6 +292,10 @@ serialize_span_op dc (AttributeChange attr) out_ptr fattr = do
283serialize_span_op _dc (TextSpan _ _ str) out_ptr fattr = do
292serialize_span_op _dc (TextSpan _ _ str) out_ptr fattr = do
284 out_ptr' <- serialize_utf8_text str out_ptr
293 out_ptr' <- serialize_utf8_text str out_ptr
285 return (out_ptr', fattr)
294 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)
286
299
287send_to_terminal :: Terminal -> Int -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()
300send_to_terminal :: Terminal -> Int -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()
288send_to_terminal t c f = allocaBytes (fromEnum c) $ \start_ptr -> do
301send_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
79 , serialize_default_attr = \ptr -> do
79 , serialize_default_attr = \ptr -> do
80 poke ptr (toEnum $ fromEnum 'D')
80 poke ptr (toEnum $ fromEnum 'D')
81 return $ ptr `plusPtr` 1
81 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
82 }
87 }
83 }
88 }
84
89
src/Graphics/Vty/Terminal/TerminfoBased.hs+5−1
@@ -47,6 +47,7 @@ data TerminfoCaps = TerminfoCaps
47 , set_back_color :: CapExpression
47 , set_back_color :: CapExpression
48 , set_default_attr :: CapExpression
48 , set_default_attr :: CapExpression
49 , clear_screen :: CapExpression
49 , clear_screen :: CapExpression
50+ , clear_eol :: CapExpression
50 , display_attr_caps :: DisplayAttrCaps
51 , display_attr_caps :: DisplayAttrCaps
51 }
52 }
52
53
@@ -109,6 +110,7 @@ reserve_terminal in_ID out_handle = liftIO $ mfix $ \self -> do
109 <*> pure set_back_cap
110 <*> pure set_back_cap
110 <*> require_cap ti "sgr0"
111 <*> require_cap ti "sgr0"
111 <*> require_cap ti "clear"
112 <*> require_cap ti "clear"
113+ <*> require_cap ti "el"
112 <*> current_display_attr_caps ti
114 <*> current_display_attr_caps ti
113 let send_cap s = send_cap_to_terminal self (s terminfo_caps)
115 let send_cap s = send_cap_to_terminal self (s terminfo_caps)
114 maybe_send_cap s = when (isJust $ s terminfo_caps) . send_cap (fromJust . s)
116 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
212 , attr_required_bytes = \_prev_attr _req_attr _diffs -> assumed_attr_required_bytes
214 , attr_required_bytes = \_prev_attr _req_attr _diffs -> assumed_attr_required_bytes
213 , serialize_set_attr = terminfo_serialize_set_attr self terminfo_caps
215 , serialize_set_attr = terminfo_serialize_set_attr self terminfo_caps
214 , default_attr_required_bytes = cap_expression_required_bytes (set_default_attr terminfo_caps) []
216 , 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) []
216 }
220 }
217
221
218-- | Instead of evaluating all the rules related to setting display attributes twice (once in
222-- | 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
59 qc_result <- quickCheckResult p
59 qc_result <- quickCheckResult p
60 case qc_result of
60 case qc_result of
61 QC.Success {..} -> return $ Finished TS.Pass
61 QC.Success {..} -> return $ Finished TS.Pass
62+ QC.Failure {output} -> return $ Finished $ TS.Fail output
62 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
63 _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"
63 , tags = []
64 , tags = []
64 , options = []
65 , options = []
test/Verify/Data/Terminfo/Parse.hs−6
@@ -12,12 +12,6 @@ import Data.Word
12
12
13import Numeric
13import Numeric
14
14
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−
21hex_dump :: [Word8] -> String
15hex_dump :: [Word8] -> String
22hex_dump bytes = foldr (\b s -> showHex b s) "" bytes
16hex_dump bytes = foldr (\b s -> showHex b s) "" bytes
23
17
test/Verify/Graphics/Vty/Image.hs+3−3
@@ -80,9 +80,9 @@ instance Arbitrary SingleAttrSingleSpanStack where
80 arbitrary = do
80 arbitrary = do
81 image_list <- Verify.resize 128 (listOf1 arbitrary)
81 image_list <- Verify.resize 128 (listOf1 arbitrary)
82 let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
82 let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]
83− return $ SingleAttrSingleSpanStack
83+ return $ SingleAttrSingleSpanStack
84− image
84+ image
85− image_list
85+ image_list
86 ( maximum $ map expected_columns image_list )
86 ( maximum $ map expected_columns image_list )
87 ( toEnum $ length image_list )
87 ( toEnum $ length image_list )
88
88
test/VerifySpanOps.hs+3−2
@@ -2,12 +2,13 @@
2{-# LANGUAGE NamedFieldPuns #-}
2{-# LANGUAGE NamedFieldPuns #-}
3module VerifySpanOps where
3module VerifySpanOps where
4
4
5−import Verify.Graphics.Vty.Picture
5+import Verify.Graphics.Vty.DisplayRegion
6import Verify.Graphics.Vty.Image
6import Verify.Graphics.Vty.Image
7+import Verify.Graphics.Vty.Picture
7import Verify.Graphics.Vty.Span
8import Verify.Graphics.Vty.Span
8−import Verify.Graphics.Vty.DisplayRegion
9
9
10import Graphics.Vty.Debug
10import Graphics.Vty.Debug
11+import Graphics.Vty.PictureToSpans
11
12
12import Verify
13import Verify
13
14
test/VerifyTerminal.hs+12−12
@@ -18,6 +18,14 @@ import qualified Data.String.UTF8 as UTF8
18
18
19import System.IO
19import System.IO
20
20
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+
21unit_image_unit_bounds :: UnitImage -> Property
29unit_image_unit_bounds :: UnitImage -> Property
22unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
30unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do
23 (_,t) <- mock_terminal (DisplayRegion 1 1)
31 (_,t) <- mock_terminal (DisplayRegion 1 1)
@@ -49,9 +57,7 @@ single_T_row (MockWindow w h) = liftIOResult $ do
49 -- character
57 -- character
50 ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
58 ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')
51 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
59 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 }
60+ compare_bytes out_bytes expected_bytes
54− else return succeeded
55
61
56many_T_rows :: MockWindow -> Property
62many_T_rows :: MockWindow -> Property
57many_T_rows (MockWindow w h) = liftIOResult $ do
63many_T_rows (MockWindow w h) = liftIOResult $ do
@@ -66,9 +72,7 @@ many_T_rows (MockWindow w h) = liftIOResult $ do
66 -- attribute change. 'A', followed by w 'T's
72 -- attribute change. 'A', followed by w 'T's
67 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
73 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
68 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
74 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 }
75+ compare_bytes out_bytes expected_bytes
71− else return succeeded
72
76
73many_T_rows_cropped_width :: MockWindow -> Property
77many_T_rows_cropped_width :: MockWindow -> Property
74many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
78many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
@@ -83,9 +87,7 @@ many_T_rows_cropped_width (MockWindow w h) = liftIOResult $ do
83 -- attribute change. 'A', followed by w 'T's
87 -- attribute change. 'A', followed by w 'T's
84 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
88 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
85 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
89 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 }
90+ compare_bytes out_bytes expected_bytes
88− else return succeeded
89
91
90many_T_rows_cropped_height :: MockWindow -> Property
92many_T_rows_cropped_height :: MockWindow -> Property
91many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
93many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
@@ -100,9 +102,7 @@ many_T_rows_cropped_height (MockWindow w h) = liftIOResult $ do
100 -- attribute change. 'A', followed by w count 'T's
102 -- attribute change. 'A', followed by w count 'T's
101 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
103 let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')
102 expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected
104 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+ compare_bytes out_bytes expected_bytes
105− else return succeeded
106
106
107tests :: IO [Test]
107tests :: IO [Test]
108tests = return [ verify "unit_image_unit_bounds" unit_image_unit_bounds
108tests = return [ verify "unit_image_unit_bounds" unit_image_unit_bounds
test/benchmark.hs+2−2
@@ -32,10 +32,10 @@ main = do
32 ["-h" ] -> help
32 ["-h" ] -> help
33 _ -> do
33 _ -> do
34 let args' = if args /= []
34 let args' = if args /= []
35− then args
35+ then map (drop 2) args
36 else map fst benches
36 else map fst benches
37 -- drop the dash-dash "--"
37 -- drop the dash-dash "--"
38− results <- forM args' $ \(_ : _ : b_name) -> do
38+ results <- forM args' $ \b_name -> do
39 case lookup b_name benches of
39 case lookup b_name benches of
40 Just b -> bench b_name b
40 Just b -> bench b_name b
41 Nothing -> fail $ "No benchmark named " ++ b_name
41 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
2[("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)]
3[("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)]
3[("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[("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+
5−starting no-diff-opt-0
6+on ooyala, no screen, iTerm:
6−user time: 226
7+
7−system time: 13
8+ * 5.0 initial:
8−starting render-char-0
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)]
9−user time: 411
10+
10−system time: 22
11+ * post background span replacement:
11−starting render-char-1
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)]
12−user time: 386
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)]
13−system time: 21
14+
14−starting vertical-scroll-0
15+release 4.7 on toast
15−user time: 1930
16−system time: 96
17−starting image-fuzz-0
18−user time: 1225
19−system time: 52
20[("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)]
16[("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
58 Graphics.Vty.Error
58 Graphics.Vty.Error
59 Graphics.Vty.Image
59 Graphics.Vty.Image
60 Graphics.Vty.Inline
60 Graphics.Vty.Inline
61+ Graphics.Vty.Inline.Unsafe
61 Graphics.Vty.LLInput
62 Graphics.Vty.LLInput
62 Graphics.Vty.Picture
63 Graphics.Vty.Picture
63 Graphics.Vty.Terminal
64 Graphics.Vty.Terminal
@@ -79,7 +80,6 @@ library
79 Graphics.Vty.Terminal.XTermColor
80 Graphics.Vty.Terminal.XTermColor
80 Graphics.Vty.Terminal.TerminfoBased
81 Graphics.Vty.Terminal.TerminfoBased
81
82
82− other-modules: Graphics.Vty.Inline.Hack
83
83
84 c-sources: cbits/gwinsz.c
84 c-sources: cbits/gwinsz.c
85 cbits/set_term_timing.c
85 cbits/set_term_timing.c