1{-# LANGUAGE RecordWildCards #-} 2{-# LANGUAGE FlexibleInstances #-} 3{-# LANGUAGE OverloadedStrings #-} 4{-# LANGUAGE CPP #-} 5{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 -fno-warn-warnings-deprecations #-} 6{-# CFILES gwinsz.c #-} 7 8-- | Terminfo-based terminal output driver. 9-- 10-- Copyright Corey O'Connor (coreyoconnor@gmail.com) 11module Graphics.Vty.Output.TerminfoBased 12 ( reserveTerminal 13 , setWindowSize 14 ) 15where 16 17import Control.Monad (when) 18import Data.Bits (shiftL) 19import qualified Data.ByteString as BS 20import Data.ByteString.Internal (toForeignPtr) 21import Data.Terminfo.Parse 22import Data.Terminfo.Eval 23 24import Graphics.Vty.Attributes 25import Graphics.Vty.Image (DisplayRegion) 26import Graphics.Vty.DisplayAttributes 27import Graphics.Vty.Output.Interface 28 29import Blaze.ByteString.Builder (Write, writeToByteString, writeStorable) 30 31import Data.Bits ((.&.)) 32import Data.IORef 33import Data.Maybe (isJust, isNothing, fromJust) 34import Data.Word 35 36#if !MIN_VERSION_base(4,8,0) 37import Data.Foldable (foldMap) 38#endif 39 40import Foreign.C.Types ( CInt(..), CLong(..) ) 41import Foreign.ForeignPtr (withForeignPtr) 42import Foreign.Ptr (Ptr, plusPtr) 43 44import qualified System.Console.Terminfo as Terminfo 45import System.Posix.IO (fdWriteBuf) 46import System.Posix.Types (Fd(..)) 47 48data TerminfoCaps = TerminfoCaps 49 { smcup :: Maybe CapExpression 50 , rmcup :: Maybe CapExpression 51 , cup :: CapExpression 52 , cnorm :: Maybe CapExpression 53 , civis :: Maybe CapExpression 54 , supportsNoColors :: Bool 55 , useAltColorMap :: Bool 56 , setForeColor :: CapExpression 57 , setBackColor :: CapExpression 58 , setDefaultAttr :: CapExpression 59 , clearScreen :: CapExpression 60 , clearEol :: CapExpression 61 , displayAttrCaps :: DisplayAttrCaps 62 , ringBellAudio :: Maybe CapExpression 63 } 64 65data DisplayAttrCaps = DisplayAttrCaps 66 { setAttrStates :: Maybe CapExpression 67 , enterStandout :: Maybe CapExpression 68 , exitStandout :: Maybe CapExpression 69 , enterItalic :: Maybe CapExpression 70 , exitItalic :: Maybe CapExpression 71 , enterStrikethrough :: Maybe CapExpression 72 , exitStrikethrough :: Maybe CapExpression 73 , enterUnderline :: Maybe CapExpression 74 , exitUnderline :: Maybe CapExpression 75 , enterReverseVideo :: Maybe CapExpression 76 , enterDimMode :: Maybe CapExpression 77 , enterBoldMode :: Maybe CapExpression 78 } 79 80-- kinda like: 81-- https://code.google.com/p/vim/source/browse/src/fileio.c#10422 82-- fdWriteBuf will throw on error. Unless the error is EINTR. On EINTR 83-- the write will be retried. 84fdWriteAll :: Fd -> Ptr Word8 -> Int -> Int -> IO Int 85fdWriteAll outFd ptr len count 86 | len < 0 = fail "fdWriteAll: len is less than 0" 87 | len == 0 = return count 88 | otherwise = do 89 writeCount <- fromEnum <$> fdWriteBuf outFd ptr (toEnum len) 90 let len' = len - writeCount 91 ptr' = ptr `plusPtr` writeCount 92 count' = count + writeCount 93 fdWriteAll outFd ptr' len' count' 94 95sendCapToTerminal :: Output -> CapExpression -> [CapParam] -> IO () 96sendCapToTerminal t cap capParams = do 97 outputByteBuffer t $ writeToByteString $ writeCapExpr cap capParams 98 99-- | Constructs an output driver that uses terminfo for all control 100-- codes. While this should provide the most compatible terminal, 101-- terminfo does not support some features that would increase 102-- efficiency and improve compatibility: 103-- 104-- * determining the character encoding supported by the terminal. 105-- Should this be taken from the LANG environment variable? 106-- 107-- * Providing independent string capabilities for all display 108-- attributes. 109reserveTerminal :: String -> Fd -> IO Output 110reserveTerminal termName outFd = do 111 ti <- Terminfo.setupTerm termName 112 -- assumes set foreground always implies set background exists. 113 -- if set foreground is not set then all color changing style 114 -- attributes are filtered. 115 msetaf <- probeCap ti "setaf" 116 msetf <- probeCap ti "setf" 117 let (noColors, useAlt, setForeCap) 118 = case msetaf of 119 Just setaf -> (False, False, setaf) 120 Nothing -> case msetf of 121 Just setf -> (False, True, setf) 122 Nothing -> (True, True, error $ "no fore color support for terminal " ++ termName) 123 msetab <- probeCap ti "setab" 124 msetb <- probeCap ti "setb" 125 let set_back_cap 126 = case msetab of 127 Nothing -> case msetb of 128 Just setb -> setb 129 Nothing -> error $ "no back color support for terminal " ++ termName 130 Just setab -> setab 131 132 hyperlinkModeStatus <- newIORef False 133 newAssumedStateRef <- newIORef initialAssumedState 134 135 let terminfoSetMode m newStatus = do 136 curStatus <- terminfoModeStatus m 137 when (newStatus /= curStatus) $ 138 case m of 139 Hyperlink -> do 140 writeIORef hyperlinkModeStatus newStatus 141 writeIORef newAssumedStateRef initialAssumedState 142 _ -> return () 143 terminfoModeStatus m = 144 case m of 145 Hyperlink -> readIORef hyperlinkModeStatus 146 _ -> return False 147 terminfoModeSupported Hyperlink = True 148 terminfoModeSupported _ = False 149 150 terminfoCaps <- pure TerminfoCaps 151 <*> probeCap ti "smcup" 152 <*> probeCap ti "rmcup" 153 <*> requireCap ti "cup" 154 <*> probeCap ti "cnorm" 155 <*> probeCap ti "civis" 156 <*> pure noColors 157 <*> pure useAlt 158 <*> pure setForeCap 159 <*> pure set_back_cap 160 <*> requireCap ti "sgr0" 161 <*> requireCap ti "clear" 162 <*> requireCap ti "el" 163 <*> currentDisplayAttrCaps ti 164 <*> probeCap ti "bel" 165 let t = Output 166 { terminalID = termName 167 , releaseTerminal = do 168 sendCap setDefaultAttr [] 169 maybeSendCap cnorm [] 170 , supportsBell = return $ isJust $ ringBellAudio terminfoCaps 171 , supportsItalics = return $ (isJust $ enterItalic (displayAttrCaps terminfoCaps)) && 172 (isJust $ exitItalic (displayAttrCaps terminfoCaps)) 173 , supportsStrikethrough = return $ (isJust $ enterStrikethrough (displayAttrCaps terminfoCaps)) && 174 (isJust $ exitStrikethrough (displayAttrCaps terminfoCaps)) 175 , ringTerminalBell = maybeSendCap ringBellAudio [] 176 , reserveDisplay = do 177 -- If there is no support for smcup: Clear the screen 178 -- and then move the mouse to the home position to 179 -- approximate the behavior. 180 maybeSendCap smcup [] 181 sendCap clearScreen [] 182 , releaseDisplay = do 183 maybeSendCap rmcup [] 184 maybeSendCap cnorm [] 185 , setDisplayBounds = \(w, h) -> 186 setWindowSize outFd (w, h) 187 , displayBounds = do 188 rawSize <- getWindowSize outFd 189 case rawSize of 190 (w, h) | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show rawSize 191 | otherwise -> return (w,h) 192 , outputByteBuffer = \outBytes -> do 193 let (fptr, offset, len) = toForeignPtr outBytes 194 actualLen <- withForeignPtr fptr 195 $ \ptr -> fdWriteAll outFd (ptr `plusPtr` offset) len 0 196 when (toEnum len /= actualLen) $ fail $ "Graphics.Vty.Output: outputByteBuffer " 197 ++ "length mismatch. " ++ show len ++ " /= " ++ show actualLen 198 ++ " Please report this bug to vty project." 199 , contextColorCount 200 = case supportsNoColors terminfoCaps of 201 False -> case Terminfo.getCapability ti (Terminfo.tiGetNum "colors" ) of 202 Nothing -> 8 203 Just v -> toEnum v 204 True -> 1 205 , supportsCursorVisibility = isJust $ civis terminfoCaps 206 , supportsMode = terminfoModeSupported 207 , setMode = terminfoSetMode 208 , getModeStatus = terminfoModeStatus 209 , assumedStateRef = newAssumedStateRef 210 -- I think fix would help assure tActual is the only 211 -- reference. I was having issues tho. 212 , mkDisplayContext = \tActual -> terminfoDisplayContext tActual terminfoCaps 213 } 214 sendCap s = sendCapToTerminal t (s terminfoCaps) 215 maybeSendCap s = when (isJust $ s terminfoCaps) . sendCap (fromJust . s) 216 return t 217 218requireCap :: Terminfo.Terminal -> String -> IO CapExpression 219requireCap ti capName 220 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of 221 Nothing -> fail $ "Terminal does not define required capability \"" ++ capName ++ "\"" 222 Just capStr -> parseCap capStr 223 224probeCap :: Terminfo.Terminal -> String -> IO (Maybe CapExpression) 225probeCap ti capName 226 = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of 227 Nothing -> return Nothing 228 Just capStr -> Just <$> parseCap capStr 229 230parseCap :: String -> IO CapExpression 231parseCap capStr = do 232 case parseCapExpression capStr of 233 Left e -> fail $ show e 234 Right cap -> return cap 235 236currentDisplayAttrCaps :: Terminfo.Terminal -> IO DisplayAttrCaps 237currentDisplayAttrCaps ti 238 = pure DisplayAttrCaps 239 <*> probeCap ti "sgr" 240 <*> probeCap ti "smso" 241 <*> probeCap ti "rmso" 242 <*> probeCap ti "sitm" 243 <*> probeCap ti "ritm" 244 <*> probeCap ti "smxx" 245 <*> probeCap ti "rmxx" 246 <*> probeCap ti "smul" 247 <*> probeCap ti "rmul" 248 <*> probeCap ti "rev" 249 <*> probeCap ti "dim" 250 <*> probeCap ti "bold" 251 252foreign import ccall "gwinsz.h vty_c_get_window_size" c_getWindowSize :: Fd -> IO CLong 253 254getWindowSize :: Fd -> IO (Int,Int) 255getWindowSize fd = do 256 (a,b) <- (`divMod` 65536) `fmap` c_getWindowSize fd 257 return (fromIntegral b, fromIntegral a) 258 259foreign import ccall "gwinsz.h vty_c_set_window_size" c_setWindowSize :: Fd -> CLong -> IO () 260 261setWindowSize :: Fd -> (Int, Int) -> IO () 262setWindowSize fd (w, h) = do 263 let val = (h `shiftL` 16) + w 264 c_setWindowSize fd $ fromIntegral val 265 266terminfoDisplayContext :: Output -> TerminfoCaps -> DisplayRegion -> IO DisplayContext 267terminfoDisplayContext tActual terminfoCaps r = return dc 268 where dc = DisplayContext 269 { contextDevice = tActual 270 , contextRegion = r 271 , writeMoveCursor = \x y -> writeCapExpr (cup terminfoCaps) [toEnum y, toEnum x] 272 , writeShowCursor = case cnorm terminfoCaps of 273 Nothing -> error "this terminal does not support show cursor" 274 Just c -> writeCapExpr c [] 275 , writeHideCursor = case civis terminfoCaps of 276 Nothing -> error "this terminal does not support hide cursor" 277 Just c -> writeCapExpr c [] 278 , writeSetAttr = terminfoWriteSetAttr dc terminfoCaps 279 , writeDefaultAttr = \urlsEnabled -> 280 writeCapExpr (setDefaultAttr terminfoCaps) [] `mappend` 281 (if urlsEnabled then writeURLEscapes EndLink else mempty) `mappend` 282 (case exitStrikethrough $ displayAttrCaps terminfoCaps of 283 Just cap -> writeCapExpr cap [] 284 Nothing -> mempty 285 ) 286 , writeRowEnd = writeCapExpr (clearEol terminfoCaps) [] 287 , inlineHack = return () 288 } 289 290-- | Write the escape sequences that are used in some terminals to 291-- include embedded hyperlinks. As of yet, this information isn't 292-- included in termcap or terminfo, so this writes them directly 293-- instead of looking up the appropriate capabilities. 294writeURLEscapes :: URLDiff -> Write 295writeURLEscapes (LinkTo url) = 296 foldMap writeStorable (BS.unpack "\x1b]8;;") `mappend` 297 foldMap writeStorable (BS.unpack url) `mappend` 298 writeStorable (0x07 :: Word8) 299writeURLEscapes EndLink = 300 foldMap writeStorable (BS.unpack "\x1b]8;;\a") 301writeURLEscapes NoLinkChange = 302 mempty 303 304-- | Portably setting the display attributes is a giant pain in the ass. 305-- 306-- If the terminal supports the sgr capability (which sets the on/off 307-- state of each style directly ; and, for no good reason, resets the 308-- colors to the default) this procedure is used: 309-- 310-- 0. set the style attributes. This resets the fore and back color. 311-- 312-- 1, If a foreground color is to be set then set the foreground color 313-- 314-- 2. likewise with the background color 315-- 316-- If the terminal does not support the sgr cap then: if there is a 317-- change from an applied color to the default (in either the fore or 318-- back color) then: 319-- 320-- 0. reset all display attributes (sgr0) 321-- 322-- 1. enter required style modes 323-- 324-- 2. set the fore color if required 325-- 326-- 3. set the back color if required 327-- 328-- Entering the required style modes could require a reset of the 329-- display attributes. If this is the case then the back and fore colors 330-- always need to be set if not default. 331-- 332-- This equation implements the above logic. 333-- 334-- Note that this assumes the removal of color changes in the 335-- display attributes is done as expected with noColors == True. See 336-- `limitAttrForDisplay`. 337-- 338-- Note that this optimizes for fewer state changes followed by fewer 339-- bytes. 340terminfoWriteSetAttr :: DisplayContext -> TerminfoCaps -> Bool -> FixedAttr -> Attr -> DisplayAttrDiff -> Write 341terminfoWriteSetAttr dc terminfoCaps urlsEnabled prevAttr reqAttr diffs = 342 urlAttrs urlsEnabled `mappend` case (foreColorDiff diffs == ColorToDefault) || (backColorDiff diffs == ColorToDefault) of 343 -- The only way to reset either color, portably, to the default 344 -- is to use either the set state capability or the set default 345 -- capability. 346 True -> do 347 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps) 348 (fixedStyle attr ) 349 (styleToApplySeq $ fixedStyle attr) of 350 -- only way to reset a color to the defaults 351 EnterExitSeq caps -> writeDefaultAttr dc urlsEnabled 352 `mappend` 353 foldMap (\cap -> writeCapExpr cap []) caps 354 `mappend` 355 setColors 356 -- implicitly resets the colors to the defaults 357 SetState state -> writeCapExpr (fromJust $ setAttrStates 358 $ displayAttrCaps 359 $ terminfoCaps 360 ) 361 (sgrArgsForState state) 362 `mappend` setItalics 363 `mappend` setStrikethrough 364 `mappend` setColors 365 -- Otherwise the display colors are not changing or changing 366 -- between two non-default points. 367 False -> do 368 -- Still, it could be the case that the change in display 369 -- attributes requires the colors to be reset because the 370 -- required capability was not available. 371 case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps) 372 (fixedStyle attr) 373 (styleDiffs diffs) of 374 -- Really, if terminals were re-implemented with modern 375 -- concepts instead of bowing down to 40 yr old dumb 376 -- terminal requirements this would be the only case 377 -- ever reached! Changes the style and color states 378 -- according to the differences with the currently 379 -- applied states. 380 EnterExitSeq caps -> foldMap (\cap -> writeCapExpr cap []) caps 381 `mappend` 382 writeColorDiff setForeColor (foreColorDiff diffs) 383 `mappend` 384 writeColorDiff setBackColor (backColorDiff diffs) 385 -- implicitly resets the colors to the defaults 386 SetState state -> writeCapExpr (fromJust $ setAttrStates 387 $ displayAttrCaps terminfoCaps 388 ) 389 (sgrArgsForState state) 390 `mappend` setItalics 391 `mappend` setStrikethrough 392 `mappend` setColors 393 where 394 urlAttrs True = writeURLEscapes (urlDiff diffs) 395 urlAttrs False = mempty 396 colorMap = case useAltColorMap terminfoCaps of 397 False -> ansiColorIndex 398 True -> altColorIndex 399 attr = fixDisplayAttr prevAttr reqAttr 400 401 -- italics can't be set via SGR, so here we manually 402 -- apply the enter and exit sequences as needed after 403 -- changing the SGR 404 setItalics 405 | hasStyle (fixedStyle attr) italic 406 , Just sitm <- enterItalic (displayAttrCaps terminfoCaps) 407 = writeCapExpr sitm [] 408 | otherwise = mempty 409 setStrikethrough 410 | hasStyle (fixedStyle attr) strikethrough 411 , Just smxx <- enterStrikethrough (displayAttrCaps terminfoCaps) 412 = writeCapExpr smxx [] 413 | otherwise = mempty 414 setColors = 415 (case fixedForeColor attr of 416 Just c -> writeCapExpr (setForeColor terminfoCaps) 417 [toEnum $ colorMap c] 418 Nothing -> mempty) 419 `mappend` 420 (case fixedBackColor attr of 421 Just c -> writeCapExpr (setBackColor terminfoCaps) 422 [toEnum $ colorMap c] 423 Nothing -> mempty) 424 writeColorDiff _f NoColorChange 425 = mempty 426 writeColorDiff _f ColorToDefault 427 = error "ColorToDefault is not a possible case for applyColorDiffs" 428 writeColorDiff f (SetColor c) 429 = writeCapExpr (f terminfoCaps) [toEnum $ colorMap c] 430 431-- | The color table used by a terminal is a 16 color set followed by a 432-- 240 color set that might not be supported by the terminal. 433-- 434-- This takes a Color which clearly identifies which pallete to use and 435-- computes the index into the full 256 color pallete. 436ansiColorIndex :: Color -> Int 437ansiColorIndex (ISOColor v) = fromEnum v 438ansiColorIndex (Color240 v) = 16 + fromEnum v 439 440-- | For terminals without setaf/setab 441-- 442-- See table in `man terminfo` 443-- Will error if not in table. 444altColorIndex :: Color -> Int 445altColorIndex (ISOColor 0) = 0 446altColorIndex (ISOColor 1) = 4 447altColorIndex (ISOColor 2) = 2 448altColorIndex (ISOColor 3) = 6 449altColorIndex (ISOColor 4) = 1 450altColorIndex (ISOColor 5) = 5 451altColorIndex (ISOColor 6) = 3 452altColorIndex (ISOColor 7) = 7 453altColorIndex (ISOColor v) = fromEnum v 454altColorIndex (Color240 v) = 16 + fromEnum v 455 456{- | The sequence of terminfo caps to apply a given style are determined 457 - according to these rules. 458 - 459 - 1. The assumption is that it's preferable to use the simpler 460 - enter/exit mode capabilities than the full set display attribute 461 - state capability. 462 - 463 - 2. If a mode is supposed to be removed but there is not an exit 464 - capability defined then the display attributes are reset to defaults 465 - then the display attribute state is set. 466 - 467 - 3. If a mode is supposed to be applied but there is not an enter 468 - capability defined then then display attribute state is set if 469 - possible. Otherwise the mode is not applied. 470 - 471 - 4. If the display attribute state is being set then just update the 472 - arguments to that for any apply/remove. 473 -} 474data DisplayAttrSeq 475 = EnterExitSeq [CapExpression] 476 | SetState DisplayAttrState 477 478data DisplayAttrState = DisplayAttrState 479 { applyStandout :: Bool 480 , applyUnderline :: Bool 481 , applyItalic :: Bool 482 , applyStrikethrough :: Bool 483 , applyReverseVideo :: Bool 484 , applyBlink :: Bool 485 , applyDim :: Bool 486 , applyBold :: Bool 487 } 488 489sgrArgsForState :: DisplayAttrState -> [CapParam] 490sgrArgsForState attrState = map (\b -> if b then 1 else 0) 491 [ applyStandout attrState 492 , applyUnderline attrState 493 , applyReverseVideo attrState 494 , applyBlink attrState 495 , applyDim attrState 496 , applyBold attrState 497 , False -- invis 498 , False -- protect 499 , False -- alt char set 500 ] 501 502reqDisplayCapSeqFor :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq 503reqDisplayCapSeqFor caps s diffs 504 -- if the state transition implied by any diff cannot be supported 505 -- with an enter/exit mode cap then either the state needs to be set 506 -- or the attribute change ignored. 507 = case (any noEnterExitCap diffs, isJust $ setAttrStates caps) of 508 -- If all the diffs have an enter-exit cap then just use those 509 ( False, _ ) -> EnterExitSeq $ map enterExitCap diffs 510 -- If not all the diffs have an enter-exit cap and there is no 511 -- set state cap then filter out all unsupported diffs and just 512 -- apply the rest 513 ( True, False ) -> EnterExitSeq $ map enterExitCap 514 $ filter (not . noEnterExitCap) diffs 515 -- if not all the diffs have an enter-exit can and there is a 516 -- set state cap then just use the set state cap. 517 ( True, True ) -> SetState $ stateForStyle s 518 where 519 noEnterExitCap ApplyStrikethrough = isNothing $ enterStrikethrough caps 520 noEnterExitCap RemoveStrikethrough = isNothing $ exitStrikethrough caps 521 noEnterExitCap ApplyItalic = isNothing $ enterItalic caps 522 noEnterExitCap RemoveItalic = isNothing $ exitItalic caps 523 noEnterExitCap ApplyStandout = isNothing $ enterStandout caps 524 noEnterExitCap RemoveStandout = isNothing $ exitStandout caps 525 noEnterExitCap ApplyUnderline = isNothing $ enterUnderline caps 526 noEnterExitCap RemoveUnderline = isNothing $ exitUnderline caps 527 noEnterExitCap ApplyReverseVideo = isNothing $ enterReverseVideo caps 528 noEnterExitCap RemoveReverseVideo = True 529 noEnterExitCap ApplyBlink = True 530 noEnterExitCap RemoveBlink = True 531 noEnterExitCap ApplyDim = isNothing $ enterDimMode caps 532 noEnterExitCap RemoveDim = True 533 noEnterExitCap ApplyBold = isNothing $ enterBoldMode caps 534 noEnterExitCap RemoveBold = True 535 enterExitCap ApplyStrikethrough = fromJust $ enterStrikethrough caps 536 enterExitCap RemoveStrikethrough = fromJust $ exitStrikethrough caps 537 enterExitCap ApplyItalic = fromJust $ enterItalic caps 538 enterExitCap RemoveItalic = fromJust $ exitItalic caps 539 enterExitCap ApplyStandout = fromJust $ enterStandout caps 540 enterExitCap RemoveStandout = fromJust $ exitStandout caps 541 enterExitCap ApplyUnderline = fromJust $ enterUnderline caps 542 enterExitCap RemoveUnderline = fromJust $ exitUnderline caps 543 enterExitCap ApplyReverseVideo = fromJust $ enterReverseVideo caps 544 enterExitCap ApplyDim = fromJust $ enterDimMode caps 545 enterExitCap ApplyBold = fromJust $ enterBoldMode caps 546 enterExitCap _ = error "enterExitCap applied to diff that was known not to have one." 547 548stateForStyle :: Style -> DisplayAttrState 549stateForStyle s = DisplayAttrState 550 { applyStandout = isStyleSet standout 551 , applyUnderline = isStyleSet underline 552 , applyItalic = isStyleSet italic 553 , applyStrikethrough = isStyleSet strikethrough 554 , applyReverseVideo = isStyleSet reverseVideo 555 , applyBlink = isStyleSet blink 556 , applyDim = isStyleSet dim 557 , applyBold = isStyleSet bold 558 } 559 where isStyleSet = hasStyle s 560 561styleToApplySeq :: Style -> [StyleStateChange] 562styleToApplySeq s = concat 563 [ applyIfRequired ApplyStandout standout 564 , applyIfRequired ApplyUnderline underline 565 , applyIfRequired ApplyItalic italic 566 , applyIfRequired ApplyStrikethrough strikethrough 567 , applyIfRequired ApplyReverseVideo reverseVideo 568 , applyIfRequired ApplyBlink blink 569 , applyIfRequired ApplyDim dim 570 , applyIfRequired ApplyBold bold 571 ] 572 where 573 applyIfRequired op flag 574 = if 0 == (flag .&. s) 575 then [] 576 else [op] 577