on master help sign in
change Attr record accessors

Ignore-this: 3b7f59cd9b1e753e4d8695d699229cb2 darcs-hash:20091108225727-f0a0d-a7c91d2a369b6ed936a0c7bd889fd2052e44628b.gz

commitbde5a931bf41f30e296b955ce2a014e5a939aafa authorcoreyoconnor <coreyoconnor@gmail.com> dateSun 8 Nov 2009, 14:57 (17 years ago) parenta1cae01
9 files changed+508−108
CHANGELOG+49 README+10−16 src/Graphics/Vty/Attributes.hs+7−7 src/Graphics/Vty/Terminal.hs+7 src/Graphics/Vty/Terminal/Generic.hs+107−10 src/Graphics/Vty/Terminal/MacOSX.hs+9−2 src/Graphics/Vty/Terminal/TerminfoBased.hs+314−71 src/Graphics/Vty/Terminal/XTermColor.hs+2 test/interactive_terminal_test.hs+3−2
CHANGELOGnew file+49
@@ -0,0 +1,49 @@
1+4.1.0.0
2+ * API changes:
3+ * Attr record accessor fore_color changed to attr_fore_color
4+ * Attr record accessor back_color changed to attr_back_color
5+ * Attr record accessor style changed to attr_style
6+ * Added an "inline" attribute changing DSL:
7+ * put_attr_change applies and attribute change monad action
8+ * can be used to change the display attrbiutes of text output via putStrLn and putStr,
9+ for instance. EX: "put_attr_change $ back_color red" will set the background color to
10+ red.
11+ * Does not apply to output_picture.
12+ * See Graphics.Vty.AttributeChange
13+4.0.0.1
14+ * binding for mk_wcswidth was incorrect. Most platforms just magically worked due to
15+ coincidence.
16+
17+4.0.0
18+ * Completely rewritten output backend.
19+ * Efficient, scanline style output span generator. Has not been fully optimized, but good
20+ enough.
21+ * The details required to display the desired picture on a terminal are well encapsulated.
22+ * Terminfo based display terminal implementation. With specialized derivitives for xterm,
23+ Terminal.app, and iTerm.app.
24+ * Attempts to robustly handle even terminals that don't support all display attributes.
25+ * I've tested the following terminals with success: iTerm.app, Terminal.app, xterm,
26+ rxvt, mlterm, Eterm, gnome-terminal, konsole, screen, linux vty. Hopefully you will be
27+ as successfull.
28+ * Improved unicode support. Double wide characters will display as expected.
29+ * 256 color support. See Graphics.Vty.Attributes.Color240. The actual output color is adjusted
30+ according to the number of colors the terminal supports.
31+ * The Graphics.Vty.Image combinators no longer require matching dimensions to arguments.
32+ Unspecified areas are filled in with a user-customizable background pattern. See
33+ Graphics.Vty.Picture.
34+ * output images are always cropped to display size.
35+ * Significant code coverage by QuickCheck tests. An interactive test for those final properties
36+ that couldn't be automatically verified.
37+
38+ issues resolved:
39+ * "gnome terminal displays non-basic attributes as strikethrough"
40+ * http://trac.haskell.org/vty/ticket/14
41+ * "Multi-byte characters are not displayed correctly on update"
42+ * http://trac.haskell.org/vty/ticket/10
43+ * "Redraw does not handle rendering a line that extends beyond screen width characters"
44+ * http://trac.haskell.org/vty/ticket/13
45+ * "The <|> and <-> combinators should be more forgiving of mismatched dimensions"
46+ * http://trac.haskell.org/vty/ticket/9
47+ * "256-color support"
48+ * http://trac.haskell.org/vty/ticket/19
49+
README+10−16
@@ -1,15 +1,13 @@
1−vty is a very simple terminal interface library.
1+vty is a terminal interface library.
22
33Vty currently provides:
44
5−* Automatic handling of suspend/resume (SIGTSTP+SIGCONT).
6−
75* Automatic handling of window resizes.
86
97* Supports Unicode characters on output, automatically setting and
10− resetting UTF-8 mode (beware double width and combining characters!)
8+ resetting UTF-8 mode for xterm. Other terminals are assumed to support
119
12−* Automatic computation of minimal differences.
10+* Efficient output.
1311
1412* Minimizes repaint area, thus virtually eliminating the flicker
1513 problem that plagues ncurses programs.
@@ -27,17 +25,13 @@ Vty currently provides:
2725* Interface is designed for relatively easy compatible extension.
2826
2927* Supports all ANSI SGR-modes (defined in console_codes(4)) with
30− a simple type-safe interface.
28+ a type-safe interface.
3129
32−* Properly handles cleanup, leaving the cursor at the bottom of
33− the screen and erasing the last line.
30+* Properly handles cleanup.
3431
3532Current disadvantages:
3633
37−* No current support for non-ANSI terminals.
38−
39−* UTF-8 support only works properly on the linux console/xterm,
40− and even then only if it is not the system default.
34+* The character encoding of the output terminal is assumed to be UTF-8.
4135
4236* Minimal support for special keys on terminals other than the
4337 linux-console. (F1-5 and arrow keys should work, but anything
@@ -46,9 +40,9 @@ Current disadvantages:
4640* Uses the TIOCGWINSZ ioctl to find the current window size, which
4741 appears to be limited to Linux and *BSD.
4842
49−darcs get --tag=rel-3.0.0 http://code.haskell.org/vty/
43+darcs get --tag=vty-4.0.0.1 http://code.haskell.org/vty/
5044
51−To compile the demonstration program: ghc --make Test.hs gwinsz.c
45+To compile the demonstration program: ghc --make test/Test.hs gwinsz.c
5246
53−The main documentation consists of the haddock-comments and the
54−demonstration program
47+The main documentation consists of the haddock-comments and the demonstration
48+program
src/Graphics/Vty/Attributes.hs+7−7
@@ -46,9 +46,9 @@ import Data.Word
4646-- foreground. The 240 colors and 16 colors are points in different palettes. See Color for more
4747-- information.
4848data Attr = Attr
49− { style :: !(MaybeDefault Style)
50− , fore_color :: !(MaybeDefault Color)
51− , back_color :: !(MaybeDefault Color)
49+ { attr_style :: !(MaybeDefault Style)
50+ , attr_fore_color :: !(MaybeDefault Color)
51+ , attr_back_color :: !(MaybeDefault Color)
5252 } deriving ( Eq, Show )
5353
5454-- | Specifies the display attributes such that the final style and color values do not depend on
@@ -168,7 +168,7 @@ default_style_mask = 0x00
168168
169169style_mask :: Attr -> Word8
170170style_mask attr
171− = case style attr of
171+ = case attr_style attr of
172172 Default -> 0
173173 KeepCurrent -> 0
174174 SetTo v -> v
@@ -179,15 +179,15 @@ has_style s bit_mask = ( s .&. bit_mask ) /= 0
179179
180180-- | Set the foreground color of an `Attr'.
181181with_fore_color :: Attr -> Color -> Attr
182−with_fore_color attr c = attr { fore_color = SetTo c }
182+with_fore_color attr c = attr { attr_fore_color = SetTo c }
183183
184184-- | Set the background color of an `Attr'.
185185with_back_color :: Attr -> Color -> Attr
186−with_back_color attr c = attr { back_color = SetTo c }
186+with_back_color attr c = attr { attr_back_color = SetTo c }
187187
188188-- | Add the given style attribute
189189with_style :: Attr -> Style -> Attr
190−with_style attr style_flag = attr { style = SetTo $ style_mask attr .|. style_flag }
190+with_style attr style_flag = attr { attr_style = SetTo $ style_mask attr .|. style_flag }
191191
192192-- | Sets the style, background color and foreground color to the default values for the terminal.
193193-- There is no easy way to determine what the default background and foreground colors are.
src/Graphics/Vty/Terminal.hs+7
@@ -19,6 +19,7 @@ module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal
1919 , TerminalHandle(..)
2020 , DisplayHandle(..)
2121 , output_picture
22+ -- , put_attr_change
2223 , display_context
2324 )
2425 where
@@ -118,6 +119,12 @@ hide_cursor t = do
118119 d <- display_context t bounds
119120 marshall_to_terminal t (hide_cursor_required_bytes d) (serialize_hide_cursor d)
120121
122+{-
123+put_attr_change :: TerminalHandle -> -> m ()
124+put_attr_change d img = do
125+ return ()
126+-}
127+
121128-- | Shows the cursor
122129show_cursor :: TerminalHandle -> IO ()
123130show_cursor t = do
src/Graphics/Vty/Terminal/Generic.hs+107−10
@@ -4,7 +4,9 @@
44-- Copyright 2009 Corey O'Connor
55{-# LANGUAGE GADTs #-}
66{-# LANGUAGE FlexibleContexts #-}
7−module Graphics.Vty.Terminal.Generic
7+module Graphics.Vty.Terminal.Generic ( module Graphics.Vty.Terminal.Generic
8+ , OutputBuffer
9+ )
810 where
911
1012import Data.Marshalling
@@ -14,11 +16,14 @@ import Graphics.Vty.Span
1416import Graphics.Vty.DisplayRegion
1517
1618import Control.Monad ( liftM )
19+import Control.Monad.Trans
1720
1821import Data.Array
22+import Data.Bits ( (.&.) )
1923import qualified Data.ByteString.Internal as BSCore
2024import Data.Foldable
2125import Data.IORef
26+import Data.Monoid ( mconcat )
2227import Data.Word
2328import Data.String.UTF8 hiding ( foldl )
2429
@@ -107,6 +112,9 @@ class DisplayTerminal d where
107112 -- | Provide the bounds of the display context.
108113 context_region :: d -> DisplayRegion
109114
115+ -- | Maximum number of colors supported by the context.
116+ context_color_count :: d -> Word
117+
110118 -- | sets the output position to the specified row and column. Where the number of bytes
111119 -- required for the control codes can be specified seperate from the actual byte sequence.
112120 move_cursor_required_bytes :: d -> Word -> Word -> Word
@@ -129,8 +137,8 @@ class DisplayTerminal d where
129137 -- attributes. In order to support this the currently applied display attributes are required.
130138 -- In addition it may be possible to optimize the state changes based off the currently applied
131139 -- display attributes.
132− attr_required_bytes :: d -> FixedAttr -> Attr -> Word
133− serialize_set_attr :: d -> FixedAttr -> Attr -> OutputBuffer -> IO OutputBuffer
140+ attr_required_bytes :: d -> FixedAttr -> Attr -> DisplayAttrDiffs -> Word
141+ serialize_set_attr :: d -> FixedAttr -> Attr -> DisplayAttrDiffs -> OutputBuffer -> IO OutputBuffer
134142
135143 -- | Reset the display attributes to the default display attributes
136144 default_attr_required_bytes :: d -> Word
@@ -139,6 +147,7 @@ class DisplayTerminal d where
139147
140148instance DisplayTerminal DisplayHandle where
141149 context_region (DisplayHandle d _ _) = context_region d
150+ context_color_count (DisplayHandle d _ _) = context_color_count d
142151 move_cursor_required_bytes (DisplayHandle d _ _) = move_cursor_required_bytes d
143152 serialize_move_cursor (DisplayHandle d _ _) = serialize_move_cursor d
144153 show_cursor_required_bytes (DisplayHandle d _ _) = show_cursor_required_bytes d
@@ -256,8 +265,9 @@ span_ops_required_bytes d y in_fattr span_ops =
256265
257266span_op_required_bytes :: DisplayTerminal d => d -> FixedAttr -> SpanOp -> (Word, FixedAttr)
258267span_op_required_bytes d fattr (AttributeChange attr) =
259− let c = attr_required_bytes d fattr attr
260− fattr' = fix_display_attr fattr attr
268+ let attr' = limit_attr_for_display d attr
269+ c = attr_required_bytes d fattr attr' (display_attr_diffs fattr fattr')
270+ fattr' = fix_display_attr fattr attr'
261271 in (c, fattr')
262272span_op_required_bytes _d fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)
263273
@@ -304,8 +314,9 @@ serialize_span_op :: DisplayTerminal d
304314 -> FixedAttr
305315 -> IO (OutputBuffer, FixedAttr)
306316serialize_span_op d (AttributeChange attr) out_ptr fattr = do
307− out_ptr' <- serialize_set_attr d fattr attr out_ptr
308− let fattr' = fix_display_attr fattr attr
317+ let attr' = limit_attr_for_display d attr
318+ fattr' = fix_display_attr fattr attr'
319+ out_ptr' <- serialize_set_attr d fattr attr' (display_attr_diffs fattr fattr') out_ptr
309320 return (out_ptr', fattr')
310321serialize_span_op _d (TextSpan _ _ str) out_ptr fattr = do
311322 out_ptr' <- serialize_utf8_text str out_ptr
@@ -330,9 +341,9 @@ marshall_to_terminal t c f = do
330341-- abstraction.
331342fix_display_attr :: FixedAttr -> Attr -> FixedAttr
332343fix_display_attr fattr attr
333− = FixedAttr ( fix_style (fixed_style fattr) (style attr) )
334− ( fix_color (fixed_fore_color fattr) (fore_color attr) )
335− ( fix_color (fixed_back_color fattr) (back_color attr) )
344+ = FixedAttr ( fix_style (fixed_style fattr) (attr_style attr) )
345+ ( fix_color (fixed_fore_color fattr) (attr_fore_color attr) )
346+ ( fix_color (fixed_back_color fattr) (attr_back_color attr) )
336347 where
337348 fix_style _s Default = default_style_mask
338349 fix_style s KeepCurrent = s
@@ -341,6 +352,68 @@ fix_display_attr fattr attr
341352 fix_color c KeepCurrent = c
342353 fix_color _c (SetTo c) = Just c
343354
355+data DisplayAttrDiffs = DisplayAttrDiffs
356+ { style_diffs :: [ StyleStateChange ]
357+ , fore_color_diff :: DisplayColorDiff
358+ , back_color_diff :: DisplayColorDiff
359+ }
360+
361+data DisplayColorDiff
362+ = ColorToDefault
363+ | NoColorChange
364+ | SetColor !Color
365+ deriving Eq
366+
367+data StyleStateChange
368+ = ApplyStandout
369+ | RemoveStandout
370+ | ApplyUnderline
371+ | RemoveUnderline
372+ | ApplyReverseVideo
373+ | RemoveReverseVideo
374+ | ApplyBlink
375+ | RemoveBlink
376+ | ApplyDim
377+ | RemoveDim
378+ | ApplyBold
379+ | RemoveBold
380+
381+display_attr_diffs :: FixedAttr -> FixedAttr -> DisplayAttrDiffs
382+display_attr_diffs attr attr' = DisplayAttrDiffs
383+ { style_diffs = diff_styles ( fixed_style attr ) ( fixed_style attr' )
384+ , fore_color_diff = diff_color ( fixed_fore_color attr ) ( fixed_fore_color attr' )
385+ , back_color_diff = diff_color ( fixed_back_color attr ) ( fixed_back_color attr' )
386+ }
387+
388+diff_color Nothing (Just c') = SetColor c'
389+diff_color (Just c) (Just c')
390+ | c == c' = NoColorChange
391+ | otherwise = SetColor c'
392+diff_color Nothing Nothing = NoColorChange
393+diff_color (Just _) Nothing = ColorToDefault
394+
395+diff_styles :: Style -> Style -> [StyleStateChange]
396+diff_styles prev cur
397+ = mconcat
398+ [ style_diff standout ApplyStandout RemoveStandout
399+ , style_diff underline ApplyUnderline RemoveUnderline
400+ , style_diff reverse_video ApplyReverseVideo RemoveReverseVideo
401+ , style_diff blink ApplyBlink RemoveBlink
402+ , style_diff dim ApplyDim RemoveDim
403+ , style_diff bold ApplyBold RemoveBold
404+ ]
405+ where
406+ style_diff s sm rm
407+ = case ( 0 == prev .&. s, 0 == cur .&. s ) of
408+ -- not set in either
409+ ( True, True ) -> []
410+ -- set in both
411+ ( False, False ) -> []
412+ -- now set
413+ ( True, False) -> [ sm ]
414+ -- now unset
415+ ( False, True) -> [ rm ]
416+
344417data CursorOutputMap = CursorOutputMap
345418 { char_to_output_pos :: (Word, Word) -> (Word, Word)
346419 }
@@ -374,3 +447,27 @@ cursor_column_offset span_ops cx cy =
374447 (0, 0, False)
375448 cursor_row_ops
376449 in out_offset
450+
451+limit_attr_for_display :: DisplayTerminal d => d -> Attr -> Attr
452+limit_attr_for_display d attr
453+ = attr { attr_fore_color = clamp_color $ attr_fore_color attr
454+ , attr_back_color = clamp_color $ attr_back_color attr
455+ }
456+ where
457+ clamp_color Default = Default
458+ clamp_color KeepCurrent = KeepCurrent
459+ clamp_color (SetTo c) = clamp_color' c
460+ clamp_color' (ISOColor v)
461+ | context_color_count d < 8 = Default
462+ | context_color_count d < 16 && v >= 8 = SetTo $ ISOColor (v - 8)
463+ | otherwise = SetTo $ ISOColor v
464+ clamp_color' (Color240 v)
465+ -- TODO: Choose closes ISO color?
466+ | context_color_count d < 8 = Default
467+ | context_color_count d < 16 = Default
468+ | context_color_count d == 240 = SetTo $ Color240 v
469+ | otherwise
470+ = let p :: Double = fromIntegral v / 240.0
471+ v' = floor $ p * (fromIntegral $ context_color_count d)
472+ in SetTo $ Color240 v'
473+
src/Graphics/Vty/Terminal/MacOSX.hs+9−2
@@ -34,15 +34,21 @@ flushed_put str = do
3434 hPutStr stdout str
3535 hFlush stdout
3636
37+-- Terminal.app really does want the xterm-color smcup and rmcup caps. Not the generic xterm ones.
38+smcup_str = "\ESC7\ESC[?47h"
39+rmcup_str = "\ESC[2J\ESC[?47l\ESC8"
40+
3741instance Terminal Term where
3842 terminal_ID t = "Terminal.app :: MacOSX"
3943
4044 release_terminal t = do
4145 release_terminal $ super_term t
4246
43− reserve_display t = reserve_display (super_term t)
47+ reserve_display t = do
48+ flushed_put smcup_str
4449
45− release_display t = release_display (super_term t)
50+ release_display t = do
51+ flushed_put rmcup_str
4652
4753 display_terminal_instance t b c = do
4854 d <- display_context (super_term t) b
@@ -58,6 +64,7 @@ data DisplayContext = DisplayContext
5864
5965instance DisplayTerminal DisplayContext where
6066 context_region d = context_region (super_display d)
67+ context_color_count d = context_color_count (super_display d)
6168
6269 move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
6370 serialize_move_cursor d = serialize_move_cursor (super_display d)
src/Graphics/Vty/Terminal/TerminfoBased.hs+314−71
@@ -15,6 +15,13 @@ import Graphics.Vty.Attributes
1515import Graphics.Vty.Terminal.Generic
1616import Graphics.Vty.DisplayRegion
1717
18+import Control.Applicative
19+import Control.Monad ( foldM )
20+
21+import Data.Bits ( (.&.) )
22+import Data.List ( foldl' )
23+import Data.Maybe ( fromJust )
24+import Data.Monoid
1825import Data.Word
1926
2027import Foreign.C.Types ( CLong )
@@ -24,17 +31,30 @@ import System.IO
2431
2532data Term = Term
2633 { term_info_ID :: String
27− , smcup :: CapExpression
28− , rmcup :: CapExpression
34+ , term_info :: Terminfo.Terminal
35+ , smcup :: Maybe CapExpression
36+ , rmcup :: Maybe CapExpression
2937 , cup :: CapExpression
3038 , cnorm :: CapExpression
3139 , civis :: CapExpression
3240 , set_fore_color :: CapExpression
3341 , set_back_color :: CapExpression
3442 , set_default_attr :: CapExpression
35− , set_attr :: CapExpression
43+ , clear_screen :: CapExpression
44+ , display_attr_caps :: DisplayAttrCaps
3645 }
3746
47+data DisplayAttrCaps = DisplayAttrCaps
48+ { set_attr_states :: Maybe CapExpression
49+ , enter_standout :: Maybe CapExpression
50+ , exit_standout :: Maybe CapExpression
51+ , enter_underline :: Maybe CapExpression
52+ , exit_underline :: Maybe CapExpression
53+ , enter_reverse_video :: Maybe CapExpression
54+ , enter_dim_mode :: Maybe CapExpression
55+ , enter_bold_mode :: Maybe CapExpression
56+ }
57+
3858marshall_cap_to_terminal :: Term -> (Term -> CapExpression) -> [CapParam] -> IO ()
3959marshall_cap_to_terminal t cap_selector cap_params = do
4060 marshall_to_terminal t (cap_expression_required_bytes (cap_selector t) cap_params)
@@ -53,31 +73,50 @@ marshall_cap_to_terminal t cap_selector cap_params = do
5373 -}
5474terminal_instance :: String -> IO Term
5575terminal_instance in_ID = do
56− term_info <- Terminfo.setupTerm in_ID
57− let str_cap str = case Terminfo.getCapability term_info (Terminfo.tiGetStr str) of
58− Nothing -> fail $ "Terminal does not define required capability \"" ++ str ++ "\""
59− Just cap_str -> case parse_cap_expression cap_str of
60− Left e -> fail $ show e
61− Right cap -> return cap
62− smcup_cap <- str_cap "smcup"
63− rmcup_cap <- str_cap "rmcup"
64− cup_cap <- str_cap "cup"
65− cnorm_cap <- str_cap "cnorm"
66− civis_cap <- str_cap "civis"
67− setaf_cap <- str_cap "setaf"
68− setab_cap <- str_cap "setab"
69− sgr0_cap <- str_cap "sgr0"
70− sgr_cap <- str_cap "sgr"
71− return $ Term in_ID
72− smcup_cap
73− rmcup_cap
74− cup_cap
75− cnorm_cap
76− civis_cap
77− setaf_cap
78− setab_cap
79− sgr0_cap
80− sgr_cap
76+ ti <- Terminfo.setupTerm in_ID
77+ let require_cap str
78+ = case Terminfo.getCapability ti (Terminfo.tiGetStr str) of
79+ Nothing -> fail $ "Terminal does not define required capability \"" ++ str ++ "\""
80+ Just cap_str -> case parse_cap_expression cap_str of
81+ Left e -> fail $ show e
82+ Right cap -> return cap
83+ probe_cap cap_name
84+ = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
85+ Nothing -> return Nothing
86+ Just cap_str -> case parse_cap_expression cap_str of
87+ Left e -> fail $ show e
88+ Right cap -> return $ Just cap
89+ pure Term
90+ <*> pure in_ID
91+ <*> pure ti
92+ <*> probe_cap "smcup"
93+ <*> probe_cap "rmcup"
94+ <*> require_cap "cup"
95+ <*> require_cap "cnorm"
96+ <*> require_cap "civis"
97+ <*> require_cap "setaf"
98+ <*> require_cap "setab"
99+ <*> require_cap "sgr0"
100+ <*> require_cap "clear"
101+ <*> current_display_attr_caps ti
102+
103+current_display_attr_caps :: Terminfo.Terminal -> IO DisplayAttrCaps
104+current_display_attr_caps ti
105+ = pure DisplayAttrCaps
106+ <*> probe_cap "sgr"
107+ <*> probe_cap "smso"
108+ <*> probe_cap "rmso"
109+ <*> probe_cap "smul"
110+ <*> probe_cap "rmul"
111+ <*> probe_cap "rev"
112+ <*> probe_cap "dim"
113+ <*> probe_cap "bold"
114+ where probe_cap cap_name
115+ = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of
116+ Nothing -> return Nothing
117+ Just cap_str -> case parse_cap_expression cap_str of
118+ Left e -> fail $ show e
119+ Right cap -> return $ Just cap
81120
82121instance Terminal Term where
83122 terminal_ID t = term_info_ID t ++ " :: TerminfoBased"
@@ -86,17 +125,26 @@ instance Terminal Term where
86125 return ()
87126
88127 reserve_display t = do
89− marshall_cap_to_terminal t smcup []
90− marshall_cap_to_terminal t cup [0, 0]
128+ if (isJust $ smcup t)
129+ then marshall_cap_to_terminal t (fromJust . smcup) []
130+ else return ()
131+ -- Screen on OS X does not appear to support smcup?
132+ marshall_cap_to_terminal t clear_screen []
91133 return ()
92134
93135 release_display t = do
94− marshall_cap_to_terminal t rmcup []
136+ if (isJust $ rmcup t)
137+ then marshall_cap_to_terminal t (fromJust . rmcup) []
138+ else return ()
95139 marshall_cap_to_terminal t cnorm []
96140 return ()
97141
98142 display_terminal_instance t b c = do
99− return $ c (DisplayContext b t)
143+ let color_count
144+ = case Terminfo.getCapability (term_info t) (Terminfo.tiGetNum "colors" ) of
145+ Nothing -> 8
146+ Just v -> toEnum v
147+ return $ c (DisplayContext b t color_count)
100148
101149 display_bounds _t = do
102150 raw_size <- get_window_size
@@ -119,11 +167,14 @@ get_window_size = do
119167data DisplayContext = DisplayContext
120168 { bounds :: DisplayRegion
121169 , term :: Term
170+ , supported_colors :: Word
122171 }
123172
124173instance DisplayTerminal DisplayContext where
125174 context_region d = bounds d
126175
176+ context_color_count d = supported_colors d
177+
127178 move_cursor_required_bytes d x y
128179 = cap_expression_required_bytes (cup $ term d) [y, x]
129180 serialize_move_cursor d x y out_ptr
@@ -139,41 +190,106 @@ instance DisplayTerminal DisplayContext where
139190 serialize_hide_cursor d out_ptr
140191 = serialize_cap_expression (civis $ term d) [] out_ptr
141192
193+ -- Instead of evaluating all the rules related to setting display attributes twice (once in
194+ -- required bytes and again in serialize) or some memoization scheme just return a size
195+ -- requirement as big the longest possible control string.
196+ --
197+ -- Which is assumed to the be less than 512 for now.
198+ --
199+ -- \todo Not verified as safe and wastes memory.
200+ attr_required_bytes d _prev_attr _req_attr _diffs = 512
201+
202+ -- Portably setting the display attributes is a giant pain in the ass.
203+ -- If the terminal supports the sgr capability (which sets the on/off state of each style
204+ -- directly ; and, for no good reason, resets the colors to the default) this always works:
142205 -- 0. set the style attributes. This resets the fore and back color.
143206 -- 1, If a foreground color is to be set then set the foreground color
144207 -- 2. likewise with the background color
145− --
146208 --
147− -- todo: This process is accurate but inefficient. Optimize later.
148− attr_required_bytes d prev_attr req_attr =
149− let attr = fix_display_attr prev_attr req_attr
150− in cap_expression_required_bytes ( set_attr $ term d )
151− ( sgr_args_for_style (fixed_style attr) )
152− + case fixed_fore_color attr of
153− Just c -> cap_expression_required_bytes ( set_fore_color $ term d )
154− [ ansi_color_index c ]
155− Nothing -> 0
156− + case fixed_back_color attr of
157− Just c -> cap_expression_required_bytes ( set_back_color $ term d )
158− [ ansi_color_index c ]
159− Nothing -> 0
160−
161− serialize_set_attr d prev_attr req_attr out_ptr = do
162− let attr = fix_display_attr prev_attr req_attr
163− out_ptr' <- serialize_cap_expression ( set_attr $ term d )
164− ( sgr_args_for_style (fixed_style attr) )
165− out_ptr
166− out_ptr'' <- case fixed_fore_color attr of
167− Just c -> serialize_cap_expression ( set_fore_color $ term d )
168− [ ansi_color_index c ]
169− out_ptr'
170− Nothing -> return out_ptr'
171− out_ptr''' <- case fixed_back_color attr of
172− Just c -> serialize_cap_expression ( set_back_color $ term d )
173− [ ansi_color_index c ]
174− out_ptr''
175− Nothing -> return out_ptr''
176− return out_ptr'''
209+ -- If the terminal does not support the sgr then
210+ -- if there is a change from an applied color to the default (in either the fore or back color)
211+ -- then:
212+ -- 0. reset all display attributes (sgr0)
213+ -- 1. enter required style modes
214+ -- 2. set the fore color if required
215+ -- 3. set the back color if required
216+ --
217+ -- Entering the required style modes could require a reset of the display attributes. If this is
218+ -- the case then the back and fore colors always need to be set if not default.
219+ --
220+ -- All this (I think) is satisfied by the following logic:
221+ serialize_set_attr d prev_attr req_attr diffs out_ptr = do
222+ case (fore_color_diff diffs == ColorToDefault) || (back_color_diff diffs == ColorToDefault) of
223+ -- The only way to reset either color, portably, to the default is to use either the set
224+ -- state capability or the set default capability.
225+ True -> do
226+ case req_display_cap_seq_for ( display_attr_caps $ term d )
227+ ( fixed_style attr )
228+ ( style_to_apply_seq $ fixed_style attr )
229+ of
230+ EnterExitSeq caps
231+ -- only way to reset a color to the defaults
232+ -> serialize_default_attr d out_ptr
233+ >>= (\ptr -> foldM (\ptr cap -> serialize_cap_expression cap [] ptr) ptr caps)
234+ >>= set_colors
235+ SetState state
236+ -- implicitly resets the colors to the defaults
237+ -> serialize_cap_expression ( fromJust $ set_attr_states
238+ $ display_attr_caps
239+ $ term d
240+ )
241+ ( sgr_args_for_state state )
242+ out_ptr
243+ >>= set_colors
244+ -- Otherwise the display colors are not changing or changing between two non-default
245+ -- points.
246+ False -> do
247+ -- Still, it could be the case that the change in display attributes requires the
248+ -- colors to be reset because the required capability was not available.
249+ case req_display_cap_seq_for ( display_attr_caps $ term d )
250+ ( fixed_style attr )
251+ ( style_diffs diffs )
252+ of
253+ -- Really, if terminals were re-implemented with modern concepts instead of
254+ -- bowing down to 40 yr old dumb terminal requirements this would be the
255+ -- only case ever reached!
256+ -- Changes the style and color states according to the differences with the
257+ -- currently applied states.
258+ EnterExitSeq caps
259+ -> foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr caps
260+ >>= apply_color_diff set_fore_color ( fore_color_diff diffs )
261+ >>= apply_color_diff set_back_color ( back_color_diff diffs )
262+ SetState state
263+ -- implicitly resets the colors to the defaults
264+ -> serialize_cap_expression ( fromJust $ set_attr_states
265+ $ display_attr_caps
266+ $ term d
267+ )
268+ ( sgr_args_for_state state )
269+ out_ptr
270+ >>= set_colors
271+ where
272+ attr = fix_display_attr prev_attr req_attr
273+ set_colors ptr = do
274+ ptr' <- case fixed_fore_color attr of
275+ Just c -> serialize_cap_expression ( set_fore_color $ term d )
276+ [ ansi_color_index c ]
277+ ptr
278+ Nothing -> return ptr
279+ ptr'' <- case fixed_back_color attr of
280+ Just c -> serialize_cap_expression ( set_back_color $ term d )
281+ [ ansi_color_index c ]
282+ ptr'
283+ Nothing -> return ptr'
284+ return ptr''
285+ apply_color_diff _f NoColorChange ptr
286+ = return ptr
287+ apply_color_diff _f ColorToDefault ptr
288+ = fail "ColorToDefault is not a possible case for apply_color_diffs"
289+ apply_color_diff f ( SetColor c ) ptr
290+ = serialize_cap_expression ( f $ term d )
291+ [ ansi_color_index c ]
292+ ptr
177293
178294 default_attr_required_bytes d
179295 = cap_expression_required_bytes (set_default_attr $ term d) []
@@ -185,16 +301,143 @@ ansi_color_index :: Color -> Word
185301ansi_color_index (ISOColor v) = toEnum $ fromEnum v
186302ansi_color_index (Color240 v) = 16 + ( toEnum $ fromEnum v )
187303
188−sgr_args_for_style :: Style -> [CapParam]
189−sgr_args_for_style s = map ( \b -> if b then 1 else 0 )
190− [ s `has_style` standout
191− , s `has_style` underline
192− , s `has_style` reverse_video
193− , s `has_style` blink
194− , s `has_style` dim
195− , s `has_style` bold
304+{- The sequence of terminfo caps to apply a given style are determined according to these rules.
305+ -
306+ - 1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than
307+ - the full set display attribute state capability.
308+ -
309+ - 2. If a mode is supposed to be removed but there is not an exit capability defined then the
310+ - display attributes are reset to defaults then the display attribute state is set.
311+ -
312+ - 3. If a mode is supposed to be applied but there is not an enter capability defined then then
313+ - display attribute state is set if possible. Otherwise the mode is not applied.
314+ -
315+ - 4. If the display attribute state is being set then just update the arguments to that for any
316+ - apply/remove.
317+ -
318+ - The style diffs each imply either the enter/exit control code or a reset to defaults ; set state
319+ - sequence. This mapping satisfies the communitive monoid properties:
320+ - - no diff * diff == diff
321+ - - diff * no diff == diff
322+ - - (diff_0 * diff_1) * diff_2 == diff_0 * ( diff_1 * diff_2 )
323+ - - diff_0 * diff_1 == diff_1 * diff_0
324+ - so the mapping is a sequence of mappend's applied to mempty. Where the monoid points appended
325+ - depend on the diff. The accumulated value is the monad point that represents the final sequence
326+ - to apply. The application (*) operator assures all the rules are followed.
327+ -
328+ - The diff implies an enter/exit control code if:
329+ - - The current
330+ -}
331+
332+data DisplayAttrSeq v
333+ = EnterExitSeq [v]
334+ | SetState DisplayAttrState
335+
336+instance Monoid (DisplayAttrSeq v) where
337+ mempty = EnterExitSeq []
338+ SetState s `mappend` _ = SetState s
339+ _ `mappend` SetState s = SetState s
340+ (EnterExitSeq caps_0) `mappend` (EnterExitSeq caps_1) = EnterExitSeq (caps_0 `mappend` caps_1)
341+
342+data DisplayAttrState = DisplayAttrState
343+ { apply_standout :: Bool
344+ , apply_underline :: Bool
345+ , apply_reverse_video :: Bool
346+ , apply_blink :: Bool
347+ , apply_dim :: Bool
348+ , apply_bold :: Bool
349+ }
350+
351+sgr_args_for_state :: DisplayAttrState -> [CapParam]
352+sgr_args_for_state attr_state = map (\b -> if b then 1 else 0)
353+ [ apply_standout attr_state
354+ , apply_underline attr_state
355+ , apply_reverse_video attr_state
356+ , apply_blink attr_state
357+ , apply_dim attr_state
358+ , apply_bold attr_state
196359 , False -- invis
197360 , False -- protect
198361 , False -- alt char set
199362 ]
200363
364+req_display_cap_seq_for :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq CapExpression
365+req_display_cap_seq_for caps style diffs =
366+ -- First pass: concat the monoid points that are implied by the diffs
367+ let base = mconcat $ map diff_point diffs
368+ -- Second pass: Apply the capability restrictions.
369+ in apply_caps base
370+ where
371+ set_state = SetState $ state_for_style style
372+ diff_point ApplyStandout = EnterExitSeq [ApplyStandout]
373+ diff_point ApplyUnderline = EnterExitSeq [ApplyUnderline]
374+ diff_point ApplyReverseVideo = EnterExitSeq [ApplyReverseVideo]
375+ diff_point ApplyBlink = set_state
376+ diff_point ApplyDim = EnterExitSeq [ApplyDim]
377+ diff_point ApplyBold = EnterExitSeq [ApplyBold]
378+ diff_point RemoveStandout = EnterExitSeq [RemoveStandout]
379+ diff_point RemoveUnderline = EnterExitSeq [RemoveUnderline]
380+ diff_point RemoveReverseVideo = set_state
381+ diff_point RemoveBlink = set_state
382+ diff_point RemoveDim = set_state
383+ diff_point RemoveBold = set_state
384+ apply_caps ( SetState _ )
385+ = case set_attr_states caps of
386+ Nothing -> EnterExitSeq []
387+ Just _ -> set_state
388+ apply_caps (EnterExitSeq [])
389+ = EnterExitSeq []
390+ apply_caps (EnterExitSeq (diff : diffs'))
391+ = case apply_caps' diff of
392+ SetState _ -> set_state
393+ p -> p `mappend` apply_caps (EnterExitSeq diffs')
394+ apply_caps' ApplyStandout = m $ enter_standout caps
395+ apply_caps' ApplyUnderline = m $ enter_underline caps
396+ apply_caps' ApplyReverseVideo = m $ enter_reverse_video caps
397+ apply_caps' ApplyBlink = set_state
398+ apply_caps' ApplyDim = m $ enter_dim_mode caps
399+ apply_caps' ApplyBold = m $ enter_bold_mode caps
400+ apply_caps' RemoveStandout = m $ exit_standout caps
401+ apply_caps' RemoveUnderline = m $ exit_underline caps
402+ apply_caps' RemoveReverseVideo = set_state
403+ apply_caps' RemoveBlink = set_state
404+ apply_caps' RemoveDim = set_state
405+ apply_caps' RemoveBold = set_state
406+ m = maybe set_state (EnterExitSeq . return)
407+
408+{-
409+set_style_required_bytes caps style diffs reset_cap =
410+ let state = state_for_style style
411+ req_seq = req_seq_for caps style diffs
412+ in case req_seq of
413+ EnterExitSeq caps -> sum $ map (\cap -> cap_expression_required_bytes cap []) caps
414+ SetState -> cap_expression_required_bytes ( fromJust $ set_attr_states $ caps )
415+ ( sgr_args_for_state state )
416+-}
417+
418+state_for_style :: Style -> DisplayAttrState
419+state_for_style s = DisplayAttrState
420+ { apply_standout = is_style_set standout
421+ , apply_underline = is_style_set underline
422+ , apply_reverse_video = is_style_set reverse_video
423+ , apply_blink = is_style_set blink
424+ , apply_dim = is_style_set dim
425+ , apply_bold = is_style_set bold
426+ }
427+ where is_style_set = has_style s
428+
429+style_to_apply_seq :: Style -> [StyleStateChange]
430+style_to_apply_seq s = mconcat
431+ [ apply_if_required ApplyStandout standout
432+ , apply_if_required ApplyUnderline underline
433+ , apply_if_required ApplyReverseVideo reverse_video
434+ , apply_if_required ApplyBlink blink
435+ , apply_if_required ApplyDim dim
436+ , apply_if_required ApplyBlink bold
437+ ]
438+ where
439+ apply_if_required ap flag
440+ = if 0 == ( flag .&. s )
441+ then []
442+ else [ ap ]
443+
src/Graphics/Vty/Terminal/XTermColor.hs+2
@@ -62,6 +62,8 @@ data DisplayContext = DisplayContext
6262instance DisplayTerminal DisplayContext where
6363 context_region d = context_region (super_display d)
6464
65+ context_color_count d = context_color_count (super_display d)
66+
6567 move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)
6668 serialize_move_cursor d = serialize_move_cursor (super_display d)
6769
test/interactive_terminal_test.hs+3−2
@@ -852,9 +852,10 @@ inline_test_0 = Test
852852 , test_action = do
853853 t <- terminal_handle
854854 d <- display_bounds t >>= display_context t
855− let image = string ( def_attr `with_back_color` red ) "line 1."
856855 putStrLn "line 0."
857− put_image_ln d image
856+ -- put_attr_change $ back_color red >> style underline
857+ putStrLn "line 1."
858+ -- put_attr_change $ default_all
858859 putStrLn "line 2."
859860 release_terminal t
860861 return ()