packages feed

vty 4.7.5 → 5.0.0

raw patch · 91 files changed

+5945/−5098 lines, 91 filesdep +HUnitdep +blaze-builderdep +data-defaultdep −ghc-primdep ~Cabaldep ~basenew-component:exe:vty-demo

Dependencies added: HUnit, blaze-builder, data-default, directory, filepath, hashable, lens, quickcheck-assertions, smallcheck, test-framework, test-framework-hunit, test-framework-smallcheck, text, transformers, vty

Dependencies removed: ghc-prim

Dependency ranges changed: Cabal, base

Files

CHANGELOG view
@@ -1,5 +1,67 @@-4.7.4-  * backported rgb_color from 5.0. Maps from 8 bit integer RGB to Color+5.0.0+    * The naming convention now matches:+        * http://www.haskell.org/haskellwiki/Programming_guidelines#Naming_Conventions+    * all projects using vty for input must be compiled with -threaded. Please notify vty author if+      this is not acceptable.+    * mkVtyEscDelay has been removed. Use "mkVty def". Which initialized vty with the default+      configuration.+    * input handling changes+        * KASCII is now KChar+        * KPN5 is now KCenter+        * tests exist.+        * Applications can add to the input tables by setting inputMap of the Config.+          See Graphics.Vty.Config+        * Users can define input table extensions that will apply to all vty applications.+          See Graphics.Vty.Config+        * terminal timing is now handled by selecting an appropriate VTIME. Previously this was+          implemented within Vty itself. This reduced complexity in vty but provides a different+          meta key behavior and implies a requirement on -threaded.+        * The time vty will wait to verify an ESC byte means a single ESC key is the+          singleEscPeriod of the Input Config structure.+    * removed the typeclass based terminal and display context interface in favor of a data+      structure of properties interface.+    * renamed the Terminal interface to Output+    * The default picture for an image now uses the "clear" background. This background fills+      background spans with spaces or just ends the line.+        * Previously the background defaulted to the space character. This causes issues copying+          text from a text editor. The text would end up with extra spaces at the end of the line.+    * Layer support+        * Each layer is an image.+        * The layers for a picture are a list of images.+        * The first image is the top-most layer. The images are ordered from top to bottom.+        * The transparent areas for a layer are the backgroundFill areas. backgroundFill is+          added to pad images when images of different sizes are joined.+        * If the background is clear there is no background layer.+        * If there is a background character then the bottom layer is the background layer.+        * emptyPicture is a Picture with no layers and no cursor+        * addToTop and addToBottom add a layer to the top and bottom of the given Picture.+    * compatibility improvements:+        * terminfo based terminals with no cursor support are silently accepted. The cursor+          visibility changes in the Picture will have no effect.+        * alternate (setf/setb) color maps supported. Though colors beyond the first 8 are just a+          guess.+        * added "rgbColor" for easy support of RGB specified colors.+        * Both applications and users can add to the mapping used to translate from input bytes to+          events.+    * Additional information about input and output process can be appended to a debug log+        * Set environment variable VTY_DEBUG_LOG to path of debug log+        * Or use "debugLog <path>" config directive+        * Or set 'debugLog' property of the Config provided to mkVty.+    * examples moved to vty-examples package. See test directory for cabal file.+        * vty-interactive-terminal-test+            * interactive test. Useful for building a bug report for vty's author.+            * test/interactive_terminal_test.hs+        * vty-event-echo+            * view a input event log for vty. Example of interacting with user.+            * test/EventEcho.hs+        * vty-rouge+            * The start of a rouge-like game. Example of layers and image build operations.+            * test/Rouge.hs+        * vty-benchmark+            * benchmarks vty. A series of tests that push random pictures to the terminal. The+              random pictures are generated using QuickCheck. The same generators used in the+              automated tests.+            * test/benchmark.hs  4.7.0.0     * API changes:
+ Demo.hs view
@@ -0,0 +1,80 @@+module Main where++import Graphics.Vty++import Control.Applicative hiding ((<|>))+import Control.Arrow+import Control.Monad.RWS++import Data.Default (def)+import Data.Sequence (Seq, (<|) )+import qualified Data.Sequence as Seq+import Data.Foldable++eventBufferSize = 1000++type App = RWST Vty () (Seq String) IO++main = do+    vty <- mkVty def+    _ <- execRWST (vtyInteract False) vty Seq.empty+    shutdown vty++vtyInteract :: Bool -> App ()+vtyInteract shouldExit = do+    updateDisplay+    unless shouldExit $ handleNextEvent >>= vtyInteract++introText = vertCat $ map (string defAttr)+    [ "this line is hidden by the top layer"+    , "The vty demo program will echo the events generated by the pressed keys."+    , "Below there is a 240 color box."+    , "Followed by a description of the 16 color pallete."+    , "If the 240 color box is not visible then the terminal"+    , "claims 240 colors are not supported."+    , "Try setting TERM to xterm-256color"+    , "This text is on a lower layer than the event list."+    , "Which means it'll be hidden soon."+    , "Bye!"+    , "Great Faith in the ¯\\_(ツ)_/¯"+    , "¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯ ¯\\_(ツ)_/¯"+    ]++colorbox_240 :: Image+colorbox_240 = vertCat $ map horizCat $ splitColorImages colorImages+    where+        colorImages = map (\i -> string (currentAttr `withBackColor` Color240 i) " ") [0..239]+        splitColorImages [] = []+        splitColorImages is = (take 20 is ++ [string defAttr " "]) : (splitColorImages (drop 20 is))++colorbox_16 :: Image+colorbox_16 = border <|> column0 <|> border <|> column1 <|> border <|> column2 <|> border+    where+        column0 = vertCat $ map lineWithColor normal+        column1 = vertCat $ map lineWithColor bright+        border = vertCat $ replicate (length normal) $ string defAttr " | "+        column2 = vertCat $ map (string defAttr . snd) normal+        lineWithColor (c, cName) = string (defAttr `withForeColor` c) cName+        normal = zip [ black, red, green, yellow, blue, magenta, cyan, white ]+                     [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]+        bright = zip [ brightBlack, brightRed, brightGreen, brightYellow, brightBlue+                     , brightMagenta, brightCyan, brightWhite ]+                     [ "bright black", "bright red", "bright green", "bright yellow"+                     , "bright blue", "bright magenta", "bright cyan", "bright white" ]++updateDisplay :: App ()+updateDisplay = do+    let info = string (defAttr `withForeColor` black `withBackColor` green)+                      "Press ESC to exit. Events for keys below."+    eventLog <- foldMap (string defAttr) <$> get+    let pic = picForImage (info <-> eventLog)+              `addToBottom` (introText <-> colorbox_240 <|> colorbox_16)+    vty <- ask+    liftIO $ update vty pic++handleNextEvent = ask >>= liftIO . nextEvent >>= handleEvent+    where+        handleEvent e               = do+            modify $ (<|) (show e) >>> Seq.take eventBufferSize+            return $ e == EvKey KEsc []+
− README
@@ -1,50 +0,0 @@-vty is a terminal interface library.--Vty currently provides:--* Automatic handling of window resizes.--* Supports Unicode characters on output, automatically setting and-  resetting UTF-8 mode for xterm. Other terminals are assumed to support --* Efficient output. --* Minimizes repaint area, thus virtually eliminating the flicker-  problem that plagues ncurses programs.--* A pure, compositional interface for efficiently constructing display-  images.--* Automatically decodes keyboard keys into (key,[modifier]) tuples.--* Automatically supports refresh on Ctrl-L.--* Automatically supports timeout after 50ms for lone ESC (a barely-  noticable delay)--* Interface is designed for relatively easy compatible extension.--* Supports all ANSI SGR-modes (defined in console_codes(4)) with-  a type-safe interface. --* Properly handles cleanup.--Current disadvantages:--* The character encoding of the output terminal is assumed to be UTF-8.--* Minimal support for special keys on terminals other than the-  linux-console.  (F1-5 and arrow keys should work, but anything-  shifted isn't likely to.)--* Uses the TIOCGWINSZ ioctl to find the current window size, which-  appears to be limited to Linux and *BSD.--Project is hosted on github.com: https://github.com/coreyoconnor/vty--git clone git://github.com/coreyoconnor/vty.git--To compile the demonstration program: ghc --make test/Test.hs gwinsz.c--The main documentation consists of the haddock-comments and the demonstration-program
+ README.md view
@@ -0,0 +1,79 @@+vty is a terminal interface library.++Project is hosted on github.com: https://github.com/coreyoconnor/vty++git clone git://github.com/coreyoconnor/vty.git++# Features++* Automatic handling of window resizes.++* Supports Unicode characters on output, automatically setting and+  resetting UTF-8 mode for xterm. Other terminals are assumed to support ++* Efficient output. ++* Minimizes repaint area, thus virtually eliminating the flicker+  problem that plagues ncurses programs.++* A pure, compositional interface for efficiently constructing display+  images.++* Automatically decodes keyboard keys into (key,[modifier]) tuples.++* Automatically supports refresh on Ctrl-L.++* Automatically supports timeout after 50ms for lone ESC (a barely+  noticable delay)++* Interface is designed for relatively easy compatible extension.++* Supports all ANSI SGR-modes (defined in console_codes(4)) with+  a type-safe interface. ++* Properly handles cleanup, but not due to signals.++# Known Issues++* Signal handling of STOP, TERM and INT are non existent.++* The character encoding of the terminal is assumed to be UTF-8.++* Terminfo is assumed to be correct unless the terminal (as declared by TERM) starts with xterm or+  ansi. This means that some terminals will not have correct special key support (shifted F10 etc)++* Uses the TIOCGWINSZ ioctl to find the current window size, which+  appears to be limited to Linux and *BSD.++# Platform Support++## Posix Terminals++Uses terminfo to determine terminal protocol. Some special rules for Mac terminal applications. The+special rules might be invalid on newer Mac OS.++## Windows++None!++# Development Notes++## Coverage++Profiling appears to cause issues with coverage when enabled. To evaluate coverage configure as+follows:++~~~+rm -rf dist ; cabal configure --enable-tests --enable-library-coverage \+  --disable-library-profiling \+  --disable-executable-profiling+~~~++## Profiling+++~~~+rm -rf dist ; cabal configure --enable-tests --disable-library-coverage \+  --enable-library-profiling \+  --enable-executable-profiling+~~~
cbits/gwinsz.c view
@@ -1,8 +1,8 @@ #include <sys/ioctl.h> -unsigned long vty_c_get_window_size(void) {+unsigned long vty_c_get_window_size(int fd) { 	struct winsize w;-	if (ioctl (0, TIOCGWINSZ, &w) >= 0)+	if (ioctl (fd, TIOCGWINSZ, &w) >= 0) 		return (w.ws_row << 16) + w.ws_col; 	else 		return 0x190050;
cbits/set_term_timing.c view
@@ -3,12 +3,11 @@ #include <unistd.h> #include <stdlib.h> -void vty_set_term_timing(void)+void vty_set_term_timing(int fd, int vmin, int vtime) {     struct termios trm;-    tcgetattr(STDIN_FILENO, &trm);-    trm.c_cc[VMIN] = 0;-    trm.c_cc[VTIME] = 0;-    tcsetattr(STDIN_FILENO, TCSANOW, &trm); +    tcgetattr(fd, &trm);+    trm.c_cc[VMIN] = vmin;+    trm.c_cc[VTIME] = vtime;+    tcsetattr(fd, TCSANOW, &trm); }-
+ dist/build/verify-attribute-opsStub/verify-attribute-opsStub-tmp/verify-attribute-opsStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyAttributeOps ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-crop-span-generationStub/verify-crop-span-generationStub-tmp/verify-crop-span-generationStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyCropSpanGeneration ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-display-attributesStub/verify-display-attributesStub-tmp/verify-display-attributesStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyDisplayAttributes ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-empty-image-propsStub/verify-empty-image-propsStub-tmp/verify-empty-image-propsStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyEmptyImageProps ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-eval-terminfo-capsStub/verify-eval-terminfo-capsStub-tmp/verify-eval-terminfo-capsStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyEvalTerminfoCaps ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-image-opsStub/verify-image-opsStub-tmp/verify-image-opsStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyImageOps ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-image-transStub/verify-image-transStub-tmp/verify-image-transStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyImageTrans ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-inlineStub/verify-inlineStub-tmp/verify-inlineStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyInline ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-layers-span-generationStub/verify-layers-span-generationStub-tmp/verify-layers-span-generationStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyLayersSpanGeneration ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-parse-terminfo-capsStub/verify-parse-terminfo-capsStub-tmp/verify-parse-terminfo-capsStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyParseTerminfoCaps ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-simple-span-generationStub/verify-simple-span-generationStub-tmp/verify-simple-span-generationStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifySimpleSpanGeneration ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-terminalStub/verify-terminalStub-tmp/verify-terminalStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyOutput ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-using-mock-terminalStub/verify-using-mock-terminalStub-tmp/verify-using-mock-terminalStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyUsingMockTerminal ( tests )+main :: IO ()+main = stubMain tests
+ dist/build/verify-utf8-widthStub/verify-utf8-widthStub-tmp/verify-utf8-widthStub.hs view
@@ -0,0 +1,5 @@+module Main ( main ) where+import Distribution.Simple.Test.LibV09 ( stubMain )+import VerifyUtf8Width ( tests )+main :: IO ()+main = stubMain tests
+ src/Codec/Binary/UTF8/Debug.hs view
@@ -0,0 +1,18 @@+-- Copyright 2009 Corey O'Connor+module Codec.Binary.UTF8.Debug +    where++import Codec.Binary.UTF8.String ( encode )++import Data.Word++import Numeric++-- | Converts an array of ISO-10646 characters (Char type) to an array of Word8 bytes that is the+-- corresponding UTF8 byte sequence+utf8FromIso :: [Int] -> [Word8]+utf8FromIso = encode . map toEnum++ppUtf8 :: [Int] -> IO ()+ppUtf8 = print . map (\f -> f "") . map showHex . utf8FromIso+
− src/Codec/Binary/UTF8/Width.hs
@@ -1,33 +0,0 @@--- Copyright 2009 Corey O'Connor-{-# OPTIONS_GHC -D_XOPEN_SOURCE -fno-cse #-}-{-# LANGUAGE ForeignFunctionInterface, BangPatterns #-}-module Codec.Binary.UTF8.Width ( wcwidth-                               , wcswidth-                               )-    where--import Foreign.C.Types-import Foreign.C.String-import Foreign.Storable-import Foreign.Ptr--import System.IO.Unsafe--wcwidth :: Char -> Int-wcwidth c = unsafePerformIO (withCWString [c] $! \ws -> do-    wc <- peek ws-    let !w = fromIntegral $! wcwidth' wc-    return w-    )-{-# NOINLINE wcwidth #-}--foreign import ccall unsafe "vty_mk_wcwidth" wcwidth' :: CWchar -> CInt--wcswidth :: String -> Int-wcswidth str = unsafePerformIO (withCWStringLen str $! \(ws, ws_len) -> do-    let !w = fromIntegral $! wcswidth' ws (fromIntegral ws_len)-    return w-    )-{-# NOINLINE wcswidth #-}--foreign import ccall unsafe "vty_mk_wcswidth" wcswidth' :: Ptr CWchar -> CSize -> CInt
− src/Data/Marshalling.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE BangPatterns #-}--- Copyright 2009 Corey O'Connor-module Data.Marshalling ( module Data.Marshalling-                        , module Data.Word-                        , module Foreign.Ptr-                        , module Foreign.ForeignPtr-                        , module Foreign.Marshal-                        , module Foreign.Storable-                        )-    where--import Control.Monad.Trans--import Data.Word--import Foreign.Ptr-import Foreign.ForeignPtr-import Foreign.Marshal-import Foreign.Storable--type OutputBuffer = Ptr Word8--string_to_bytes :: String -> [Word8]-string_to_bytes str = map (toEnum . fromEnum) str--serialize_bytes :: MonadIO m => [Word8] -> OutputBuffer -> m OutputBuffer-serialize_bytes bytes !out_ptr = do-    liftIO $! pokeArray out_ptr bytes-    return $! out_ptr `plusPtr` ( length bytes )-
src/Data/Terminfo/Eval.hs view
@@ -1,249 +1,130 @@-{-# LANGUAGE MagicHash #-} {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE NamedFieldPuns #-} {-# OPTIONS_GHC -funbox-strict-fields #-} {- Evaluates the paramaterized terminfo string capability with the given parameters.  -- - todo: This can be greatly simplified.  -}-module Data.Terminfo.Eval ( cap_expression_required_bytes-                          , serialize_cap_expression-                          )+module Data.Terminfo.Eval (writeCapExpr)     where -import Data.ByteString.Internal ( memcpy ) -import Data.Marshalling+import Blaze.ByteString.Builder.Word+import Blaze.ByteString.Builder import Data.Terminfo.Parse  import Control.Monad.Identity import Control.Monad.State.Strict+import Control.Monad.Writer -import Data.Bits ( (.|.), (.&.), xor )+import Data.Bits ((.|.), (.&.), xor) import Data.List +import Data.Word  -import GHC.Prim-import GHC.Word+import qualified Data.Vector.Unboxed as Vector  -- | capability evaluator state data EvalState = EvalState-    { eval_stack :: ![ CapParam ]-    , eval_expression :: !CapExpression-    , eval_params :: ![ CapParam ]+    { evalStack :: ![CapParam]+    , evalExpression :: !CapExpression+    , evalParams :: ![CapParam]     } -type EvalT m a = StateT EvalState m a-type Eval a = EvalT Identity a+type Eval a = StateT EvalState (Writer Write) a -{-# SPECIALIZE pop :: EvalT IO CapParam #-}-pop :: Monad m => EvalT m CapParam+pop :: Eval CapParam pop = do     s <- get-    let v : stack' = eval_stack s-        s' = s { eval_stack = stack' }+    let v : stack' = evalStack s+        s' = s { evalStack = stack' }     put s'     return v -{-# SPECIALIZE read_param :: Word -> EvalT IO CapParam #-}-read_param :: Monad m => Word -> EvalT m CapParam-read_param pn = do-    !params <- get >>= return . eval_params+readParam :: Word -> Eval CapParam+readParam pn = do+    !params <- get >>= return . evalParams     return $! genericIndex params pn -{-# SPECIALIZE push :: CapParam -> EvalT IO () #-}-push :: Monad m => CapParam -> EvalT m ()+push :: CapParam -> Eval () push !v = do     s <- get-    let s' = s { eval_stack = v : eval_stack s }+    let s' = s { evalStack = v : evalStack s }     put s' -apply_param_ops :: CapExpression -> [CapParam] -> [CapParam]-apply_param_ops cap params = foldl apply_param_op params (param_ops cap)--apply_param_op :: [CapParam] -> ParamOp -> [CapParam]-apply_param_op params IncFirstTwo = map (+ 1) params--cap_expression_required_bytes :: CapExpression -> [CapParam] -> Word-cap_expression_required_bytes cap params = -    let params' = apply_param_ops cap params-        s_0 = EvalState [] cap params'-    in fst $! runIdentity $! runStateT ( cap_ops_required_bytes $! cap_ops cap ) s_0--cap_ops_required_bytes :: CapOps -> Eval Word-cap_ops_required_bytes ops = do-    counts <- mapM cap_op_required_bytes ops-    return $ sum counts+applyParamOps :: CapExpression -> [CapParam] -> [CapParam]+applyParamOps cap params = foldl applyParamOp params (paramOps cap) -cap_op_required_bytes :: CapOp -> Eval Word-cap_op_required_bytes (Bytes _ _ c) = return $ toEnum c-cap_op_required_bytes DecOut = do-    p <- pop-    return $ toEnum $ length $ show p-cap_op_required_bytes CharOut = do-    _ <- pop-    return 1-cap_op_required_bytes (PushParam pn) = do-    read_param pn >>= push-    return 0-cap_op_required_bytes (PushValue v) = do-    push v-    return 0-cap_op_required_bytes (Conditional expr parts) = do-    c_expr <- cap_ops_required_bytes expr-    c_parts <- cond_parts_required_bytes parts-    return $ c_expr + c_parts-    where -        cond_parts_required_bytes [] = return 0-        cond_parts_required_bytes ( (true_ops, false_ops) : false_parts ) = do-            -- (man 5 terminfo)-            -- Usually the %? expr part pushes a value onto the stack, and %t pops  it  from  the-            -- stack, testing if it is nonzero (true).  If it is zero (false), control-            -- passes to the %e (else) part.-            v <- pop-            c_total <- if v /= 0-                        then cap_ops_required_bytes true_ops-                        else do-                            c_false <- cap_ops_required_bytes false_ops-                            c_remain <- cond_parts_required_bytes false_parts-                            return $ c_false + c_remain-            return c_total-cap_op_required_bytes BitwiseOr = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 .|. v_1-    return 0-cap_op_required_bytes BitwiseAnd = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 .&. v_1-    return 0-cap_op_required_bytes BitwiseXOr = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 `xor` v_1-    return 0-cap_op_required_bytes ArithPlus = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 + v_1-    return 0-cap_op_required_bytes ArithMinus = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 - v_1-    return 0-cap_op_required_bytes CompareEq = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 == v_1 then 1 else 0-    return 0-cap_op_required_bytes CompareLt = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 < v_1 then 1 else 0-    return 0-cap_op_required_bytes CompareGt = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 > v_1 then 1 else 0-    return 0+applyParamOp :: [CapParam] -> ParamOp -> [CapParam]+applyParamOp params IncFirstTwo = map (+ 1) params -serialize_cap_expression :: CapExpression -> [CapParam] -> OutputBuffer -> IO OutputBuffer-serialize_cap_expression cap params out_ptr = do-    let params' = apply_param_ops cap params-        s_0 = EvalState [] cap params'-    (!out_ptr', _) <- runStateT ( serialize_cap_ops out_ptr (cap_ops cap) ) s_0-    return $! out_ptr'+writeCapExpr :: CapExpression -> [CapParam] -> Write+writeCapExpr cap params =+    let params' = applyParamOps cap params+        s0 = EvalState [] cap params'+    in snd $ runWriter (runStateT (writeCapOps (capOps cap)) s0) -serialize_cap_ops :: OutputBuffer -> CapOps -> EvalT IO OutputBuffer-serialize_cap_ops out_ptr ops = foldM serialize_cap_op out_ptr ops+writeCapOps :: CapOps -> Eval ()+writeCapOps ops = mapM_ writeCapOp ops -serialize_cap_op :: OutputBuffer -> CapOp -> EvalT IO OutputBuffer-serialize_cap_op !out_ptr ( Bytes !offset !byte_count !next_offset ) = do-    !cap <- get >>= return . eval_expression-    let ( !start_ptr, _ ) = cap_bytes cap-        !src_ptr = start_ptr `plusPtr` offset-        !out_ptr' = out_ptr `plusPtr` next_offset-    liftIO $! memcpy out_ptr src_ptr (fromIntegral byte_count)-    return $! out_ptr'-serialize_cap_op out_ptr DecOut = do+writeCapOp :: CapOp -> Eval ()+writeCapOp (Bytes !offset !count) = do+    !cap <- get >>= return . evalExpression+    let bytes = Vector.take count $ Vector.drop offset (capBytes cap)+    Vector.forM_ bytes $ tell.writeWord8+writeCapOp DecOut = do     p <- pop-    let out_str = show p-        out_bytes = string_to_bytes out_str-    serialize_bytes out_bytes out_ptr-serialize_cap_op out_ptr CharOut = do-    W# p <- pop-    -- XXX Truncate the character value to a single byte?-    let !out_byte = W8# (and# p 0xFF##)-        !out_ptr' = out_ptr `plusPtr` 1-    liftIO $ poke out_ptr out_byte-    return out_ptr'-serialize_cap_op out_ptr (PushParam pn) = do-    read_param pn >>= push-    return out_ptr-serialize_cap_op out_ptr (PushValue v) = do+    forM_ (show p) $ tell.writeWord8.toEnum.fromEnum+writeCapOp CharOut = do+    pop >>= tell.writeWord8.toEnum.fromEnum +writeCapOp (PushParam pn) = do+    readParam pn >>= push+writeCapOp (PushValue v) = do     push v-    return out_ptr-serialize_cap_op out_ptr (Conditional expr parts) = do-    out_ptr' <- serialize_cap_ops out_ptr expr-    out_ptr'' <- serialize_cond_parts out_ptr' parts-    return out_ptr''+writeCapOp (Conditional expr parts) = do+    writeCapOps expr+    writeContitionalParts parts     where -        serialize_cond_parts ptr [] = return ptr-        serialize_cond_parts ptr ( (true_ops, false_ops) : false_parts ) = do+        writeContitionalParts [] = return ()+        writeContitionalParts ((trueOps, falseOps) : falseParts) = do             -- (man 5 terminfo)             -- Usually the %? expr part pushes a value onto the stack, and %t pops  it  from  the             -- stack, testing if it is nonzero (true).  If it is zero (false), control             -- passes to the %e (else) part.             v <- pop-            ptr'' <- if v /= 0-                        then serialize_cap_ops ptr true_ops-                        else do-                            ptr' <- serialize_cap_ops ptr false_ops-                            serialize_cond_parts ptr' false_parts-            return ptr''+            if v /= 0+                then writeCapOps trueOps+                else do+                    writeCapOps falseOps+                    writeContitionalParts falseParts -serialize_cap_op out_ptr BitwiseOr = do-    v_0 <- pop-    v_1 <- pop-    push $ v_0 .|. v_1-    return out_ptr-serialize_cap_op out_ptr BitwiseAnd = do-    v_0 <- pop-    v_1 <- pop-    push $ v_0 .&. v_1-    return out_ptr-serialize_cap_op out_ptr BitwiseXOr = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 `xor` v_1-    return out_ptr-serialize_cap_op out_ptr ArithPlus = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 + v_1-    return out_ptr-serialize_cap_op out_ptr ArithMinus = do-    v_1 <- pop-    v_0 <- pop-    push $ v_0 - v_1-    return out_ptr-serialize_cap_op out_ptr CompareEq = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 == v_1 then 1 else 0-    return out_ptr-serialize_cap_op out_ptr CompareLt = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 < v_1 then 1 else 0-    return out_ptr-serialize_cap_op out_ptr CompareGt = do-    v_1 <- pop-    v_0 <- pop-    push $ if v_0 > v_1 then 1 else 0-    return out_ptr+writeCapOp BitwiseOr = do+    v0 <- pop+    v1 <- pop+    push $ v0 .|. v1+writeCapOp BitwiseAnd = do+    v0 <- pop+    v1 <- pop+    push $ v0 .&. v1+writeCapOp BitwiseXOr = do+    v1 <- pop+    v0 <- pop+    push $ v0 `xor` v1+writeCapOp ArithPlus = do+    v1 <- pop+    v0 <- pop+    push $ v0 + v1+writeCapOp ArithMinus = do+    v1 <- pop+    v0 <- pop+    push $ v0 - v1+writeCapOp CompareEq = do+    v1 <- pop+    v0 <- pop+    push $ if v0 == v1 then 1 else 0+writeCapOp CompareLt = do+    v1 <- pop+    v0 <- pop+    push $ if v0 < v1 then 1 else 0+writeCapOp CompareGt = do+    v1 <- pop+    v0 <- pop+    push $ if v0 > v1 then 1 else 0 
src/Data/Terminfo/Parse.hs view
@@ -1,47 +1,49 @@-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE NamedFieldPuns #-} {-# OPTIONS_GHC -funbox-strict-fields -O #-} module Data.Terminfo.Parse ( module Data.Terminfo.Parse-                           , Text.ParserCombinators.Parsec.ParseError+                           , Text.Parsec.ParseError                            )     where -import Control.Applicative ( Applicative(..), pure, (<*>) )   import Control.Monad ( liftM )-import Control.Monad.Trans import Control.DeepSeq  import Data.Monoid import Data.Word--import Foreign.C.Types-import Foreign.Marshal.Array-import Foreign.Ptr+import qualified Data.Vector.Unboxed as Vector -import Text.ParserCombinators.Parsec+import Numeric (showHex) -type CapBytes = ( Ptr Word8, CSize )+import Text.Parsec  data CapExpression = CapExpression-    { cap_ops :: !CapOps-    , cap_bytes :: !CapBytes-    , source_string :: !String-    , param_count :: !Word-    , param_ops :: !ParamOps-    }+    { capOps :: !CapOps+    , capBytes :: !(Vector.Vector Word8)+    , sourceString :: !String+    , paramCount :: !Int+    , paramOps :: !ParamOps+    } deriving (Eq) +instance Show CapExpression where+    show c+        = "CapExpression { " ++ show (capOps c) ++ " }"+        ++ " <- [" ++ hexDump ( map ( toEnum . fromEnum ) $! sourceString c ) ++ "]"+        ++ " <= " ++ show (sourceString c)+        where+            hexDump :: [Word8] -> String+            hexDump = foldr (\b s -> showHex b s) ""+ instance NFData CapExpression where-    rnf (CapExpression ops !_bytes !str !c !p_ops) -        = rnf ops `seq` rnf str `seq` rnf c `seq` rnf p_ops+    rnf (CapExpression ops !_bytes !str !c !pOps) +        = rnf ops `seq` rnf str `seq` rnf c `seq` rnf pOps  type CapParam = Word  type CapOps = [CapOp] data CapOp = -      Bytes !Int !CSize !Int+      Bytes !Int !Int -- offset count     | DecOut | CharOut     -- This stores a 0-based index to the parameter. However the operation that implies this op is     -- 1-based@@ -49,19 +51,19 @@     -- The conditional parts are the sequence of (%t expression, %e expression) pairs.     -- The %e expression may be NOP     | Conditional -      { conditional_expr :: !CapOps-      , conditional_parts :: ![(CapOps, CapOps)]+      { conditionalExpr :: !CapOps+      , conditionalParts :: ![(CapOps, CapOps)]       }     | BitwiseOr | BitwiseXOr | BitwiseAnd     | ArithPlus | ArithMinus     | CompareEq | CompareLt | CompareGt-    deriving ( Show )+    deriving (Show, Eq)  instance NFData CapOp where-    rnf (Bytes offset _count next_offset) = rnf offset `seq` rnf next_offset+    rnf (Bytes offset byteCount ) = rnf offset `seq` rnf byteCount     rnf (PushParam pn) = rnf pn     rnf (PushValue v) = rnf v -    rnf (Conditional c_expr c_parts) = rnf c_expr `seq` rnf c_parts +    rnf (Conditional cExpr cParts) = rnf cExpr `seq` rnf cParts      rnf BitwiseOr = ()     rnf BitwiseXOr = ()     rnf BitwiseAnd = ()@@ -76,266 +78,261 @@ type ParamOps = [ParamOp] data ParamOp =       IncFirstTwo-    deriving ( Show )+    deriving (Show, Eq)  instance NFData ParamOp where     rnf IncFirstTwo = () -parse_cap_expression :: ( Applicative m-                        , MonadIO m-                        )-                     => String -                     -> m ( Either ParseError CapExpression )-parse_cap_expression cap_string = -    let v = runParser cap_expression_parser-                           initial_build_state-                           "terminfo cap" -                           cap_string +parseCapExpression :: String -> Either ParseError CapExpression+parseCapExpression capString = +    let v = runParser capExpressionParser+                      initialBuildState+                      "terminfo cap" +                      capString      in case v of-        Left e -> return $ Left e-        Right build_results -> pure Right <*> construct_cap_expression cap_string build_results+        Left e -> Left e+        Right buildResults -> Right $ constructCapExpression capString buildResults -construct_cap_expression :: MonadIO m => [Char] -> BuildResults -> m CapExpression-construct_cap_expression cap_string build_results = do-    byte_array <- liftIO $ newArray (map ( toEnum . fromEnum ) cap_string )+constructCapExpression :: [Char] -> BuildResults -> CapExpression+constructCapExpression capString buildResults =     let expr = CapExpression-                { cap_ops = out_cap_ops build_results+                { capOps = outCapOps buildResults                 -- The cap bytes are the lower 8 bits of the input string's characters.                 -- \todo Verify the input string actually contains an 8bit byte per character.-                , cap_bytes = ( byte_array, toEnum $! length cap_string )-                , source_string = cap_string-                , param_count = out_param_count build_results-                , param_ops = out_param_ops build_results+                , capBytes = Vector.fromList $ map (toEnum.fromEnum) capString+                , sourceString = capString+                , paramCount = outParamCount buildResults+                , paramOps = outParamOps buildResults                 } -    return $! rnf expr `seq` expr+    in rnf expr `seq` expr -type CapParser a = GenParser Char BuildState a +type CapParser a = Parsec String BuildState a  -cap_expression_parser :: CapParser BuildResults-cap_expression_parser = do-    rs <- many $ param_escape_parser <|> bytes_op_parser +capExpressionParser :: CapParser BuildResults+capExpressionParser = do+    rs <- many $ paramEscapeParser <|> bytesOpParser      return $ mconcat rs -param_escape_parser :: CapParser BuildResults-param_escape_parser = do+paramEscapeParser :: CapParser BuildResults+paramEscapeParser = do     _ <- char '%'-    inc_offset 1-    literal_percent_parser <|> param_op_parser +    incOffset 1+    literalPercentParser <|> paramOpParser  -literal_percent_parser :: CapParser BuildResults-literal_percent_parser = do+literalPercentParser :: CapParser BuildResults+literalPercentParser = do     _ <- char '%'-    start_offset <- getState >>= return . next_offset-    inc_offset 1-    return $ BuildResults 0 [Bytes start_offset 1 1] []+    startOffset <- getState >>= return . nextOffset+    incOffset 1+    return $ BuildResults 0 [Bytes startOffset 1] [] -param_op_parser :: CapParser BuildResults-param_op_parser-    = increment_op_parser -    <|> push_op_parser-    <|> dec_out_parser-    <|> char_out_parser-    <|> conditional_op_parser-    <|> bitwise_op_parser-    <|> arith_op_parser-    <|> literal_int_op_parser-    <|> compare_op_parser-    <|> char_const_parser+paramOpParser :: CapParser BuildResults+paramOpParser+    = incrementOpParser +    <|> pushOpParser+    <|> decOutParser+    <|> charOutParser+    <|> conditionalOpParser+    <|> bitwiseOpParser+    <|> arithOpParser+    <|> literalIntOpParser+    <|> compareOpParser+    <|> charConstParser -increment_op_parser :: CapParser BuildResults-increment_op_parser = do+incrementOpParser :: CapParser BuildResults+incrementOpParser = do     _ <- char 'i'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [] [ IncFirstTwo ] -push_op_parser :: CapParser BuildResults-push_op_parser = do+pushOpParser :: CapParser BuildResults+pushOpParser = do     _ <- char 'p'-    param_n <- digit >>= return . (\d -> read [d])-    inc_offset 2-    return $ BuildResults param_n [ PushParam $ param_n - 1 ] []+    paramN <- digit >>= return . (\d -> read [d])+    incOffset 2+    return $ BuildResults (fromEnum paramN) [PushParam $ paramN - 1] [] -dec_out_parser :: CapParser BuildResults-dec_out_parser = do+decOutParser :: CapParser BuildResults+decOutParser = do     _ <- char 'd'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ DecOut ] [] -char_out_parser :: CapParser BuildResults-char_out_parser = do+charOutParser :: CapParser BuildResults+charOutParser = do     _ <- char 'c'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ CharOut ] [] -conditional_op_parser :: CapParser BuildResults-conditional_op_parser = do+conditionalOpParser :: CapParser BuildResults+conditionalOpParser = do     _ <- char '?'-    inc_offset 1-    cond_part <- many_expr conditional_true_parser-    parts <- many_p -                ( do-                    true_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser-                                                    , conditional_false_parser -                                                    ]-                    false_part <- many_expr $ choice [ try $ lookAhead conditional_end_parser-                                                     , conditional_true_parser-                                                     ]-                    return ( true_part, false_part )-                ) -                conditional_end_parser+    incOffset 1+    condPart <- manyExpr conditionalTrueParser+    parts <- manyP +             ( do+                truePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser+                                              , conditionalFalseParser +                                              ]+                falsePart <- manyExpr $ choice [ try $ lookAhead conditionalEndParser+                                               , conditionalTrueParser+                                               ]+                return ( truePart, falsePart )+             ) +             conditionalEndParser -    let true_parts = map fst parts-        false_parts = map snd parts-        BuildResults n cond cond_param_ops = cond_part+    let trueParts = map fst parts+        falseParts = map snd parts+        BuildResults n cond condParamOps = condPart -    let n' = maximum $ n : map out_param_count true_parts-        n'' = maximum $ n' : map out_param_count false_parts+    let n' = maximum $ n : map outParamCount trueParts+        n'' = maximum $ n' : map outParamCount falseParts -    let true_ops = map out_cap_ops true_parts-        false_ops = map out_cap_ops false_parts-        cond_parts = zip true_ops false_ops+    let trueOps = map outCapOps trueParts+        falseOps = map outCapOps falseParts+        condParts = zip trueOps falseOps -    let true_param_ops = mconcat $ map out_param_ops true_parts-        false_param_ops = mconcat $ map out_param_ops false_parts-        p_ops = mconcat [cond_param_ops, true_param_ops, false_param_ops]+    let trueParamOps = mconcat $ map outParamOps trueParts+        falseParamOps = mconcat $ map outParamOps falseParts+        pOps = mconcat [condParamOps, trueParamOps, falseParamOps] -    return $ BuildResults n'' [ Conditional cond cond_parts ] p_ops+    return $ BuildResults n'' [ Conditional cond condParts ] pOps      where -        many_p !p !end = choice +        manyP !p !end = choice              [ try end >> return []             , do !v <- p -                 !vs <- many_p p end+                 !vs <- manyP p end                  return $! v : vs             ]-        many_expr end = liftM mconcat $ many_p ( param_escape_parser <|> bytes_op_parser ) end+        manyExpr end = liftM mconcat $ manyP ( paramEscapeParser <|> bytesOpParser ) end -conditional_true_parser :: CapParser ()-conditional_true_parser = do+conditionalTrueParser :: CapParser ()+conditionalTrueParser = do     _ <- string "%t"-    inc_offset 2+    incOffset 2 -conditional_false_parser :: CapParser ()-conditional_false_parser = do+conditionalFalseParser :: CapParser ()+conditionalFalseParser = do     _ <- string "%e"-    inc_offset 2+    incOffset 2 -conditional_end_parser :: CapParser ()-conditional_end_parser = do+conditionalEndParser :: CapParser ()+conditionalEndParser = do     _ <- string "%;"-    inc_offset 2+    incOffset 2 -bitwise_op_parser :: CapParser BuildResults-bitwise_op_parser -    =   bitwise_or_parser-    <|> bitwise_and_parser-    <|> bitwise_xor_parser+bitwiseOpParser :: CapParser BuildResults+bitwiseOpParser +    =   bitwiseOrParser+    <|> bitwiseAndParser+    <|> bitwiseXorParser -bitwise_or_parser :: CapParser BuildResults-bitwise_or_parser = do+bitwiseOrParser :: CapParser BuildResults+bitwiseOrParser = do     _ <- char '|'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ BitwiseOr ] [ ] -bitwise_and_parser :: CapParser BuildResults-bitwise_and_parser = do+bitwiseAndParser :: CapParser BuildResults+bitwiseAndParser = do     _ <- char '&'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ BitwiseAnd ] [ ] -bitwise_xor_parser :: CapParser BuildResults-bitwise_xor_parser = do+bitwiseXorParser :: CapParser BuildResults+bitwiseXorParser = do     _ <- char '^'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ BitwiseXOr ] [ ] -arith_op_parser :: CapParser BuildResults-arith_op_parser -    =   plus_op -    <|> minus_op +arithOpParser :: CapParser BuildResults+arithOpParser +    =   plusOp +    <|> minusOp      where-        plus_op = do+        plusOp = do             _ <- char '+'-            inc_offset 1+            incOffset 1             return $ BuildResults 0 [ ArithPlus ] [ ]-        minus_op = do+        minusOp = do             _ <- char '-'-            inc_offset 1+            incOffset 1             return $ BuildResults 0 [ ArithMinus ] [ ] -literal_int_op_parser :: CapParser BuildResults-literal_int_op_parser = do+literalIntOpParser :: CapParser BuildResults+literalIntOpParser = do     _ <- char '{'-    inc_offset 1-    n_str <- many1 digit-    inc_offset $ toEnum $ length n_str-    let n :: Word = read n_str+    incOffset 1+    nStr <- many1 digit+    incOffset $ toEnum $ length nStr+    let n :: Word = read nStr     _ <- char '}'-    inc_offset 1+    incOffset 1     return $ BuildResults 0 [ PushValue n ] [ ] -compare_op_parser :: CapParser BuildResults-compare_op_parser -    =   compare_eq_op-    <|> compare_lt_op -    <|> compare_gt_op +compareOpParser :: CapParser BuildResults+compareOpParser +    =   compareEqOp+    <|> compareLtOp +    <|> compareGtOp      where-        compare_eq_op = do+        compareEqOp = do             _ <- char '='-            inc_offset 1+            incOffset 1             return $ BuildResults 0 [ CompareEq ] [ ]-        compare_lt_op = do+        compareLtOp = do             _ <- char '<'-            inc_offset 1+            incOffset 1             return $ BuildResults 0 [ CompareLt ] [ ]-        compare_gt_op = do+        compareGtOp = do             _ <- char '>'-            inc_offset 1+            incOffset 1             return $ BuildResults 0 [ CompareGt ] [ ] -bytes_op_parser :: CapParser BuildResults-bytes_op_parser = do+bytesOpParser :: CapParser BuildResults+bytesOpParser = do     bytes <- many1 $ satisfy (/= '%')-    start_offset <- getState >>= return . next_offset+    startOffset <- getState >>= return . nextOffset     let !c = length bytes     !s <- getState-    let s' = s { next_offset = start_offset + c }+    let s' = s { nextOffset = startOffset + c }     setState s'-    return $ BuildResults 0 [Bytes start_offset ( toEnum c ) c ] []+    return $ BuildResults 0 [Bytes startOffset c] [] -char_const_parser :: CapParser BuildResults-char_const_parser = do+charConstParser :: CapParser BuildResults+charConstParser = do     _ <- char '\''-    char_value <- liftM (toEnum . fromEnum) anyChar +    charValue <- liftM (toEnum . fromEnum) anyChar      _ <- char '\''-    inc_offset 3-    return $ BuildResults 0 [ PushValue char_value ] [ ]+    incOffset 3+    return $ BuildResults 0 [ PushValue charValue ] [ ]  data BuildState = BuildState -    { next_offset :: Int+    { nextOffset :: Int     }  -inc_offset :: Int -> CapParser ()-inc_offset n = do+incOffset :: Int -> CapParser ()+incOffset n = do     s <- getState-    let s' = s { next_offset = next_offset s + n }+    let s' = s { nextOffset = nextOffset s + n }     setState s' -initial_build_state :: BuildState-initial_build_state = BuildState 0+initialBuildState :: BuildState+initialBuildState = BuildState 0  data BuildResults = BuildResults-    { out_param_count :: !Word-    , out_cap_ops :: !CapOps-    , out_param_ops :: !ParamOps+    { outParamCount :: !Int+    , outCapOps :: !CapOps+    , outParamOps :: !ParamOps     }  instance Monoid BuildResults where     mempty = BuildResults 0 [] []     v0 `mappend` v1          = BuildResults-        { out_param_count = (out_param_count v0) `max` (out_param_count v1)-        , out_cap_ops = (out_cap_ops v0) `mappend` (out_cap_ops v1)-        , out_param_ops = (out_param_ops v0) `mappend` (out_param_ops v1)+        { outParamCount = (outParamCount v0) `max` (outParamCount v1)+        , outCapOps = (outCapOps v0) `mappend` (outCapOps v1)+        , outParamOps = (outParamOps v0) `mappend` (outParamOps v1)         } 
+ src/Graphics/Text/Width.hs view
@@ -0,0 +1,34 @@+-- Copyright 2009 Corey O'Connor+{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}+{-# LANGUAGE ForeignFunctionInterface #-}+module Graphics.Text.Width ( wcwidth+                           , wcswidth+                           , safeWcwidth+                           , safeWcswidth+                           )+    where++foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int++wcswidth :: String -> Int+wcswidth = sum . map wcwidth++-- XXX: Characters with unknown widths occupy 1 column?+-- +-- Not sure if this is actually correct.  I presume there is a replacement character that is output+-- by the terminal instead of the character and this replacement character is 1 column wide. If this+-- is not true for all terminals then a per-terminal replacement character width needs to be+-- implemented.++-- | Returns the display width of a character. Assumes all characters with unknown widths are 0 width+safeWcwidth :: Char -> Int+safeWcwidth c = case wcwidth c of+    i   | i < 0 -> 0+        | otherwise -> i++-- | Returns the display width of a string. Assumes all characters with unknown widths are 0 width+safeWcswidth :: String -> Int+safeWcswidth str = case wcswidth str of+    i   | i < 0 -> 0+        | otherwise -> i+
src/Graphics/Vty.hs view
@@ -1,69 +1,98 @@--- Copyright 2009-2010 Corey O'Connor-{-# LANGUAGE ForeignFunctionInterface, BangPatterns, UnboxedTuples #-}-{-# CFILES gwinsz.c #-}-+-- | Vty supports input and output to terminal devices.+--+--  - Input to the terminal is provided to the app as a sequence of 'Event's.+--+--  - The output is defined by a 'Picture'. Which is one or more layers of 'Image's.+--+--      - The module "Graphics.Vty.Image" provides a number of constructor equations that will build+--      correct 'Image' values. See 'string', '<|>', and '<->' for starters.+--+--      - The constructors in "Graphics.Vty.Image.Internal" should not be used.+--+--  - 'Image's can be styled using 'Attr'. See "Graphics.Vty.Attributes".+-- +-- See the vty-examples package for a number of examples.+--+-- @+--  main = do+--      vty <- 'mkVty' def+--      let line0 = 'string' (def `withForeColor` 'green') \"first line\"+--          line1 = 'string' (def `withBackColor` 'blue') \"second line\"+--          img = line0 '<->' line1+--          pic = 'picForImage' img+--      'update' vty pic+--      e :: 'Event' <- 'nextEvent' vty+--      'shutdown' vty+--      print $ \"Last event was: \" ++ show e+-- @+--  -- Good sources of documentation for terminal programming are:--- vt100 control sequences: http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.3--- Xterm control sequences: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html-+--+--  - <https://github.com/b4winckler/vim/blob/master/src/term.c>+--+--  - <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>+--+--  - <http://ulisse.elettra.trieste.it/services/doc/serial/config.html>+--+--  - <http://www.leonerd.org.uk/hacks/hints/xterm-8bit.html>+--+--  - <http://www.unixwiz.net/techtips/termios-vmin-vtime.html>+--+--  - <http://vt100.net/docs/vt100-ug/chapter3.html vt100 control sequences> module Graphics.Vty ( Vty(..)                     , mkVty-                    , mkVtyEscDelay-                    , module Graphics.Vty.Terminal+                    , module Graphics.Vty.Config+                    , module Graphics.Vty.Input+                    , module Graphics.Vty.Output                     , module Graphics.Vty.Picture-                    , module Graphics.Vty.DisplayRegion-                    , Key(..)-                    , Modifier(..)-                    , Button(..)-                    , Event(..)+                    , DisplayRegion                     )      where +import Graphics.Vty.Prelude -import Graphics.Vty.Terminal+import Graphics.Vty.Config+import Graphics.Vty.Input+import Graphics.Vty.Output import Graphics.Vty.Picture-import Graphics.Vty.DisplayRegion-import Graphics.Vty.LLInput -import Data.IORef+import Control.Concurrent -import qualified System.Console.Terminfo as Terminfo+import Data.IORef+import Data.Monoid  -- | The main object.  At most one should be created.--- An alternative is to use unsafePerformIO to automatically create a singleton Vty instance when--- required. --+-- The use of Vty typically follows this process:+--+--    0. initialize vty+--+--    1. use the update equation of Vty to display a picture+--+--    2. repeat+--+--    3. shutdown vty.+--+-- An alternative to tracking the Vty instance is to use 'withVty' in "Graphics.Vty.Inline.Unsafe".+-- -- This does not assure any thread safety. In theory, as long as an update action is not executed -- when another update action is already then it's safe to call this on multiple threads.--- --- todo: Once the Terminal interface encompasses input this interface will be deprecated.--- Currently, just using the Terminal interface there is no support for input events.+--+-- \todo Remove explicit `shutdown` requirement. data Vty = Vty -    { -- | Outputs the given Picture. Equivalent to output_picture applied to a display context-      -- implicitly managed by Vty.  +    { -- | Outputs the given Picture. Equivalent to 'outputPicture' applied to a display context+      -- implicitly managed by Vty. The managed display context is reset on resize.       update :: Picture -> IO ()-      -- | Get one Event object, blocking if necessary.-    , next_event :: IO Event-      -- | Handle to the terminal interface. See `Terminal`-      ---      -- The use of Vty typically follows this process:-      ---      --    0. initialize vty-      ---      --    1. use the update equation of Vty to display a picture-      ---      --    2. repeat-      ---      --    3. shutdown vty. -      -- -      -- todo: provide a similar abstraction to Graphics.Vty.Terminal for input. Use haskeline's-      -- input backend for implementation.-      -- -      -- todo: remove explicit `shutdown` requirement. -    , terminal :: TerminalHandle-      -- | Refresh the display. Normally the library takes care of refreshing.  Nonetheless, some-      -- other program might output to the terminal and mess the display.  In that case the user-      -- might want to force a refresh.+      -- | Get one Event object, blocking if necessary. This will refresh the terminal if the event+      -- is a 'EvResize'.+    , nextEvent :: IO Event+      -- | The input interface. See 'Input'+    , inputIface :: Input+      -- | The output interface. See 'Output'+    , outputIface :: Output+      -- | Refresh the display. 'nextEvent' will refresh the display if a resize occurs.+      -- If other programs output to the terminal and mess up the display then the application might+      -- want to force a refresh.     , refresh :: IO ()       -- | Clean up after vty.       -- The above methods will throw an exception if executed after this is executed.@@ -71,79 +100,80 @@     }  -- | Set up the state object for using vty.  At most one state object should be--- created at a time.-mkVty :: IO Vty-mkVty = mkVtyEscDelay 0---- | Set up the state object for using vty.  At most one state object should be--- created at a time. The delay, in microseconds, specifies the period of time to wait for a key--- following reading ESC from the terminal before considering the ESC key press as a discrete event.-mkVtyEscDelay :: Int -> IO Vty-mkVtyEscDelay escDelay = do -    term_info <- Terminfo.setupTermFromEnv -    t <- terminal_handle-    reserve_display t-    (kvar, endi) <- initTermInput escDelay term_info-    intMkVty kvar ( endi >> release_display t >> release_terminal t ) t+-- created at a time for a given terminal device.+--+-- The specified config is added to the 'userConfig'. With the 'userConfig' taking precedence.+-- See "Graphics.Vty.Config"+--+-- For most applications @mkVty def@ is sufficient.+mkVty :: Config -> IO Vty+mkVty appConfig = do+    config <- mappend <$> pure appConfig <*> userConfig+    input  <- inputForCurrentTerminal config+    out    <- outputForCurrentTerminal config+    intMkVty input out -intMkVty :: IO Event -> IO () -> TerminalHandle -> IO Vty-intMkVty kvar fend t = do-    last_pic_ref <- newIORef Nothing-    last_update_ref <- newIORef Nothing+intMkVty :: Input -> Output -> IO Vty+intMkVty input out = do+    reserveDisplay out+    let shutdownIo = do+            shutdownInput input+            releaseDisplay out+            releaseTerminal out+    lastPicRef <- newIORef Nothing+    lastUpdateRef <- newIORef Nothing -    let inner_update in_pic = do-            b <- display_bounds t-            let DisplayRegion w h = b-                cursor  = pic_cursor in_pic-                in_pic' = case cursor of+    let innerUpdate inPic = do+            b@(w,h) <- displayBounds out+            let cursor  = picCursor inPic+                inPic' = case cursor of                   Cursor x y ->                     let                        x'      = case x of-                                   _ | x >= 0x80000000 -> 0-                                     | x >= w          -> w - 1-                                     | otherwise       -> x+                                   _ | x < 0     -> 0+                                     | x >= w    -> w - 1+                                     | otherwise -> x                        y'      = case y of-                                   _ | y >= 0x80000000 -> 0-                                     | y >= h          -> h - 1-                                     | otherwise       -> y-                     in in_pic { pic_cursor = Cursor x' y' }-                  _          -> in_pic-            mlast_update <- readIORef last_update_ref-            update_data <- case mlast_update of+                                   _ | y < 0     -> 0+                                     | y >= h    -> h - 1+                                     | otherwise -> y+                     in inPic { picCursor = Cursor x' y' }+                  _ -> inPic+            mlastUpdate <- readIORef lastUpdateRef+            updateData <- case mlastUpdate of                 Nothing -> do-                    d <- display_context t b-                    output_picture d in_pic'-                    return (b, d)-                Just (last_bounds, last_context) -> do-                    if b /= last_bounds+                    dc <- displayContext out b+                    outputPicture dc inPic'+                    return (b, dc)+                Just (lastBounds, lastContext) -> do+                    if b /= lastBounds                         then do-                            d <- display_context t b-                            output_picture d in_pic'-                            return (b, d)+                            dc <- displayContext out b+                            outputPicture dc inPic'+                            return (b, dc)                         else do-                            output_picture last_context in_pic'-                            return (b, last_context)-            writeIORef last_update_ref $ Just update_data-            writeIORef last_pic_ref $ Just in_pic'+                            outputPicture lastContext inPic'+                            return (b, lastContext)+            writeIORef lastUpdateRef $ Just updateData+            writeIORef lastPicRef $ Just inPic' -    let inner_refresh -            =   writeIORef last_update_ref Nothing-            >>  readIORef last_pic_ref -            >>= maybe ( return () ) ( \pic -> inner_update pic ) +    let innerRefresh +            =   writeIORef lastUpdateRef Nothing+            >>  readIORef lastPicRef +            >>= maybe ( return () ) ( \pic -> innerUpdate pic )  -    let gkey = do k <- kvar+    let gkey = do k <- readChan $ _eventChannel input                   case k of -                    (EvResize _ _)  -> inner_refresh -                                       >> display_bounds t -                                       >>= return . ( \(DisplayRegion w h) -                                                        -> EvResize (fromEnum w) (fromEnum h)-                                                    )+                    (EvResize _ _)  -> innerRefresh+                                       >> displayBounds out+                                       >>= return . (\(w,h)-> EvResize w h)                     _               -> return k -    return $ Vty { update = inner_update-                 , next_event = gkey-                 , terminal = t-                 , refresh = inner_refresh-                 , shutdown = fend +    return $ Vty { update = innerUpdate+                 , nextEvent = gkey+                 , inputIface = input+                 , outputIface = out+                 , refresh = innerRefresh+                 , shutdown = shutdownIo                  } 
src/Graphics/Vty/Attributes.hs view
@@ -3,7 +3,55 @@ {-# LANGUAGE RankNTypes #-} -- | Display attributes ----- For efficiency, this could be encoded into a single 32 bit word. The 32 bit word is first divided+-- Typically the values 'defAttr' or 'currentAttr' are modified to form attributes:+--+-- @+--     defAttr `withForeColor` red+-- @+--+-- Is the attribute that will set the foreground color to red and the background color to the+-- default.+--+-- This can then be used to build an image wiht a red foreground like so:+--+-- @+--      string (defAttr `withForeColor` red) "this text will be red"+-- @+--+-- The default attributes set by 'defAttr' have a presentation determined by the terminal.  This is+-- not something VTY can control. The user is free to define the color scheme of the terminal as+-- they see fit. Up to the limits of the terminal anyways.+--+-- The value 'currentAttr' will keep the attributes of whatever was output previously.+--+-- \todo This API is very verbose IMO. I'd like something more succinct.+module Graphics.Vty.Attributes ( module Graphics.Vty.Attributes+                               , module Graphics.Vty.Attributes.Color+                               , module Graphics.Vty.Attributes.Color240+                               )+    where++import Data.Bits+import Data.Default+import Data.Monoid+import Data.Word++import Graphics.Vty.Attributes.Color+import Graphics.Vty.Attributes.Color240++-- | A display attribute defines the Color and Style of all the characters rendered after the+-- attribute is applied.+--+--  At most 256 colors, picked from a 240 and 16 color palette, are possible for the background and+--  foreground. The 240 colors and 16 colors are points in different palettes. See Color for more+--  information.+data Attr = Attr+    { attrStyle :: !(MaybeDefault Style)+    , attrForeColor :: !(MaybeDefault Color)+    , attrBackColor :: !(MaybeDefault Color)+    } deriving ( Eq, Show )++-- This could be encoded into a single 32 bit word. The 32 bit word is first divided -- into 4 groups of 8 bits where: The first group codes what action should be taken with regards to -- the other groups. --      XXYYZZ__@@ -33,46 +81,21 @@ -- --  Then the foreground color encoded into 8 bits. --  Then the background color encoded into 8 bits.----module Graphics.Vty.Attributes ( module Graphics.Vty.Attributes-                               , module Graphics.Vty.Attributes.Color-                               , module Graphics.Vty.Attributes.Color240-                               )-    where -import Graphics.Vty.Attributes.Color-import Graphics.Vty.Attributes.Color240--import Data.Bits-import Data.Monoid-import Data.Word---- | A display attribute defines the Color and Style of all the characters rendered after the--- attribute is applied.------  At most 256 colors, picked from a 240 and 16 color palette, are possible for the background and---  foreground. The 240 colors and 16 colors are points in different palettes. See Color for more---  information.-data Attr = Attr -    { attr_style :: !(MaybeDefault Style)-    , attr_fore_color :: !(MaybeDefault Color)-    , attr_back_color :: !(MaybeDefault Color)-    } deriving ( Eq, Show )- instance Monoid Attr where     mempty = Attr mempty mempty mempty-    mappend attr_0 attr_1 = -        Attr ( attr_style attr_0 `mappend` attr_style attr_1 )-             ( attr_fore_color attr_0 `mappend` attr_fore_color attr_1 )-             ( attr_back_color attr_0 `mappend` attr_back_color attr_1 )+    mappend attr0 attr1 = +        Attr ( attrStyle attr0     `mappend` attrStyle attr1 )+             ( attrForeColor attr0 `mappend` attrForeColor attr1 )+             ( attrBackColor attr0 `mappend` attrBackColor attr1 )  -- | Specifies the display attributes such that the final style and color values do not depend on -- the previously applied display attribute. The display attributes can still depend on the -- terminal's default colors (unfortunately). data FixedAttr = FixedAttr-    { fixed_style :: !Style-    , fixed_fore_color :: !(Maybe Color)-    , fixed_back_color :: !(Maybe Color)+    { fixedStyle :: !Style+    , fixedForeColor :: !(Maybe Color)+    , fixedBackColor :: !(Maybe Color)     } deriving ( Eq, Show )  -- | The style and color attributes can either be the terminal defaults. Or be equivalent to the@@ -109,16 +132,16 @@ white  = ISOColor 7  -- | Bright/Vivid variants of the standard 8-color ANSI -bright_black, bright_red, bright_green, bright_yellow :: Color-bright_blue, bright_magenta, bright_cyan, bright_white :: Color-bright_black  = ISOColor 8-bright_red    = ISOColor 9-bright_green  = ISOColor 10-bright_yellow = ISOColor 11-bright_blue   = ISOColor 12-bright_magenta= ISOColor 13-bright_cyan   = ISOColor 14-bright_white  = ISOColor 15+brightBlack, brightRed, brightGreen, brightYellow :: Color+brightBlue, brightMagenta, brightCyan, brightWhite :: Color+brightBlack  = ISOColor 8+brightRed    = ISOColor 9+brightGreen  = ISOColor 10+brightYellow = ISOColor 11+brightBlue   = ISOColor 12+brightMagenta= ISOColor 13+brightCyan   = ISOColor 14+brightWhite  = ISOColor 15  -- | Styles are represented as an 8 bit word. Each bit in the word is 1 if the style attribute -- assigned to that bit should be applied and 0 if the style attribute should not be applied.@@ -130,7 +153,7 @@ -- --      * underline -----      * reverse_video+--      * reverseVideo -- --      * blink --@@ -140,52 +163,55 @@ -- --  ( The invisible, protect, and altcharset display attributes some terminals support are not --  supported via VTY.)-standout, underline, reverse_video, blink, dim, bold :: Style+standout, underline, reverseVideo, blink, dim, bold :: Style standout        = 0x01 underline       = 0x02-reverse_video   = 0x04+reverseVideo   = 0x04 blink           = 0x08 dim             = 0x10 bold            = 0x20 -default_style_mask :: Style-default_style_mask = 0x00+defaultStyleMask :: Style+defaultStyleMask = 0x00 -style_mask :: Attr -> Word8-style_mask attr -    = case attr_style attr of+styleMask :: Attr -> Word8+styleMask attr +    = case attrStyle attr of         Default  -> 0         KeepCurrent -> 0         SetTo v  -> v  -- | true if the given Style value has the specified Style set.-has_style :: Style -> Style -> Bool-has_style s bit_mask = ( s .&. bit_mask ) /= 0+hasStyle :: Style -> Style -> Bool+hasStyle s bitMask = ( s .&. bitMask ) /= 0  -- | Set the foreground color of an `Attr'.-with_fore_color :: Attr -> Color -> Attr-with_fore_color attr c = attr { attr_fore_color = SetTo c }+withForeColor :: Attr -> Color -> Attr+withForeColor attr c = attr { attrForeColor = SetTo c }  -- | Set the background color of an `Attr'.-with_back_color :: Attr -> Color -> Attr-with_back_color attr c = attr { attr_back_color = SetTo c }+withBackColor :: Attr -> Color -> Attr+withBackColor attr c = attr { attrBackColor = SetTo c }  -- | Add the given style attribute-with_style :: Attr -> Style -> Attr-with_style attr style_flag = attr { attr_style = SetTo $ style_mask attr .|. style_flag }+withStyle :: Attr -> Style -> Attr+withStyle attr styleFlag = attr { attrStyle = SetTo $ styleMask attr .|. styleFlag }  -- | Sets the style, background color and foreground color to the default values for the terminal. -- There is no easy way to determine what the default background and foreground colors are.-def_attr :: Attr-def_attr = Attr Default Default Default+defAttr :: Attr+defAttr = Attr Default Default Default +instance Default Attr where+    def = defAttr+ -- | Keeps the style, background color and foreground color that was previously set. Used to -- override some part of the previous style. ----- EG: current_style `with_fore_color` bright_magenta+-- EG: current_style `withForeColor` brightMagenta -- -- Would be the currently applied style (be it underline, bold, etc) but with the foreground color--- set to bright_magenta.-current_attr :: Attr-current_attr = Attr KeepCurrent KeepCurrent KeepCurrent+-- set to brightMagenta.+currentAttr :: Attr+currentAttr = Attr KeepCurrent KeepCurrent KeepCurrent 
src/Graphics/Vty/Attributes/Color240.hs view
@@ -7,13 +7,13 @@  import Text.Printf --- | 8 bit RGB color to 240 color palette.+-- | RGB color to 240 color palette. -- -- generated from 256colres.pl which is forked from xterm 256colres.pl -- todo: all values get clamped high.-rgb_color :: Integral i => i -> i -> i -> Color-rgb_color r g b-    | r < 0 && g < 0 && b < 0 = error "rgb_color with negative color component intensity"+rgbColor :: Integral i => i -> i -> i -> Color+rgbColor r g b+    | r < 0 && g < 0 && b < 0 = error "rgbColor with negative color component intensity"     | r == 8 && g == 8 && b == 8 = Color240 216     | r == 18 && g == 18 && b == 18 = Color240 217     | r == 28 && g == 28 && b == 28 = Color240 218
+ src/Graphics/Vty/Config.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+-- | A 'Config' can be provided to mkVty to customize the applications use of vty. A config file can+-- be used to customize vty for a user's system.+--+-- The 'Config' provided is mappend'd to 'Config's loaded from @'getAppUserDataDirectory'/config@+-- and @$VTY_CONFIG_FILE@. The @$VTY_CONFIG_FILE@ takes precedence over the @config@ file or the+-- application provided 'Config'.+--+-- Each line of the input config is processed individually. Lines that fail to parse are ignored.+-- Later entries take precedence over earlier.+--+-- For all directives:+-- +-- @+--  string := \"\\\"\" chars+ \"\\\"\"+-- @+--+-- = Debug Directives+--+-- == @debugLog@+--+-- Format:+--+-- @+--  \"debugLog\" string+-- @+--+-- The value of the environment variable @VTY_DEBUG_LOG@ is equivalent to a debugLog entry at the+-- end of the last config file.+--+-- = Input Table Directives+--+-- == @map@+--+-- Directive format:+--+-- @+--  \"map\" term string key modifier_list+--  where +--      key := KEsc | KChar Char | KBS ... (same as 'Key')+--      modifier_list := \"[\" modifier+ \"]\"+--      modifier := MShift | MCtrl | MMeta | MAlt+--      term := "_" | string+-- @+--+-- EG: If the contents are+--+-- @+--  map _       \"\\ESC[B\"    KUp   []+--  map _       \"\\ESC[1;3B\" KDown [MAlt]+--  map "xterm" \"\\ESC[D\"    KLeft []+-- @+--+-- Then the bytes @\"\\ESC[B\"@ will result in the KUp event on all terminals. The bytes+-- @\"\\ESC[1;3B\"@ will result in the event KDown with the MAlt modifier on all terminals.+-- The bytes @\"\\ESC[D\"@ will result in the KLeft event when @TERM@ is @xterm@.+--+-- If a debug log is requested then vty will output the current input table to the log in the above+-- format.+--+-- EG: Set VTY_DEBUG_LOG. Run vty. Check debug log for incorrect mappings. Add corrected mappings to+-- .vty/config+--+module Graphics.Vty.Config where++-- ignore warning on GHC 7.6+. Required for GHC 7.4+import Prelude hiding (catch)++import Control.Applicative hiding (many)++import Control.Exception (tryJust, catch, IOException)+import Control.Monad (void, guard)+import Control.Monad.Trans.Class+import Control.Monad.Trans.Writer++import qualified Data.ByteString as BS+import Data.Default+import Data.Monoid++import Graphics.Vty.Input.Events++import System.Directory (getAppUserDataDirectory)+import System.Environment (getEnv)+import System.IO.Error (isDoesNotExistError)++import Text.Parsec hiding ((<|>))+import Text.Parsec.Token ( GenLanguageDef(..) )+import qualified Text.Parsec.Token as P++-- | Mappings from input bytes to event in the order specified. Later entries take precedence over+-- earlier in the case multiple entries have the same byte string.+type InputMap = [(Maybe String, String, Event)]++data Config = Config+    { specifiedEscPeriod :: Maybe Int            +    -- | Debug information is appended to this file if not Nothing.+    , debugLog           :: Maybe FilePath+    -- | The (input byte, output event) pairs extend the internal input table of VTY and the table+    -- from terminfo.+    --+    -- See "Graphics.Vty.Config" module documentation for documentation of the @map@ directive.+    , inputMap           :: InputMap+    } deriving (Show, Eq)++-- | AKA VTIME. The default is 100000 microseconds or 0.1 seconds. Set using the+-- 'specifiedEscPeriod' field of 'Config'+singleEscPeriod :: Config -> Int+singleEscPeriod = maybe 100000 id . specifiedEscPeriod++instance Default Config where+    def = mempty++instance Monoid Config where+    mempty = Config+        { specifiedEscPeriod = Nothing+        , debugLog           = mempty+        , inputMap           = mempty+        }+    mappend c0 c1 = Config+        -- latter config takes priority in specifiedEscPeriod+        { specifiedEscPeriod = specifiedEscPeriod c1 <|> specifiedEscPeriod c0+        -- latter config takes priority in debugInputLog+        , debugLog           = debugLog c1           <|> debugLog c0+        , inputMap           = inputMap c0           <>  inputMap c1+        }++type ConfigParser s a = ParsecT s () (Writer Config) a++-- | Config from @'getAppUserDataDirectory'/config@ and @$VTY_CONFIG_FILE@+userConfig :: IO Config+userConfig = do+    configFile <- (mappend <$> getAppUserDataDirectory "vty" <*> pure "/config") >>= parseConfigFile+    let maybeEnv = tryJust (guard . isDoesNotExistError) . getEnv+    overridePath <- maybeEnv "VTY_CONFIG_FILE"+    overrideConfig <- either (const $ return def) parseConfigFile overridePath+    debugLogPath <- maybeEnv "VTY_DEBUG_LOG"+    let debugLogConfig = either (const def) (\p -> def { debugLog = Just p }) debugLogPath+    return $ mconcat [configFile, overrideConfig, debugLogConfig]++parseConfigFile :: FilePath -> IO Config+parseConfigFile path = do+    catch (runParseConfig path <$> BS.readFile path)+          (\(_ :: IOException) -> return def)++runParseConfig :: Stream s (Writer Config) Char => String -> s -> Config+runParseConfig name = execWriter . runParserT parseConfig () name++-- I tried to use the haskellStyle here but that was specialized (without requirement?) to the+-- String stream type.+configLanguage :: Stream s m Char => P.GenLanguageDef s u m+configLanguage = LanguageDef+    { commentStart = "{-"+    , commentEnd = "-}"+    , commentLine = "--"+    , nestedComments = True+    , identStart = letter <|> char '_'+    , identLetter = alphaNum <|> oneOf "_'"+    , opStart = opLetter configLanguage+    , opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"+    , reservedOpNames = []+    , reservedNames = []+    , caseSensitive = True+    }++configLexer :: Stream s m Char => P.GenTokenParser s u m+configLexer = P.makeTokenParser configLanguage++mapDecl = do+    void $ string "map"+    P.whiteSpace configLexer+    termIdent <- (char '_' >> P.whiteSpace configLexer >> return Nothing)+             <|> (Just <$> P.stringLiteral configLexer)+    bytes <- P.stringLiteral configLexer+    key <- parseKey+    modifiers <- parseModifiers+    lift $ tell $ def { inputMap = [(termIdent, bytes, EvKey key modifiers)] }++-- TODO: Generated by a vim macro. There is a better way here. Derive parser? Use Read+-- instance? Generics?+parseKey = do+    key <- P.identifier configLexer+    case key of+     "KChar" -> KChar <$> P.charLiteral configLexer+     "KFun" -> KFun . fromInteger <$> P.natural configLexer+     "KEsc" -> return KEsc+     "KBS" -> return KBS+     "KEnter" -> return KEnter+     "KLeft" -> return KLeft+     "KRight" -> return KRight+     "KUp" -> return KUp+     "KDown" -> return KDown+     "KUpLeft" -> return KUpLeft+     "KUpRight" -> return KUpRight+     "KDownLeft" -> return KDownLeft+     "KDownRight" -> return KDownRight+     "KCenter" -> return KCenter+     "KBackTab" -> return KBackTab+     "KPrtScr" -> return KPrtScr+     "KPause" -> return KPause+     "KIns" -> return KIns+     "KHome" -> return KHome+     "KPageUp" -> return KPageUp+     "KDel" -> return KDel+     "KEnd" -> return KEnd+     "KPageDown" -> return KPageDown+     "KBegin" -> return KBegin+     "KMenu" -> return KMenu+     _ -> fail $ key ++ " is not a valid key identifier"++parseModifiers = P.brackets configLexer (parseModifier `sepBy` P.symbol configLexer ",")++parseModifier = do+    m <- P.identifier configLexer+    case m of+        "KMenu" -> return MShift+        "MCtrl" -> return MCtrl+        "MMeta" -> return MMeta+        "MAlt" -> return MAlt+        _ -> fail $ m ++ " is not a valid modifier identifier"++debugLogDecl = do+    void $ string "debugLog"+    P.whiteSpace configLexer+    path <- P.stringLiteral configLexer+    lift $ tell $ def { debugLog = Just path }++ignoreLine = void $ manyTill anyChar newline++parseConfig = void $ many $ do+    P.whiteSpace configLexer+    let directives = [mapDecl, debugLogDecl]+    try (choice directives) <|> ignoreLine
src/Graphics/Vty/Debug.hs view
@@ -1,43 +1,40 @@ -- Copyright 2009-2010 Corey O'Connor-{-# LANGUAGE ScopedTypeVariables #-} module Graphics.Vty.Debug ( module Graphics.Vty.Debug                           , module Graphics.Vty.Debug.Image                           ) where +import Graphics.Vty.Prelude+ import Graphics.Vty.Attributes import Graphics.Vty.Debug.Image import Graphics.Vty.Span-import Graphics.Vty.DisplayRegion  import qualified Data.Vector as Vector -import Data.Word -row_ops_effected_columns :: DisplayOps -> [Word]-row_ops_effected_columns spans -    = Vector.toList $ Vector.map span_ops_effected_columns $ display_ops spans+rowOpsEffectedColumns :: DisplayOps -> [Int]+rowOpsEffectedColumns ops +    = Vector.toList $ Vector.map spanOpsEffectedColumns ops -all_spans_have_width :: DisplayOps -> Word -> Bool-all_spans_have_width spans expected-    = all (== expected) $ Vector.toList $ Vector.map span_ops_effected_columns $ display_ops spans+allSpansHaveWidth :: DisplayOps -> Int -> Bool+allSpansHaveWidth ops expected+    = all (== expected) $ Vector.toList $ Vector.map spanOpsEffectedColumns ops -span_ops_effected_rows :: DisplayOps -> Word-span_ops_effected_rows (DisplayOps _ the_row_ops) -    = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList the_row_ops))+spanOpsEffectedRows :: DisplayOps -> Int+spanOpsEffectedRows ops+    = toEnum $ length (filter (not . null . Vector.toList) (Vector.toList ops))          type SpanConstructLog = [SpanConstructEvent] data SpanConstructEvent = SpanSetAttr Attr -is_set_attr :: Attr -> SpanConstructEvent -> Bool-is_set_attr expected_attr (SpanSetAttr in_attr)-    | in_attr == expected_attr = True-is_set_attr _attr _event = False+isSetAttr :: Attr -> SpanConstructEvent -> Bool+isSetAttr expectedAttr (SpanSetAttr inAttr)+    | inAttr == expectedAttr = True+isSetAttr _attr _event = False -data DebugWindow = DebugWindow Word Word+data MockWindow = MockWindow Int Int     deriving (Show, Eq) -region_for_window :: DebugWindow -> DisplayRegion-region_for_window (DebugWindow w h) = DisplayRegion w h--type TestWindow = DebugWindow+regionForWindow :: MockWindow -> DisplayRegion+regionForWindow (MockWindow w h) = (w,h) 
src/Graphics/Vty/Debug/Image.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE ScopedTypeVariables #-} module Graphics.Vty.Debug.Image where  import Graphics.Vty.Image@@ -7,26 +6,26 @@ data ImageConstructEvent = ImageConstructEvent     deriving ( Show, Eq ) -forward_image_ops :: [Image -> Image]-forward_image_ops = map forward_transform debug_image_ops+forwardImageOps :: [Image -> Image]+forwardImageOps = map forwardTransform debugImageOps -forward_transform, reverse_transform :: ImageOp -> (Image -> Image)+forwardTransform, reverseTransform :: ImageOp -> (Image -> Image) -forward_transform (ImageOp f _) = f-reverse_transform (ImageOp _ r) = r+forwardTransform (ImageOp f _) = f+reverseTransform (ImageOp _ r) = r  data ImageOp = ImageOp ImageEndo ImageEndo type ImageEndo = Image -> Image -debug_image_ops :: [ImageOp]-debug_image_ops = -    [ id_image_op-    -- , render_single_column_char_op-    -- , render_double_column_char_op+debugImageOps :: [ImageOp]+debugImageOps = +    [ idImageOp+    -- , renderSingleColumnCharOp+    -- , renderDoubleColumnCharOp     ] -id_image_op :: ImageOp-id_image_op = ImageOp id id+idImageOp :: ImageOp+idImageOp = ImageOp id id --- render_char_op :: ImageOp--- render_char_op = ImageOp id id+-- renderCharOp :: ImageOp+-- renderCharOp = ImageOp id id
src/Graphics/Vty/DisplayAttributes.hs view
@@ -5,25 +5,25 @@  import Graphics.Vty.Attributes -import Data.Bits ( (.&.) )-import Data.Monoid ( Monoid(..), mconcat )+import Data.Bits ((.&.))+import Data.Monoid (Monoid(..), mconcat)  -- | Given the previously applied display attributes as a FixedAttr and the current display -- attributes as an Attr produces a FixedAttr that represents the current display attributes. This -- is done by using the previously applied display attributes to remove the "KeepCurrent" -- abstraction.-fix_display_attr :: FixedAttr -> Attr -> FixedAttr-fix_display_attr fattr attr -    = FixedAttr ( fix_style (fixed_style fattr) (attr_style attr) )-                ( fix_color (fixed_fore_color fattr) (attr_fore_color attr) )-                ( fix_color (fixed_back_color fattr) (attr_back_color attr) )+fixDisplayAttr :: FixedAttr -> Attr -> FixedAttr+fixDisplayAttr fattr attr+    = FixedAttr (fixStyle (fixedStyle fattr)     (attrStyle attr))+                (fixColor (fixedForeColor fattr) (attrForeColor attr))+                (fixColor (fixedBackColor fattr) (attrBackColor attr))     where-        fix_style _s Default = default_style_mask-        fix_style s KeepCurrent = s-        fix_style _s (SetTo new_style) = new_style-        fix_color _c Default = Nothing-        fix_color c KeepCurrent = c-        fix_color _c (SetTo c) = Just c+        fixStyle _s Default           = defaultStyleMask+        fixStyle s KeepCurrent        = s+        fixStyle _s (SetTo newStyle)  = newStyle+        fixColor _c Default           = Nothing+        fixColor c KeepCurrent        = c+        fixColor _c (SetTo c)         = Just c  -- | difference between two display attributes. Used in the calculation of the operations required -- to go from one display attribute to the next.@@ -32,41 +32,41 @@ -- This turned out to be very expensive: A *lot* more data would be sent to the terminal than -- required. data DisplayAttrDiff = DisplayAttrDiff-    { style_diffs :: [ StyleStateChange ]-    , fore_color_diff :: DisplayColorDiff-    , back_color_diff :: DisplayColorDiff+    { styleDiffs    :: [StyleStateChange]+    , foreColorDiff :: DisplayColorDiff+    , backColorDiff :: DisplayColorDiff     }-    deriving ( Show )+    deriving (Show)  instance Monoid DisplayAttrDiff where     mempty = DisplayAttrDiff [] NoColorChange NoColorChange-    mappend d_0 d_1 = -        let ds = simplify_style_diffs ( style_diffs d_0 ) ( style_diffs d_1 )-            fcd = simplify_color_diffs ( fore_color_diff d_0 ) ( fore_color_diff d_1 )-            bcd = simplify_color_diffs ( back_color_diff d_0 ) ( back_color_diff d_1 )+    mappend d0 d1 =+        let ds  = simplifyStyleDiffs (styleDiffs d0)    (styleDiffs d1)+            fcd = simplifyColorDiffs (foreColorDiff d0) (foreColorDiff d1)+            bcd = simplifyColorDiffs (backColorDiff d0) (backColorDiff d1)         in DisplayAttrDiff ds fcd bcd  -- | Used in the computation of a final style attribute change. -- -- TODO(corey): not really a simplify but a monoid instance.-simplify_style_diffs :: [ StyleStateChange ] -> [ StyleStateChange ] -> [ StyleStateChange ]-simplify_style_diffs cs_0 cs_1 = cs_0 `mappend` cs_1+simplifyStyleDiffs :: [StyleStateChange] -> [StyleStateChange] -> [StyleStateChange]+simplifyStyleDiffs cs0 cs1 = cs0 `mappend` cs1  -- | Consider two display color attributes diffs. What display color attribute diff are these -- equivalent to? -- -- TODO(corey): not really a simplify but a monoid instance.-simplify_color_diffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff-simplify_color_diffs _cd             ColorToDefault  = ColorToDefault-simplify_color_diffs cd              NoColorChange   = cd-simplify_color_diffs _cd             ( SetColor !c ) = SetColor c+simplifyColorDiffs :: DisplayColorDiff -> DisplayColorDiff -> DisplayColorDiff+simplifyColorDiffs _cd             ColorToDefault = ColorToDefault+simplifyColorDiffs cd              NoColorChange  = cd+simplifyColorDiffs _cd             (SetColor !c)  = SetColor c  -- | Difference between two display color attribute changes.-data DisplayColorDiff +data DisplayColorDiff     = ColorToDefault     | NoColorChange     | SetColor !Color-    deriving ( Show, Eq )+    deriving (Show, Eq)  -- | Style attribute changes are transformed into a sequence of apply/removes of the individual -- attributes.@@ -83,44 +83,43 @@     | RemoveDim     | ApplyBold     | RemoveBold-    deriving ( Show, Eq )+    deriving (Show, Eq)  -- | Determines the diff between two display&color attributes. This diff determines the operations -- that actually get output to the terminal.-display_attr_diffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff-display_attr_diffs attr attr' = DisplayAttrDiff-    { style_diffs = diff_styles ( fixed_style attr ) ( fixed_style attr' )-    , fore_color_diff = diff_color ( fixed_fore_color attr ) ( fixed_fore_color attr' )-    , back_color_diff = diff_color ( fixed_back_color attr ) ( fixed_back_color attr' )+displayAttrDiffs :: FixedAttr -> FixedAttr -> DisplayAttrDiff+displayAttrDiffs attr attr' = DisplayAttrDiff+    { styleDiffs    = diffStyles (fixedStyle attr)      (fixedStyle attr')+    , foreColorDiff = diffColor  (fixedForeColor attr) (fixedForeColor attr')+    , backColorDiff = diffColor  (fixedBackColor attr) (fixedBackColor attr')     } -diff_color :: Maybe Color -> Maybe Color -> DisplayColorDiff-diff_color Nothing  (Just c') = SetColor c'-diff_color (Just c) (Just c') +diffColor :: Maybe Color -> Maybe Color -> DisplayColorDiff+diffColor Nothing  (Just c') = SetColor c'+diffColor (Just c) (Just c')     | c == c'   = NoColorChange     | otherwise = SetColor c'-diff_color Nothing  Nothing = NoColorChange-diff_color (Just _) Nothing = ColorToDefault+diffColor Nothing  Nothing = NoColorChange+diffColor (Just _) Nothing = ColorToDefault -diff_styles :: Style -> Style -> [StyleStateChange]-diff_styles prev cur -    = mconcat -    [ style_diff standout ApplyStandout RemoveStandout-    , style_diff underline ApplyUnderline RemoveUnderline-    , style_diff reverse_video ApplyReverseVideo RemoveReverseVideo-    , style_diff blink ApplyBlink RemoveBlink-    , style_diff dim ApplyDim RemoveDim-    , style_diff bold ApplyBold RemoveBold+diffStyles :: Style -> Style -> [StyleStateChange]+diffStyles prev cur+    = mconcat+    [ styleDiff standout      ApplyStandout     RemoveStandout+    , styleDiff underline     ApplyUnderline    RemoveUnderline+    , styleDiff reverseVideo  ApplyReverseVideo RemoveReverseVideo+    , styleDiff blink         ApplyBlink        RemoveBlink+    , styleDiff dim           ApplyDim          RemoveDim+    , styleDiff bold          ApplyBold         RemoveBold     ]-    where -        style_diff s sm rm -            = case ( 0 == prev .&. s, 0 == cur .&. s ) of+    where+        styleDiff s sm rm+            = case (0 == prev .&. s, 0 == cur .&. s) of                 -- not set in either-                ( True, True ) -> []+                (True, True)   -> []                 -- set in both-                ( False, False ) -> []+                (False, False) -> []                 -- now set-                ( True, False) -> [ sm ]+                (True, False)  -> [sm]                 -- now unset-                ( False, True) -> [ rm ]-+                (False, True)  -> [rm]
− src/Graphics/Vty/DisplayRegion.hs
@@ -1,12 +0,0 @@--- Copyright 2009 Corey O'Connor-module Graphics.Vty.DisplayRegion-    where--import Data.Word---- | Region of the terminal that vty will output to. Units are columns not characters.-data DisplayRegion = DisplayRegion -    { region_width :: !Word -    , region_height :: !Word-    } deriving ( Show, Eq )-
+ src/Graphics/Vty/Error.hs view
@@ -0,0 +1,8 @@+module Graphics.Vty.Error+    where++-- | The type of exceptions specific to vty.+--+-- These have fully qualified names by default since, IMO, exception handling requires this.+data VtyException+    =  VtyFailure String -- ^ Uncategorized failure specific to vty.
src/Graphics/Vty/Image.hs view
@@ -2,297 +2,110 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DisambiguateRecordFields #-}-{-# OPTIONS_GHC -funbox-strict-fields #-}-module Graphics.Vty.Image ( DisplayString-                          , Image(..)-                          , image_width-                          , image_height+module Graphics.Vty.Image ( DisplayText+                          , Image+                          , imageWidth+                          , imageHeight+                          , horizJoin                           , (<|>)+                          , vertJoin                           , (<->)-                          , horiz_cat-                          , vert_cat-                          , background_fill+                          , horizCat+                          , vertCat+                          , backgroundFill+                          , text+                          , text'                           , char                           , string-                          , iso_10646_string-                          , utf8_string-                          , utf8_bytestring-                          , char_fill-                          , empty_image-                          , translate-                          , safe_wcwidth-                          , safe_wcswidth+                          , iso10646String+                          , utf8String+                          , utf8Bytestring+                          , utf8Bytestring'+                          , charFill+                          , emptyImage+                          , safeWcwidth+                          , safeWcswidth                           , wcwidth                           , wcswidth                           , crop+                          , cropRight+                          , cropLeft+                          , cropBottom+                          , cropTop                           , pad+                          , resize+                          , resizeWidth+                          , resizeHeight+                          , translate+                          , translateX+                          , translateY                           -- | The possible display attributes used in constructing an `Image`.                           , module Graphics.Vty.Attributes                           )     where  import Graphics.Vty.Attributes--import Codec.Binary.UTF8.Width--import Codec.Binary.UTF8.String ( decode )+import Graphics.Vty.Image.Internal+import Graphics.Text.Width -import qualified Data.ByteString as BS-import Data.Monoid-import qualified Data.Sequence as Seq-import qualified Data.String.UTF8 as UTF8+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL import Data.Word  infixr 5 <|> infixr 4 <-> --- | We pair each character with it's display length. This way we only compute the length once per--- character.--- * Though currently the width of some strings is still compute multiple times. -type DisplayString = Seq.Seq (Char, Word)---- | An image in VTY defines:------  * properties required to display the image. These are properties that effect the output image---    but are independent of position ------  * A set of position-dependent text and attribute regions. The possible regions are:------      * a point. ( char )------      * a horizontal line of characters with a single attribute. (string, utf8_string,---      utf8_bytestring )------      * a fill of a single character. (char_fill)------      * a fill of the picture's background. (background_fill)------ todo: increase the number of encoded bytestring formats supported.-data Image = -    -- A horizontal text span is always >= 1 column and has a row height of 1.-      HorizText-      { attr :: !Attr-      -- All character data is stored as Char sequences with the ISO-10646 encoding.-      , text :: DisplayString-      , output_width :: !Word -- >= 0-      , char_width :: !Word -- >= 1-      }-    -- A horizontal join can be constructed between any two images. However a HorizJoin instance is-    -- required to be between two images of equal height. The horiz_join constructor adds background-    -- filles to the provided images that assure this is true for the HorizJoin value produced.-    | HorizJoin-      { part_left :: Image -      , part_right :: Image-      , output_width :: !Word -- >= 1-      , output_height :: !Word -- >= 1-      }-    -- A veritical join can be constructed between any two images. However a VertJoin instance is-    -- required to be between two images of equal width. The vert_join constructor adds background-    -- fills to the provides images that assure this is true for the VertJoin value produced.-    | VertJoin-      { part_top :: Image-      , part_bottom :: Image-      , output_width :: !Word -- >= 1-      , output_height :: !Word -- >= 1-      }-    -- A background fill will be filled with the background pattern. The background pattern is-    -- defined as a property of the Picture this Image is used to form. -    | BGFill-      { output_width :: !Word -- >= 1-      , output_height :: !Word -- >= 1-      }-    -- The combining operators identity constant. -    -- EmptyImage <|> a = a-    -- EmptyImage <-> a = a-    -- -    -- Any image of zero size equals the empty image.-    | EmptyImage-    | Translation (Int, Int) Image-    -- Crop an image to a size-    | ImageCrop (Word, Word) Image-    -- Pad an image up to a size-    | ImagePad (Word, Word) Image-    deriving Eq--instance Show Image where-    show ( HorizText { output_width = ow, text = txt } ) -        = "HorizText [" ++ show ow ++ "] (" ++ show (fmap fst txt) ++ ")"-    show ( BGFill { output_width = c, output_height = r } ) -        = "BGFill (" ++ show c ++ "," ++ show r ++ ")"-    show ( HorizJoin { part_left = l, part_right = r, output_width = c } ) -        = "HorizJoin " ++ show c ++ " ( " ++ show l ++ " <|> " ++ show r ++ " )"-    show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } ) -        = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"-    show ( Translation offset i )-        = "Translation " ++ show offset ++ " ( " ++ show i ++ " )"-    show ( ImageCrop size i )-        = "ImageCrop " ++ show size ++ " ( " ++ show i ++ " )"-    show ( ImagePad size i )-        = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"-    show ( EmptyImage ) = "EmptyImage"---- | Currently append in the Monoid instance is equivalent to <->. -instance Monoid Image where-    mempty = empty_image-    mappend = (<->)-    --- A horizontal text image of 0 characters in width simplifies to the EmptyImage-horiz_text :: Attr -> DisplayString -> Word -> Image-horiz_text a txt ow-    | ow == 0    = EmptyImage-    | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)--horiz_join :: Image -> Image -> Word -> Word -> Image-horiz_join i_0 i_1 w h-    -- A horiz join of two 0 width images simplifies to the EmptyImage-    | w == 0 = EmptyImage-    -- A horizontal join where either part is 0 columns in width simplifies to the other part.-    -- This covers the case where one part is the EmptyImage.-    | image_width i_0 == 0 = i_1-    | image_width i_1 == 0 = i_0-    -- If the images are of the same height then no BG padding is required-    | image_height i_0 == image_height i_1 = HorizJoin i_0 i_1 w h-    -- otherwise one of the imagess needs to be padded to the right size.-    | image_height i_0 < image_height i_1  -- Pad i_0-        = let pad_amount = image_height i_1 - image_height i_0-          in horiz_join ( vert_join i_0 -                                    ( BGFill ( image_width i_0 ) pad_amount ) -                                    ( image_width i_0 )-                                    ( image_height i_1 )-                        ) -                        i_1 -                        w h-    | image_height i_0 > image_height i_1  -- Pad i_1-        = let pad_amount = image_height i_0 - image_height i_1-          in horiz_join i_0 -                        ( vert_join i_1 -                                    ( BGFill ( image_width i_1 ) pad_amount ) -                                    ( image_width i_1 )-                                    ( image_height i_0 )-                        )-                        w h-horiz_join _ _ _ _ = error "horiz_join applied to undefined values."--vert_join :: Image -> Image -> Word -> Word -> Image-vert_join i_0 i_1 w h-    -- A vertical join of two 0 height images simplifies to the EmptyImage-    | h == 0                = EmptyImage-    -- A vertical join where either part is 0 rows in height simplifies to the other part.-    -- This covers the case where one part is the EmptyImage-    | image_height i_0 == 0 = i_1-    | image_height i_1 == 0 = i_0-    -- If the images are of the same height then no background padding is required-    | image_width i_0 == image_width i_1 = VertJoin i_0 i_1 w h-    -- Otherwise one of the images needs to be padded to the size of the other image.-    | image_width i_0 < image_width i_1-        = let pad_amount = image_width i_1 - image_width i_0-          in vert_join ( horiz_join i_0-                                    ( BGFill pad_amount ( image_height i_0 ) )-                                    ( image_width i_1 )-                                    ( image_height i_0 )-                       )-                       i_1-                       w h-    | image_width i_0 > image_width i_1-        = let pad_amount = image_width i_0 - image_width i_1-          in vert_join i_0-                       ( horiz_join i_1-                                    ( BGFill pad_amount ( image_height i_1 ) )-                                    ( image_width i_0 )-                                    ( image_height i_1 )-                       )-                       w h-vert_join _ _ _ _ = error "vert_join applied to undefined values."- -- | An area of the picture's bacground (See Background) of w columns and h rows.-background_fill :: Word -> Word -> Image-background_fill w h +backgroundFill :: Int -> Int -> Image+backgroundFill w h      | w == 0    = EmptyImage     | h == 0    = EmptyImage     | otherwise = BGFill w h --- | The width of an Image. This is the number display columns the image will occupy.-image_width :: Image -> Word-image_width HorizText { output_width = w } = w-image_width HorizJoin { output_width = w } = w-image_width VertJoin { output_width = w } = w-image_width BGFill { output_width = w } = w-image_width EmptyImage = 0-image_width ( Translation v i ) = toEnum $ max 0 $ (fst v +) $ fromEnum $ image_width i-image_width ( ImageCrop v i ) = min (image_width i) $ fst v-image_width ( ImagePad v i ) = max (image_width i) $ fst v---- | The height of an Image. This is the number of display rows the image will occupy.-image_height :: Image -> Word-image_height HorizText {} = 1-image_height HorizJoin { output_height = r } = r-image_height VertJoin { output_height = r } = r-image_height BGFill { output_height = r } = r-image_height EmptyImage = 0-image_height ( Translation v i ) = toEnum $ max 0 $ (snd v +) $ fromEnum $ image_height i-image_height ( ImageCrop v i ) = min (image_height i) $ snd v-image_height ( ImagePad v i ) = max (image_height i) $ snd v---- | Combines two images side by side.+-- | Combines two images horizontally. Alias for horizJoin ----- The result image will have a width equal to the sum of the two images width.  And the height will--- equal the largest height of the two images.  The area not defined in one image due to a height--- missmatch will be filled with the background pattern.+-- infixr 5 (<|>) :: Image -> Image -> Image---- Two horizontal text spans with the same attributes can be merged.-h0@(HorizText attr_0 text_0 ow_0 _) <|> h1@(HorizText attr_1 text_1 ow_1 _)  -    | attr_0 == attr_1  = horiz_text attr_0 (text_0 Seq.>< text_1) (ow_0 + ow_1)-    | otherwise         = horiz_join h0 h1 (ow_0 + ow_1) 1---- Anything placed to the right of a join wil be joined to the right sub image.--- The total columns for the join is the sum of the two arguments columns-h0@( HorizJoin {} ) <|> h1 -    = horiz_join ( part_left h0 ) -                 ( part_right h0 <|> h1 )-                 ( image_width h0 + image_width h1 )-                 ( max (image_height h0) (image_height h1) )---- Anything but a join placed to the left of a join wil be joined to the left sub image.--- The total columns for the join is the sum of the two arguments columns-h0 <|> h1@( HorizJoin {} ) -    = horiz_join ( h0 <|> part_left h1 ) -                 ( part_right h1 )-                 ( image_width h0 + image_width h1 )-                 ( max (image_height h0) (image_height h1) )--h0 <|> h1 -    = horiz_join h0 -                 h1 -                 ( image_width h0 + image_width h1 )-                 ( max (image_height h0) (image_height h1) )+(<|>) = horizJoin --- | Combines two images vertically.--- The result image will have a height equal to the sum of the heights of both images.--- The width will equal the largest width of the two images.--- The area not defined in one image due to a width missmatch will be filled with the background--- pattern.+-- | Combines two images vertically. Alias for vertJoin+--+-- infixr 4 (<->) :: Image -> Image -> Image-im_t <-> im_b -    = vert_join im_t -                im_b -                ( max (image_width im_t) (image_width im_b) ) -                ( image_height im_t + image_height im_b )+(<->) = vertJoin  -- | Compose any number of images horizontally.-horiz_cat :: [Image] -> Image-horiz_cat = foldr (<|>) EmptyImage+horizCat :: [Image] -> Image+horizCat = foldr horizJoin EmptyImage  -- | Compose any number of images vertically.-vert_cat :: [Image] -> Image-vert_cat = foldr (<->) EmptyImage+vertCat :: [Image] -> Image+vertCat = foldr vertJoin EmptyImage +-- | A Data.Text.Lazy value+text :: Attr -> TL.Text -> Image+text a txt+    | TL.length txt == 0 = EmptyImage+    | otherwise          = let displayWidth = safeWcswidth (TL.unpack txt)+                           in HorizText a txt displayWidth (fromIntegral $! TL.length txt)++-- | A Data.Text value+text' :: Attr -> T.Text -> Image+text' a txt+    | T.length txt == 0 = EmptyImage+    | otherwise         = let displayWidth = safeWcswidth (T.unpack txt)+                          in HorizText a (TL.fromStrict txt) displayWidth (T.length txt)+ -- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in -- the ISO-10646 encoding. char :: Attr -> Char -> Image-char !a !c = -    let display_width = safe_wcwidth c-    in HorizText a (Seq.singleton (c, display_width)) display_width 1+char a c =+    let displayWidth = safeWcwidth c+    in HorizText a (TL.singleton c) displayWidth 1  -- | A string of characters layed out on a single row with the same display attribute. The string is -- assumed to be a sequence of ISO-10646 characters. @@ -301,78 +114,192 @@ -- UTF-8 encoded source file, for example, may be represented as a ISO-10646 string.  -- That is, I think, the case with GHC 6.10. This means, for the most part, you don't need to worry -- about the encoding format when outputting string literals. Just provide the string literal--- directly to iso_10646_string or string.+-- directly to iso10646String or string. -- -iso_10646_string :: Attr -> String -> Image-iso_10646_string !a !str = -    let display_text = Seq.fromList $ map (\c -> (c, safe_wcwidth c)) str-    in horiz_text a display_text (safe_wcswidth str)+iso10646String :: Attr -> String -> Image+iso10646String a str = +    let displayWidth = safeWcswidth str+    in HorizText a (TL.pack str) displayWidth (length str) --- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is+-- | Alias for iso10646String. Since the usual case is that a literal string like "foo" is -- represented internally as a list of ISO 10646 31 bit characters.   -- -- Note: Keep in mind that GHC will compile source encoded as UTF-8 but the literal strings, while -- UTF-8 encoded in the source, will be transcoded to a ISO 10646 31 bit characters runtime -- representation. string :: Attr -> String -> Image-string = iso_10646_string---- | A string of characters layed out on a single row. The string is assumed to be a sequence of--- UTF-8 characters.-utf8_string :: Attr -> [Word8] -> Image-utf8_string !a !str = string a ( decode str )---- XXX: Characters with unknown widths occupy 1 column?--- --- Not sure if this is actually correct.  I presume there is a replacement character that is output--- by the terminal instead of the character and this replacement character is 1 column wide. If this--- is not true for all terminals then a per-terminal replacement character width needs to be--- implemented.+string = iso10646String --- | Returns the display width of a character. Assumes all characters with unknown widths are 1 width-safe_wcwidth :: Char -> Word-safe_wcwidth c = case wcwidth c of-    i   | i < 0 -> 1 -        | otherwise -> toEnum i+-- | A string of characters layed out on a single row. The input is assumed to be the bytes for+-- UTF-8 encoded text.+utf8String :: Attr -> [Word8] -> Image+utf8String a bytes = utf8Bytestring a (BL.pack bytes) --- | Returns the display width of a string. Assumes all characters with unknown widths are 1 width-safe_wcswidth :: String -> Word-safe_wcswidth str = case wcswidth str of-    i   | i < 0 -> 1 -        | otherwise -> toEnum i+-- | Renders a UTF-8 encoded lazy bytestring. +utf8Bytestring :: Attr -> BL.ByteString -> Image+utf8Bytestring a bs = text a (TL.decodeUtf8 bs) --- | Renders a UTF-8 encoded bytestring. -utf8_bytestring :: Attr -> BS.ByteString -> Image-utf8_bytestring !a !bs = string a (UTF8.toString $ UTF8.fromRep bs)+-- | Renders a UTF-8 encoded strict bytestring. +utf8Bytestring' :: Attr -> B.ByteString -> Image+utf8Bytestring' a bs = text' a (T.decodeUtf8 bs)  -- | creates a fill of the specified character. The dimensions are in number of characters wide and -- number of rows high.------ Unlike the Background fill character this character can have double column display width.-char_fill :: Enum d => Attr -> Char -> d -> d -> Image-char_fill !a !c w h = -    vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) $ char a c+charFill :: Integral d => Attr -> Char -> d -> d -> Image+charFill _a _c 0  _h = EmptyImage+charFill _a _c _w 0  = EmptyImage+charFill a c w h =+    vertCat $ replicate (fromIntegral h) $ HorizText a txt displayWidth charWidth+    where +        txt = TL.replicate (fromIntegral w) (TL.singleton c)+        displayWidth = safeWcwidth c * (fromIntegral w)+        charWidth = fromIntegral w  -- | The empty image. Useful for fold combinators. These occupy no space nor define any display -- attributes.-empty_image :: Image -empty_image = EmptyImage+emptyImage :: Image +emptyImage = EmptyImage --- | Apply the given offset to the image.-translate :: (Int, Int) -> Image -> Image-translate v i = Translation v i+-- | pad the given image. This adds background character fills to the left, top, right, bottom.+-- The pad values are how many display columns or rows to add.+pad :: Int -> Int -> Int -> Int -> Image -> Image+pad 0 0 0 0 i = i+pad inL inT inR inB inImage+    | inL < 0 || inT < 0 || inR < 0 || inB < 0 = error "cannot pad by negative amount"+    | otherwise = go inL inT inR inB inImage+        where +            -- TODO: uh.+            go 0 0 0 0 i = i+            go 0 0 0 b i = VertJoin i (BGFill w b) w h+                where w = imageWidth  i+                      h = imageHeight i + b+            go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h+                where w = imageWidth  i + r+                      h = imageHeight i+            go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h+                where w = imageWidth  i+                      h = imageHeight i + t+            go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h+                where w = imageWidth  i + l+                      h = imageHeight i --- | Ensure an image is no larger than the provided size. If the image is larger then crop.-crop :: (Word, Word) -> Image -> Image-crop (0,_) _ = EmptyImage-crop (_,0) _ = EmptyImage-crop v (ImageCrop _size i) = ImageCrop (min (fst v) (fst _size), min (snd v) (snd _size)) i-crop v i = ImageCrop v i+-- | translates an image by padding or cropping the top and left.+--+-- This can have an unexpected effect: Translating an image to less than (0,0) then to greater than+-- (0,0) will crop the image.+translate :: Int -> Int -> Image -> Image+translate x y i = translateX x (translateY y i) --- | Ensure an image is at least the provided size. If the image is smaller then pad.-pad :: (Word, Word) -> Image -> Image-pad (0,_) _ = EmptyImage-pad (_,0) _ = EmptyImage-pad v (ImagePad _size i) = ImagePad (max (fst v) (fst _size), max (snd v) (snd _size)) i-pad v i = ImagePad v i+-- | translates an image by padding or cropping the left+translateX :: Int -> Image -> Image+translateX x i+    | x < 0     = let s = abs x in CropLeft i s (imageWidth i - s) (imageHeight i)+    | x == 0    = i+    | otherwise = let h = imageHeight i in HorizJoin (BGFill x h) i (imageWidth i + x) h++-- | translates an image by padding or cropping the top+translateY :: Int -> Image -> Image+translateY y i+    | y < 0     = let s = abs y in CropTop i s (imageWidth i) (imageHeight i - s)+    | y == 0    = i+    | otherwise = let w = imageWidth i in VertJoin (BGFill w y) i w (imageHeight i + y)++-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right+-- or bottom.+--+-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the+-- right.+crop :: Int -> Int -> Image -> Image+crop 0 _ _ = EmptyImage+crop _ 0 _ = EmptyImage+crop w h i = cropBottom h (cropRight w i)++-- | crop the display height. If the image is less than or equal in height then this operation has+-- no effect. Otherwise the image is cropped from the bottom.+cropBottom :: Int -> Image -> Image+cropBottom 0 _ = EmptyImage+cropBottom h inI+    | h < 0     = error "cannot crop height to less than zero"+    | otherwise = go inI+        where+            go EmptyImage = EmptyImage+            go i@(CropBottom {croppedImage, outputWidth, outputHeight})+                | outputHeight <= h = i+                | otherwise          = CropBottom croppedImage outputWidth h+            go i+                | h >= imageHeight i = i+                | otherwise           = CropBottom i (imageWidth i) h++-- | ensure the image is no wider than the given width. If the image is wider then crop the right+-- side.+cropRight :: Int -> Image -> Image+cropRight 0 _ = EmptyImage+cropRight w inI+    | w < 0     = error "cannot crop width to less than zero"+    | otherwise = go inI+        where+            go EmptyImage = EmptyImage+            go i@(CropRight {croppedImage, outputWidth, outputHeight})+                | outputWidth <= w = i+                | otherwise         = CropRight croppedImage w outputHeight+            go i+                | w >= imageWidth i = i+                | otherwise          = CropRight i w (imageHeight i)++-- | ensure the image is no wider than the given width. If the image is wider then crop the left+-- side.+cropLeft :: Int -> Image -> Image+cropLeft 0 _ = EmptyImage+cropLeft w inI+    | w < 0     = error "cannot crop the width to less than zero"+    | otherwise = go inI+        where+            go EmptyImage = EmptyImage+            go i@(CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})+                | outputWidth <= w = i+                | otherwise         =+                    let leftSkip' = leftSkip + outputWidth - w+                    in CropLeft croppedImage leftSkip' w outputHeight+            go i+                | imageWidth i <= w = i+                | otherwise          = CropLeft i (imageWidth i - w) w (imageHeight i)++-- | crop the display height. If the image is less than or equal in height then this operation has+-- no effect. Otherwise the image is cropped from the top.+cropTop :: Int -> Image -> Image+cropTop 0 _ = EmptyImage+cropTop h inI+    | h < 0  = error "cannot crop the height to less than zero"+    | otherwise = go inI+        where+            go EmptyImage = EmptyImage+            go i@(CropTop {croppedImage, topSkip, outputWidth, outputHeight})+                | outputHeight <= h = i+                | otherwise         =+                    let topSkip' = topSkip + outputHeight - h+                    in CropTop croppedImage topSkip' outputWidth h+            go i+                | imageHeight i <= h = i+                | otherwise          = CropTop i (imageHeight i - h) (imageWidth i) h++-- | Generic resize. Pads and crops as required to assure the given display width and height.+-- This is biased to pad/crop the right and bottom.+resize :: Int -> Int -> Image -> Image+resize w h i = resizeHeight h (resizeWidth w i)++-- | Resize the width. Pads and crops as required to assure the given display width.+-- This is biased to pad/crop the right.+resizeWidth :: Int -> Image -> Image+resizeWidth w i = case w `compare` imageWidth i of+    LT -> cropRight w i+    EQ -> i+    GT -> i <|> BGFill (w - imageWidth i) (imageHeight i)++-- | Resize the height. Pads and crops as required to assure the given display height.+-- This is biased to pad/crop the bottom.+resizeHeight :: Int -> Image -> Image+resizeHeight h i = case h `compare` imageHeight i of+    LT -> cropBottom h i+    EQ -> i+    GT -> i <-> BGFill (imageWidth i) (h - imageHeight i) 
+ src/Graphics/Vty/Image/Internal.hs view
@@ -0,0 +1,293 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# OPTIONS_HADDOCK hide #-}+module Graphics.Vty.Image.Internal where++import Graphics.Vty.Attributes+import Graphics.Text.Width++import Control.DeepSeq++import Data.Monoid+import qualified Data.Text.Lazy as TL++-- | A display text is a Data.Text.Lazy+--+-- TODO(corey): hm. there is an explicit equation for each type which goes to a lazy text. Each+-- application probably uses a single type. Perhaps parameterize the entire vty interface by the+-- input text type?+-- TODO: Try using a builder instead of a TL.Text instance directly. That might improve performance+-- for the usual case of appending a bunch of characters with the same attribute together.+type DisplayText = TL.Text++-- TODO: store a skip list in HorizText(?)+-- TODO: represent display strings containing chars that are not 1 column chars as a separate+-- display string value?+clipText :: DisplayText -> Int -> Int -> DisplayText+clipText txt leftSkip rightClip =+    -- CPS would clarify this I think+    let (toDrop,padPrefix) = clipForCharWidth leftSkip txt 0+        txt' = if padPrefix then TL.cons '…' (TL.drop (toDrop+1) txt) else TL.drop toDrop txt+        (toTake,padSuffix) = clipForCharWidth rightClip txt' 0+        txt'' = TL.append (TL.take toTake txt') (if padSuffix then TL.singleton '…' else TL.empty)+        clipForCharWidth 0 _ n = (n, False)+        clipForCharWidth w t n+            | TL.null t = (n, False)+            | w <  cw = (n, True)+            | w == cw = (n+1, False)+            | w >  cw = clipForCharWidth (w - cw) (TL.tail t) (n + 1)+            where cw = safeWcwidth (TL.head t)+        clipForCharWidth _ _ _ = error "clipForCharWidth applied to undefined"+    in txt''++-- | This is the internal representation of Images. Use the constructors in "Graphics.Vty.Image" to+-- create instances.+--+-- Images are:+--+-- * a horizontal span of text+--+-- * a horizontal or vertical join of two images+--+-- * a two dimensional fill of the 'Picture's background character+--+-- * a cropped image+--+-- * an empty image of no size or content.+data Image = +    -- | A horizontal text span is always >= 1 column and has a row height of 1.+      HorizText+      { attr :: Attr+      -- | The text to display. The display width of the text is always outputWidth.+      , displayText :: DisplayText+      -- | The number of display columns for the text. Always > 0.+      , outputWidth :: Int+      -- | the number of characters in the text. Always > 0.+      , charWidth :: Int+      }+    -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is+    -- required to be between two images of equal height. The horizJoin constructor adds background+    -- fills to the provided images that assure this is true for the HorizJoin value produced.+    | HorizJoin+      { partLeft :: Image +      , partRight :: Image+      , outputWidth :: Int -- ^ imageWidth partLeft == imageWidth partRight. Always > 1+      , outputHeight :: Int -- ^ imageHeight partLeft == imageHeight partRight. Always > 0+      }+    -- | A veritical join can be constructed between any two images. However a VertJoin instance is+    -- required to be between two images of equal width. The vertJoin constructor adds background+    -- fills to the provides images that assure this is true for the VertJoin value produced.+    | VertJoin+      { partTop :: Image+      , partBottom :: Image+      , outputWidth :: Int -- ^ imageWidth partTop == imageWidth partBottom. always > 0+      , outputHeight :: Int -- ^ imageHeight partTop == imageHeight partBottom. always > 1+      }+    -- | A background fill will be filled with the background char. The background char is+    -- defined as a property of the Picture this Image is used to form.+    | BGFill+      { outputWidth :: Int -- ^ always > 0+      , outputHeight :: Int -- ^ always > 0+      }+    -- | Crop an image horizontally to a size by reducing the size from the right.+    | CropRight+      { croppedImage :: Image+      -- | Always < imageWidth croppedImage > 0+      , outputWidth :: Int+      , outputHeight :: Int -- ^ imageHeight croppedImage+      }+    -- | Crop an image horizontally to a size by reducing the size from the left.+    | CropLeft+      { croppedImage :: Image+      -- | Always < imageWidth croppedImage > 0+      , leftSkip :: Int+      -- | Always < imageWidth croppedImage > 0+      , outputWidth :: Int+      , outputHeight :: Int+      }+    -- | Crop an image vertically to a size by reducing the size from the bottom+    | CropBottom+      { croppedImage :: Image+      -- | imageWidth croppedImage+      , outputWidth :: Int+      -- | height image is cropped to. Always < imageHeight croppedImage > 0+      , outputHeight :: Int+      }+    -- | Crop an image vertically to a size by reducing the size from the top+    | CropTop+      { croppedImage :: Image+      -- | Always < imageHeight croppedImage > 0+      , topSkip :: Int+      -- | imageWidth croppedImage+      , outputWidth :: Int+      -- | Always < imageHeight croppedImage > 0+      , outputHeight :: Int+      }+    -- | The empty image+    --+    -- The combining operators identity constant. +    -- EmptyImage <|> a = a+    -- EmptyImage <-> a = a+    -- +    -- Any image of zero size equals the empty image.+    | EmptyImage+    deriving Eq++instance Show Image where+    show ( HorizText { attr, displayText, outputWidth, charWidth } )+        = "HorizText " ++ show displayText+                       ++ "@(" ++ show attr ++ ","+                               ++ show outputWidth ++ ","+                               ++ show charWidth ++ ")"+    show ( BGFill { outputWidth, outputHeight } )+        = "BGFill (" ++ show outputWidth ++ "," ++ show outputHeight ++ ")"+    show ( HorizJoin { partLeft = l, partRight = r, outputWidth = c } )+        = "HorizJoin " ++ show c ++ " (" ++ show l ++ " <|> " ++ show r ++ ")"+    show ( VertJoin { partTop = t, partBottom = b, outputWidth = c, outputHeight = r } )+        = "VertJoin [" ++ show c ++ ", " ++ show r ++ "] (" ++ show t ++ ") <-> (" ++ show b ++ ")"+    show ( CropRight { croppedImage, outputWidth, outputHeight } )+        = "CropRight [" ++ show outputWidth ++ "," ++ show outputHeight ++ "]"+                     ++ " (" ++ show croppedImage ++ ")"+    show ( CropLeft { croppedImage, leftSkip, outputWidth, outputHeight } )+        = "CropLeft [" ++ show leftSkip ++ "," ++ show outputWidth ++ "," ++ show outputHeight ++ "]"+                    ++ " (" ++ show croppedImage ++ ")"+    show ( CropBottom { croppedImage, outputWidth, outputHeight } )+        = "CropBottom [" ++ show outputWidth ++ "," ++ show outputHeight ++ "]"+                      ++ " (" ++ show croppedImage ++ ")"+    show ( CropTop { croppedImage, topSkip, outputWidth, outputHeight } )+        = "CropTop [" ++ show topSkip ++ "," ++ show outputWidth ++ "," ++ show outputHeight ++ "]"+                   ++ " (" ++ show croppedImage ++ ")"+    show ( EmptyImage ) = "EmptyImage"++-- | pretty print just the structure of an image.+ppImageStructure :: Image -> String+ppImageStructure inImg = go 0 inImg+    where+        go indent img = tab indent ++ pp indent img+        tab indent = concat $ replicate indent "  "+        pp _ (HorizText {outputWidth}) = "HorizText(" ++ show outputWidth ++ ")"+        pp _ (BGFill {outputWidth, outputHeight})+            = "BGFill(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")"+        pp i (HorizJoin {partLeft = l, partRight = r, outputWidth = c})+            = "HorizJoin(" ++ show c ++ ")\n" ++ go (i+1) l ++ "\n" ++ go (i+1) r+        pp i (VertJoin {partTop = t, partBottom = b, outputWidth = c, outputHeight = r})+            = "VertJoin(" ++ show c ++ ", " ++ show r ++ ")\n"+              ++ go (i+1) t ++ "\n"+              ++ go (i+1) b+        pp i (CropRight {croppedImage, outputWidth, outputHeight})+            = "CropRight(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"+              ++ go (i+1) croppedImage+        pp i (CropLeft {croppedImage, leftSkip, outputWidth, outputHeight})+            = "CropLeft(" ++ show leftSkip ++ "->" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"+              ++ go (i+1) croppedImage+        pp i (CropBottom {croppedImage, outputWidth, outputHeight})+            = "CropBottom(" ++ show outputWidth ++ "," ++ show outputHeight ++ ")\n"+              ++ go (i+1) croppedImage+        pp i (CropTop {croppedImage, topSkip, outputWidth, outputHeight})+            = "CropTop("++ show outputWidth ++ "," ++ show topSkip ++ "->" ++ show outputHeight ++ ")\n"+              ++ go (i+1) croppedImage+        pp _ EmptyImage = "EmptyImage"+        +instance NFData Image where+    rnf EmptyImage = ()+    rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()+    rnf (CropLeft i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()+    rnf (CropBottom i w h) = i `deepseq` w `seq` h `seq` ()+    rnf (CropTop i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()+    rnf (BGFill w h) = w `seq` h `seq` ()+    rnf (VertJoin t b w h) = t `deepseq` b `deepseq` w `seq` h `seq` ()+    rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()+    rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()++-- | The width of an Image. This is the number display columns the image will occupy.+imageWidth :: Image -> Int+imageWidth HorizText { outputWidth = w } = w+imageWidth HorizJoin { outputWidth = w } = w+imageWidth VertJoin { outputWidth = w } = w+imageWidth BGFill { outputWidth = w } = w+imageWidth CropRight { outputWidth  = w } = w+imageWidth CropLeft { outputWidth  = w } = w+imageWidth CropBottom { outputWidth = w } = w+imageWidth CropTop { outputWidth = w } = w+imageWidth EmptyImage = 0++-- | The height of an Image. This is the number of display rows the image will occupy.+imageHeight :: Image -> Int+imageHeight HorizText {} = 1+imageHeight HorizJoin { outputHeight = h } = h+imageHeight VertJoin { outputHeight = h } = h+imageHeight BGFill { outputHeight = h } = h+imageHeight CropRight { outputHeight  = h } = h+imageHeight CropLeft { outputHeight  = h } = h+imageHeight CropBottom { outputHeight = h } = h+imageHeight CropTop { outputHeight = h } = h+imageHeight EmptyImage = 0++-- | Append in the Monoid instance is equivalent to <->. +instance Monoid Image where+    mempty = EmptyImage+    mappend = vertJoin++-- | combines two images side by side+--+-- Combines text chunks where possible. Assures outputWidth and outputHeight properties are not+-- violated.+--+-- The result image will have a width equal to the sum of the two images width.  And the height will+-- equal the largest height of the two images.  The area not defined in one image due to a height+-- missmatch will be filled with the background pattern.+--+-- TODO: the bg fill is biased towards top to bottom languages(?)+horizJoin :: Image -> Image -> Image+horizJoin EmptyImage i          = i+horizJoin i          EmptyImage = i+horizJoin i0@(HorizText a0 t0 w0 cw0) i1@(HorizText a1 t1 w1 cw1)+    | a0 == a1 = HorizText a0 (TL.append t0 t1) (w0 + w1) (cw0 + cw1)+    -- TODO: assumes horiz text height is always 1+    | otherwise  = HorizJoin i0 i1 (w0 + w1) 1+horizJoin i0 i1+    -- If the images are of the same height then no padding is required+    | h0 == h1 = HorizJoin i0 i1 w h0+    -- otherwise one of the images needs to be padded to the right size.+    | h0 < h1  -- Pad i0+        = let padAmount = h1 - h0+          in HorizJoin (VertJoin i0 (BGFill w0 padAmount) w0 h1) i1 w h1+    | h0 > h1  -- Pad i1+        = let padAmount = h0 - h1+          in HorizJoin i0 (VertJoin i1 (BGFill w1 padAmount) w1 h0) w h0+    where+        w0 = imageWidth i0+        w1 = imageWidth i1+        w   = w0 + w1+        h0 = imageHeight i0+        h1 = imageHeight i1+horizJoin _ _ = error "horizJoin applied to undefined values."++-- | combines two images vertically+--+-- The result image will have a height equal to the sum of the heights of both images.+-- The width will equal the largest width of the two images.+-- The area not defined in one image due to a width missmatch will be filled with the background+-- pattern.+--+-- TODO: the bg fill is biased towards right to left languages(?)+vertJoin :: Image -> Image -> Image+vertJoin EmptyImage i          = i+vertJoin i          EmptyImage = i+vertJoin i0 i1+    -- If the images are of the same width then no background padding is required+    | w0 == w1 = VertJoin i0 i1 w0 h+    -- Otherwise one of the images needs to be padded to the size of the other image.+    | w0 < w1+        = let padAmount = w1 - w0+          in VertJoin (HorizJoin i0 (BGFill padAmount h0) w1 h0) i1 w1 h+    | w0 > w1+        = let padAmount = w0 - w1+          in VertJoin i0 (HorizJoin i1 (BGFill padAmount h1) w0 h1) w0 h+    where+        w0 = imageWidth i0+        w1 = imageWidth i1+        h0 = imageHeight i0+        h1 = imageHeight i1+        h   = h0 + h1+vertJoin _ _ = error "vertJoin applied to undefined values."+
src/Graphics/Vty/Inline.hs view
@@ -9,32 +9,34 @@ -- text \" Styled! \" drawn over a red background and underlined. -- -- @---      t <- terminal_handle --      putStr \"Not styled. \"---      put_attr_change t $ do---          back_color red ---          apply_style underline+--      putAttrChange_ $ do+--          backColor red +--          applyStyle underline --      putStr \" Styled! \"---      put_attr_change t $ default_all+--      putAttrChange_ $ defaultAll --      putStrLn \"Not styled.\"---      release_terminal t -- @ ----- 'put_attr_change' outputs the control codes to the terminal device 'Handle'. This is a duplicate--- of the 'stdout' handle when the 'terminal_handle' was (first) acquired. If 'stdout' has since been+-- 'putAttrChange' outputs the control codes to the terminal device 'Handle'. This is a duplicate+-- of the 'stdout' handle when the 'terminalHandle' was (first) acquired. If 'stdout' has since been -- changed then 'putStr', 'putStrLn', 'print' etc.. will output to a different 'Handle' than--- 'put_attr_change'+-- 'putAttrChange' -- -- Copyright 2009-2010 Corey O'Connor {-# LANGUAGE BangPatterns #-} module Graphics.Vty.Inline ( module Graphics.Vty.Inline+                           , withVty                            )     where -import Graphics.Vty.Attributes+import Graphics.Vty import Graphics.Vty.DisplayAttributes-import Graphics.Vty.Terminal.Generic+import Graphics.Vty.Inline.Unsafe+import Graphics.Vty.Output.Interface +import Blaze.ByteString.Builder (writeToByteString)+ import Control.Applicative import Control.Monad.State.Strict @@ -47,56 +49,62 @@ type InlineM v = State Attr v  -- | Set the background color to the provided 'Color'-back_color :: Color -> InlineM ()-back_color c = modify $ flip mappend ( current_attr `with_back_color` c )+backColor :: Color -> InlineM ()+backColor c = modify $ flip mappend ( currentAttr `withBackColor` c )  -- | Set the foreground color to the provided 'Color'-fore_color :: Color -> InlineM ()-fore_color c = modify $ flip mappend ( current_attr `with_fore_color` c )+foreColor :: Color -> InlineM ()+foreColor c = modify $ flip mappend ( currentAttr `withForeColor` c )  -- | Attempt to change the 'Style' of the following text. -- -- If the terminal does not support the style change no error is produced. The style can still be -- removed.-apply_style :: Style -> InlineM ()-apply_style s = modify $ flip mappend ( current_attr `with_style` s )+applyStyle :: Style -> InlineM ()+applyStyle s = modify $ flip mappend ( currentAttr `withStyle` s )  -- | Attempt to remove the specified 'Style' from the display of the following text. ----- This will fail if apply_style for the given style has not been previously called. -remove_style :: Style -> InlineM ()-remove_style s_mask = modify $ \attr -> -    let style' = case attr_style attr of-                    Default -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."-                    KeepCurrent -> error $ "Graphics.Vty.Inline: Cannot remove_style if apply_style never used."-                    SetTo s -> s .&. complement s_mask-    in attr { attr_style = SetTo style' } +-- This will fail if applyStyle for the given style has not been previously called. +removeStyle :: Style -> InlineM ()+removeStyle sMask = modify $ \attr -> +    let style' = case attrStyle attr of+                    Default -> error $ "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."+                    KeepCurrent -> error $ "Graphics.Vty.Inline: Cannot removeStyle if applyStyle never used."+                    SetTo s -> s .&. complement sMask+    in attr { attrStyle = SetTo style' }   -- | Reset the display attributes-default_all :: InlineM ()-default_all = put def_attr+defaultAll :: InlineM ()+defaultAll = put defAttr --- | Apply the provided display attribute changes to the terminal.+-- | Apply the provided display attribute changes to the given terminal output device. ----- This also flushes the 'stdout' handle.-put_attr_change :: ( Applicative m, MonadIO m ) => TerminalHandle -> InlineM () -> m ()-put_attr_change t c = do-    bounds <- display_bounds t-    d <- display_context t bounds-    mfattr <- liftIO $ known_fattr <$> readIORef ( state_ref t )+-- This does not flush the terminal.+putAttrChange :: ( Applicative m, MonadIO m ) => Output -> InlineM () -> m ()+putAttrChange out c = liftIO $ do+    bounds <- displayBounds out+    dc <- displayContext out bounds+    mfattr <- prevFattr <$> readIORef (assumedStateRef out)     fattr <- case mfattr of                 Nothing -> do-                    liftIO $ marshall_to_terminal t (default_attr_required_bytes d) (serialize_default_attr d) -                    return $ FixedAttr default_style_mask Nothing Nothing+                    liftIO $ outputByteBuffer out $ writeToByteString $ writeDefaultAttr dc+                    return $ FixedAttr defaultStyleMask Nothing Nothing                 Just v -> return v-    let attr = execState c current_attr-        attr' = limit_attr_for_display d attr-        fattr' = fix_display_attr fattr attr'-        diffs = display_attr_diffs fattr fattr'-    liftIO $ hFlush stdout-    liftIO $ marshall_to_terminal t ( attr_required_bytes d fattr attr' diffs )-                                    ( serialize_set_attr d fattr attr' diffs )-    liftIO $ modifyIORef ( state_ref t ) $ \s -> s { known_fattr = Just fattr' }-    inline_hack d-    liftIO $ hFlush stdout+    let attr = execState c currentAttr+        attr' = limitAttrForDisplay out attr+        fattr' = fixDisplayAttr fattr attr'+        diffs = displayAttrDiffs fattr fattr'+    outputByteBuffer out $ writeToByteString $ writeSetAttr dc fattr attr' diffs+    modifyIORef (assumedStateRef out) $ \s -> s { prevFattr = Just fattr' }+    inlineHack dc +-- | Apply the provided display attributes changes to the terminal output device that was current at+-- the time this was first used. Which, for most use cases, is the current terminal.+--+-- This will flush the terminal output.+putAttrChange_ :: ( Applicative m, MonadIO m ) => InlineM () -> m ()+putAttrChange_ c = liftIO $ withOutput $ \out -> do+    hFlush stdout+    putAttrChange out c+    hFlush stdout
+ src/Graphics/Vty/Inline/Unsafe.hs view
@@ -0,0 +1,42 @@+module Graphics.Vty.Inline.Unsafe where++import Graphics.Vty+import Graphics.Vty.Config (userConfig)++import Data.Default+import Data.IORef++import System.IO.Unsafe++globalVty :: IORef (Maybe Vty)+{-# NOINLINE globalVty #-}+globalVty = unsafePerformIO $ newIORef Nothing++globalOutput :: IORef (Maybe Output)+{-# NOINLINE globalOutput #-}+globalOutput = unsafePerformIO $ newIORef Nothing++-- | This will create a Vty instance using 'mkVty' and execute an IO action provided that instance.+-- The created Vty instance will be stored to the unsafe 'IORef' 'globalVty'.+withVty :: (Vty -> IO b) -> IO b+withVty f = do+    mvty <- readIORef globalVty+    vty <- case mvty of+        Nothing -> do+            vty <- mkVty $ def+            writeIORef globalVty (Just vty)+            return vty+        Just vty -> return vty+    f vty++withOutput :: (Output -> IO b) -> IO b+withOutput f = do+    mout <- readIORef globalOutput+    out <- case mout of+        Nothing -> do+            config <- userConfig+            out <- outputForCurrentTerminal config+            writeIORef globalOutput (Just out)+            return out+        Just out -> return out+    f out
+ src/Graphics/Vty/Input.hs view
@@ -0,0 +1,200 @@+-- | The input layer for VTY. This provides methods for initializing an 'Input' structure which can+-- then be used to read 'Event's from the terminal.+--+-- The history of terminals has resulted in a broken input process. Some keys and combinations will+-- not reliably map to the expected events by any terminal program. Even those not using vty. There+-- is no 1:1 mapping from key events to bytes read from the terminal input device. In very limited+-- cases the terminal and vty's input process can be customized to resolve these issues.+--+-- See "Graphics.Vty.Config" for how to configure vty's input processing. Customizing terminfo and+-- the terminal is beyond the scope of this documentation.+--+-- = VTY's Implementation+--+-- One can get the brain rot trying to understand all this. So, as far as I can care...+--+-- There are two input modes:+--+--  1. 7 bit+--+--  2. 8 bit+--+-- 7 bit input is the default and the expected in most use cases. This is what vty uses.+--+-- == 7 bit input encoding+--+-- Control key combinations are represented by masking the two high bits of the 7bit input.  Back in+-- the day the control key actually grounded the two high bit wires: 6 and 7. This is why+-- control key combos map to single character events: The input bytes are identical. The input byte+-- is the bit encoding of the character with bits 6 and 7 masked.  Bit 6 is set by shift. Bit 6 and+-- 7 are masked by control. EG:+--+-- * Control-I is 'i', `01101001`, has bit 6 and 7 masked to become `00001001`. Which is the ASCII+-- and UTF-8 encoding of the tab key.+--+-- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero which makes `0000011` and+-- is the "End of Text" code.+--+-- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: This is the value 1 with bit 7+-- set and 6 unset.+--+-- * Hypothesis: Bit 6 is unset by upper case letters because, initially, there were only upper case+-- letters used and a 5 bit encoding.+--+-- == 8 bit encoding+--+-- The 8th bit was originally used for parity checking. Useless for emulators. Some terminal+-- emulators support a 8 bit input encoding. While this provides some advantages the actual usage is+-- low. Most systems use 7bit mode but recognize 8bit control characters when escaped. This is what+-- vty does.+--+-- == Escaped Control Keys+--+-- Using 7 bit input encoding the @ESC@ byte can signal the start of an encoded control key. To+-- differentiate a single @ESC@ eventfrom a control key the timing of the input is used.+--+-- 1. @ESC@ individually: @ESC@ byte; no bytes for 'singleEscPeriod'.+--+-- 2. control keys that contain @ESC@ in their encoding: The @ESC byte; followed by more bytes read+-- within 'singleEscPeriod'. All bytes up until the next valid input block are passed to the+-- classifier.+--+-- If the current runtime is the threaded runtime then the terminal's @VMIN@ and @VTIME@ behavior+-- reliably implement the above rules.  If the current runtime does not support forkOS then there is+-- currently no implementation.+--+-- Vty used to emulate @VMIN@ and @VTIME@. This was a input loop which did tricky things with+-- non-blocking reads and timers. The implementation was not reliable. A reliable implementation is+-- possible, but there are no plans to implement this.+--+-- == Unicode Input and Escaped Control Key Sequences+--+-- The input encoding determines how UTF-8 encoded characters are recognize.+--+-- * 7 bit mode: UTF-8 can be input unambiguiously. UTF-8 input is a superset of ASCII. UTF-8 does+-- not overlap escaped control key sequences. However, the escape key must be differentiated from+-- escaped control key sequences by the timing of the input bytes.+--+-- * 8 bit mode: UTF-8 cannot be input unambiguously. This does not require using the timing of+-- input bytes to differentiate the escape key. Many terminals do not support 8 bit mode.+--+-- == Terminfo+--+-- The terminfo system is used to determine how some keys are encoded. Terminfo is incomplete. In+-- some cases terminfo is incorrect. Vty assumes terminfo is correct but provides a mechanism to+-- override terminfo. See "Graphics.Vty.Config" specifically 'inputOverrides'.+--+-- == Terminal Input is Broken+--+-- Clearly terminal input has fundemental issues. There is no easy way to reliably resolve these+-- issues.+--+-- One resolution would be to ditch standard terminal interfaces entirely and just go directly to+-- scancodes. A reasonable option for vty if everybody used the linux kernel console. I hear GUIs+-- are popular these days. Sadly, GUI terminal emulators don't provide access to scancodes AFAIK.+--+-- All is lost? Not really. "Graphics.Vty.Config" supports customizing the input byte to event+-- mapping and xterm supports customizing the scancode to input byte mapping. With a lot of work a+-- user's system can be set up to encode all the key combos in an almost-sane manner.+--+-- There are other tricky work arounds that can be done. I have no interest in implementing most of+-- these. They are not really worth the time.+--+-- == Terminal Output is Also Broken+--+-- This isn't the only odd aspect of terminals due to historical aspects that no longer apply. EG:+-- Some terminfo capabilities specify millisecond delays. (Capabilities are how terminfo describes+-- the control sequence to output red, for instance) This is to account for the slow speed of+-- hardcopy teletype interfaces. Cause, uh, we totally still use those.+-- +-- The output encoding of colors and attributes are also rife with issues.+--+-- == See also+--+-- * http://www.leonerd.org.uk/hacks/fixterms/+--+-- In my experience this cannot resolve the issues without changes to the terminal emulator and+-- device.+module Graphics.Vty.Input ( Key(..)+                          , Modifier(..)+                          , Button(..)+                          , Event(..)+                          , Input(..)+                          , inputForCurrentTerminal+                          , inputForNameAndIO+                          )+    where++import Graphics.Vty.Config+import Graphics.Vty.Input.Events+import Graphics.Vty.Input.Loop+import Graphics.Vty.Input.Terminfo++import Control.Concurrent+import Control.Lens++import Data.Monoid++import qualified System.Console.Terminfo as Terminfo+import System.Environment+import System.Posix.Signals.Exts+import System.Posix.IO (stdInput)+import System.Posix.Types (Fd)++-- | Set up the current terminal for input.+-- This determines the current terminal then invokes 'inputForNameAndIO'+inputForCurrentTerminal :: Config -> IO Input+inputForCurrentTerminal config = do+    termName <- getEnv "TERM"+    inputForNameAndIO config termName stdInput++-- | Set up the terminal attached to the given Fd for input.  Returns a 'Input'.+--+-- The table used to determine the 'Events' to produce for the input bytes comes from+-- 'classifyMapForTerm'. Which is then overridden by the the applicable entries from+-- 'inputMap'.+--+-- The terminal device is configured with the attributes:+--+-- * IXON disabled+--      - disables software flow control on outgoing data. This stops the process from being+--        suspended if the output terminal cannot keep up. I presume this has little effect these+--        days. I hope this means that output will be buffered if the terminal cannot keep up. In the+--        old days the output might of been dropped?+-- +-- "raw" mode is used for input.+--+-- * ISIG disabled+--      - enables keyboard combinations that result in signals. TODO: should probably be a dynamic+--      option.+--+-- * ECHO disabled+--      - input is not echod to the output. TODO: should be a dynamic option.+--+-- * ICANON disabled+--      - canonical mode (line mode) input is not used. TODO: should be a dynamic option.+--+-- * IEXTEN disabled+--      - extended functions are disabled. TODO: Uh. Whatever these are.+--+inputForNameAndIO :: Config -> String -> Fd -> IO Input+inputForNameAndIO config termName termFd = do+    terminal <- Terminfo.setupTerm termName+    let inputOverrides = [(s,e) | (t,s,e) <- inputMap config, t == Nothing || t == Just termName]+        activeInputMap = classifyMapForTerm termName terminal `mappend` inputOverrides+    (setAttrs,unsetAttrs) <- attributeControl termFd+    setAttrs+    input <- initInputForFd config termName activeInputMap termFd +    let pokeIO = Catch $ do+            let e = error "vty internal failure: this value should not propagate to users"+            setAttrs+            writeChan (input^.eventChannel) (EvResize e e)+    _ <- installHandler windowChange pokeIO Nothing+    _ <- installHandler continueProcess pokeIO Nothing+    return $ input+        { shutdownInput = do+            shutdownInput input+            _ <- installHandler windowChange Ignore Nothing+            _ <- installHandler continueProcess Ignore Nothing+            unsetAttrs+        }
+ src/Graphics/Vty/Input/Classify.hs view
@@ -0,0 +1,78 @@+-- This makes a kind of tri. Has space efficiency issues with large input blocks.+-- Likely building a parser and just applying that would be better.+-- I did not write this so I might just rewrite it for better understanding. Which is not the best+-- of reasons.+-- TODO: measure and rewrite if required.+-- TODO: The ClassifyMap interface requires this code to always assure later entries override+-- earlier.+module Graphics.Vty.Input.Classify where++import Graphics.Vty.Input.Events++import Codec.Binary.UTF8.Generic (decode)++import Data.List(tails,inits)+import qualified Data.Map as M( fromList, lookup )+import Data.Maybe ( mapMaybe )+import qualified Data.Set as S( fromList, member )++import Data.Char+import Data.Word++data KClass+    = Valid Event [Char]+    | Invalid+    | Prefix+    deriving(Show, Eq)++compile :: ClassifyMap -> [Char] -> KClass+compile table = cl' where+    -- take all prefixes and create a set of these+    prefixSet = S.fromList $ concatMap (init . inits . fst) $ table+    eventForInput = M.fromList table+    cl' [] = Prefix+    cl' inputBlock = case M.lookup inputBlock eventForInput of+            Just e -> Valid e []+            Nothing -> case S.member inputBlock prefixSet of+                True -> Prefix+                -- if the inputBlock is exactly what is expected for an event then consume the whole+                -- block and return the event+                -- look up progressively smaller tails of the input block until an event is found+                -- The assumption is that the event that consumes the most input bytes should be+                -- produced.+                -- The test verifyFullSynInputToEvent2x verifies this.+                -- H: There will always be one match. The prefixSet contains, by definition, all+                -- prefixes of an event. +                False ->+                    let inputTails = init $ tail $ tails inputBlock+                    in case mapMaybe (\s -> (,) s `fmap` M.lookup s eventForInput) inputTails of+                        (s,e) : _ -> Valid e (drop (length s) inputBlock)+                        -- neither a prefix or a full event.+                        -- TODO: debug log+                        [] -> Invalid++classify, classifyTab :: ClassifyMap -> [Char] -> KClass++-- As soon as+classify _table s@(c:_) | ord c >= 0xC2+    = if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence+classify table other+    = classifyTab table other++classifyUtf8 :: [Char] -> KClass+classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of+    Just (unicodeChar, _) -> Valid (EvKey (KChar unicodeChar) []) []+    _ -> Invalid -- something bad happened; just ignore and continue.++classifyTab table = compile table++first :: (a -> b) -> (a,c) -> (b,c)+first f (x,y) = (f x, y)++utf8Length :: (Num t, Ord a, Num a) => a -> t+utf8Length c+    | c < 0x80 = 1+    | c < 0xE0 = 2+    | c < 0xF0 = 3+    | otherwise = 4+
+ src/Graphics/Vty/Input/Events.hs view
@@ -0,0 +1,43 @@+module Graphics.Vty.Input.Events where++import Data.Map (Map)++-- | Representations of non-modifier keys.+--+-- * KFun is indexed from 0 to 63. Range of supported FKeys varies by terminal and keyboard.+--+-- * KUpLeft, KUpRight, KDownLeft, KDownRight, KCenter support varies by terminal and keyboard.+--+-- * Actually, support for most of these but KEsc, KChar, KBS, and KEnter vary by terminal and+-- keyboard.+data Key = KEsc  | KChar Char | KBS | KEnter+         | KLeft | KRight | KUp | KDown+         | KUpLeft | KUpRight | KDownLeft | KDownRight | KCenter+         | KFun Int | KBackTab | KPrtScr | KPause | KIns+         | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu+    deriving (Eq,Show,Read,Ord)++-- | Modifier keys.  Key codes are interpreted such that users are more likely to+-- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will+-- generally correspond to the physical Alt key.+data Modifier = MShift | MCtrl | MMeta | MAlt+    deriving (Eq,Show,Read,Ord)++-- | Mouse buttons.+--+-- \todo not supported.+data Button = BLeft | BMiddle | BRight+    deriving (Eq,Show,Ord)++-- | Events.+data Event+    = EvKey Key [Modifier]+    -- | \todo mouse events are not supported+    | EvMouse Int Int Button [Modifier]+    -- | if read from 'eventChannel' this is the size at the time of the signal. If read from+    -- 'nextEvent' this is the size at the time the event was processed by Vty. Typically these are+    -- the same, but if somebody is resizing the terminal quickly they can be different.+    | EvResize Int Int+    deriving (Eq,Show,Ord)++type ClassifyMap = [(String,Event)]
+ src/Graphics/Vty/Input/Loop.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP #-}+-- | The input layer used to be a single function that correctly accounted for the non-threaded+-- runtime by emulating the terminal VMIN adn VTIME handling. This has been removed and replace with+-- a more straightforward parser. The non-threaded runtime is no longer supported.+--+-- This is an example of an algorithm where code coverage could be high, even 100%, but the+-- behavior is still under tested. I should collect more of these examples...+module Graphics.Vty.Input.Loop where++import Graphics.Vty.Config+import Graphics.Vty.Input.Classify+import Graphics.Vty.Input.Events++import Control.Applicative+import Control.Concurrent+import Control.Lens+import Control.Monad (when, mzero, forM_)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.State (StateT(..), evalStateT)+import Control.Monad.Trans.Reader (ReaderT(..))++import Data.Char+import Data.IORef+import Data.Word (Word8)++import Foreign ( allocaArray, peekArray, Ptr )+import Foreign.C.Types (CInt(..))++import System.IO+import System.Posix.IO (fdReadBuf)+import System.Posix.Terminal+import System.Posix.Types (Fd(..))++import Text.Printf (hPrintf)++data Input = Input+    { -- | Channel of events direct from input processing. Unlike 'nextEvent' this will not refresh+      -- the display if the next event is an 'EvResize'.+      _eventChannel  :: Chan Event+      -- | Shuts down the input processing. This should return the terminal input state to before+      -- the input initialized.+    , shutdownInput :: IO ()+      -- | Changes to this value are reflected after the next event.+    , _configRef :: IORef Config+      -- | File descriptor used for input.+    , _inputFd :: Fd+      -- | input debug log+    , _inputDebug :: Maybe Handle+    }++makeLenses ''Input++data InputBuffer = InputBuffer+    { _ptr :: Ptr Word8+    , _size :: Int+    }++makeLenses ''InputBuffer++data InputState = InputState+    { _unprocessedBytes :: String+    , _appliedConfig :: Config+    , _inputBuffer :: InputBuffer+    , _stopRequestRef :: IORef Bool+    , _classifier :: String -> KClass+    }++makeLenses ''InputState++type InputM a = StateT InputState (ReaderT Input IO) a++logMsg :: String -> InputM ()+logMsg msg = do+    d <- view inputDebug+    case d of+        Nothing -> return ()+        Just h -> liftIO $ hPutStrLn h msg >> hFlush h++-- this must be run on an OS thread dedicated to this input handling.+-- otherwise the terminal timing read behavior will block the execution of the lightweight threads.+loopInputProcessor :: InputM ()+loopInputProcessor = do+    readFromDevice >>= addBytesToProcess+    validEvents <- many parseEvent+    forM_ validEvents emit+    dropInvalid+    stopIfRequested <|> loopInputProcessor++addBytesToProcess :: String -> InputM ()+addBytesToProcess block = unprocessedBytes <>= block++emit :: Event -> InputM ()+emit event = do+    logMsg $ "parsed event: " ++ show event+    view eventChannel >>= liftIO . flip writeChan event++-- The timing requirements are assured by the VMIN and VTIME set for the device.+--+-- Precondition: Under the threaded runtime. Only current use is from a forkOS thread. That case+-- satisfies precondition.+-- TODO: When under the non-threaded runtime emulate VMIN and VTIME+readFromDevice :: InputM String+readFromDevice = do+    newConfig <- view configRef >>= liftIO . readIORef+    oldConfig <- use appliedConfig+    fd <- view inputFd+    when (newConfig /= oldConfig) $ do+        liftIO $ applyTimingConfig fd newConfig+        appliedConfig .= newConfig+    bufferPtr <- use $ inputBuffer.ptr+    maxBytes  <- use $ inputBuffer.size+    stringRep <- liftIO $ do+        bytesRead <- fdReadBuf fd bufferPtr (fromIntegral maxBytes)+        if bytesRead > 0+        then fmap (map $ chr . fromIntegral) $ peekArray (fromIntegral bytesRead) bufferPtr+        else return []+    logMsg $ "input bytes: " ++ show stringRep+    return stringRep++applyTimingConfig :: Fd -> Config -> IO ()+applyTimingConfig fd config =+    let vtime = min 255 $ singleEscPeriod config `div` 100000+    in setTermTiming fd 1 vtime++parseEvent :: InputM Event+parseEvent = do+    c <- use classifier+    b <- use unprocessedBytes+    case c b of+        Valid e remaining -> do+            unprocessedBytes .= remaining+            return e+        _                   -> mzero ++dropInvalid :: InputM ()+dropInvalid = do+    c <- use classifier+    b <- use unprocessedBytes+    when (c b == Invalid) $ unprocessedBytes .= []++stopIfRequested :: InputM ()+stopIfRequested = do+    True <- (liftIO . readIORef) =<< use stopRequestRef+    return ()++runInputProcessorLoop :: ClassifyMap -> Input -> IORef Bool -> IO ()+runInputProcessorLoop classifyTable input stopFlag = do+    let bufferSize = 1024+    allocaArray bufferSize $ \(bufferPtr :: Ptr Word8) -> do+        s0 <- InputState [] <$> readIORef (_configRef input)+                            <*> pure (InputBuffer bufferPtr bufferSize)+                            <*> pure stopFlag+                            <*> pure (classify classifyTable)+        runReaderT (evalStateT loopInputProcessor s0) input++attributeControl :: Fd -> IO (IO (), IO ())+attributeControl fd = do+    original <- getTerminalAttributes fd+    let vtyMode = foldl withoutMode original [ StartStopOutput, KeyboardInterrupts+                                             , EnableEcho, ProcessInput, ExtendedFunctions+                                             ]+    let setAttrs = setTerminalAttributes fd vtyMode Immediately+        unsetAttrs = setTerminalAttributes fd original Immediately+    return (setAttrs,unsetAttrs)++logClassifyMap :: Input -> String -> ClassifyMap -> IO()+logClassifyMap input termName classifyTable = case _inputDebug input of+    Nothing -> return ()+    Just h  -> do+        forM_ classifyTable $ \i -> case i of+            (inBytes, EvKey k mods) -> hPrintf h "map %s %s %s %s\n" (show termName)+                                                                     (show inBytes)+                                                                     (show k)+                                                                     (show mods)+            _ -> return ()++initInputForFd :: Config -> String -> ClassifyMap -> Fd -> IO Input+initInputForFd config termName classifyTable inFd = do+    applyTimingConfig inFd config+    stopFlag <- newIORef False+    input <- Input <$> newChan+                   <*> pure (writeIORef stopFlag True)+                   <*> newIORef config+                   <*> pure inFd+                   <*> maybe (return Nothing)+                             (\f -> Just <$> openFile f AppendMode)+                             (debugLog config)+    logClassifyMap input termName classifyTable+    _ <- forkOS $ runInputProcessorLoop classifyTable input stopFlag+    return input++foreign import ccall "vty_set_term_timing" setTermTiming :: Fd -> Int -> Int -> IO ()
+ src/Graphics/Vty/Input/Terminfo.hs view
@@ -0,0 +1,184 @@+module Graphics.Vty.Input.Terminfo where++import Graphics.Vty.Input.Events+import qualified Graphics.Vty.Input.Terminfo.ANSIVT as ANSIVT++import Control.Arrow+import System.Console.Terminfo++-- | queries the terminal for all capability based input sequences then adds on a terminal dependent+-- input sequence mapping.+--+-- For reference see:+--+-- * http://vimdoc.sourceforge.net/htmldoc/term.html+--+-- * vim74/src/term.c+--+-- * http://invisible-island.net/vttest/+--+-- * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html+--+-- This is painful. Terminfo is incomplete. The vim source implies that terminfo is also incorrect.+-- Vty assumes that the an internal terminfo table added to the system provided terminfo table is+-- correct.+--+-- 1. build terminfo table for all caps. Missing caps are not added.+--+-- 2. add tables for visible chars, esc, del plus ctrl and meta+--+-- 3. add internally defined table for given terminal type.+--+-- Precedence is currently implicit in the 'compile' algorithm. Which is a bit odd.+--+-- \todo terminfo meta is not supported.+-- \todo no 8bit+classifyMapForTerm :: String -> Terminal -> ClassifyMap+classifyMapForTerm termName term =+    concat $ capsClassifyMap term keysFromCapsTable+           : universalTable+           : termSpecificTables termName++-- | key table applicable to all terminals.+--+-- TODO: some probably only applicable to ANSI/VT100 terminals.+universalTable :: ClassifyMap+universalTable = concat [visibleChars, ctrlChars, ctrlMetaChars, specialSupportKeys]++capsClassifyMap :: Terminal -> [(String,Event)] -> ClassifyMap+capsClassifyMap terminal table = [(x,y) | (Just x,y) <- map extractCap table]+    where extractCap = first (getCapability terminal . tiGetStr)++-- | tables specific to a given terminal that are not derivable from terminfo.+--+-- TODO: Adds the ANSI/VT100/VT50 tables regardless of term identifier.+termSpecificTables :: String -> [ClassifyMap]+termSpecificTables _termName = ANSIVT.classifyTable++-- | Visible characters in the ISO-8859-1 and UTF-8 common set.+--+-- we limit to < 0xC1. The UTF8 sequence detector will catch all values 0xC2 and above before this+-- classify table is reached.+--+-- TODO: resolve+-- 1. start at ' '. The earlier characters are all 'ctrlChar'+visibleChars :: ClassifyMap+visibleChars = [ ([x], EvKey (KChar x) [])+               | x <- [' ' .. toEnum 0xC1]+               ]++-- | Non visible characters in the ISO-8859-1 and UTF-8 common set translated to ctrl + char.+--+-- \todo resolve CTRL-i is the same as tab+ctrlChars :: ClassifyMap+ctrlChars =+    [ ([toEnum x],EvKey (KChar y) [MCtrl])+    | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_'])+    , y /= 'i'  -- Resolve issue #3 where CTRL-i hides TAB.+    , y /= 'h'  -- CTRL-h should not hide BS+    ]++-- | Ctrl+Meta+Char+ctrlMetaChars :: ClassifyMap+ctrlMetaChars = map (\(s,EvKey c m) -> ('\ESC':s, EvKey c (MMeta:m))) ctrlChars++-- | esc, meta esc, delete, meta delete, enter, meta enter+specialSupportKeys :: ClassifyMap+specialSupportKeys =+    [ -- special support for ESC+      ("\ESC",EvKey KEsc []), ("\ESC\ESC",EvKey KEsc [MMeta])+    -- Special support for backspace+    , ("\DEL",EvKey KBS []), ("\ESC\DEL",EvKey KBS [MMeta])+    -- Special support for Enter+    , ("\ESC\^J",EvKey KEnter [MMeta]), ("\^J",EvKey KEnter [])+    -- explicit support for tab+    , ("\t", EvKey (KChar '\t') [])+    ]++-- | classify table directly generated from terminfo cap strings+--+-- these are:+--+-- * ka1 - keypad up-left+--+-- * ka3 - keypad up-right+--+-- * kb2 - keypad center+--+-- * kbs - keypad backspace+--+-- * kbeg - begin+--+-- * kcbt - back tab+--+-- * kc1 - keypad left-down+-- +-- * kc3 - keypad right-down+--+-- * kdch1 - delete+--+-- * kcud1 - down+--+-- * kend - end+-- +-- * kent - enter+--+-- * kf0 - kf63 - function keys+--+-- * khome - KHome+--+-- * kich1 - insert+--+-- * kcub1 - left+--+-- * knp - next page (page down)+--+-- * kpp - previous page (page up)+--+-- * kcuf1 - right+--+-- * kDC - shift delete+--+-- * kEND - shift end+--+-- * kHOM - shift home+--+-- * kIC - shift insert+--+-- * kLFT - shift left+--+-- * kRIT - shift right+--+-- * kcuu1 - up+keysFromCapsTable :: ClassifyMap+keysFromCapsTable =+    [ ("ka1",   EvKey KUpLeft    [])+    , ("ka3",   EvKey KUpRight   [])+    , ("kb2",   EvKey KCenter    [])+    , ("kbs",   EvKey KBS        [])+    , ("kbeg",  EvKey KBegin     [])+    , ("kcbt",  EvKey KBackTab   [])+    , ("kc1",   EvKey KDownLeft  [])+    , ("kc3",   EvKey KDownRight [])+    , ("kdch1", EvKey KDel       [])+    , ("kcud1", EvKey KDown      [])+    , ("kend",  EvKey KEnd       [])+    , ("kent",  EvKey KEnter     [])+    , ("khome", EvKey KHome      [])+    , ("kich1", EvKey KIns       [])+    , ("kcub1", EvKey KLeft      [])+    , ("knp",   EvKey KPageDown  [])+    , ("kpp",   EvKey KPageUp    [])+    , ("kcuf1", EvKey KRight     [])+    , ("kDC",   EvKey KDel       [MShift])+    , ("kEND",  EvKey KEnd       [MShift])+    , ("kHOM",  EvKey KHome      [MShift])+    , ("kIC",   EvKey KIns       [MShift])+    , ("kLFT",  EvKey KLeft      [MShift])+    , ("kRIT",  EvKey KRight     [MShift])+    , ("kcuu1", EvKey KUp        [])+    ] ++ functionKeyCapsTable++-- | cap names for function keys+functionKeyCapsTable :: ClassifyMap+functionKeyCapsTable = flip map [0..63] $ \n -> ("kf" ++ show n, EvKey (KFun n) [])
+ src/Graphics/Vty/Input/Terminfo/ANSIVT.hs view
@@ -0,0 +1,87 @@+-- | Input mappings for ANSI/VT100/VT50 terminals that is missing from terminfo.+-- +-- Or that are sent regardless of terminfo by terminal emulators. EG: Terminal emulators will often+-- use VT50 input bytes regardless of declared terminal type. This provides compatibility with+-- programs that don't follow terminfo.+module Graphics.Vty.Input.Terminfo.ANSIVT where++import Graphics.Vty.Input.Events++-- | Encoding for navigation keys.+--+-- TODO: This is not the same as the input bytes pulled from teh caps table.+navKeys0 :: ClassifyMap+navKeys0 =+    [ k "G" KCenter+    , k "P" KPause+    , k "A" KUp+    , k "B" KDown+    , k "C" KRight+    , k "D" KLeft+    , k "H" KHome+    , k "F" KEnd+    , k "E" KBegin+    ]+    where k c s = ("\ESC["++c,EvKey s [])++-- | encoding for shift, meta and ctrl plus arrows/home/end+navKeys1 :: ClassifyMap+navKeys1 =+   [("\ESC[" ++ charCnt ++ show mc++c,EvKey s m)+    | charCnt <- ["1;", ""], -- we can have a count or not+    (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),+               ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes+    (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)] -- directions and their codes+   ]++-- | encoding for ins, del, pageup, pagedown, home, end+navKeys2 :: ClassifyMap+navKeys2 =+    let k n s = ("\ESC["++show n++"~",EvKey s [])+    in zipWith k [2::Int,3,5,6,1,4]+                 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]++-- | encoding for ctrl + ins, del, pageup, pagedown, home, end+navKeys3 :: ClassifyMap+navKeys3 =+    let k n s = ("\ESC["++show n++";5~",EvKey s [MCtrl])+    in zipWith k [2::Int,3,5,6,1,4]+                 [KIns,KDel,KPageUp,KPageDown,KHome,KEnd]++-- | encoding for shift plus function keys+-- +-- According to +--+--  * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html+--+-- This encoding depends on the terminal.+functionKeys1 :: ClassifyMap+functionKeys1 =+    let f ff nrs m = [ ("\ESC["++show n++"~",EvKey (KFun $ n-(nrs!!0)+ff) m) | n <- nrs ] in+    concat [f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ]++-- | encoding for meta plus char+--+-- TODO: resolve -+--+-- 1. removed 'ESC' from second list due to duplication with "special_support_keys".+-- 2. removed '[' from second list due to conflict with 7-bit encoding for ESC. Whether meta+[ is+-- the same as ESC should examine km and current encoding.+-- 3. stopped enumeration at '~' instead of '\DEL'. The latter is mapped to KBS by+-- special_support_keys.+functionKeys2 :: ClassifyMap+functionKeys2 = [ ('\ESC':[x],EvKey (KChar x) [MMeta])+                  | x <- '\t':[' ' .. '~']+                  , x /= '['+                  ]++classifyTable :: [ClassifyMap]+classifyTable =+    [ navKeys0+    , navKeys1+    , navKeys2+    , navKeys3+    , functionKeys1+    , functionKeys2+    ]+
− src/Graphics/Vty/LLInput.hs
@@ -1,250 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--- Copyright 2009-2010 Corey O'Connor-{-# OPTIONS_GHC -Wall #-}-{-# LANGUAGE ForeignFunctionInterface #-}-module Graphics.Vty.LLInput ( Key(..)-                            , Modifier(..)-                            , Button(..)-                            , Event(..)-                            , initTermInput-                            )-    where--import Data.Char-import Data.Maybe ( mapMaybe-                  )-import Data.List( inits )-import Data.Word-import qualified Data.Map as M( fromList, lookup )-import qualified Data.Set as S( fromList, member )--import Codec.Binary.UTF8.Generic (decode)--import Control.Monad (when)-import Control.Concurrent-import Control.Exception--import System.Console.Terminfo--import System.Posix.Signals.Exts-import System.Posix.Terminal-import System.Posix.IO ( stdInput-                        ,fdReadBuf-                        ,setFdOption-                        ,FdOption(..)-                       )--import Foreign ( alloca, poke, peek, Ptr )---- |Representations of non-modifier keys.-data Key = KEsc | KFun Int | KBackTab | KPrtScr | KPause | KASCII Char | KBS | KIns-         | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin |  KNP5 | KUp | KMenu-         | KLeft | KDown | KRight | KEnter-    deriving (Eq,Show,Ord)---- |Modifier keys.  Key codes are interpreted such that users are more likely to--- have Meta than Alt; for instance on the PC Linux console, 'MMeta' will--- generally correspond to the physical Alt key.-data Modifier = MShift | MCtrl | MMeta | MAlt-    deriving (Eq,Show,Ord)---- |Mouse buttons.  Not yet used.-data Button = BLeft | BMiddle | BRight-    deriving (Eq,Show,Ord)---- |Generic events.-data Event = EvKey Key [Modifier] | EvMouse Int Int Button [Modifier]-           | EvResize Int Int-    deriving (Eq,Show,Ord)--data KClass = Valid Key [Modifier] | Invalid | Prefix | MisPfx Key [Modifier] [Char]-    deriving(Show)---- | Set up the terminal for input.  Returns a function which reads key--- events, and a function for shutting down the terminal access.-initTermInput :: Int -> Terminal -> IO (IO Event, IO ())-initTermInput escDelay terminal = do-  eventChannel <- newChan-  inputChannel <- newChan-  hadInput <- newEmptyMVar-  oattr <- getTerminalAttributes stdInput-  let nattr = foldl withoutMode oattr [StartStopOutput, KeyboardInterrupts,-                                       EnableEcho, ProcessInput, ExtendedFunctions]-  setTerminalAttributes stdInput nattr Immediately-  set_term_timing-  let inputToEventThread :: IO ()-      inputToEventThread = loop []-          where loop kb = case (classify kb) of-                              Prefix       -> do c <- readChan inputChannel-                                                 loop (kb ++ [c])-                              Invalid      -> do c <- readChan inputChannel-                                                 loop [c]-                              MisPfx k m s -> writeChan eventChannel (EvKey k m) >> loop s-                              Valid k m    -> writeChan eventChannel (EvKey k m) >> loop ""--      finishAtomicInput = writeChan inputChannel '\xFFFE'--      inputThread :: IO ()-      inputThread = do-        _ <- alloca $ \(input_buffer :: Ptr Word8) -> do-            let loop = do-                    setFdOption stdInput NonBlockingRead False-                    threadWaitRead stdInput-                    setFdOption stdInput NonBlockingRead True-                    _ <- try readAll :: IO (Either IOException ())-                    when (escDelay == 0) finishAtomicInput-                    loop-                readAll = do-                    poke input_buffer 0-                    bytes_read <- fdReadBuf stdInput input_buffer 1-                    input_char <- fmap (chr . fromIntegral) $ peek input_buffer-                    when (bytes_read > 0) $ do-                        _ <- tryPutMVar hadInput () -- signal input-                        writeChan inputChannel input_char-                        readAll-            loop-        return ()--      -- | If there is no input for some time, this thread puts '\xFFFE' in the-      -- inputChannel.-      noInputThread :: IO ()-      noInputThread = when (escDelay > 0) loop-            where loop = do-                    takeMVar hadInput -- wait for some input-                    threadDelay escDelay -- microseconds-                    hadNoInput <- isEmptyMVar hadInput -- no input yet?-                    -- TODO(corey): there is a race between here and the inputThread.-                    when hadNoInput $ do-                        finishAtomicInput-                    loop---      compile :: [[([Char],(Key,[Modifier]))]] -> [Char] -> KClass-      compile lst = cl' where-          lst' = concat lst-          pfx = S.fromList $ concatMap (init . inits . fst) $ lst'-          mlst = M.fromList lst'-          cl' str = case S.member str pfx of-                      True -> Prefix-                      False -> case M.lookup str mlst of-                                 Just (k,m) -> Valid k m-                                 Nothing -> case head $ mapMaybe (\s -> (,) s `fmap` M.lookup s mlst) $ init $ inits str of-                                              (s,(k,m)) -> MisPfx k m (drop (length s) str)--      -- ANSI specific bits---      classify, classifyTab :: [Char] -> KClass--      -- As soon as-      classify "\xFFFE" = Invalid-      classify s@(c:_) | ord c >= 0xC2 =-          if utf8Length (ord c) > length s then Prefix else classifyUtf8 s -- beginning of an utf8 sequence-      classify other = classifyTab other--      classifyUtf8 s = case decode ((map (fromIntegral . ord) s) :: [Word8]) of-          Just (unicodeChar, _) -> Valid (KASCII unicodeChar) []-          _ -> Invalid -- something bad happened; just ignore and continue.--      classifyTab = compile (caps_classify_table : ansi_classify_table)--      caps_tabls = [("khome", (KHome, [])),-                    ("kend",  (KEnd,  [])),-                    ("cbt",   (KBackTab, [])),-                    ("kcud1", (KDown,  [])),-                    ("kcuu1", (KUp,  [])),-                    ("kcuf1", (KRight,  [])),-                    ("kcub1", (KLeft,  [])),--                    ("kLFT", (KLeft, [MShift])),-                    ("kRIT", (KRight, [MShift]))-                   ]--      caps_classify_table = [(x,y) | (Just x,y) <- map (first (getCapability terminal . tiGetStr)) $ caps_tabls]--      ansi_classify_table :: [[([Char], (Key, [Modifier]))]]-      ansi_classify_table =-         [ let k c s = ("\ESC["++c,(s,[])) in [ k "G" KNP5-                                              , k "P" KPause-                                              , k "A" KUp-                                              , k "B" KDown-                                              , k "C" KRight-                                              , k "D" KLeft-                                              , k "H" KHome-                                              , k "F" KEnd-                                              , k "E" KBegin-                                              ],--           -- Support for arrows and KHome/KEnd-           [("\ESC[" ++ charCnt ++ show mc++c,(s,m))-            | charCnt <- ["1;", ""], -- we can have a count or not-            (m,mc) <- [([MShift],2::Int), ([MCtrl],5), ([MMeta],3),-                       ([MShift, MCtrl],6), ([MShift, MMeta],4)], -- modifiers and their codes-            (c,s) <- [("A", KUp), ("B", KDown), ("C", KRight), ("D", KLeft), ("H", KHome), ("F", KEnd)] -- directions and their codes-           ],--           let k n s = ("\ESC["++show n++"~",(s,[]))-           in zipWith k [2::Int,3,5,6,1,4]-                        [KIns,KDel,KPageUp,KPageDown,KHome,KEnd],--           let k n s = ("\ESC["++show n++";5~",(s,[MCtrl]))-           in zipWith k [2::Int,3,5,6,1,4]-                        [KIns,KDel,KPageUp,KPageDown,KHome,KEnd],--           -- Support for simple characters.-           [ (x:[],(KASCII x,[])) | x <- map toEnum [0..255] ],--           -- Support for function keys (should use terminfo)-           [ ("\ESC[["++[toEnum(64+i)],(KFun i,[])) | i <- [1..5] ],-           let f ff nrs m = [ ("\ESC["++show n++"~",(KFun (n-(nrs!!0)+ff), m)) | n <- nrs ] in-           concat [ f 6 [17..21] [], f 11 [23,24] [], f 1 [25,26] [MShift], f 3 [28,29] [MShift], f 5 [31..34] [MShift] ],-           [ ('\ESC':[x],(KASCII x,[MMeta])) | x <- '\ESC':'\t':[' ' .. '\DEL'] ],--           -- Ctrl+Char-           [ ([toEnum x],(KASCII y,[MCtrl]))-              | (x,y) <- zip ([0..31]) ('@':['a'..'z']++['['..'_']),-                y /= 'i' -- Resolve issue #3 where CTRL-i hides TAB.-           ],--           -- Ctrl+Meta+Char-           [ ('\ESC':[toEnum x],(KASCII y,[MMeta,MCtrl])) | (x,y) <- zip [0..31] ('@':['a'..'z']++['['..'_']) ],--           -- Special support-           [ -- special support for ESC-             ("\ESC",(KEsc,[])) , ("\ESC\ESC",(KEsc,[MMeta])),--             -- Special support for backspace-             ("\DEL",(KBS,[])), ("\ESC\DEL",(KBS,[MMeta])),--             -- Special support for Enter-             ("\ESC\^J",(KEnter,[MMeta])), ("\^J",(KEnter,[])) ]-         ]--  eventThreadId <- forkIO $ inputToEventThread-  inputThreadId <- forkIO $ inputThread-  noInputThreadId <- forkIO $ noInputThread-  let pokeIO = (Catch $ do let e = error "(getsize in input layer)"-                           setTerminalAttributes stdInput nattr Immediately-                           writeChan eventChannel (EvResize e e))-  _ <- installHandler windowChange pokeIO Nothing-  _ <- installHandler continueProcess pokeIO Nothing-  -- TODO(corey): killThread is a bit risky for my tastes.-  let uninit = do killThread eventThreadId-                  killThread inputThreadId-                  killThread noInputThreadId-                  _ <- installHandler windowChange Ignore Nothing-                  _ <- installHandler continueProcess Ignore Nothing-                  setTerminalAttributes stdInput oattr Immediately-  return (readChan eventChannel, uninit)--first :: (a -> b) -> (a,c) -> (b,c)-first f (x,y) = (f x, y)--utf8Length :: (Num t, Ord a, Num a) => a -> t-utf8Length c-    | c < 0x80 = 1-    | c < 0xE0 = 2-    | c < 0xF0 = 3-    | otherwise = 4--foreign import ccall "vty_set_term_timing" set_term_timing :: IO ()
+ src/Graphics/Vty/Output.hs view
@@ -0,0 +1,140 @@+--  | Output interface.+--+--  Access to the current terminal or a specific terminal device.+--+--  See also:+--+--  1. "Graphics.Vty.Output": This instantiates an abtract interface to the terminal interface based+--  on the TERM and COLORTERM environment variables. +--  +--  2. "Graphics.Vty.Output.Interface": Defines the generic interface all terminals need to implement.+--+--  3. "Graphics.Vty.Output.TerminfoBased": Defines a terminal instance that uses terminfo for all+--  control strings.  No attempt is made to change the character set to UTF-8 for these terminals.+--  I don't know a way to reliably determine if that is required or how to do so.+--+--  4. "Graphics.Vty.Output.XTermColor": This module contains an interface suitable for xterm-like+--  terminals. These are the terminals where TERM == xterm. This does use terminfo for as many+--  control codes as possible. +module Graphics.Vty.Output ( module Graphics.Vty.Output+                           , Output(..) -- \todo hide constructors+                           , AssumedState(..)+                           , DisplayContext(..) -- \todo hide constructors+                           , outputPicture+                           , displayContext+                           )+    where+++import Graphics.Vty.Prelude++import Graphics.Vty.Config++import Graphics.Vty.Output.Interface+import Graphics.Vty.Output.MacOSX as MacOSX+import Graphics.Vty.Output.XTermColor as XTermColor+import Graphics.Vty.Output.TerminfoBased as TerminfoBased++import Blaze.ByteString.Builder (writeToByteString)++import Control.Exception ( SomeException, try )+import Control.Monad.Trans++import Data.List ( isPrefixOf )++import GHC.IO.Handle++import System.Environment+import System.IO++-- | Returns a `Output` for the current terminal as determined by TERM.+--+-- The specific Output implementation used is hidden from the API user. All terminal implementations+-- are assumed to perform more, or less, the same. Currently all implementations use terminfo for at+-- least some terminal specific information. This is why platforms without terminfo are not+-- supported. However, as mentioned before, any specifics about it being based on terminfo are+-- hidden from the API user.  If a terminal implementation is developed for a terminal for a+-- platform without terminfo support then Vty should work as expected on that terminal.+--+-- Selection of a terminal is done as follows:+--+--      * If TERM == xterm+--          then the terminal might be one of the Mac OS X .app terminals. Check if that might be+--          the case and use MacOSX if so.+--          otherwise use XTermColor.+--+--      * for any other TERM value TerminfoBased is used.+--+-- To differentiate between Mac OS X terminals this uses the TERM_PROGRAM environment variable.+-- However, an xterm started by Terminal or iTerm *also* has TERM_PROGRAM defined since the+-- environment variable is not reset/cleared by xterm. However a Terminal.app or iTerm.app started+-- from an xterm under X11 on mac os x will likely be done via open. Since this does not propogate+-- environment variables (I think?) this assumes that XTERM_VERSION will never be set for a true+-- Terminal.app or iTerm.app session.+--+-- The file descriptor used for output will a be a duplicate of the current stdout file descriptor.+--+-- \todo add an implementation for windows that does not depend on terminfo. Should be installable+-- with only what is provided in the haskell platform. Use ansi-terminal+outputForCurrentTerminal :: ( Applicative m, MonadIO m ) => Config -> m Output+outputForCurrentTerminal _config = do+    termType <- liftIO $ getEnv "TERM"+    outHandle <- liftIO $ hDuplicate stdout+    outputForNameAndIO termType outHandle++-- | gives an output method structure for a terminal with the given name and the given 'Handle'.+outputForNameAndIO :: (Applicative m, MonadIO m) => String -> Handle -> m Output+outputForNameAndIO termType outHandle = do+    t <- if "xterm" `isPrefixOf` termType+        then do+            maybeTerminalApp <- mGetEnv "TERM_PROGRAM"+            case maybeTerminalApp of+                Nothing+                    -> XTermColor.reserveTerminal termType outHandle+                Just v | v == "Apple_Terminal" || v == "iTerm.app" +                    -> do+                        maybeXterm <- mGetEnv "XTERM_VERSION"+                        case maybeXterm of+                            Nothing -> MacOSX.reserveTerminal v outHandle+                            Just _  -> XTermColor.reserveTerminal termType outHandle+                -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app+                -- like terminal?+                _   -> XTermColor.reserveTerminal termType outHandle+        -- Not an xterm-like terminal. try for generic terminfo.+        else TerminfoBased.reserveTerminal termType outHandle+    return t+    where+        mGetEnv var = do+            mv <- liftIO $ try $ getEnv var+            case mv of+                Left (_e :: SomeException)  -> return $ Nothing+                Right v -> return $ Just v++-- | Sets the cursor position to the given output column and row. +--+-- This is not necessarially the same as the character position with the same coordinates.+-- Characters can be a variable number of columns in width.+--+-- Currently, the only way to set the cursor position to a given character coordinate is to specify+-- the coordinate in the Picture instance provided to outputPicture or refresh.+setCursorPos :: MonadIO m => Output -> Int -> Int -> m ()+setCursorPos t x y = do+    bounds <- displayBounds t+    when (x >= 0 && x < regionWidth bounds && y >= 0 && y < regionHeight bounds) $ do+        dc <- displayContext t bounds+        liftIO $ outputByteBuffer t $ writeToByteString $ writeMoveCursor dc x y++-- | Hides the cursor+hideCursor :: MonadIO m => Output -> m ()+hideCursor t = do+    bounds <- displayBounds t+    dc <- displayContext t bounds+    liftIO $ outputByteBuffer t $ writeToByteString $ writeHideCursor dc+    +-- | Shows the cursor+showCursor :: MonadIO m => Output -> m ()+showCursor t = do+    bounds <- displayBounds t+    dc <- displayContext t bounds+    liftIO $ outputByteBuffer t $ writeToByteString $ writeShowCursor dc+
+ src/Graphics/Vty/Output/Interface.hs view
@@ -0,0 +1,256 @@+-- Copyright Corey O'Connor+-- General philosophy is: MonadIO is for equations exposed to clients.+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+module Graphics.Vty.Output.Interface+where++import Graphics.Vty.Prelude++import Graphics.Vty.Picture+import Graphics.Vty.PictureToSpans+import Graphics.Vty.Span++import Graphics.Vty.DisplayAttributes++import Blaze.ByteString.Builder (Write, writeToByteString)+import Blaze.ByteString.Builder.ByteString (writeByteString)++import Control.Monad.Trans++import qualified Data.ByteString as BS+import Data.IORef+import Data.Monoid (mempty, mappend)+import qualified Data.Text.Encoding as T+import qualified Data.Text.Lazy as TL+import qualified Data.Vector as Vector++data Output = Output+    { -- | Text identifier for the output device. Used for debugging. +      terminalID :: String+    , releaseTerminal :: MonadIO m => m ()+    -- | Clear the display and initialize the terminal to some initial display state. +    --+    -- The expectation of a program is that the display starts in some initial state. +    -- The initial state would consist of fixed values:+    --+    --  - cursor at top left+    --  - UTF-8 character encoding+    --  - drawing characteristics are the default+    --+    -- The abstract operation I think all these behaviors are instances of is reserving exclusive+    -- access to a display such that:+    --+    --  - The previous state cannot be determined+    --  - When exclusive access to a display is released the display returns to the previous state.+    , reserveDisplay :: MonadIO m => m ()+    -- | Return the display to the state before `reserveDisplay`+    -- If no previous state then set the display state to the initial state.+    , releaseDisplay :: MonadIO m => m ()+    -- | Returns the current display bounds.+    , displayBounds :: MonadIO m => m DisplayRegion+    -- | Output the byte string to the terminal device.+    , outputByteBuffer :: BS.ByteString -> IO ()+    -- | Maximum number of colors supported by the context.+    , contextColorCount :: Int+    -- | if the cursor can be shown / hidden+    , supportsCursorVisibility :: Bool+    , assumedStateRef :: IORef AssumedState+    -- | Acquire display access to the given region of the display.+    -- Currently all regions have the upper left corner of (0,0) and the lower right corner at +    -- (max displayWidth providedWidth, max displayHeight providedHeight)+    , mkDisplayContext :: MonadIO m => Output -> DisplayRegion -> m DisplayContext+    }++displayContext :: MonadIO m => Output -> DisplayRegion -> m DisplayContext+displayContext t = liftIO . mkDisplayContext t t++data AssumedState = AssumedState+    { prevFattr :: Maybe FixedAttr+    , prevOutputOps :: Maybe DisplayOps+    }++initialAssumedState :: AssumedState+initialAssumedState = AssumedState Nothing Nothing++data DisplayContext = DisplayContext+    { contextDevice :: Output+    -- | Provide the bounds of the display context. +    , contextRegion :: DisplayRegion+    --  | sets the output position to the specified row and column. Where the number of bytes+    --  required for the control codes can be specified seperate from the actual byte sequence.+    , writeMoveCursor :: Int -> Int -> Write+    , writeShowCursor :: Write+    , writeHideCursor :: Write+    --  | Assure the specified output attributes will be applied to all the following text until the+    --  next output attribute change. Where the number of bytes required for the control codes can+    --  be specified seperate from the actual byte sequence.  The required number of bytes must be+    --  at least the maximum number of bytes required by any attribute changes.  The serialization+    --  equations must provide the ptr to the next byte to be specified in the output buffer.+    --+    --  The currently applied display attributes are provided as well. The Attr data type can+    --  specify the style or color should not be changed from the currently applied display+    --  attributes. In order to support this the currently applied display attributes are required.+    --  In addition it may be possible to optimize the state changes based off the currently applied+    --  display attributes.+    , writeSetAttr :: FixedAttr -> Attr -> DisplayAttrDiff -> Write+    -- | Reset the display attributes to the default display attributes+    , writeDefaultAttr :: Write+    , writeRowEnd :: Write+    -- | See `Graphics.Vty.Output.XTermColor.inlineHack`+    , inlineHack :: IO ()+    }++-- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.+writeUtf8Text  :: BS.ByteString -> Write+writeUtf8Text = writeByteString++-- | Displays the given `Picture`.+--+--      0. The image is cropped to the display size. +--+--      1. Converted into a sequence of attribute changes and text spans.+--      +--      2. The cursor is hidden.+--+--      3. Serialized to the display.+--+--      4. The cursor is then shown and positioned or kept hidden.+-- +-- todo: specify possible IO exceptions.+-- abstract from IO monad to a MonadIO instance.+outputPicture :: MonadIO m => DisplayContext -> Picture -> m ()+outputPicture dc pic = liftIO $ do+    as <- readIORef (assumedStateRef $ contextDevice dc)+    let manipCursor = supportsCursorVisibility (contextDevice dc)+        r = contextRegion dc+        ops = displayOpsForPic pic r+        initialAttr = FixedAttr defaultStyleMask Nothing Nothing+        -- Diff the previous output against the requested output. Differences are currently on a per-row+        -- basis.+        -- \todo handle resizes that crop the dominate directions better.+        diffs :: [Bool] = case prevOutputOps as of+            Nothing -> replicate (fromEnum $ regionHeight $ effectedRegion ops) True+            Just previousOps -> if effectedRegion previousOps /= effectedRegion ops+                then replicate (displayOpsRows ops) True+                else zipWith (/=) (Vector.toList previousOps)+                                  (Vector.toList ops)+        -- build the Write corresponding to the output image+        out = (if manipCursor then writeHideCursor dc else mempty)+              `mappend` writeDefaultAttr dc+              `mappend` writeOutputOps dc initialAttr diffs ops+              `mappend`+                (case picCursor pic of+                    _ | not manipCursor -> mempty+                    NoCursor             -> mempty+                    Cursor x y           ->+                        let m = cursorOutputMap ops $ picCursor pic+                            (ox, oy) = charToOutputPos m (x,y)+                        in writeShowCursor dc `mappend` writeMoveCursor dc ox oy+                )+    -- ... then serialize+    outputByteBuffer (contextDevice dc) (writeToByteString out)+    -- Cache the output spans.+    let as' = as { prevOutputOps = Just ops }+    writeIORef (assumedStateRef $ contextDevice dc) as'++writeOutputOps :: DisplayContext -> FixedAttr -> [Bool] -> DisplayOps -> Write+writeOutputOps dc inFattr diffs ops =+    let (_, out, _, _) = Vector.foldl' writeOutputOps' +                                       (0, mempty, inFattr, diffs) +                                       ops+    in out+    where +        writeOutputOps' (y, out, fattr, True : diffs') spanOps+            = let (spanOut, fattr') = writeSpanOps dc y fattr spanOps+              in (y+1, out `mappend` spanOut, fattr', diffs')+        writeOutputOps' (y, out, fattr, False : diffs') _spanOps+            = (y + 1, out, fattr, diffs')+        writeOutputOps' (_y, _out, _fattr, []) _spanOps+            = error "vty - output spans without a corresponding diff."++writeSpanOps :: DisplayContext -> Int -> FixedAttr -> SpanOps -> (Write, FixedAttr)+writeSpanOps dc y inFattr spanOps =+    -- The first operation is to set the cursor to the start of the row+    let start = writeMoveCursor dc 0 y+    -- then the span ops are serialized in the order specified+    in Vector.foldl' (\(out, fattr) op -> case writeSpanOp dc op fattr of+                                            (opOut, fattr') -> (out `mappend` opOut, fattr')+                     )+                     (start, inFattr)+                     spanOps++writeSpanOp :: DisplayContext -> SpanOp -> FixedAttr -> (Write, FixedAttr)+writeSpanOp dc (TextSpan attr _ _ str) fattr =+    let attr' = limitAttrForDisplay (contextDevice dc) attr+        fattr' = fixDisplayAttr fattr attr'+        diffs = displayAttrDiffs fattr fattr'+        out =  writeSetAttr dc fattr attr' diffs+               `mappend` writeUtf8Text (T.encodeUtf8 $ TL.toStrict str)+    in (out, fattr')+writeSpanOp _dc (Skip _) _fattr = error "writeSpanOp for Skip"+writeSpanOp dc (RowEnd _) fattr = (writeRowEnd dc, fattr)++-- | The cursor position is given in X,Y character offsets. Due to multi-column characters this+-- needs to be translated to column, row positions.+data CursorOutputMap = CursorOutputMap+    { charToOutputPos :: (Int, Int) -> (Int, Int)+    } ++cursorOutputMap :: DisplayOps -> Cursor -> CursorOutputMap+cursorOutputMap spanOps _cursor = CursorOutputMap+    { charToOutputPos = \(cx, cy) -> (cursorColumnOffset spanOps cx cy, cy)+    }++cursorColumnOffset :: DisplayOps -> Int -> Int -> Int+cursorColumnOffset ops cx cy =+    let cursorRowOps = Vector.unsafeIndex ops (fromEnum cy)+        (outOffset, _, _) +            = Vector.foldl' ( \(d, currentCx, done) op -> +                        if done then (d, currentCx, done) else case spanOpHasWidth op of+                            Nothing -> (d, currentCx, False)+                            Just (cw, ow) -> case compare cx (currentCx + cw) of+                                    GT -> ( d + ow+                                          , currentCx + cw+                                          , False +                                          )+                                    EQ -> ( d + ow+                                          , currentCx + cw+                                          , True +                                          )+                                    LT -> ( d + columnsToCharOffset (cx - currentCx) op+                                          , currentCx + cw+                                          , True+                                          )+                      )+                      (0, 0, False)+                      cursorRowOps+    in outOffset++-- | Not all terminals support all display attributes. This filters a display attribute to what the+-- given terminal can display.+limitAttrForDisplay :: Output -> Attr -> Attr+limitAttrForDisplay t attr +    = attr { attrForeColor = clampColor $ attrForeColor attr+           , attrBackColor = clampColor $ attrBackColor attr+           }+    where+        clampColor Default     = Default+        clampColor KeepCurrent = KeepCurrent+        clampColor (SetTo c)   = clampColor' c+        clampColor' (ISOColor v) +            | contextColorCount t < 8            = Default+            | contextColorCount t < 16 && v >= 8 = SetTo $ ISOColor (v - 8)+            | otherwise                          = SetTo $ ISOColor v+        clampColor' (Color240 v)+            -- TODO: Choose closes ISO color?+            | contextColorCount t < 8            = Default+            | contextColorCount t < 16           = Default+            | contextColorCount t == 240         = SetTo $ Color240 v+            | otherwise +                = let p :: Double = fromIntegral v / 240.0 +                      v' = floor $ p * (fromIntegral $ contextColorCount t)+                  in SetTo $ Color240 v'
+ src/Graphics/Vty/Output/MacOSX.hs view
@@ -0,0 +1,63 @@+-- Copyright Corey O'Connor+-- The standard Mac OS X terminals Terminal.app and iTerm both declare themselves to be+-- "xterm-color" by default. However the terminfo database for xterm-color included with OS X is+-- incomplete. +--+-- This terminal implementation modifies the standard terminfo terminal as required for complete OS+-- X support.+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+module Graphics.Vty.Output.MacOSX ( reserveTerminal )+    where++import Graphics.Vty.Output.Interface+import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased++import Control.Applicative+import Control.Monad.Trans++import System.IO++-- | for Terminal.app the terminal identifier "xterm" is used. For iTerm.app the terminal identifier+-- "xterm-256color" is used.+--+-- This effects the terminfo lookup.+reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Output+reserveTerminal v outHandle = do+    let remapTerm "iTerm.app" = "xterm-256color"+        remapTerm _ = "xterm"+        flushedPut :: String -> IO ()+        flushedPut str = do+            hPutStr outHandle str+            hFlush outHandle+    t <- TerminfoBased.reserveTerminal (remapTerm v) outHandle+    return $ t { terminalID = terminalID t ++ " (Mac)"+               , reserveDisplay = terminalAppReserveDisplay flushedPut+               , releaseDisplay = terminalAppReleaseDisplay flushedPut+               }++-- | Terminal.app requires the xterm-color smcup and rmcup caps. Not the generic xterm ones.+-- Otherwise, Terminal.app expects the xterm caps.+smcupStr, rmcupStr :: String+smcupStr = "\ESC7\ESC[?47h"+rmcupStr = "\ESC[2J\ESC[?47l\ESC8"++-- | always smcup then clear the screen on terminal.app+--+-- \todo really?+terminalAppReserveDisplay :: MonadIO m => (String -> IO ()) -> m ()+terminalAppReserveDisplay flushedPut = liftIO $ do+    flushedPut smcupStr+    flushedPut clearScreenStr++terminalAppReleaseDisplay :: MonadIO m => (String -> IO ()) -> m ()+terminalAppReleaseDisplay flushedPut = liftIO $ do+    flushedPut rmcupStr++-- | iTerm needs a clear screen after smcup as well.+--+-- \todo but we apply to all mac terminals?+clearScreenStr :: String+clearScreenStr = "\ESC[H\ESC[2J"+
+ src/Graphics/Vty/Output/Mock.hs view
@@ -0,0 +1,70 @@+-- Copyright Corey O'Connor+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+-- | This provides a mock terminal implementation that is nice for testing. This transforms the+-- output operations to visible characters. Which is nice for some tests.+module Graphics.Vty.Output.Mock ( MockData, mockTerminal )+    where++import Graphics.Vty.Prelude++import Graphics.Vty.Output.Interface++import Blaze.ByteString.Builder.Word (writeWord8)++import Control.Monad.Trans++import qualified Data.ByteString as BS+import Data.IORef+import qualified Data.String.UTF8 as UTF8++type MockData = IORef (UTF8.UTF8 BS.ByteString)++-- | The mock display terminal produces a string representation of the requested picture.  There is+-- *not* an isomorphism between the string representation and the picture.  The string+-- representation is a simplification of the picture that is only useful in debugging VTY without+-- considering terminal specific issues.+--+-- The mock implementation is useful in manually determining if the sequence of terminal operations+-- matches the expected sequence. So requirement of the produced representation is simplicity in+-- parsing the text representation and determining how the picture was mapped to terminal+-- operations.+--+-- The string representation is a sequence of identifiers where each identifier is the name of an+-- operation in the algebra.+mockTerminal :: (Applicative m, MonadIO m) => DisplayRegion -> m (MockData, Output)+mockTerminal r = liftIO $ do+    outRef <- newIORef undefined+    newAssumedStateRef <- newIORef initialAssumedState+    let t = Output+            { terminalID = "mock terminal"+            , releaseTerminal = return ()+            , reserveDisplay = return ()+            , releaseDisplay = return ()+            , displayBounds = return r+            , outputByteBuffer = \bytes -> do+                putStrLn $ "mock outputByteBuffer of " ++ show (BS.length bytes) ++ " bytes"+                writeIORef outRef $ UTF8.fromRep bytes+            , contextColorCount = 16+            , supportsCursorVisibility = True+            , assumedStateRef = newAssumedStateRef+            , mkDisplayContext = \tActual rActual -> return $ DisplayContext+                { contextRegion = rActual+                , contextDevice = tActual+                -- A cursor move is always visualized as the single character 'M'+                , writeMoveCursor = \_x _y -> writeWord8 $ toEnum $ fromEnum 'M'+                -- Show cursor is always visualized as the single character 'S'+                , writeShowCursor =  writeWord8 $ toEnum $ fromEnum 'S'+                -- Hide cursor is always visualized as the single character 'H'+                , writeHideCursor = writeWord8 $ toEnum $ fromEnum 'H'+                -- An attr change is always visualized as the single character 'A'+                , writeSetAttr = \_fattr _diffs _attr -> writeWord8 $ toEnum $ fromEnum 'A'+                -- default attr is always visualized as the single character 'D'+                , writeDefaultAttr = writeWord8 $ toEnum $ fromEnum 'D'+                -- row end is always visualized as the single character 'E'+                , writeRowEnd = writeWord8 $ toEnum $ fromEnum 'E'+                , inlineHack = return ()+                }+            }+    return (outRef, t)+
+ src/Graphics/Vty/Output/TerminfoBased.hs view
@@ -0,0 +1,483 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 #-}+{-# CFILES gwinsz.c #-}+-- |  Terminfo based terminal handling.+--+-- The color handling assumes tektronix like. No HP support provided. If the terminal is not one I+-- have easy access to then color support is entirely based of the docs. Probably with some+-- assumptions mixed in.+--+-- Copyright Corey O'Connor (coreyoconnor@gmail.com)+module Graphics.Vty.Output.TerminfoBased ( reserveTerminal )+    where++import Graphics.Vty.Prelude++import qualified Data.ByteString as BS+import Data.Terminfo.Parse+import Data.Terminfo.Eval++import Graphics.Vty.Attributes+import Graphics.Vty.DisplayAttributes+import Graphics.Vty.Output.Interface++import Blaze.ByteString.Builder (Write, writeToByteString)++import Control.Monad.Trans++import Data.Bits ((.&.))+import Data.Foldable (foldMap)+import Data.IORef+import Data.Maybe (isJust, isNothing, fromJust)+import Data.Monoid++import Foreign.C.Types ( CInt(..), CLong(..) )++import GHC.IO.Handle+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+import GHC.IO.Handle.Internals (withHandle_)+import GHC.IO.Handle.Types (Handle__(..))+import qualified GHC.IO.FD as FD+-- import qualified GHC.IO.Handle.FD as FD+import GHC.IO.Exception+import Data.Typeable (cast)+#else+import GHC.IOBase+import GHC.Handle hiding (fdToHandle)+import qualified GHC.Handle+#endif+#endif++import qualified System.Console.Terminfo as Terminfo+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+import System.IO.Error+#endif+#endif +import System.Posix.Types (Fd(..))++data TerminfoCaps = TerminfoCaps +    { smcup :: Maybe CapExpression+    , rmcup :: Maybe CapExpression+    , cup :: CapExpression+    , cnorm :: Maybe CapExpression+    , civis :: Maybe CapExpression+    , supportsNoColors :: Bool+    , useAltColorMap :: Bool+    , setForeColor :: CapExpression+    , setBackColor :: CapExpression+    , setDefaultAttr :: CapExpression+    , clearScreen :: CapExpression+    , clearEol :: CapExpression+    , displayAttrCaps :: DisplayAttrCaps+    }++data DisplayAttrCaps = DisplayAttrCaps+    { setAttrStates :: Maybe CapExpression+    , enterStandout :: Maybe CapExpression+    , exitStandout :: Maybe CapExpression+    , enterUnderline :: Maybe CapExpression+    , exitUnderline :: Maybe CapExpression+    , enterReverseVideo :: Maybe CapExpression+    , enterDimMode :: Maybe CapExpression+    , enterBoldMode :: Maybe CapExpression+    }+    +sendCapToTerminal :: Output -> CapExpression -> [CapParam] -> IO ()+sendCapToTerminal t cap capParams = do+    outputByteBuffer t $ writeToByteString $ writeCapExpr cap capParams++{- | Uses terminfo for all control codes. While this should provide the most compatible terminal+ - terminfo does not support some features that would increase efficiency and improve compatibility:+ -+ -  * determine the character encoding supported by the terminal. Should this be taken from the LANG+ - environment variable?  + -+ -  * Provide independent string capabilities for all display attributes.+ - + - todo: Some display attributes like underline and bold have independent string capabilities that+ - should be used instead of the generic "sgr" string capability.+ -}+reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Output+reserveTerminal inID outHandle = liftIO $ do+    ti <- Terminfo.setupTerm inID+    -- assumes set foreground always implies set background exists.+    -- if set foreground is not set then all color changing style attributes are filtered.+    msetaf <- probeCap ti "setaf"+    msetf <- probeCap ti "setf"+    let (noColors, useAlt, setForeCap) +            = case msetaf of+                Just setaf -> (False, False, setaf)+                Nothing -> case msetf of+                    Just setf -> (False, True, setf)+                    Nothing -> (True, True, error $ "no fore color support for terminal " ++ inID)+    msetab <- probeCap ti "setab"+    msetb <- probeCap ti "setb"+    let set_back_cap +            = case msetab of+                Nothing -> case msetb of+                    Just setb -> setb+                    Nothing -> error $ "no back color support for terminal " ++ inID+                Just setab -> setab+    terminfoCaps <- pure TerminfoCaps+        <*> probeCap ti "smcup"+        <*> probeCap ti "rmcup"+        <*> requireCap ti "cup"+        <*> probeCap ti "cnorm"+        <*> probeCap ti "civis"+        <*> pure noColors+        <*> pure useAlt+        <*> pure setForeCap+        <*> pure set_back_cap+        <*> requireCap ti "sgr0"+        <*> requireCap ti "clear"+        <*> requireCap ti "el"+        <*> currentDisplayAttrCaps ti+    newAssumedStateRef <- newIORef initialAssumedState+    let t = Output+            { terminalID = inID+            , releaseTerminal = liftIO $ do+                sendCap setDefaultAttr []+                maybeSendCap cnorm []+                hClose outHandle+            , reserveDisplay = liftIO $ do+                -- If there is no support for smcup: Clear the screen and then move the mouse to the+                -- home position to approximate the behavior.+                maybeSendCap smcup []+                hFlush outHandle+                sendCap clearScreen []+            , releaseDisplay = liftIO $ do+                maybeSendCap rmcup []+                maybeSendCap cnorm []+            , displayBounds = do+                rawSize <- liftIO $ withFd outHandle getWindowSize+                case rawSize of+                    (w, h)  | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show rawSize+                            | otherwise      -> return (w,h)+            , outputByteBuffer = \outBytes -> do+                BS.hPut outHandle outBytes+                hFlush outHandle+            , contextColorCount+                = case supportsNoColors terminfoCaps of+                    False -> case Terminfo.getCapability ti (Terminfo.tiGetNum "colors" ) of+                        Nothing -> 8+                        Just v -> toEnum v+                    True -> 1+            , supportsCursorVisibility = isJust $ civis terminfoCaps+            , assumedStateRef = newAssumedStateRef+            -- I think fix would help assure tActual is the only reference. I was having issues+            -- tho.+            , mkDisplayContext = \tActual -> liftIO . terminfoDisplayContext tActual terminfoCaps+            }+        sendCap s = sendCapToTerminal t (s terminfoCaps)+        maybeSendCap s = when (isJust $ s terminfoCaps) . sendCap (fromJust . s)+    return t++requireCap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m CapExpression+requireCap ti capName +    = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of+        Nothing     -> fail $ "Terminal does not define required capability \"" ++ capName ++ "\""+        Just capStr -> parseCap capStr++probeCap :: (Applicative m, MonadIO m) => Terminfo.Terminal -> String -> m (Maybe CapExpression)+probeCap ti capName +    = case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of+        Nothing     -> return Nothing+        Just capStr -> Just <$> parseCap capStr++parseCap :: (Applicative m, MonadIO m) => String -> m CapExpression+parseCap capStr = do+    case parseCapExpression capStr of+        Left e -> fail $ show e+        Right cap -> return cap++currentDisplayAttrCaps :: ( Applicative m, MonadIO m ) +                       => Terminfo.Terminal +                       -> m DisplayAttrCaps+currentDisplayAttrCaps ti +    =   pure DisplayAttrCaps +    <*> probeCap ti "sgr"+    <*> probeCap ti "smso"+    <*> probeCap ti "rmso"+    <*> probeCap ti "smul"+    <*> probeCap ti "rmul"+    <*> probeCap ti "rev"+    <*> probeCap ti "dim"+    <*> probeCap ti "bold"++foreign import ccall "gwinsz.h vty_c_get_window_size" c_getWindowSize :: Fd -> IO CLong++getWindowSize :: Fd -> IO (Int,Int)+getWindowSize fd = do +    (a,b) <- (`divMod` 65536) `fmap` c_getWindowSize fd+    return (fromIntegral b, fromIntegral a)++terminfoDisplayContext :: Output -> TerminfoCaps -> DisplayRegion -> IO DisplayContext+terminfoDisplayContext tActual terminfoCaps r = return dc+    where dc = DisplayContext+            { contextDevice = tActual+            , contextRegion = r+            , writeMoveCursor = \x y -> writeCapExpr (cup terminfoCaps) [toEnum y, toEnum x]+            , writeShowCursor = case cnorm terminfoCaps of+                Nothing -> error "this terminal does not support show cursor"+                Just c -> writeCapExpr c []+            , writeHideCursor = case civis terminfoCaps of+                Nothing -> error "this terminal does not support hide cursor"+                Just c -> writeCapExpr c []+            , writeSetAttr = terminfoWriteSetAttr dc terminfoCaps+            , writeDefaultAttr = writeCapExpr (setDefaultAttr terminfoCaps) []+            , writeRowEnd = writeCapExpr (clearEol terminfoCaps) []+            , inlineHack = return ()+            }++-- | Portably setting the display attributes is a giant pain in the ass.+--+-- If the terminal supports the sgr capability (which sets the on/off state of each style+-- directly ; and, for no good reason, resets the colors to the default) this procedure is used: +--+--  0. set the style attributes. This resets the fore and back color.+--+--  1, If a foreground color is to be set then set the foreground color+--+--  2. likewise with the background color+-- +-- If the terminal does not support the sgr cap then:+--  if there is a change from an applied color to the default (in either the fore or back color)+--  then:+--+--      0. reset all display attributes (sgr0)+--+--      1. enter required style modes+--+--      2. set the fore color if required+--+--      3. set the back color if required+--+-- Entering the required style modes could require a reset of the display attributes. If this is+-- the case then the back and fore colors always need to be set if not default.+--+-- This equation implements the above logic.+--+-- \todo This assumes the removal of color changes in the display attributes is done as expected+-- with noColors == True. See `limit_attr_for_display`+--+-- \todo This assumes that fewer state changes, followed by fewer bytes, is what to optimize. I+-- haven't measured this or even examined terminal implementations. *shrug*+terminfoWriteSetAttr :: DisplayContext -> TerminfoCaps -> FixedAttr -> Attr -> DisplayAttrDiff -> Write+terminfoWriteSetAttr dc terminfoCaps prevAttr reqAttr diffs = do+    case (foreColorDiff diffs == ColorToDefault) || (backColorDiff diffs == ColorToDefault) of+        -- The only way to reset either color, portably, to the default is to use either the set+        -- state capability or the set default capability.+        True  -> do+            case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)+                                     (fixedStyle attr )+                                     (styleToApplySeq $ fixedStyle attr) of+                -- only way to reset a color to the defaults+                EnterExitSeq caps -> writeDefaultAttr dc+                                     `mappend` +                                     foldMap (\cap -> writeCapExpr cap []) caps+                                     `mappend`+                                     setColors+                -- implicitly resets the colors to the defaults+                SetState state -> writeCapExpr (fromJust $ setAttrStates +                                                         $ displayAttrCaps +                                                         $ terminfoCaps+                                               )+                                               (sgrArgsForState state)+                                  `mappend`+                                  setColors+        -- Otherwise the display colors are not changing or changing between two non-default+        -- points.+        False -> do+            -- Still, it could be the case that the change in display attributes requires the+            -- colors to be reset because the required capability was not available.+            case reqDisplayCapSeqFor (displayAttrCaps terminfoCaps)+                                     (fixedStyle attr)+                                     (styleDiffs diffs) of+                -- Really, if terminals were re-implemented with modern concepts instead of bowing+                -- down to 40 yr old dumb terminal requirements this would be the only case ever+                -- reached!  Changes the style and color states according to the differences with+                -- the currently applied states.+                EnterExitSeq caps -> foldMap (\cap -> writeCapExpr cap []) caps+                                     `mappend`+                                     writeColorDiff setForeColor (foreColorDiff diffs)+                                     `mappend`+                                     writeColorDiff setBackColor (backColorDiff diffs)+                -- implicitly resets the colors to the defaults+                SetState state -> writeCapExpr (fromJust $ setAttrStates +                                                         $ displayAttrCaps terminfoCaps+                                               )+                                               (sgrArgsForState state)+                                  `mappend` setColors+    where +        colorMap = case useAltColorMap terminfoCaps of+                        False -> ansiColorIndex+                        True -> altColorIndex+        attr = fixDisplayAttr prevAttr reqAttr+        setColors =+            (case fixedForeColor attr of+                Just c -> writeCapExpr (setForeColor terminfoCaps)+                                       [toEnum $ colorMap c]+                Nothing -> mempty)+            `mappend`+            (case fixedBackColor attr of+                Just c -> writeCapExpr (setBackColor terminfoCaps)+                                       [toEnum $ colorMap c]+                Nothing -> mempty)+        writeColorDiff _f NoColorChange+            = mempty+        writeColorDiff _f ColorToDefault+            = error "ColorToDefault is not a possible case for applyColorDiffs"+        writeColorDiff f (SetColor c)+            = writeCapExpr (f terminfoCaps) [toEnum $ colorMap c]++-- | The color table used by a terminal is a 16 color set followed by a 240 color set that might not+-- be supported by the terminal.+--+-- This takes a Color which clearly identifies which pallete to use and computes the index+-- into the full 256 color pallete.+ansiColorIndex :: Color -> Int+ansiColorIndex (ISOColor v) = fromEnum v+ansiColorIndex (Color240 v) = 16 + fromEnum v++-- | For terminals without setaf/setab+-- +-- See table in `man terminfo`+-- Will error if not in table.+altColorIndex :: Color -> Int+altColorIndex (ISOColor 0) = 0+altColorIndex (ISOColor 1) = 4+altColorIndex (ISOColor 2) = 2+altColorIndex (ISOColor 3) = 6+altColorIndex (ISOColor 4) = 1+altColorIndex (ISOColor 5) = 5+altColorIndex (ISOColor 6) = 3+altColorIndex (ISOColor 7) = 7+altColorIndex (ISOColor v) = fromEnum v+altColorIndex (Color240 v) = 16 + fromEnum v++{- | The sequence of terminfo caps to apply a given style are determined according to these rules.+ -+ -  1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than+ -  the full set display attribute state capability. + -+ -  2. If a mode is supposed to be removed but there is not an exit capability defined then the+ -  display attributes are reset to defaults then the display attribute state is set.+ -+ -  3. If a mode is supposed to be applied but there is not an enter capability defined then then+ -  display attribute state is set if possible. Otherwise the mode is not applied.+ -+ -  4. If the display attribute state is being set then just update the arguments to that for any+ -  apply/remove.+ -+ -}+data DisplayAttrSeq+    = EnterExitSeq [CapExpression]+    | SetState DisplayAttrState++data DisplayAttrState = DisplayAttrState+    { applyStandout :: Bool+    , applyUnderline :: Bool+    , applyReverseVideo :: Bool+    , applyBlink :: Bool+    , applyDim :: Bool+    , applyBold :: Bool+    }++sgrArgsForState :: DisplayAttrState -> [CapParam]+sgrArgsForState attrState = map (\b -> if b then 1 else 0)+    [ applyStandout attrState+    , applyUnderline attrState+    , applyReverseVideo attrState+    , applyBlink attrState+    , applyDim attrState+    , applyBold attrState+    , False -- invis+    , False -- protect+    , False -- alt char set+    ]++reqDisplayCapSeqFor :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq+reqDisplayCapSeqFor caps s diffs+    -- if the state transition implied by any diff cannot be supported with an enter/exit mode cap+    -- then either the state needs to be set or the attribute change ignored.+    = case (any noEnterExitCap diffs, isJust $ setAttrStates caps) of+        -- If all the diffs have an enter-exit cap then just use those+        ( False, _    ) -> EnterExitSeq $ map enterExitCap diffs+        -- If not all the diffs have an enter-exit cap and there is no set state cap then filter out+        -- all unsupported diffs and just apply the rest+        ( True, False ) -> EnterExitSeq $ map enterExitCap +                                        $ filter (not . noEnterExitCap) diffs+        -- if not all the diffs have an enter-exit can and there is a set state cap then just use+        -- the set state cap.+        ( True, True  ) -> SetState $ stateForStyle s+    where+        noEnterExitCap ApplyStandout = isNothing $ enterStandout caps+        noEnterExitCap RemoveStandout = isNothing $ exitStandout caps+        noEnterExitCap ApplyUnderline = isNothing $ enterUnderline caps+        noEnterExitCap RemoveUnderline = isNothing $ exitUnderline caps+        noEnterExitCap ApplyReverseVideo = isNothing $ enterReverseVideo caps+        noEnterExitCap RemoveReverseVideo = True+        noEnterExitCap ApplyBlink = True+        noEnterExitCap RemoveBlink = True+        noEnterExitCap ApplyDim = isNothing $ enterDimMode caps+        noEnterExitCap RemoveDim = True+        noEnterExitCap ApplyBold = isNothing $ enterBoldMode caps+        noEnterExitCap RemoveBold = True+        enterExitCap ApplyStandout = fromJust $ enterStandout caps+        enterExitCap RemoveStandout = fromJust $ exitStandout caps+        enterExitCap ApplyUnderline = fromJust $ enterUnderline caps+        enterExitCap RemoveUnderline = fromJust $ exitUnderline caps+        enterExitCap ApplyReverseVideo = fromJust $ enterReverseVideo caps+        enterExitCap ApplyDim = fromJust $ enterDimMode caps+        enterExitCap ApplyBold = fromJust $ enterBoldMode caps+        enterExitCap _ = error "enterExitCap applied to diff that was known not to have one."++stateForStyle :: Style -> DisplayAttrState+stateForStyle s = DisplayAttrState+    { applyStandout = isStyleSet standout+    , applyUnderline = isStyleSet underline+    , applyReverseVideo = isStyleSet reverseVideo+    , applyBlink = isStyleSet blink+    , applyDim = isStyleSet dim+    , applyBold = isStyleSet bold+    }+    where isStyleSet = hasStyle s++styleToApplySeq :: Style -> [StyleStateChange]+styleToApplySeq s = concat+    [ applyIfRequired ApplyStandout standout+    , applyIfRequired ApplyUnderline underline+    , applyIfRequired ApplyReverseVideo reverseVideo+    , applyIfRequired ApplyBlink blink+    , applyIfRequired ApplyDim dim+    , applyIfRequired ApplyBlink bold+    ]+    where +        applyIfRequired op flag +            = if 0 == (flag .&. s)+                then []+                else [op]++-- from https://patch-tag.com/r/mae/sendfile/snapshot/current/content/pretty/src/Network/Socket/SendFile/Internal.hs+-- The Fd should not be used after the action returns because the+-- Handler may be garbage collected and than will cause the finalizer+-- to close the fd.+withFd :: Handle -> (Fd -> IO a) -> IO a+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+withFd h f = withHandle_ "withFd" h $ \ Handle__{..} -> do+  case cast haDevice of+    Nothing -> ioError (ioeSetErrorString (mkIOError IllegalOperation+                                           "withFd" (Just h) Nothing)+                        "handle is not a file descriptor")+    Just fd -> f (Fd (fromIntegral (FD.fdFD fd)))+#else+withFd h f =+    withHandle_ "withFd" h $ \ h_ ->+      f (Fd (fromIntegral (haFD h_)))+#endif+#endif
+ src/Graphics/Vty/Output/XTermColor.hs view
@@ -0,0 +1,54 @@+-- Copyright 2009-2010 Corey O'Connor+module Graphics.Vty.Output.XTermColor ( reserveTerminal )+    where++import Graphics.Vty.Output.Interface+import qualified Graphics.Vty.Output.TerminfoBased as TerminfoBased++import Blaze.ByteString.Builder (writeToByteString)+import Blaze.ByteString.Builder.Word (writeWord8)++import Control.Applicative+import Control.Monad.Trans++import Data.Foldable (foldMap)++import System.IO++-- | Initialize the display to UTF-8. +reserveTerminal :: ( Applicative m, MonadIO m ) => String -> Handle -> m Output+reserveTerminal variant outHandle = liftIO $ do+    let flushedPut str = do+            hPutStr outHandle str+            hFlush outHandle+    -- If the terminal variant is xterm-color use xterm instead since, more often than not,+    -- xterm-color is broken.+    let variant' = if variant == "xterm-color" then "xterm" else variant+    flushedPut setUtf8CharSet+    t <- TerminfoBased.reserveTerminal variant' outHandle+    let t' = t+             { terminalID = terminalID t ++ " (xterm-color)"+             , releaseTerminal = do+                 liftIO $ flushedPut setDefaultCharSet+                 releaseTerminal t+             , mkDisplayContext = \tActual r -> do+                dc <- mkDisplayContext t tActual r+                return $ dc { inlineHack = xtermInlineHack t' }+             }+    return t'++-- | These sequences set xterm based terminals to UTF-8 output.+--+-- \todo I don't know of a terminfo cap that is equivalent to this.+setUtf8CharSet, setDefaultCharSet :: String+setUtf8CharSet = "\ESC%G"+setDefaultCharSet = "\ESC%@"++-- | I think xterm is broken: Reseting the background color as the first bytes serialized on a+-- new line does not effect the background color xterm uses to clear the line. Which is used+-- *after* the next newline.+xtermInlineHack :: Output -> IO ()+xtermInlineHack t = do+    let writeReset = foldMap (writeWord8.toEnum.fromEnum) "\ESC[K"+    outputByteBuffer t $ writeToByteString writeReset+
src/Graphics/Vty/Picture.hs view
@@ -1,58 +1,66 @@--- | The Picture data structure is representative of the final terminal view.------ This module re-exports most of the Graphics.Vty.Image and Graphics.Vty.Attributes modules.+{-# LANGUAGE BangPatterns #-}+-- | The 'Picture' data structure is representative of the final terminal view. ----- Copyright 2009-2010 Corey O'Connor+-- A 'Picture' is a background paired with a layer of 'Image's. module Graphics.Vty.Picture ( module Graphics.Vty.Picture-                            , Image-                            , image_width-                            , image_height-                            , (<|>)-                            , (<->)-                            , horiz_cat-                            , vert_cat-                            , background_fill-                            , char-                            , string-                            , iso_10646_string-                            , utf8_string-                            , utf8_bytestring-                            , char_fill-                            , empty_image-                            , translate-                            , crop-                            , pad-                            -- | The possible display attributes used in constructing an `Image`.-                            , module Graphics.Vty.Attributes+                            , module Graphics.Vty.Image                             )     where -import Graphics.Vty.Attributes-import Graphics.Vty.Image hiding ( attr )+import Graphics.Vty.Image -import Data.Word+import Control.DeepSeq  -- | The type of images to be displayed using 'update'.  --- Can be constructed directly or using `pic_for_image`. Which provides an initial instance with--- reasonable defaults for pic_cursor and pic_background.+--+-- Can be constructed directly or using `picForImage`. Which provides an initial instance with+-- reasonable defaults for picCursor and picBackground. data Picture = Picture-    { pic_cursor :: Cursor-    , pic_image :: Image -    , pic_background :: Background+    { picCursor :: Cursor+    , picLayers :: [Image]+    , picBackground :: Background     }  instance Show Picture where-    show (Picture _ image _ ) = "Picture ?? " ++ show image ++ " ??"+    show (Picture _ layers _ ) = "Picture ?? " ++ show layers ++ " ??" +instance NFData Picture where+    rnf (Picture c l b) = c `deepseq` l `deepseq` b `deepseq` ()++-- | a picture with no cursor, background or image layers+emptyPicture :: Picture+emptyPicture = Picture NoCursor [] ClearBackground++-- | The given 'Image' is added as the top layer of the 'Picture'+addToTop :: Picture -> Image -> Picture+addToTop p i = p {picLayers = i : picLayers p}++-- | The given 'Image' is added as the bottom layer of the 'Picture'+addToBottom :: Picture -> Image -> Picture+addToBottom p i = p {picLayers = picLayers p ++ [i]}+ -- | Create a picture for display for the given image. The picture will not have a displayed cursor--- and the background display attribute will be `current_attr`.-pic_for_image :: Image -> Picture-pic_for_image i = Picture -    { pic_cursor = NoCursor-    , pic_image = i-    , pic_background = Background ' ' current_attr+-- and no background pattern (ClearBackground) will be used.+picForImage :: Image -> Picture+picForImage i = Picture +    { picCursor = NoCursor+    , picLayers = [i]+    , picBackground = ClearBackground     } +-- | Create a picture for display with the given layers. Ordered top to bottom.+--+-- The picture will not have a displayed cursor and no background apttern (ClearBackgroun) will be+-- used.+-- +-- The first 'Image' is the top layer.+picForLayers :: [Image] -> Picture+picForLayers is = Picture +    { picCursor = NoCursor+    , picLayers = is+    , picBackground = ClearBackground+    }+ -- | A picture can be configured either to not show the cursor or show the cursor at the specified -- character position.  --@@ -63,19 +71,35 @@ -- output region. In this case the cursor will not be shown. data Cursor =        NoCursor-    | Cursor Word Word+    | Cursor Int Int --- | Unspecified regions are filled with the picture's background pattern.  The background pattern--- can specify a character and a display attribute. If the display attribute used previously should--- be used for a background fill then use `current_attr` for the background attribute. This is the--- default background display attribute.+instance NFData Cursor where+    rnf NoCursor = ()+    rnf (Cursor w h) = w `seq` h `seq` ()++-- | A 'Picture' has a background pattern. The background is either ClearBackground. Which shows the+-- layer below or is blank if the bottom layer. Or the background pattern is a character and a+-- display attribute. If the display attribute used previously should be used for a background fill+-- then use `currentAttr` for the background attribute. -- -- \todo The current attribute is always set to the default attributes at the start of updating the -- screen to a picture.------ \todo The background character *must* occupy a single column and no more.-data Background = Background -    { background_char :: Char -    , background_attr :: Attr+data Background+    = Background +    { backgroundChar :: Char+    , backgroundAttr :: Attr     }+     -- | A ClearBackground is: +     --+     -- * the space character if there are remaining non-skip ops+     --+     -- * End of line if there are no remaining non-skip ops.+    | ClearBackground +instance NFData Background where+    rnf (Background c a) = c `seq` a `seq` ()+    rnf ClearBackground = ()++-- | Compatibility with applications that do not use more than a single layer.+picImage :: Picture -> Image+picImage = head . picLayers
+ src/Graphics/Vty/PictureToSpans.hs view
@@ -0,0 +1,344 @@+-- Copyright Corey O'Connor<coreyoconnor@gmail.com>+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE NamedFieldPuns #-}+{- | Transforms an image into rows of operations.+ -}+module Graphics.Vty.PictureToSpans where++import Graphics.Vty.Prelude++import Graphics.Vty.Image+import Graphics.Vty.Image.Internal+import Graphics.Vty.Picture+import Graphics.Vty.Span++import Control.Lens hiding ( op )+import Control.Monad.Reader+import Control.Monad.State.Strict hiding ( state )+import Control.Monad.ST.Strict hiding ( unsafeIOToST )++import qualified Data.Vector as Vector hiding ( take, replicate )+import Data.Monoid (mappend)+import Data.Vector.Mutable ( MVector(..))+import qualified Data.Vector.Mutable as MVector++import qualified Data.Text.Lazy as TL++type MRowOps s = MVector s SpanOps++type MSpanOps s = MVector s SpanOp++-- transform plus clip. More or less.+data BlitState = BlitState+    -- we always snoc to the operation vectors. Thus the columnOffset = length of row at rowOffset+    -- although, one possibility is to merge layers right in snocOp (naming it something else, of+    -- course). In which case columnnOffset would be applicable.+    -- Right now we need it to exist.+    { _columnOffset :: Int+    , _rowOffset :: Int+    -- clip coordinate space is in image space. Which means it's >= 0 and < imageWidth.+    , _skipColumns :: Int+    -- >= 0 and < imageHeight+    , _skipRows :: Int+    -- includes consideration of skipColumns. In display space.+    -- The number of columns from the next column to be defined to the end of the display for the+    -- row.+    , _remainingColumns :: Int+    -- includes consideration of skipRows. In display space.+    , _remainingRows :: Int+    }++makeLenses ''BlitState++data BlitEnv s = BlitEnv+    { _region :: DisplayRegion+    , _mrowOps :: MRowOps s+    }++makeLenses ''BlitEnv++type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a++-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the+-- specified region.+displayOpsForPic :: Picture -> DisplayRegion -> DisplayOps+displayOpsForPic pic r = Vector.create (combinedOpsForLayers pic r)++-- | Returns the DisplayOps for an image rendered to a window the size of the image.+--+-- largerly used only for debugging.+displayOpsForImage :: Image -> DisplayOps+displayOpsForImage i = displayOpsForPic (picForImage i) (imageWidth i, imageHeight i)++-- | Produces the span ops for each layer then combines them.+--+-- TODO: a fold over a builder function. start with span ops that are a bg fill of the entire+-- region.+combinedOpsForLayers :: Picture -> DisplayRegion -> ST s (MRowOps s)+combinedOpsForLayers pic r+    | regionWidth r == 0 || regionHeight r == 0 = MVector.new 0+    | otherwise = do+        layerOps <- mapM (\layer -> buildSpans layer r) (picLayers pic)+        case layerOps of+            []    -> fail "empty picture"+            [ops] -> substituteSkips (picBackground pic) ops+            -- instead of merging ops after generation the merging can be performed as part of+            -- snocOp.+            topOps : lowerOps -> do+                ops <- foldM mergeUnder topOps lowerOps+                substituteSkips (picBackground pic) ops++substituteSkips :: Background -> MRowOps s -> ST s (MRowOps s)+substituteSkips ClearBackground ops = do+    forM_ [0 .. MVector.length ops - 1] $ \row -> do+        rowOps <- MVector.read ops row+        -- the image operations assure that background fills are combined.+        -- clipping a background fill does not split the background fill.+        -- merging of image layers can split a skip, but only by the insertion of a non skip.+        -- all this combines to mean we can check the last operation and remove it if it's a skip+        -- todo: or does it?+        let rowOps' = case Vector.last rowOps of+                        Skip w -> Vector.init rowOps `Vector.snoc` RowEnd w+                        _      -> rowOps+        -- now all the skips can be replaced by replications of ' ' of the required width.+        let rowOps'' = swapSkipsForSingleColumnCharSpan ' ' currentAttr rowOps'+        MVector.write ops row rowOps''+    return ops+substituteSkips (Background {backgroundChar, backgroundAttr}) ops = do+    -- At this point we decide if the background character is single column or not.+    -- obviously, single column is easier.+    case safeWcwidth backgroundChar of+        w | w == 0 -> fail $ "invalid background character " ++ show backgroundChar+          | w == 1 -> do+                forM_ [0 .. MVector.length ops - 1] $ \row -> do+                    rowOps <- MVector.read ops row+                    let rowOps' = swapSkipsForSingleColumnCharSpan backgroundChar backgroundAttr rowOps+                    MVector.write ops row rowOps'+          | otherwise -> do+                forM_ [0 .. MVector.length ops - 1] $ \row -> do+                    rowOps <- MVector.read ops row+                    let rowOps' = swapSkipsForCharSpan w backgroundChar backgroundAttr rowOps+                    MVector.write ops row rowOps'+    return ops++mergeUnder :: MRowOps s -> MRowOps s -> ST s (MRowOps s)+mergeUnder upper lower = do+    forM_ [0 .. MVector.length upper - 1] $ \row -> do+        upperRowOps <- MVector.read upper row+        lowerRowOps <- MVector.read lower row+        let rowOps = mergeRowUnder upperRowOps lowerRowOps+        MVector.write upper row rowOps+    return upper++-- fugly+mergeRowUnder :: SpanOps -> SpanOps -> SpanOps+mergeRowUnder upperRowOps lowerRowOps =+    onUpperOp Vector.empty (Vector.head upperRowOps) (Vector.tail upperRowOps) lowerRowOps+    where+        -- H: it will never be the case that we are out of upper ops before lower ops.+        onUpperOp :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps+        onUpperOp outOps op@(TextSpan _ w _ _) upperOps lowerOps =+            let lowerOps' = dropOps w lowerOps+                outOps' = Vector.snoc outOps op+            in if Vector.null lowerOps'+                then outOps'+                else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'+        onUpperOp outOps (Skip w) upperOps lowerOps =+            let (ops', lowerOps') = splitOpsAt w lowerOps+                outOps' = outOps `mappend` ops'+            in if Vector.null lowerOps'+                then outOps'+                else onUpperOp outOps' (Vector.head upperOps) (Vector.tail upperOps) lowerOps'+        onUpperOp _ (RowEnd _) _ _ = error "cannot merge rows containing RowEnd ops"+++swapSkipsForSingleColumnCharSpan :: Char -> Attr -> SpanOps -> SpanOps+swapSkipsForSingleColumnCharSpan c a = Vector.map f+    where f (Skip ow) = let txt = TL.pack $ replicate ow c+                        in TextSpan a ow ow txt+          f v = v++swapSkipsForCharSpan :: Int -> Char -> Attr -> SpanOps -> SpanOps+swapSkipsForCharSpan w c a = Vector.map f+    where+        f (Skip ow) = let txt0Cw = ow `div` w+                          txt0 = TL.pack $ replicate txt0Cw c+                          txt1Cw = ow `mod` w+                          txt1 = TL.pack $ replicate txt1Cw '…'+                          cw = txt0Cw + txt1Cw+                          txt = txt0 `TL.append` txt1+                      in TextSpan a ow cw txt+        f v = v++-- | Builds a vector of row operations that will output the given picture to the terminal.+--+-- Crops to the given display region.+--+-- \todo I'm pretty sure there is an algorithm that does not require a mutable buffer.+buildSpans :: Image -> DisplayRegion -> ST s (MRowOps s)+buildSpans image outRegion = do+    -- First we create a mutable vector for each rows output operations.+    outOps <- MVector.replicate (regionHeight outRegion) Vector.empty+    -- \todo I think building the span operations in display order would provide better performance.+    -- However, I got stuck trying to implement an algorithm that did this. This will be considered+    -- as a possible future optimization. +    --+    -- A depth first traversal of the image is performed.  ordered according to the column range+    -- defined by the image from least to greatest.  The output row ops will at least have the+    -- region of the image specified. Iterate over all output rows and output background fills for+    -- all unspecified columns.+    --+    -- The images are made into span operations from left to right. It's possible that this could+    -- easily be made to assure top to bottom output as well. +    when (regionHeight outRegion > 0 && regionWidth outRegion > 0) $ do+        -- The ops builder recursively descends the image and outputs span ops that would+        -- display that image. The number of columns remaining in this row before exceeding the+        -- bounds is also provided. This is used to clip the span ops produced to the display.+        let fullBuild = do+                startImageBuild image+                -- Fill in any unspecified columns with a skip.+                forM_ [0 .. (regionHeight outRegion - 1)] (addRowCompletion outRegion)+            initEnv   = BlitEnv outRegion outOps+            initState = BlitState 0 0 0 0 (regionWidth outRegion) (regionHeight outRegion)+        _ <- runStateT (runReaderT fullBuild initEnv) initState+        return ()+    return outOps++-- | Add the operations required to build a given image to the current set of row operations+-- returns the number of columns and rows contributed to the output.+startImageBuild :: Image -> BlitM s ()+startImageBuild image = do+    outOfBounds <- isOutOfBounds image <$> get+    when (not outOfBounds) $ addMaybeClipped image++isOutOfBounds :: Image -> BlitState -> Bool+isOutOfBounds i s+    | s ^. remainingColumns <= 0              = True+    | s ^. remainingRows    <= 0              = True+    | s ^. skipColumns      >= imageWidth i  = True+    | s ^. skipRows         >= imageHeight i = True+    | otherwise = False++-- | This adds an image that might be partially clipped to the output ops.+--+-- This is a very touchy algorithm. Too touchy. For instance, the CropRight and CropBottom+-- implementations are odd. They pass the current tests but something seems terribly wrong about all+-- this.+--+-- \todo prove this cannot be called in an out of bounds case.+addMaybeClipped :: forall s . Image -> BlitM s ()+addMaybeClipped EmptyImage = return ()+addMaybeClipped (HorizText a textStr ow _cw) = do+    -- TODO: assumption that text spans are only 1 row high.+    s <- use skipRows+    when (s < 1) $ do+        leftClip <- use skipColumns+        rightClip <- use remainingColumns+        let leftClipped = leftClip > 0+            rightClipped = (ow - leftClip) > rightClip+        if leftClipped || rightClipped+            then let textStr' = clipText textStr leftClip rightClip+                 in addUnclippedText a textStr'+            else addUnclippedText a textStr+addMaybeClipped (VertJoin topImage bottomImage _ow oh) = do+    addMaybeClippedJoin "vert_join" skipRows remainingRows rowOffset+                           (imageHeight topImage)+                           topImage+                           bottomImage+                           oh+addMaybeClipped (HorizJoin leftImage rightImage ow _oh) = do+    addMaybeClippedJoin "horiz_join" skipColumns remainingColumns columnOffset+                           (imageWidth leftImage)+                           leftImage+                           rightImage+                           ow+addMaybeClipped BGFill {outputWidth, outputHeight} = do+    s <- get+    let outputWidth'  = min (outputWidth  - s^.skipColumns) (s^.remainingColumns)+        outputHeight' = min (outputHeight - s^.skipRows   ) (s^.remainingRows)+    y <- use rowOffset+    forM_ [y..y+outputHeight'-1] $ snocOp (Skip outputWidth')+addMaybeClipped CropRight {croppedImage, outputWidth} = do+    s <- use skipColumns+    r <- use remainingColumns+    let x = outputWidth - s+    when (x < r) $ remainingColumns .= x+    addMaybeClipped croppedImage+addMaybeClipped CropLeft {croppedImage, leftSkip} = do+    skipColumns += leftSkip+    addMaybeClipped croppedImage+addMaybeClipped CropBottom {croppedImage, outputHeight} = do+    s <- use skipRows+    r <- use remainingRows+    let x = outputHeight - s+    when (x < r) $ remainingRows .= x+    addMaybeClipped croppedImage+addMaybeClipped CropTop {croppedImage, topSkip} = do+    skipRows += topSkip+    addMaybeClipped croppedImage++addMaybeClippedJoin :: forall s . String +                       -> Lens BlitState BlitState Int Int+                       -> Lens BlitState BlitState Int Int+                       -> Lens BlitState BlitState Int Int+                       -> Int+                       -> Image+                       -> Image+                       -> Int+                       -> BlitM s ()+addMaybeClippedJoin name skip remaining offset i0Dim i0 i1 size = do+    state <- get+    when (state^.remaining <= 0) $ fail $ name ++ " with remaining <= 0"+    case state^.skip of+        s -- TODO: check if clipped in other dim. if not use addUnclipped+          | s >= size -> fail $ name ++ " on fully clipped"+          | s == 0    -> if state^.remaining > i0Dim +                            then do+                                addMaybeClipped i0+                                put $ state & offset +~ i0Dim & remaining -~ i0Dim+                                addMaybeClipped i1+                            else addMaybeClipped i0+          | s < i0Dim  ->+                let i0Dim' = i0Dim - s+                in if state^.remaining <= i0Dim'+                    then addMaybeClipped i0+                    else do+                        addMaybeClipped i0+                        put $ state & offset +~ i0Dim' & remaining -~ i0Dim' & skip .~ 0+                        addMaybeClipped i1+          | s >= i0Dim -> do+                put $ state & skip -~ i0Dim+                addMaybeClipped i1+        _ -> fail $ name ++ " has unhandled skip class"++addUnclippedText :: Attr -> DisplayText -> BlitM s ()+addUnclippedText a txt = do+    let op = TextSpan a usedDisplayColumns+                      (fromIntegral $ TL.length txt)+                      txt+        usedDisplayColumns = wcswidth $ TL.unpack txt+    use rowOffset >>= snocOp op++addRowCompletion :: DisplayRegion -> Int -> BlitM s ()+addRowCompletion displayRegion row = do+    allRowOps <- view mrowOps+    rowOps <- lift $ lift $ MVector.read allRowOps row+    let endX = spanOpsEffectedColumns rowOps+    when (endX < regionWidth displayRegion) $ do+        let ow = regionWidth displayRegion - endX+        snocOp (Skip ow) row++-- | snocs the operation to the operations for the given row.+snocOp :: SpanOp -> Int -> BlitM s ()+snocOp !op !row = do+    theMrowOps <- view mrowOps+    theRegion <- view region+    lift $ lift $ do+        ops <- MVector.read theMrowOps row+        let ops' = Vector.snoc ops op+        when (spanOpsEffectedColumns ops' > regionWidth theRegion)+             $ fail $ "row " ++ show row ++ " now exceeds region width"+        MVector.write theMrowOps row ops'
+ src/Graphics/Vty/Prelude.hs view
@@ -0,0 +1,18 @@+-- | Prelude for Vty modules. Not particularly useful outside of Vty.+module Graphics.Vty.Prelude ( module Graphics.Vty.Prelude+                            , module Control.Applicative+                            , module Control.Monad+                            )+where++import Control.Applicative hiding ((<|>))+import Control.Monad++-- | Named alias for a Int pair+type DisplayRegion = (Int,Int)++regionWidth :: DisplayRegion -> Int+regionWidth = fst++regionHeight :: DisplayRegion -> Int+regionHeight = snd
src/Graphics/Vty/Span.hs view
@@ -1,354 +1,138 @@--- Copyright 2009-2010 Corey O'Connor+-- Copyright Corey O'Connor {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE ScopedTypeVariables #-}--- The ops to define the content for an output region. -module Graphics.Vty.Span-    where--import Graphics.Vty.Image-import Graphics.Vty.Picture-import Graphics.Vty.DisplayRegion--import Codec.Binary.UTF8.String ( encode )--import Control.Monad ( forM_ )-import Control.Monad.ST.Strict hiding ( unsafeIOToST )-import Control.Monad.ST.Unsafe ( unsafeIOToST )--import Data.Vector (Vector)-import qualified Data.Vector as Vector hiding ( take, replicate )-import Data.Vector.Mutable ( MVector(..))-import qualified Data.Vector.Mutable as Vector--import qualified Data.ByteString as B-import qualified Data.ByteString.Internal as BInt-import qualified Data.Foldable as Foldable-import qualified Data.String.UTF8 as UTF8-import Data.Word--import Foreign.Storable ( pokeByteOff )- {- | A picture is translated into a sequences of state changes and character spans.  - State changes are currently limited to new attribute values. The attribute is applied to all  - following spans. Including spans of the next row.  The nth element of the sequence represents the  - nth row (from top to bottom) of the picture to render.  -  - A span op sequence will be defined for all rows and columns (and no more) of the region provided- - with the picture to spans_for_pic.- - + - with the picture to `spansForPic`.+ -  - todo: Partition attribute changes into multiple categories according to the serialized  - representation of the various attributes.  -}+module Graphics.Vty.Span+    where -data DisplayOps = DisplayOps-    { effected_region :: DisplayRegion -    , display_ops :: RowOps-    }+import Graphics.Vty.Prelude --- | vector of span operation vectors. One per row of the screen.-type RowOps = Vector SpanOps+import Graphics.Vty.Image+import Graphics.Vty.Image.Internal ( clipText ) -type MRowOps s = MVector s SpanOps+import qualified Data.Text.Lazy as TL+import Data.Vector (Vector)+import qualified Data.Vector as Vector +-- | This represents an operation on the terminal. Either an attribute change or the output of a+-- text string.+data SpanOp =+    -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF+    -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width+    -- TextSpan [Attr] [output width in columns] [number of characters] [data]+      TextSpan +      { textSpanAttr :: !Attr+      , textSpanOutputWidth :: !Int+      , textSpanCharWidth :: !Int+      , textSpanText :: DisplayText+      }+    -- | Skips the given number of columns+    -- A skip is transparent.... maybe? I am not sure how attribute changes interact.+    -- todo: separate from this type.+    | Skip !Int+    -- | Marks the end of a row. specifies how many columns are remaining. These columns will not be+    -- explicitly overwritten with the span ops. The terminal is require to assure the remaining+    -- columns are clear.+    -- todo: separate from this type.+    | RowEnd !Int+    deriving Eq+ -- | vector of span operations. executed in succession. This represents the operations required to -- render a row of the terminal. The operations in one row may effect subsequent rows. -- EG: Setting the foreground color in one row will effect all subsequent rows until the foreground -- color is changed. type SpanOps = Vector SpanOp -type MSpanOps s = MVector s SpanOp+dropOps :: Int -> SpanOps -> SpanOps+dropOps w = snd . splitOpsAt w -instance Show DisplayOps where-    show (DisplayOps _ the_row_ops)-        = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"+splitOpsAt :: Int -> SpanOps -> (SpanOps, SpanOps)+splitOpsAt inW inOps = splitOpsAt' inW inOps+    where+        splitOpsAt' 0 ops = (Vector.empty, ops)+        splitOpsAt' remainingColumns ops = case Vector.head ops of+            t@(TextSpan {}) -> if remainingColumns >= textSpanOutputWidth t+                then let (pre,post) = splitOpsAt' (remainingColumns - textSpanOutputWidth t)+                                                  (Vector.tail ops)+                     in (Vector.cons t pre, post)+                else let preTxt = clipText (textSpanText t) 0 remainingColumns+                         preOp = TextSpan { textSpanAttr = textSpanAttr t+                                           , textSpanOutputWidth = remainingColumns+                                           , textSpanCharWidth = fromIntegral $! TL.length preTxt+                                           , textSpanText = preTxt+                                           }+                         postWidth = textSpanOutputWidth t - remainingColumns+                         postTxt = clipText (textSpanText t) remainingColumns postWidth+                         postOp = TextSpan { textSpanAttr = textSpanAttr t+                                            , textSpanOutputWidth = postWidth+                                            , textSpanCharWidth = fromIntegral $! TL.length postTxt+                                            , textSpanText = postTxt+                                            }+                     in ( Vector.singleton preOp+                        , Vector.cons postOp (Vector.tail ops)+                        )+            Skip w -> if remainingColumns >= w+                then let (pre,post) = splitOpsAt' (remainingColumns - w) (Vector.tail ops)+                     in (Vector.cons (Skip w) pre, post)+                else ( Vector.singleton $ Skip remainingColumns+                     , Vector.cons (Skip (w - remainingColumns)) (Vector.tail ops)+                     )+            RowEnd _ -> error "cannot split ops containing a row end"+         +-- | vector of span operation vectors for display. One per row of the output region.+type DisplayOps = Vector SpanOps+ instance Show SpanOp where-    show (AttributeChange attr) = show attr-    show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw+    show (TextSpan attr ow cw _) = "TextSpan(" ++ show attr ++ ")(" ++ show ow ++ ", " ++ show cw ++ ")"+    show (Skip ow) = "Skip(" ++ show ow ++ ")"+    show (RowEnd ow) = "RowEnd(" ++ show ow ++ ")"  -- | Number of columns the DisplayOps are defined for-span_ops_columns :: DisplayOps -> Word-span_ops_columns ops = region_width $ effected_region ops+--+-- All spans are verified to define same number of columns. See: VerifySpanOps+displayOpsColumns :: DisplayOps -> Int+displayOpsColumns ops +    | Vector.length ops == 0 = 0+    | otherwise              = Vector.length $ Vector.head ops  -- | Number of rows the DisplayOps are defined for-span_ops_rows :: DisplayOps -> Word-span_ops_rows ops = region_height $ effected_region ops+displayOpsRows :: DisplayOps -> Int+displayOpsRows ops = Vector.length ops +effectedRegion :: DisplayOps -> DisplayRegion+effectedRegion ops = (displayOpsColumns ops, displayOpsRows ops)+ -- | The number of columns a SpanOps effects.-span_ops_effected_columns :: SpanOps -> Word-span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops+spanOpsEffectedColumns :: SpanOps -> Int+spanOpsEffectedColumns inOps = Vector.foldl' spanOpsEffectedColumns' 0 inOps     where -        span_ops_effected_columns' t (TextSpan w _ _ ) = t + w-        span_ops_effected_columns' t _ = t---- | This represents an operation on the terminal. Either an attribute change or the output of a--- text string.--- --- todo: This type may need to be restructured to increase sharing in the bytestring--- --- todo: Make foldable-data SpanOp =-      AttributeChange !Attr-    -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF-    -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width-    -- TextSpan [output width in columns] [number of characters] [data]-    | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)-    deriving Eq+        spanOpsEffectedColumns' t (TextSpan _ w _ _ ) = t + w+        spanOpsEffectedColumns' t (Skip w) = t + w+        spanOpsEffectedColumns' t (RowEnd w) = t + w  -- | The width of a single SpanOp in columns-span_op_has_width :: SpanOp -> Maybe (Word, Word)-span_op_has_width (TextSpan ow cw _) = Just (cw, ow)-span_op_has_width _ = Nothing+spanOpHasWidth :: SpanOp -> Maybe (Int, Int)+spanOpHasWidth (TextSpan _ ow cw _) = Just (cw, ow)+spanOpHasWidth (Skip ow) = Just (ow,ow)+spanOpHasWidth (RowEnd ow) = Just (ow,ow)  -- | returns the number of columns to the character at the given position in the span op-columns_to_char_offset :: Word -> SpanOp -> Word-columns_to_char_offset cx (TextSpan _ _ utf8_str) =-    let str = UTF8.toString utf8_str-    in toEnum $! sum $! map wcwidth $! take (fromEnum cx) str-columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"---- | Produces the span ops that will render the given picture, possibly cropped or padded, into the--- specified region.-spans_for_pic :: Picture -> DisplayRegion -> DisplayOps-spans_for_pic pic r = DisplayOps r $ Vector.create (build_spans pic r)---- | Builds a vector of row operations that will output the given picture to the terminal.------ Crops to the given display region.-build_spans :: Picture -> DisplayRegion -> ST s (MRowOps s)-build_spans pic region = do-    -- First we create a mutable vector for each rows output operations.-    mrow_ops <- Vector.replicate (fromEnum $ region_height region) Vector.empty-    -- \todo I think building the span operations in display order would provide better performance.-    -- However, I got stuck trying to implement an algorithm that did this. This will be considered-    -- as a possible future optimization. -    ---    -- A depth first traversal of the image is performed.  ordered according to the column range-    -- defined by the image from least to greatest.  The output row ops will at least have the-    -- region of the image specified. Iterate over all output rows and output background fills for-    -- all unspecified columns.-    ---    -- The images are made into span operations from left to right. It's possible that this could-    -- easily be made to assure top to bottom output as well. -    if region_height region > 0-        then do -            -- The ops builder recursively descends the image and outputs span ops that would-            -- display that image. The number of columns remaining in this row before exceeding the-            -- bounds is also provided. This is used to clip the span ops produced to the display.-            -- The skip dimensions provided do....???-            _ <- row_ops_for_image mrow_ops -                                   (pic_image pic)-                                   (pic_background pic) -                                   region -                                   (0,0) -                                   0 -                                   (region_width region)-                                   (fromEnum $ region_height region)-            -- Fill in any unspecified columns with the background pattern.-            forM_ [0 .. (fromEnum $ region_height region - 1)] $! \row -> do-                end_x <- Vector.read mrow_ops row >>= return . span_ops_effected_columns-                if end_x < region_width region -                    then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row-                    else return ()-        else return ()-    return mrow_ops---- | Add the operations required to build a given image to the current set of row operations.-row_ops_for_image :: MRowOps s -> Image -> Background -> DisplayRegion -> (Word, Word) -> Int -> Word -> Int -> ST s (Word, Word)-row_ops_for_image mrow_ops                      -- the image to output the ops to-                  image                         -- the image to rasterize in column order to mrow_ops-                  bg                            -- the background fill-                  region                        -- ???-                  skip_dim@(skip_row,skip_col)  -- the number of rows -                  y                             -- ???-                  remaining_columns             -- ???-                  remain_rows-    | remaining_columns == 0 = return skip_dim-    | remain_rows == 0  = return skip_dim-    | y >= fromEnum (region_height region) = return skip_dim-    | otherwise = case image of-        EmptyImage -> return skip_dim-        -- The width provided is the number of columns this text span will occupy when displayed.-        -- if this is greater than the number of remaining columsn the output has to be produced a-        -- character at a time.-        HorizText a text_str _ _ -> do-            if skip_row > 0-                then return (skip_row - 1, skip_col)-                else do-                    skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns-                    return (skip_row, skip_col')-        VertJoin top_image bottom_image _ _ -> do-            (skip_row',skip_col') <- row_ops_for_image mrow_ops -                                                       top_image-                                                       bg -                                                       region -                                                       skip_dim -                                                       y -                                                       remaining_columns-                                                       remain_rows-            let top_height = (fromEnum $! image_height top_image) - (fromEnum $! skip_row - skip_row')-            (skip_row'',skip_col'') <- row_ops_for_image mrow_ops -                                                         bottom_image-                                                         bg -                                                         region -                                                         (skip_row', skip_col) -                                                         (y + top_height)-                                                         remaining_columns-                                                         (max 0 $ remain_rows - top_height)-            return (skip_row'', min skip_col' skip_col'')-        HorizJoin l r _ _ -> do-            (skip_row',skip_col') <- row_ops_for_image mrow_ops l bg region skip_dim y remaining_columns remain_rows-            -- Don't output the right part unless there is at least a single column left after-            -- outputting the left part.-            if image_width l - (skip_col - skip_col') > remaining_columns-                then return (skip_row,skip_col')-                else do-                    (skip_row'',skip_col'') <- row_ops_for_image mrow_ops r bg region (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col')) remain_rows-                    return (min skip_row' skip_row'', skip_col'')-        BGFill width height -> do-            let min_height = if y + (fromEnum height) > (fromEnum $! region_height region)-                                then region_height region - (toEnum y)-                                else min height (toEnum remain_rows)-                min_width = min width remaining_columns-                actual_height = if skip_row > min_height-                                    then 0-                                    else min_height - skip_row-                actual_width = if skip_col > min_width-                                    then 0-                                    else min_width - skip_col-            forM_ [y .. y + fromEnum actual_height - 1] $! \y' -> snoc_bg_fill mrow_ops bg actual_width y'-            let skip_row' = if actual_height > skip_row-                                then 0-                                else skip_row - min_height-                skip_col' = if actual_width > skip_col-                                then 0-                                else skip_col - min_width-            return (skip_row',skip_col')-        Translation (dx,dy) i -> do-            if dx < 0-                -- Translation left-                -- Extract the delta and add it to skip_col.-                then row_ops_for_image mrow_ops (translate (0, dy) i) bg region (skip_row, skip_col + dw) y remaining_columns remain_rows-                -- Translation right-                else if dy < 0-                        -- Translation up-                        -- Extract the delta and add it to skip_row.-                        then row_ops_for_image mrow_ops (translate (dx, 0) i) bg region (skip_row + dh, skip_col) y remaining_columns remain_rows-                        -- Translation down-                        -- Pad the start of lines and above the image with a-                        -- background_fill image-                        else row_ops_for_image mrow_ops (background_fill ow dh <-> (background_fill dw ih <|> i)) bg region skip_dim y remaining_columns remain_rows-            where-                dw = toEnum $ abs dx-                dh = toEnum $ abs dy-                ow = image_width image-                ih = image_height i-        ImageCrop (max_w,max_h) i ->-            row_ops_for_image mrow_ops i bg region skip_dim y (min remaining_columns max_w) (min remain_rows $ fromEnum max_h)-        ImagePad (min_w,min_h) i -> do-            let hpad = if image_width i < min_w-                        then background_fill (min_w - image_width i) (image_height i)-                        else empty_image-            let vpad = if image_height i < min_h-                        then background_fill (image_width i) (min_h - image_height i)-                        else empty_image-            row_ops_for_image mrow_ops ((i <|> hpad) <-> vpad) bg region skip_dim y remaining_columns remain_rows--snoc_text_span :: Attr           -- the display attributes of the text span-                -> DisplayString -- the text to output-                -> MRowOps s     -- the display operations to add to-                -> Word          -- the number of display columns in the text span to -                                 -- skip before outputting-                -> Int           -- the row of the display operations to add to-                -> Word          -- the number of columns from the next column to be -                                 -- defined to the end of the display for the row.-                -> ST s Word-snoc_text_span a text_str mrow_ops columns_to_skip y remaining_columns = do-    {-# SCC "snoc_text_span-pre" #-} snoc_op mrow_ops y $! AttributeChange a-    -- At most a text span will consist of remaining_columns characters-    -- we keep track of the position of the next character.-    let max_len :: Int = fromEnum remaining_columns-    mspan_chars <- Vector.new max_len-    ( used_display_columns, display_columns_skipped, used_char_count ) -        <- {-# SCC "snoc_text_span-foldlM" #-} Foldable.foldlM (build_text_span mspan_chars) ( 0, 0, 0 ) text_str-    -- once all characters have been output to mspan_chars we grab the used head -    out_text <- Vector.unsafeFreeze $! Vector.take used_char_count mspan_chars-    -- convert to UTF8 bytestring.-    -- This could be made faster. Hopefully the optimizer does a fair job at fusing the fold-    -- contained in fromString with the unfold in toList. No biggy right now then.-    {-# SCC "snoc_text_span-post" #-} snoc_op mrow_ops y $! TextSpan used_display_columns (toEnum used_char_count)-                       $! UTF8.fromString -                       $! Vector.toList out_text-    return $ columns_to_skip - display_columns_skipped-    where-        build_text_span mspan_chars (!used_display_columns, !display_columns_skipped, !used_char_count) -                                    (out_char, char_display_width) = {-# SCC "build_text_span" #-}-            -- Only valid if the maximum width of a character is 2 display columns.-            -- XXX: Optimize into a skip pass then clipped fill pass-            if display_columns_skipped == columns_to_skip-                then if used_display_columns == remaining_columns-                        then return $! ( used_display_columns, display_columns_skipped, used_char_count )-                        else if ( used_display_columns + char_display_width ) > remaining_columns-                                then do-                                    Vector.unsafeWrite mspan_chars used_char_count '…'-                                    return $! ( used_display_columns + 1-                                              , display_columns_skipped-                                              , used_char_count  + 1-                                              )-                                else do-                                    Vector.unsafeWrite mspan_chars used_char_count out_char-                                    return $! ( used_display_columns + char_display_width-                                              , display_columns_skipped-                                              , used_char_count + 1-                                              )-                else if (display_columns_skipped + char_display_width) > columns_to_skip-                        then do-                            Vector.unsafeWrite mspan_chars used_char_count '…'-                            return $! ( used_display_columns + 1-                                      , columns_to_skip-                                      , used_char_count + 1-                                      )-                        else return $ ( used_display_columns-                                      , display_columns_skipped + char_display_width-                                      , used_char_count-                                      )---- | Add a background fill of the given column width to the row display operations.------ This has a fast path for background characters that are a single column and a single byte.--- Otherwise this has to compute the width of the background character and replicate a sequence of--- bytes to fill in the required width.-snoc_bg_fill :: MRowOps s -> Background -> Word -> Int -> ST s ()-snoc_bg_fill _row_ops _bg 0 _row -    = return ()-snoc_bg_fill mrow_ops (Background c back_attr) fill_length row -    = do-        snoc_op mrow_ops row $ AttributeChange back_attr-        -- By all likelyhood the background character will be an ASCII character. Which is a single-        -- byte in utf8. Optimize for this special case.-        utf8_bs <- if c <= (toEnum 255 :: Char)-            then-                let !(c_byte :: Word8) = BInt.c2w c-                in unsafeIOToST $ do-                    BInt.create ( fromEnum fill_length ) -                                $ \ptr -> mapM_ (\i -> pokeByteOff ptr i c_byte)-                                                [0 .. fromEnum (fill_length - 1)]-            else -                let !(c_bytes :: [Word8]) = encode [c]-                in unsafeIOToST $ do-                    BInt.create (fromEnum fill_length * length c_bytes) -                                $ \ptr -> mapM_ (\(i,b) -> pokeByteOff ptr i b)-                                                $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)-        snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)---- | snocs the operation to the operations for the given row.-snoc_op :: MRowOps s -> Int -> SpanOp -> ST s ()-snoc_op !mrow_ops !row !op = do-    ops <- Vector.read mrow_ops row-    let ops' = Vector.snoc ops op-    Vector.write mrow_ops row ops'+columnsToCharOffset :: Int -> SpanOp -> Int+columnsToCharOffset cx (TextSpan _ _ _ utf8Str) =+    let str = TL.unpack utf8Str+    in wcswidth (take cx str)+columnsToCharOffset cx (Skip _) = cx+columnsToCharOffset cx (RowEnd _) = cx 
− src/Graphics/Vty/Terminal.hs
@@ -1,137 +0,0 @@---  | Generic Terminal interface.------  Defines the common interface supported by all terminals.------  See also:------  1. Graphics.Vty.Terminal: This instantiates an abtract interface to the terminal interface based---  on the TERM and COLORTERM environment variables. ---  ---  2. Graphics.Vty.Terminal.Generic: Defines the generic interface all terminals need to implement.------  3. Graphics.Vty.Terminal.TerminfoBased: Defines a terminal instance that uses terminfo for all---  control strings.  No attempt is made to change the character set to UTF-8 for these terminals.---  I don't know a way to reliably determine if that is required or how to do so.------  4. Graphics.Vty.Terminal.XTermColor: This module contains an interface suitable for xterm-like---  terminals. These are the terminals where TERM == xterm. This does use terminfo for as many---  control codes as possible. ------ Copyright 2009-2010 Corey O'Connor-{-# LANGUAGE ScopedTypeVariables #-}-module Graphics.Vty.Terminal ( module Graphics.Vty.Terminal-                             , Terminal(..)-                             , TerminalHandle(..)-                             , DisplayHandle(..)-                             , output_picture-                             , display_context-                             )-    where---import Graphics.Vty.Terminal.Generic-import Graphics.Vty.Terminal.MacOSX as MacOSX-import Graphics.Vty.Terminal.XTermColor as XTermColor-import Graphics.Vty.Terminal.TerminfoBased as TerminfoBased--import Control.Applicative-import Control.Exception ( SomeException, try )-import Control.Monad.Trans--import Data.List ( isPrefixOf )-import Data.Word--import System.Environment---- | Returns a TerminalHandle (an abstract Terminal instance) for the current terminal.------ The specific Terminal implementation used is hidden from the API user. All terminal--- implementations are assumed to perform more, or less, the same. Currently all implementations use--- terminfo for at least some terminal specific information. This is why platforms without terminfo--- are not supported. However, as mentioned before, any specifics about it being based on terminfo--- are hidden from the API user.  If a terminal implementation is developed for a terminal for a--- platform without terminfo support then Vty should work as expected on that terminal.------ Selection of a terminal is done as follows:------      * If TERM == xterm---          then the terminal might be one of the Mac OS X .app terminals. Check if that might be---          the case and use MacOSX if so.---          otherwise use XTermColor.------      * for any other TERM value TerminfoBased is used.--------- The terminal has to be determined dynamically at runtime. To satisfy this requirement all--- terminals instances are lifted into an abstract terminal handle via existential qualification.--- This implies that the only equations that can used are those in the terminal class.------ To differentiate between Mac OS X terminals this uses the TERM_PROGRAM environment variable.--- However, an xterm started by Terminal or iTerm *also* has TERM_PROGRAM defined since the--- environment variable is not reset/cleared by xterm. However a Terminal.app or iTerm.app started--- from an xterm under X11 on mac os x will likely be done via open. Since this does not propogate--- environment variables (I think?) this assumes that XTERM_VERSION will never be set for a true--- Terminal.app or iTerm.app session.--------- The file descriptor used for output will a duplicate of the current stdout file descriptor.------ todo: add an implementation for windows that does not depend on terminfo. Should be installable--- with only what is provided in the haskell platform.------ todo: The Terminal interface does not provide any input support.-terminal_handle :: ( Applicative m, MonadIO m ) => m TerminalHandle-terminal_handle = do-    term_type <- liftIO $ getEnv "TERM"-    t <- if "xterm" `isPrefixOf` term_type-        then do-            maybe_terminal_app <- get_env "TERM_PROGRAM"-            case maybe_terminal_app of-                Nothing -                    -> XTermColor.terminal_instance term_type >>= new_terminal_handle-                Just v | v == "Apple_Terminal" || v == "iTerm.app" -                    -> do-                        maybe_xterm <- get_env "XTERM_VERSION"-                        case maybe_xterm of-                            Nothing -> MacOSX.terminal_instance v >>= new_terminal_handle-                            Just _  -> XTermColor.terminal_instance term_type >>= new_terminal_handle-                -- Assume any other terminal that sets TERM_PROGRAM to not be an OS X terminal.app-                -- like terminal?-                _   -> XTermColor.terminal_instance term_type >>= new_terminal_handle-        -- Not an xterm-like terminal. try for generic terminfo.-        else TerminfoBased.terminal_instance term_type >>= new_terminal_handle-    return t-    where-        get_env var = do-            mv <- liftIO $ try $ getEnv var-            case mv of-                Left (_e :: SomeException)  -> return $ Nothing-                Right v -> return $ Just v---- | Sets the cursor position to the given output column and row. ------ This is not necessarially the same as the character position with the same coordinates.--- Characters can be a variable number of columns in width.------ Currently, the only way to set the cursor position to a given character coordinate is to specify--- the coordinate in the Picture instance provided to output_picture or refresh.-set_cursor_pos :: MonadIO m => TerminalHandle -> Word -> Word -> m ()-set_cursor_pos t x y = do-    bounds <- display_bounds t-    d <- display_context t bounds-    liftIO $ marshall_to_terminal t (move_cursor_required_bytes d x y) (serialize_move_cursor d x y)---- | Hides the cursor-hide_cursor :: MonadIO m => TerminalHandle -> m ()-hide_cursor t = do-    bounds <- display_bounds t-    d <- display_context t bounds-    liftIO $ marshall_to_terminal t (hide_cursor_required_bytes d) (serialize_hide_cursor d) -    --- | Shows the cursor-show_cursor :: MonadIO m => TerminalHandle -> m ()-show_cursor t = do-    bounds <- display_bounds t-    d <- display_context t bounds-    liftIO $ marshall_to_terminal t (show_cursor_required_bytes d) (serialize_show_cursor d) -
− src/Graphics/Vty/Terminal/Debug.hs
@@ -1,121 +0,0 @@--- Copyright 2009-2010 Corey O'Connor-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-module Graphics.Vty.Terminal.Debug ( DebugTerminal(..)-                                   , DebugDisplay(..)-                                   , terminal_instance-                                   , dehandle-                                   )-    where--import Graphics.Vty.DisplayRegion-import Graphics.Vty.Terminal.Generic--import Control.Applicative-import Control.Monad.Trans-import Control.Monad.State.Strict--import qualified Data.ByteString.UTF8 as BS-import qualified Data.ByteString as BSCore-import Data.IORef-import qualified Data.Sequence as Seq-import qualified Data.String.UTF8 as UTF8-import Data.Word--import Foreign.Marshal.Array ( peekArray )-import Foreign.Ptr ( plusPtr )-import Foreign.Storable ( poke )--import System.IO--import Unsafe.Coerce---- | The debug display terminal produces a string representation of the requested picture.  There is--- *not* an isomorphism between the string representation and the picture.  The string--- representation is a simplification of the picture that is only useful in debugging VTY without--- considering terminal specific issues.------ The debug implementation is useful in manually determining if the sequence of terminal operations--- matches the expected sequence. So requirement of the produced representation is simplicity in--- parsing the text representation and determining how the picture was mapped to terminal--- operations.------ All terminals support the operations specified in the Terminal class defined in--- Graphics.Vty.Terminal. As an instance of the Terminal class is also an instance of the Monad--- class there exists a monoid that defines it's algebra. The string representation is a sequence of--- identifiers where each identifier is the name of an operation in the algebra.--data DebugTerminal = DebugTerminal-    { debug_terminal_last_output :: IORef (UTF8.UTF8 BS.ByteString)-    , debug_terminal_bounds :: DisplayRegion-    } --instance Terminal DebugTerminal where-    terminal_ID _t = "debug_terminal"-    release_terminal _t = return ()-    reserve_display _t = return ()-    release_display _t = return ()-    display_bounds t = return $ debug_terminal_bounds t-    display_terminal_instance t r c = return $ c (DebugDisplay r)-    output_byte_buffer t out_buffer buffer_size -        =   liftIO $ do-            putStrLn $ "output_byte_buffer ?? " ++ show buffer_size-            peekArray (fromEnum buffer_size) out_buffer -            >>= return . UTF8.fromRep . BSCore.pack-            >>= writeIORef (debug_terminal_last_output t)--    output_handle t = return stdout--data DebugDisplay = DebugDisplay-    { debug_display_bounds :: DisplayRegion-    } --terminal_instance :: ( Applicative m, MonadIO m ) => DisplayRegion -> m TerminalHandle-terminal_instance r = do-    output_ref <- liftIO $ newIORef undefined-    new_terminal_handle $ DebugTerminal output_ref r--dehandle :: TerminalHandle -> DebugTerminal-dehandle (TerminalHandle t _) = unsafeCoerce t--instance DisplayTerminal DebugDisplay where-    -- | Provide the current bounds of the output terminal.-    context_region d = debug_display_bounds d--    -- | A cursor move is always visualized as the single character 'M'-    move_cursor_required_bytes _d _x _y = 1--    -- | A cursor move is always visualized as the single character 'M'-    serialize_move_cursor _d _x _y ptr = do-        liftIO $ poke ptr (toEnum $ fromEnum 'M') -        return $ ptr `plusPtr` 1--    -- | Show cursor is always visualized as the single character 'S'-    show_cursor_required_bytes _d = 1--    -- | Show cursor is always visualized as the single character 'S'-    serialize_show_cursor _d ptr = do-        liftIO $ poke ptr (toEnum $ fromEnum 'S') -        return $ ptr `plusPtr` 1--    -- | Hide cursor is always visualized as the single character 'H'-    hide_cursor_required_bytes _d = 1--    -- | Hide cursor is always visualized as the single character 'H'-    serialize_hide_cursor _d ptr = do-        liftIO $ poke ptr (toEnum $ fromEnum 'H') -        return $ ptr `plusPtr` 1--    -- | An attr change is always visualized as the single character 'A'-    attr_required_bytes _d _fattr _diffs _attr = 1--    -- | An attr change is always visualized as the single character 'A'-    serialize_set_attr _d _fattr _diffs _attr ptr = do-        liftIO $ poke ptr (toEnum $ fromEnum 'A')-        return $ ptr `plusPtr` 1--    default_attr_required_bytes _d = 1-    serialize_default_attr _d ptr = do-        liftIO $ poke ptr (toEnum $ fromEnum 'D')-        return $ ptr `plusPtr` 1-        
− src/Graphics/Vty/Terminal/Generic.hs
@@ -1,417 +0,0 @@--- Copyright 2009-2011 Corey O'Connor-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE FlexibleContexts #-}-module Graphics.Vty.Terminal.Generic ( module Graphics.Vty.Terminal.Generic-                                     , OutputBuffer-                                     )-    where--import Data.Marshalling--import Graphics.Vty.Picture-import Graphics.Vty.Span-import Graphics.Vty.DisplayRegion--import Graphics.Vty.DisplayAttributes--import Control.Monad ( liftM )-import Control.Monad.Trans--import qualified Data.ByteString.Internal as BSCore-import Data.IORef-import Data.String.UTF8 hiding ( foldl )-import qualified Data.Vector as Vector --import System.IO---- | An handle to a terminal that hides the implementation.-data TerminalHandle where-    TerminalHandle :: Terminal t => t -> IORef TerminalState -> TerminalHandle--state_ref :: TerminalHandle -> IORef TerminalState-state_ref (TerminalHandle _ s_ref) = s_ref--new_terminal_handle :: forall m t. ( MonadIO m, Terminal t ) => t -> m TerminalHandle-new_terminal_handle t = do-    s_ref <- liftIO $ newIORef initial_terminal_state-    return $ TerminalHandle t s_ref---- | The current terminal state. This may not exactly be known.-data TerminalState = TerminalState-    { known_fattr :: Maybe FixedAttr-    }---- | Initially we know nothing about a terminal's state.-initial_terminal_state :: TerminalState-initial_terminal_state = TerminalState Nothing--class Terminal t where-    -- | Text identifier for the terminal. Used for debugging.-    terminal_ID :: t -> String-    -- | -    release_terminal :: MonadIO m => t -> m ()-    -- | Clear the display and initialize the terminal to some initial display state. -    ---    -- The expectation of a program is that the display starts in some initial state. -    -- The initial state would consist of fixed values:-    --  - cursor at top left-    --  - UTF-8 character encoding-    --  - drawing characteristics are the default-    -- The abstract operation I think all these behaviors are instances of is reserving exclusive-    -- access to a display such that:-    --  - The previous state cannot be determined-    --  - When exclusive access to a display is release the display returns to the previous state.-    reserve_display :: MonadIO m => t -> m ()--    -- | Return the display to the state before reserve_display-    -- If no previous state then set the display state to the initial state.-    release_display :: MonadIO m => t -> m ()-    -    -- | Returns the current display bounds.-    display_bounds :: MonadIO m => t -> m DisplayRegion--    -- Internal method used to provide the DisplayTerminal instance to the DisplayHandle-    -- constructor.-    display_terminal_instance :: MonadIO m -                              => t -                              -> DisplayRegion -                              -> (forall d. DisplayTerminal d => d -> DisplayHandle) -                              -> m DisplayHandle--    -- | Output the byte buffer of the specified size to the terminal device.  The size is equal to-    -- end_ptr - start_ptr-    output_byte_buffer :: t -> OutputBuffer -> Word -> IO ()--    -- | Handle of output device-    output_handle :: t -> IO Handle--instance Terminal TerminalHandle where-    terminal_ID (TerminalHandle t _) = terminal_ID t-    release_terminal (TerminalHandle t _) = release_terminal t-    reserve_display (TerminalHandle t _) = reserve_display t-    release_display (TerminalHandle t _) = release_display t-    display_bounds (TerminalHandle t _) = display_bounds t-    display_terminal_instance (TerminalHandle t _) = display_terminal_instance t-    output_byte_buffer (TerminalHandle t _) = output_byte_buffer t-    output_handle (TerminalHandle t _) = output_handle t--data DisplayHandle where-    DisplayHandle :: forall d . DisplayTerminal d => d -> TerminalHandle -> DisplayState -> DisplayHandle---- | Acquire display access to the given region of the display.--- Currently all regions have the upper left corner of (0,0) and the lower right corner at --- (max display_width provided_width, max display_height provided_height)-display_context :: MonadIO m => TerminalHandle -> DisplayRegion -> m DisplayHandle-display_context t b = do-    s <- initial_display_state-    display_terminal_instance t b (\ d -> DisplayHandle d t s)--data DisplayState = DisplayState-    { previous_output_ref :: IORef (Maybe DisplayOps)-    }--initial_display_state :: MonadIO m => m DisplayState-initial_display_state = liftM DisplayState $ liftIO $ newIORef Nothing--class DisplayTerminal d where-    -- | Provide the bounds of the display context. -    context_region :: d -> DisplayRegion--    -- | Maximum number of colors supported by the context.-    context_color_count :: d -> Word--    --  | sets the output position to the specified row and column. Where the number of bytes-    --  required for the control codes can be specified seperate from the actual byte sequence.-    move_cursor_required_bytes :: d -> Word -> Word -> Word-    serialize_move_cursor :: MonadIO m => d -> Word -> Word -> OutputBuffer -> m OutputBuffer--    show_cursor_required_bytes :: d -> Word-    serialize_show_cursor :: MonadIO m => d -> OutputBuffer -> m OutputBuffer--    hide_cursor_required_bytes :: d -> Word-    serialize_hide_cursor :: MonadIO m => d -> OutputBuffer -> m OutputBuffer--    --  | Assure the specified output attributes will be applied to all the following text until the-    --  next output attribute change. Where the number of bytes required for the control codes can-    --  be specified seperate from the actual byte sequence.  The required number of bytes must be-    --  at least the maximum number of bytes required by any attribute changes.  The serialization-    --  equations must provide the ptr to the next byte to be specified in the output buffer.-    ---    --  The currently applied display attributes are provided as well. The Attr data type can-    --  specify the style or color should not be changed from the currently applied display-    --  attributes. In order to support this the currently applied display attributes are required.-    --  In addition it may be possible to optimize the state changes based off the currently applied-    --  display attributes.-    attr_required_bytes :: d -> FixedAttr -> Attr -> DisplayAttrDiff -> Word-    serialize_set_attr :: MonadIO m => d -> FixedAttr -> Attr -> DisplayAttrDiff -> OutputBuffer -> m OutputBuffer--    -- | Reset the display attributes to the default display attributes-    default_attr_required_bytes :: d -> Word-    serialize_default_attr :: MonadIO m => d -> OutputBuffer -> m OutputBuffer--    -- | See Graphics.Vty.Terminal.XTermColor.inline_hack-    inline_hack :: MonadIO m => d -> m ()-    inline_hack _d = return ()---instance DisplayTerminal DisplayHandle where-    context_region (DisplayHandle d _ _) = context_region d-    context_color_count (DisplayHandle d _ _) = context_color_count d-    move_cursor_required_bytes (DisplayHandle d _ _) = move_cursor_required_bytes d-    serialize_move_cursor (DisplayHandle d _ _) = serialize_move_cursor d-    show_cursor_required_bytes (DisplayHandle d _ _) = show_cursor_required_bytes d-    serialize_show_cursor (DisplayHandle d _ _) = serialize_show_cursor d-    hide_cursor_required_bytes (DisplayHandle d _ _) = hide_cursor_required_bytes d-    serialize_hide_cursor (DisplayHandle d _ _) = serialize_hide_cursor d-    attr_required_bytes (DisplayHandle d _ _) = attr_required_bytes d-    serialize_set_attr (DisplayHandle d _ _) = serialize_set_attr d-    default_attr_required_bytes (DisplayHandle d _ _) = default_attr_required_bytes d-    serialize_default_attr (DisplayHandle d _ _) = serialize_default_attr d-    inline_hack (DisplayHandle d _ _) = inline_hack d -    --- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.-utf8_text_required_bytes ::  UTF8 BSCore.ByteString -> Word-utf8_text_required_bytes str =-    let (_, _, src_bytes_length) = BSCore.toForeignPtr (toRep str)-    in toEnum src_bytes_length---- | All terminals serialize UTF8 text to the terminal device exactly as serialized in memory.-serialize_utf8_text  :: MonadIO m => UTF8 BSCore.ByteString -> OutputBuffer -> m OutputBuffer-serialize_utf8_text str dest_ptr = -    let (src_fptr, src_ptr_offset, src_bytes_length) = BSCore.toForeignPtr (toRep str)-    in liftIO $ withForeignPtr src_fptr $ \src_ptr -> do-        let src_ptr' = src_ptr `plusPtr` src_ptr_offset-        BSCore.memcpy dest_ptr src_ptr' (toEnum src_bytes_length) -        return (dest_ptr `plusPtr` src_bytes_length)----- | Displays the given `Picture`.------      0. The image is cropped to the display size. ------      1. Converted into a sequence of attribute changes and text spans.---      ---      2. The cursor is hidden.------      3. Serialized to the display.------      4. The cursor is then shown and positioned or kept hidden.------ --- todo: specify possible IO exceptions.--- abstract from IO monad to a MonadIO instance.-output_picture :: MonadIO m => DisplayHandle -> Picture -> m ()-output_picture (DisplayHandle d t s) pic = do-    let !r = context_region d-    let !ops = spans_for_pic pic r-    let !initial_attr = FixedAttr default_style_mask Nothing Nothing-    -- Diff the previous output against the requested output. Differences are currently on a per-row-    -- basis.-    diffs :: [Bool]-        <- liftIO ( readIORef (previous_output_ref s) )-            >>= \mprevious_ops -> case mprevious_ops of-                Nothing      -                    -> return $ replicate ( fromEnum $ region_height $ effected_region ops ) -                                                   True-                Just previous_ops -                    -> if effected_region previous_ops /= effected_region ops-                            then return $ replicate ( fromEnum $ region_height $ effected_region ops ) -                                                    True-                            else return $ zipWith (/=) ( Vector.toList $ display_ops previous_ops ) -                                                       ( Vector.toList $ display_ops ops )--    -- determine the number of bytes required to completely serialize the output ops.-    let total =   hide_cursor_required_bytes d -                + default_attr_required_bytes d-                + required_bytes d initial_attr diffs ops -                + case pic_cursor pic of-                    NoCursor -> 0-                    Cursor x y -> let m = cursor_output_map ops $ pic_cursor pic-		                      ( ox, oy ) = char_to_output_pos m ( x, y )-		                   in show_cursor_required_bytes d-                                      + move_cursor_required_bytes d ox oy--    -- ... then serialize-    liftIO $ allocaBytes (fromEnum total) $ \start_ptr -> do-        ptr <- serialize_hide_cursor d start_ptr-        ptr' <- serialize_default_attr d ptr-        ptr'' <- serialize_output_ops d ptr' initial_attr diffs ops-        end_ptr <- case pic_cursor pic of-                        NoCursor -> return ptr''-                        Cursor x y -> do-                            let m = cursor_output_map ops $ pic_cursor pic-                                (ox, oy) = char_to_output_pos m (x,y)-                            serialize_show_cursor d ptr'' >>= serialize_move_cursor d ox oy-        -- todo: How to handle exceptions?-        case end_ptr `minusPtr` start_ptr of-            count | count < 0 -> fail "End pointer before start of buffer."-                  | toEnum count > total -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - total)-                  | otherwise -> output_byte_buffer t start_ptr (toEnum count)-        -- Cache the output spans.-        liftIO $ writeIORef (previous_output_ref s) (Just ops)-    return ()--required_bytes :: DisplayTerminal d => d -> FixedAttr -> [Bool] -> DisplayOps -> Word-required_bytes d in_fattr diffs ops = -    let (_, n, _, _) = Vector.foldl' required_bytes' (0, 0, in_fattr, diffs) ( display_ops ops )-    in n-    where -        required_bytes' (y, current_sum, fattr, True : diffs') span_ops-            =   let (s, fattr') = span_ops_required_bytes d y fattr span_ops -                in ( y + 1, s + current_sum, fattr', diffs' )-        required_bytes' (y, current_sum, fattr, False : diffs') _span_ops-            = ( y + 1, current_sum, fattr, diffs' )-        required_bytes' (_y, _current_sum, _fattr, [] ) _span_ops-            = error "shouldn't be possible"--span_ops_required_bytes :: DisplayTerminal d => d -> Word -> FixedAttr -> SpanOps -> (Word, FixedAttr)-span_ops_required_bytes d y in_fattr span_ops = -    -- The first operation is to set the cursor to the start of the row-    let header_required_bytes = move_cursor_required_bytes d 0 y-    -- then the span ops are serialized in the order specified-    in Vector.foldl' ( \(current_sum, fattr) op -> let (c, fattr') = span_op_required_bytes d fattr op -                                                   in (c + current_sum, fattr') -                     ) -                     (header_required_bytes, in_fattr)-                     span_ops--span_op_required_bytes :: DisplayTerminal d => d -> FixedAttr -> SpanOp -> (Word, FixedAttr)-span_op_required_bytes d fattr (AttributeChange attr) = -    let attr' = limit_attr_for_display d attr-        diffs = display_attr_diffs fattr fattr'-        c = attr_required_bytes d fattr attr' diffs-        fattr' = fix_display_attr fattr attr'-    in (c, fattr')-span_op_required_bytes _d fattr (TextSpan _ _ str) = (utf8_text_required_bytes str, fattr)--serialize_output_ops :: ( MonadIO m, DisplayTerminal d )-                        => d -                        -> OutputBuffer -                        -> FixedAttr -                        -> [Bool]-                        -> DisplayOps -                        -> m OutputBuffer-serialize_output_ops d start_ptr in_fattr diffs ops = do-    (_, end_ptr, _, _) <- Vector.foldM' serialize_output_ops' -                              ( 0, start_ptr, in_fattr, diffs ) -                              ( display_ops ops )-    return end_ptr-    where -        serialize_output_ops' ( y, out_ptr, fattr, True : diffs' ) span_ops-            =   serialize_span_ops d y out_ptr fattr span_ops -                >>= return . ( \(out_ptr', fattr') -> ( y + 1, out_ptr', fattr', diffs' ) )-        serialize_output_ops' ( y, out_ptr, fattr, False : diffs' ) _span_ops-            = return ( y + 1, out_ptr, fattr, diffs' )-        serialize_output_ops' (_y, _out_ptr, _fattr, [] ) _span_ops-            = error "shouldn't be possible"--serialize_span_ops :: ( MonadIO m, DisplayTerminal d )-                      => d -                      -> Word -                      -> OutputBuffer -                      -> FixedAttr -                      -> SpanOps -                      -> m (OutputBuffer, FixedAttr)-serialize_span_ops d y out_ptr in_fattr span_ops = do-    -- The first operation is to set the cursor to the start of the row-    out_ptr' <- serialize_move_cursor d 0 y out_ptr-    -- then the span ops are serialized in the order specified-    Vector.foldM ( \(out_ptr'', fattr) op -> serialize_span_op d op out_ptr'' fattr ) -                 (out_ptr', in_fattr)-                 span_ops--serialize_span_op :: ( MonadIO m, DisplayTerminal d )-                     => d -                     -> SpanOp -                     -> OutputBuffer -                     -> FixedAttr-                     -> m (OutputBuffer, FixedAttr)-serialize_span_op d (AttributeChange attr) out_ptr fattr = do-    let attr' = limit_attr_for_display d attr-        fattr' = fix_display_attr fattr attr'-        diffs = display_attr_diffs fattr fattr'-    out_ptr' <- serialize_set_attr d fattr attr' diffs out_ptr-    return (out_ptr', fattr')-serialize_span_op _d (TextSpan _ _ str) out_ptr fattr = do-    out_ptr' <- serialize_utf8_text str out_ptr-    return (out_ptr', fattr)--marshall_to_terminal :: ( Terminal t )-                     => t -> Word -> (Ptr Word8 -> IO (Ptr Word8)) -> IO ()-marshall_to_terminal t c f = do-    start_ptr <- mallocBytes (fromEnum c)-    -- -    -- todo: capture exceptions?-    end_ptr <- f start_ptr-    case end_ptr `minusPtr` start_ptr of-        count | count < 0 -> fail "End pointer before start pointer."-              | toEnum count > c -> fail $ "End pointer past end of buffer by " ++ show (toEnum count - c)-              | otherwise -> output_byte_buffer t start_ptr (toEnum count)-    free start_ptr-    return ()---- | The cursor position is given in X,Y character offsets. Due to multi-column characters this--- needs to be translated to column, row positions.-data CursorOutputMap = CursorOutputMap-    { char_to_output_pos :: (Word, Word) -> (Word, Word)-    } --cursor_output_map :: DisplayOps -> Cursor -> CursorOutputMap-cursor_output_map span_ops _cursor = CursorOutputMap-    { char_to_output_pos = \(cx, cy) -> (cursor_column_offset span_ops cx cy, cy)-    }--cursor_column_offset :: DisplayOps -> Word -> Word -> Word-cursor_column_offset span_ops cx cy =-    let cursor_row_ops = Vector.unsafeIndex (display_ops span_ops) (fromEnum cy)-        (out_offset, _, _) -            = Vector.foldl' ( \(d, current_cx, done) op -> -                        if done then (d, current_cx, done) else case span_op_has_width op of-                            Nothing -> (d, current_cx, False)-                            Just (cw, ow) -> case compare cx (current_cx + cw) of-                                    GT -> ( d + ow-                                          , current_cx + cw-                                          , False -                                          )-                                    EQ -> ( d + ow-                                          , current_cx + cw-                                          , True -                                          )-                                    LT -> ( d + columns_to_char_offset (cx - current_cx) op-                                          , current_cx + cw-                                          , True-                                          )-                      )-                      (0, 0, False)-                      cursor_row_ops-    in out_offset---- | Not all terminals support all display attributes. This filters a display attribute to what the--- given terminal can display.-limit_attr_for_display :: DisplayTerminal d => d -> Attr -> Attr-limit_attr_for_display d attr -    = attr { attr_fore_color = clamp_color $ attr_fore_color attr-           , attr_back_color = clamp_color $ attr_back_color attr-           }-    where-        clamp_color Default     = Default-        clamp_color KeepCurrent = KeepCurrent-        clamp_color (SetTo c)   = clamp_color' c-        clamp_color' (ISOColor v) -            | context_color_count d < 8            = Default-            | context_color_count d < 16 && v >= 8 = SetTo $ ISOColor (v - 8)-            | otherwise                            = SetTo $ ISOColor v-        clamp_color' (Color240 v)-            -- TODO: Choose closes ISO color?-            | context_color_count d < 8            = Default-            | context_color_count d < 16           = Default-            | context_color_count d == 240         = SetTo $ Color240 v-            | otherwise -                = let p :: Double = fromIntegral v / 240.0 -                      v' = floor $ p * (fromIntegral $ context_color_count d)-                  in SetTo $ Color240 v'-
− src/Graphics/Vty/Terminal/MacOSX.hs
@@ -1,102 +0,0 @@--- Copyright 2009-2010 Corey O'Connor--- The standard Mac OS X terminals Terminal.app and iTerm both declare themselves to be--- "xterm-color" by default. However the terminfo database for xterm-color included with OS X is--- incomplete. ------ This terminal implementation modifies the standard terminfo terminal as required for complete OS--- X support.-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-module Graphics.Vty.Terminal.MacOSX ( terminal_instance-                                    )-    where--import Graphics.Vty.Terminal.Generic-import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased--import Control.Applicative-import Control.Monad.Trans--import System.IO---- | A Mac terminal is assumed to be an xterm based terminal.-data Term = Term -    { super_term :: TerminalHandle-    , term_app :: String-    }---- | for Terminal.app the terminal identifier "xterm" is used. For iTerm.app the terminal identifier--- "xterm-256color" is used.------ This effects the terminfo lookup.-terminal_instance :: ( Applicative m, MonadIO m ) => String -> m Term-terminal_instance v = do-    let base_term "iTerm.app" = "xterm-256color"-        base_term _ = "xterm"-    t <- TerminfoBased.terminal_instance (base_term v) >>= new_terminal_handle-    return $ Term t v--flushed_put :: MonadIO m => String -> m ()-flushed_put str = do-    liftIO $ hPutStr stdout str-    liftIO $ hFlush stdout---- | Terminal.app requires the xterm-color smcup and rmcup caps. Not the generic xterm ones.--- Otherwise, Terminal.app expects the xterm caps.-smcup_str, rmcup_str :: String-smcup_str = "\ESC7\ESC[?47h"-rmcup_str = "\ESC[2J\ESC[?47l\ESC8"---- | iTerm needs a clear screen after smcup as well.-clear_screen_str :: String-clear_screen_str = "\ESC[H\ESC[2J"--instance Terminal Term where-    terminal_ID t = term_app t ++ " :: MacOSX"--    release_terminal t = do -        release_terminal $ super_term t--    reserve_display _t = do-        flushed_put smcup_str-        flushed_put clear_screen_str--    release_display _t = do-        flushed_put rmcup_str--    display_terminal_instance t b c = do-        d <- display_context (super_term t) b-        return $ c (DisplayContext d)--    display_bounds t = display_bounds (super_term t)-        -    output_byte_buffer t = output_byte_buffer (super_term t)--    output_handle t = output_handle (super_term t)--data DisplayContext = DisplayContext-    { super_display :: DisplayHandle-    }--instance DisplayTerminal DisplayContext where-    context_region d = context_region (super_display d)-    context_color_count d = context_color_count (super_display d)--    move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)-    serialize_move_cursor d = serialize_move_cursor (super_display d)--    show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)-    serialize_show_cursor d = serialize_show_cursor (super_display d)--    hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)-    serialize_hide_cursor d = serialize_hide_cursor (super_display d)--    attr_required_bytes d = attr_required_bytes (super_display d)-    serialize_set_attr d = serialize_set_attr (super_display d)--    default_attr_required_bytes d = default_attr_required_bytes (super_display d)-    serialize_default_attr d = serialize_default_attr (super_display d)-
− src/Graphics/Vty/Terminal/TerminfoBased.hs
@@ -1,437 +0,0 @@--- Copyright Corey O'Connor (coreyoconnor@gmail.com)-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE FlexibleInstances #-}-{-# OPTIONS_GHC -D_XOPEN_SOURCE=500 #-}-module Graphics.Vty.Terminal.TerminfoBased ( terminal_instance-                                           )-    where--import Data.Terminfo.Parse-import Data.Terminfo.Eval--import Graphics.Vty.Attributes-import Graphics.Vty.DisplayAttributes-import Graphics.Vty.Terminal.Generic-import Graphics.Vty.DisplayRegion--import Control.Applicative -import Control.Monad ( foldM )-import Control.Monad.Trans--import Data.Bits ( (.&.) )-import Data.Maybe ( isJust, isNothing, fromJust )-import Data.Word--import Foreign.C.Types ( CLong(..) )--import GHC.IO.Handle--import qualified System.Console.Terminfo as Terminfo-import System.IO--data Term = Term -    { term_info_ID :: String-    , term_info :: Terminfo.Terminal-    , smcup :: Maybe CapExpression-    , rmcup :: Maybe CapExpression-    , cup :: CapExpression-    , cnorm :: CapExpression-    , civis :: CapExpression-    , set_fore_color :: CapExpression-    , set_back_color :: CapExpression-    , set_default_attr :: CapExpression-    , clear_screen :: CapExpression-    , display_attr_caps :: DisplayAttrCaps-    , term_handle :: Handle-    }--data DisplayAttrCaps = DisplayAttrCaps-    { set_attr_states :: Maybe CapExpression-    , enter_standout :: Maybe CapExpression-    , exit_standout :: Maybe CapExpression-    , enter_underline :: Maybe CapExpression-    , exit_underline :: Maybe CapExpression-    , enter_reverse_video :: Maybe CapExpression-    , enter_dim_mode :: Maybe CapExpression-    , enter_bold_mode :: Maybe CapExpression-    }-    -marshall_cap_to_terminal :: Term -> (Term -> CapExpression) -> [CapParam] -> IO ()-marshall_cap_to_terminal t cap_selector cap_params = do-    marshall_to_terminal t ( cap_expression_required_bytes (cap_selector t) cap_params )-                           ( serialize_cap_expression (cap_selector t) cap_params )-    return ()--{- | Uses terminfo for all control codes. While this should provide the most compatible terminal- - terminfo does not support some features that would increase efficiency and improve compatibility:- -  * determine the character encoding supported by the terminal. Should this be taken from the LANG- - environment variable?  - -  * Provide independent string capabilities for all display attributes.- -- - - - todo: Some display attributes like underline and bold have independent string capabilities that- - should be used instead of the generic "sgr" string capability.- -}-terminal_instance :: ( Applicative m, MonadIO m ) => String -> m Term-terminal_instance in_ID = do-    ti <- liftIO $ Terminfo.setupTerm in_ID-    let require_cap str -            = case Terminfo.getCapability ti (Terminfo.tiGetStr str) of-                Nothing -> fail $ "Terminal does not define required capability \"" ++ str ++ "\""-                Just cap_str -> do-                    parse_result <- parse_cap_expression cap_str -                    case parse_result of -                        Left e -> fail $ show e-                        Right cap -> return cap-        probe_cap cap_name -            = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of-                Nothing -> return Nothing-                Just cap_str -> do-                    parse_result <- parse_cap_expression cap_str-                    case parse_result of-                        Left e -> fail $ show e-                        Right cap -> return $ Just cap-    the_handle <- liftIO $ hDuplicate stdout-    pure Term-        <*> pure in_ID-        <*> pure ti-        <*> probe_cap "smcup"-        <*> probe_cap "rmcup"-        <*> require_cap "cup"-        <*> require_cap "cnorm"-        <*> require_cap "civis"-        <*> require_cap "setaf"-        <*> require_cap "setab"-        <*> require_cap "sgr0"-        <*> require_cap "clear"-        <*> current_display_attr_caps ti-        <*> pure the_handle--current_display_attr_caps :: ( Applicative m, MonadIO m ) -                          => Terminfo.Terminal -                          -> m DisplayAttrCaps-current_display_attr_caps ti -    =   pure DisplayAttrCaps -    <*> probe_cap "sgr"-    <*> probe_cap "smso"-    <*> probe_cap "rmso"-    <*> probe_cap "smul"-    <*> probe_cap "rmul"-    <*> probe_cap "rev"-    <*> probe_cap "dim"-    <*> probe_cap "bold"-    where probe_cap cap_name -            = case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of-                Nothing -> return Nothing-                Just cap_str -> do-                    parse_result <- parse_cap_expression cap_str-                    case parse_result of-                        Left e -> fail $ show e-                        Right cap -> return $ Just cap--instance Terminal Term where-    terminal_ID t = term_info_ID t ++ " :: TerminfoBased"--    release_terminal t = liftIO $ do-        marshall_cap_to_terminal t set_default_attr []-        marshall_cap_to_terminal t cnorm []-        hClose $ term_handle t-        return ()--    reserve_display t = liftIO $ do-        if (isJust $ smcup t)-            then marshall_cap_to_terminal t (fromJust . smcup) []-            else return ()-        -- Screen on OS X does not appear to support smcup?-        -- To approximate the expected behavior: clear the screen and then move the mouse to the-        -- home position.-        hFlush stdout-        marshall_cap_to_terminal t clear_screen []-        return ()--    release_display t = liftIO $ do-        if (isJust $ rmcup t)-            then marshall_cap_to_terminal t (fromJust . rmcup) []-            else return ()-        marshall_cap_to_terminal t cnorm []-        return ()--    display_terminal_instance t b c = do-        let color_count -                = case Terminfo.getCapability (term_info t) (Terminfo.tiGetNum "colors" ) of-                    Nothing -> 8-                    Just v -> toEnum v-        return $ c (DisplayContext b t color_count)--    display_bounds _t = do-        raw_size <- liftIO $ get_window_size-        case raw_size of-            ( w, h )    | w < 0 || h < 0 -> fail $ "getwinsize returned < 0 : " ++ show raw_size-                        | otherwise      -> return $ DisplayRegion (toEnum w) (toEnum h)--    -- | Output the byte buffer of the specified size to the terminal device.-    output_byte_buffer t out_ptr out_byte_count = do-        -- if the out fd is actually the same as stdout's then a-        -- flush is required *before* the c_output_byte_buffer call-        -- otherwise there may still be data in GHC's internal stdout buffer.-        -- _ <- handleToFd stdout-        hPutBuf (term_handle t) out_ptr (fromEnum out_byte_count)-        hFlush (term_handle t)--    output_handle t = return (term_handle t)--foreign import ccall "gwinsz.h vty_c_get_window_size" c_get_window_size -    :: IO CLong--get_window_size :: IO (Int,Int)-get_window_size = do -    (a,b) <- (`divMod` 65536) `fmap` c_get_window_size-    return (fromIntegral b, fromIntegral a)--data DisplayContext = DisplayContext-    { bounds :: DisplayRegion-    , term :: Term-    , supported_colors :: Word-    }--instance DisplayTerminal DisplayContext where-    context_region d = bounds d--    context_color_count d = supported_colors d--    move_cursor_required_bytes d x y -        = cap_expression_required_bytes (cup $ term d) [y, x]-    serialize_move_cursor d x y out_ptr -        = liftIO $ serialize_cap_expression (cup $ term d) [y, x] out_ptr--    show_cursor_required_bytes d -        = cap_expression_required_bytes (cnorm $ term d) []-    serialize_show_cursor d out_ptr -        = liftIO $ serialize_cap_expression (cnorm $ term d) [] out_ptr--    hide_cursor_required_bytes d -        = cap_expression_required_bytes (civis $ term d) []-    serialize_hide_cursor d out_ptr -        = liftIO $ serialize_cap_expression (civis $ term d) [] out_ptr--    -- | Instead of evaluating all the rules related to setting display attributes twice (once in-    -- required bytes and again in serialize) or some memoization scheme just return a size-    -- requirement as big the longest possible control string.-    -- -    -- Which is assumed to the be less than 512 for now. -    ---    -- \todo Not verified as safe and wastes memory.-    attr_required_bytes _d _prev_attr _req_attr _diffs = 512--    -- | Portably setting the display attributes is a giant pain in the ass.-    ---    -- If the terminal supports the sgr capability (which sets the on/off state of each style-    -- directly ; and, for no good reason, resets the colors to the default) this procedure is used: -    ---    --  0. set the style attributes. This resets the fore and back color.-    --  1, If a foreground color is to be set then set the foreground color-    --  2. likewise with the background color-    -- -    -- If the terminal does not support the sgr cap then:-    --  if there is a change from an applied color to the default (in either the fore or back color)-    --  then:-    --      0. reset all display attributes (sgr0)-    --      1. enter required style modes-    --      2. set the fore color if required-    --      3. set the back color if required-    ---    -- Entering the required style modes could require a reset of the display attributes. If this is-    -- the case then the back and fore colors always need to be set if not default.-    ---    -- This equation implements the above logic.-    serialize_set_attr d prev_attr req_attr diffs out_ptr = do-        case (fore_color_diff diffs == ColorToDefault) || (back_color_diff diffs == ColorToDefault) of-            -- The only way to reset either color, portably, to the default is to use either the set-            -- state capability or the set default capability.-            True  -> do-                case req_display_cap_seq_for ( display_attr_caps $ term d )-                                             ( fixed_style attr )-                                             ( style_to_apply_seq $ fixed_style attr )-                    of-                        EnterExitSeq caps -                            -- only way to reset a color to the defaults-                            ->  serialize_default_attr d out_ptr-                            >>= (\out_ptr' -> liftIO $ foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr' caps)-                            >>= set_colors-                        SetState state-                            -- implicitly resets the colors to the defaults-                            ->  liftIO $ serialize_cap_expression ( fromJust $ set_attr_states -                                                                    $ display_attr_caps -                                                                    $ term d -                                                                  )-                                                                  ( sgr_args_for_state state )-                                                                  out_ptr-                            >>= set_colors-            -- Otherwise the display colors are not changing or changing between two non-default-            -- points.-            False -> do-                -- Still, it could be the case that the change in display attributes requires the-                -- colors to be reset because the required capability was not available.-                case req_display_cap_seq_for ( display_attr_caps $ term d )-                                             ( fixed_style attr )-                                             ( style_diffs diffs )-                    of-                        -- Really, if terminals were re-implemented with modern concepts instead of-                        -- bowing down to 40 yr old dumb terminal requirements this would be the-                        -- only case ever reached! -                        -- Changes the style and color states according to the differences with the-                        -- currently applied states.-                        EnterExitSeq caps -                            ->   liftIO ( foldM (\ptr cap -> serialize_cap_expression cap [] ptr) out_ptr caps )-                            >>=  apply_color_diff set_fore_color ( fore_color_diff diffs )-                            >>=  apply_color_diff set_back_color ( back_color_diff diffs )-                        SetState state-                            -- implicitly resets the colors to the defaults-                            ->  liftIO $ serialize_cap_expression ( fromJust $ set_attr_states -                                                                             $ display_attr_caps-                                                                             $ term d-                                                                  )-                                                                  ( sgr_args_for_state state )-                                                                  out_ptr-                            >>= set_colors-        where -            attr = fix_display_attr prev_attr req_attr-            set_colors ptr = do-                ptr' <- case fixed_fore_color attr of-                    Just c -> liftIO $ serialize_cap_expression ( set_fore_color $ term d ) -                                                       [ ansi_color_index c ]-                                                       ptr-                    Nothing -> return ptr-                ptr'' <- case fixed_back_color attr of-                    Just c -> liftIO $ serialize_cap_expression ( set_back_color $ term d ) -                                                       [ ansi_color_index c ]-                                                       ptr'-                    Nothing -> return ptr'-                return ptr''-            apply_color_diff _f NoColorChange ptr-                = return ptr-            apply_color_diff _f ColorToDefault _ptr-                = fail "ColorToDefault is not a possible case for apply_color_diffs"-            apply_color_diff f ( SetColor c ) ptr-                = liftIO $ serialize_cap_expression ( f $ term d ) -                                                    [ ansi_color_index c ]-                                                    ptr--    default_attr_required_bytes d -        = cap_expression_required_bytes (set_default_attr $ term d) []-    serialize_default_attr d out_ptr = do-        liftIO $ serialize_cap_expression ( set_default_attr $ term d ) [] out_ptr---- | The color table used by a terminal is a 16 color set followed by a 240 color set that might not--- be supported by the terminal.------ This takes a Color which clearly identifies which pallete to use and computes the index--- into the full 256 color pallete.-ansi_color_index :: Color -> Word-ansi_color_index (ISOColor v) = toEnum $ fromEnum v-ansi_color_index (Color240 v) = 16 + ( toEnum $ fromEnum v )--{- | The sequence of terminfo caps to apply a given style are determined according to these rules.- -- -  1. The assumption is that it's preferable to use the simpler enter/exit mode capabilities than- -  the full set display attribute state capability. - -- -  2. If a mode is supposed to be removed but there is not an exit capability defined then the- -  display attributes are reset to defaults then the display attribute state is set.- -- -  3. If a mode is supposed to be applied but there is not an enter capability defined then then- -  display attribute state is set if possible. Otherwise the mode is not applied.- -- -  4. If the display attribute state is being set then just update the arguments to that for any- -  apply/remove.- -- -}-data DisplayAttrSeq-    = EnterExitSeq [CapExpression]-    | SetState DisplayAttrState--data DisplayAttrState = DisplayAttrState-    { apply_standout :: Bool-    , apply_underline :: Bool-    , apply_reverse_video :: Bool-    , apply_blink :: Bool-    , apply_dim :: Bool-    , apply_bold :: Bool-    }--sgr_args_for_state :: DisplayAttrState -> [CapParam]-sgr_args_for_state attr_state = map (\b -> if b then 1 else 0)-    [ apply_standout attr_state-    , apply_underline attr_state-    , apply_reverse_video attr_state-    , apply_blink attr_state-    , apply_dim attr_state-    , apply_bold attr_state-    , False -- invis-    , False -- protect-    , False -- alt char set-    ]--req_display_cap_seq_for :: DisplayAttrCaps -> Style -> [StyleStateChange] -> DisplayAttrSeq-req_display_cap_seq_for caps s diffs-    -- if the state transition implied by any diff cannot be supported with an enter/exit mode cap-    -- then either the state needs to be set or the attribute change ignored.-    = case (any no_enter_exit_cap diffs, isJust $ set_attr_states caps) of-        -- If all the diffs have an enter-exit cap then just use those-        ( False, _    ) -> EnterExitSeq $ map enter_exit_cap diffs-        -- If not all the diffs have an enter-exit cap and there is no set state cap then filter out-        -- all unsupported diffs and just apply the rest-        ( True, False ) -> EnterExitSeq $ map enter_exit_cap -                                        $ filter (not . no_enter_exit_cap) diffs-        -- if not all the diffs have an enter-exit can and there is a set state cap then just use-        -- the set state cap.-        ( True, True  ) -> SetState $ state_for_style s-    where-        no_enter_exit_cap ApplyStandout = isNothing $ enter_standout caps-        no_enter_exit_cap RemoveStandout = isNothing $ exit_standout caps-        no_enter_exit_cap ApplyUnderline = isNothing $ enter_underline caps-        no_enter_exit_cap RemoveUnderline = isNothing $ exit_underline caps-        no_enter_exit_cap ApplyReverseVideo = isNothing $ enter_reverse_video caps-        no_enter_exit_cap RemoveReverseVideo = True-        no_enter_exit_cap ApplyBlink = True-        no_enter_exit_cap RemoveBlink = True-        no_enter_exit_cap ApplyDim = isNothing $ enter_dim_mode caps-        no_enter_exit_cap RemoveDim = True-        no_enter_exit_cap ApplyBold = isNothing $ enter_bold_mode caps-        no_enter_exit_cap RemoveBold = True-        enter_exit_cap ApplyStandout = fromJust $ enter_standout caps-        enter_exit_cap RemoveStandout = fromJust $ exit_standout caps-        enter_exit_cap ApplyUnderline = fromJust $ enter_underline caps-        enter_exit_cap RemoveUnderline = fromJust $ exit_underline caps-        enter_exit_cap ApplyReverseVideo = fromJust $ enter_reverse_video caps-        enter_exit_cap ApplyDim = fromJust $ enter_dim_mode caps-        enter_exit_cap ApplyBold = fromJust $ enter_bold_mode caps-        enter_exit_cap _ = error "enter_exit_cap applied to diff that was known not to have one."--state_for_style :: Style -> DisplayAttrState-state_for_style s = DisplayAttrState-    { apply_standout = is_style_set standout-    , apply_underline = is_style_set underline-    , apply_reverse_video = is_style_set reverse_video-    , apply_blink = is_style_set blink-    , apply_dim = is_style_set dim-    , apply_bold = is_style_set bold-    }-    where is_style_set = has_style s--style_to_apply_seq :: Style -> [StyleStateChange]-style_to_apply_seq s = concat-    [ apply_if_required ApplyStandout standout-    , apply_if_required ApplyUnderline underline-    , apply_if_required ApplyReverseVideo reverse_video-    , apply_if_required ApplyBlink blink-    , apply_if_required ApplyDim dim-    , apply_if_required ApplyBlink bold-    ]-    where -        apply_if_required ap flag -            = if 0 == ( flag .&. s )-                then []-                else [ ap ]-
− src/Graphics/Vty/Terminal/XTermColor.hs
@@ -1,109 +0,0 @@--- Copyright 2009-2010 Corey O'Connor-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeFamilies #-}-module Graphics.Vty.Terminal.XTermColor ( terminal_instance-                                        )-    where--import Graphics.Vty.Terminal.Generic-import qualified Graphics.Vty.Terminal.TerminfoBased as TerminfoBased--import Control.Applicative-import Control.Monad.Trans--import qualified Data.String.UTF8 as UTF8--import System.IO---- | An xterm color based terminal is some variant paired with a standard TerminfoBased terminal--- interface.------ For the most part, xterm terminals just use the TerminfoBased implementation.-data XTermColor = XTermColor -    { xterm_variant :: String-    , super_term :: TerminalHandle-    }---- | Initialize the display to UTF-8. -terminal_instance :: ( Applicative m, MonadIO m ) => String -> m XTermColor-terminal_instance variant = do-    -- If the terminal variant is xterm-color use xterm instead since, more often than not,-    -- xterm-color is broken.-    let variant' = if variant == "xterm-color" then "xterm" else variant-    flushed_put set_utf8_char_set-    t <- TerminfoBased.terminal_instance variant' >>= new_terminal_handle-    return $ XTermColor variant' t---- | Output immediately followed by a flush.------ \todo move out of this module.-flushed_put :: MonadIO m => String -> m ()-flushed_put str = do-    liftIO $ hPutStr stdout str-    liftIO $ hFlush stdout---- | These sequences set xterm based terminals to UTF-8 output.------ \todo I don't know of a terminfo cap that is equivalent to this.-set_utf8_char_set, set_default_char_set :: String-set_utf8_char_set = "\ESC%G"-set_default_char_set = "\ESC%@"--instance Terminal XTermColor where-    terminal_ID t = (show $ xterm_variant t) ++ " :: XTermColor"--    release_terminal t = do -        flushed_put set_default_char_set-        release_terminal $ super_term t--    reserve_display t = reserve_display (super_term t)--    release_display t = release_display (super_term t)--    display_terminal_instance t b c = do-        d <- display_context (super_term t) b-        return $ c (DisplayContext d)--    display_bounds t = display_bounds (super_term t)-        -    output_byte_buffer t = output_byte_buffer (super_term t)--    output_handle t = output_handle (super_term t)--data DisplayContext = DisplayContext-    { super_display :: DisplayHandle-    }--instance DisplayTerminal DisplayContext where-    context_region d = context_region (super_display d)--    context_color_count d = context_color_count (super_display d)--    move_cursor_required_bytes d = move_cursor_required_bytes (super_display d)-    serialize_move_cursor d = serialize_move_cursor (super_display d)--    show_cursor_required_bytes d = show_cursor_required_bytes (super_display d)-    serialize_show_cursor d = serialize_show_cursor (super_display d)--    hide_cursor_required_bytes d = hide_cursor_required_bytes (super_display d)-    serialize_hide_cursor d = serialize_hide_cursor (super_display d)--    attr_required_bytes d = attr_required_bytes (super_display d)-    serialize_set_attr d = serialize_set_attr (super_display d)--    default_attr_required_bytes d = default_attr_required_bytes (super_display d)-    serialize_default_attr d = serialize_default_attr (super_display d)--    -- | I think xterm is broken: Reseting the background color as the first bytes serialized on a-    -- new line does not effect the background color xterm uses to clear the line. Which is used-    -- *after* the next newline.-    inline_hack d = do-        let t = case super_display d of-                    DisplayHandle _ t_ _ -> t_-        let s_utf8 = UTF8.fromString "\ESC[K"-        liftIO $ marshall_to_terminal t ( utf8_text_required_bytes s_utf8)-                                        ( serialize_utf8_text s_utf8 )-
test/Verify.hs view
@@ -1,5 +1,6 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-}@@ -7,7 +8,13 @@ {-# LANGUAGE DisambiguateRecordFields #-} {-# LANGUAGE NamedFieldPuns #-} module Verify ( module Verify+              , module Control.Applicative+              , module Control.DeepSeq+              , module Control.Exception+              , module Control.Monad               , module Test.QuickCheck+              , module Test.QuickCheck.Modifiers+              , module Text.Printf               , succeeded               , failed               , result@@ -19,35 +26,41 @@               )     where +import Control.Exception ( bracket, try, SomeException(..) )+ import Distribution.TestSuite hiding ( Result(..) ) import qualified Distribution.TestSuite as TS  import Test.QuickCheck hiding ( Result(..) ) import qualified Test.QuickCheck as QC+import Test.QuickCheck.Modifiers import Test.QuickCheck.Property hiding ( Result(..) ) import qualified Test.QuickCheck.Property as Prop import Test.QuickCheck.Monadic ( monadicIO )  +import Text.Printf+ import qualified Codec.Binary.UTF8.String as UTF8 +import Control.Applicative hiding ( (<|>) )+import Control.DeepSeq+import Control.Monad ( forM, mapM, mapM_, forM_ ) import Control.Monad.State.Strict -import Data.IORef-import Data.Word- import Numeric ( showHex ) -import System.IO-import System.Random- verify :: Testable t => String -> t -> Test-verify test_name p = Test $ TestInstance-  { name = test_name+verify testName p = Test $ TestInstance+  { name = testName   , run = do-    qc_result <- quickCheckResult p-    case qc_result of-      QC.Success {..} -> return $ Finished TS.Pass-      _               -> return $ Finished $ TS.Fail "TODO(corey): add failure message"+    qcResult <- quickCheckWithResult (stdArgs {chatty = False}) p+    case qcResult of+        QC.Success {..} -> return $ Finished TS.Pass+        QC.Failure {numShrinks,reason} -> return $ Finished+            $ TS.Fail $ "After " +                      ++ show numShrinks ++ " shrinks determined failure to be: "+                      ++ show reason+        _ -> return $ Finished $ TS.Fail "TODO(corey): add failure message"   , tags = []   , options = []   , setOption = \_ _ -> Left "no options supported"@@ -82,4 +95,7 @@         let (i :: Int, g') = randomR (fromEnum l,fromEnum h) g         in (toEnum i, g') #endif++data Bench where+    Bench :: forall v . NFData v => IO v -> (v -> IO ()) -> Bench 
test/Verify/Data/Terminfo/Parse.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE ScopedTypeVariables #-} module Verify.Data.Terminfo.Parse ( module Verify.Data.Terminfo.Parse                                   , module Data.Terminfo.Parse                                   )@@ -9,17 +8,12 @@ import Verify  import Data.Word+import qualified Data.Vector.Unboxed as Vector  import Numeric -instance Show CapExpression where-    show c -        = "CapExpression { " ++ show (cap_ops c) ++ " }"-        ++ " <- [" ++ hex_dump ( map ( toEnum . fromEnum ) $! source_string c ) ++ "]"-        ++ " <= " ++ show (source_string c) --hex_dump :: [Word8] -> String-hex_dump bytes = foldr (\b s -> showHex b s) "" bytes+hexDump :: [Word8] -> String+hexDump bytes = foldr (\b s -> showHex b s) "" bytes  data NonParamCapString = NonParamCapString String     deriving Show@@ -38,7 +32,7 @@     arbitrary          = ( do             NonParamCapString s <- arbitrary-            (s', bytes) <- insert_escape_op "%" [toEnum $ fromEnum '%'] s+            (s', bytes) <- insertEscapeOp "%" [toEnum $ fromEnum '%'] s             return $ LiteralPercentCap s' bytes           ) `suchThat` ( \(LiteralPercentCap str _) -> length str < 255 )  @@ -49,72 +43,70 @@     arbitrary         = ( do             NonParamCapString s <- arbitrary-            (s', bytes) <- insert_escape_op "i" [] s+            (s', bytes) <- insertEscapeOp "i" [] s             return $ IncFirstTwoCap s' bytes           ) `suchThat` ( \(IncFirstTwoCap str _) -> length str < 255 )  -data PushParamCap = PushParamCap String Word [Word8]+data PushParamCap = PushParamCap String Int [Word8]     deriving ( Show )  instance Arbitrary PushParamCap where     arbitrary         = ( do             NonParamCapString s <- arbitrary-            n :: Word <- choose (1,9) >>= return . toEnum-            (s', bytes) <- insert_escape_op ("p" ++ show n) [] s+            n <- choose (1,9)+            (s', bytes) <- insertEscapeOp ("p" ++ show n) [] s             return $ PushParamCap s' n bytes           ) `suchThat` ( \(PushParamCap str _ _) -> length str < 255 )  -data DecPrintCap = DecPrintCap String Word [Word8]+data DecPrintCap = DecPrintCap String Int [Word8]     deriving ( Show )  instance Arbitrary DecPrintCap where     arbitrary         = ( do             NonParamCapString s <- arbitrary-            n :: Word <- choose (1,9) >>= return . toEnum-            (s', bytes) <- insert_escape_op ("p" ++ show n ++ "%d") [] s+            n <- choose (1,9)+            (s', bytes) <- insertEscapeOp ("p" ++ show n ++ "%d") [] s             return $ DecPrintCap s' n bytes           ) `suchThat` ( \(DecPrintCap str _ _) -> length str < 255 )  -insert_escape_op op_str repl_bytes s = do-    insert_points <- listOf1 $ elements [0 .. length s - 1]-    let s' = f s ('%' : op_str)-        remaining_bytes = f (map (toEnum . fromEnum) s) repl_bytes-        f in_vs out_v = concat [ vs-                               | vi <- zip in_vs [0 .. length s - 1]-                               , let vs = fst vi : ( if snd vi `elem` insert_points-                                                        then out_v-                                                        else []-                                                   )-                               ]-    return (s', remaining_bytes)+insertEscapeOp opStr replBytes s = do+    insertPoints <- listOf1 $ elements [0 .. length s - 1]+    let s' = f s ('%' : opStr)+        remainingBytes = f (map (toEnum . fromEnum) s) replBytes+        f inVs out_v = concat [ vs+                              | vi <- zip inVs [0 .. length s - 1]+                              , let vs = fst vi : ( if snd vi `elem` insertPoints+                                                       then out_v+                                                       else []+                                                  )+                              ]+    return (s', remainingBytes) -is_bytes_op :: CapOp -> Bool-is_bytes_op (Bytes {}) = True--- is_bytes_op _ = False+isBytesOp :: CapOp -> Bool+isBytesOp (Bytes {}) = True+-- isBytesOp _ = False -bytes_for_range cap offset c-    = take (fromEnum c)-    $ drop (fromEnum offset)-    $ ( map ( toEnum . fromEnum ) $! source_string cap )+bytesForRange cap offset count+    = Vector.toList $ Vector.take count $ Vector.drop offset $ capBytes cap -collect_bytes :: CapExpression -> [Word8]-collect_bytes e = concat [ bytes -                         | Bytes offset c _ <- cap_ops e-                         , let bytes = bytes_for_range e offset c-                         ]+collectBytes :: CapExpression -> [Word8]+collectBytes e = concat [ bytes +                        | Bytes offset count <- capOps e+                        , let bytes = bytesForRange e offset count+                        ]      -verify_bytes_equal :: [Word8] -> [Word8] -> Result-verify_bytes_equal out_bytes expected_bytes -    = if out_bytes == expected_bytes+verifyBytesEqual :: [Word8] -> [Word8] -> Result+verifyBytesEqual outBytes expectedBytes +    = if outBytes == expectedBytes         then succeeded         else failed -             { reason = "out_bytes [" -                       ++ hex_dump out_bytes-                       ++ "] /= expected_bytes ["-                       ++ hex_dump expected_bytes-                       ++ "]"+             { reason = "outBytes [" +                      ++ hexDump outBytes+                      ++ "] /= expectedBytes ["+                      ++ hexDump expectedBytes+                      ++ "]"              } 
test/Verify/Graphics/Vty/Attributes.hs view
@@ -8,33 +8,66 @@  import Data.List ( delete ) --- Limit the possible attributes to just a few for now.-possible_attr_mods :: [ AttrOp ]-possible_attr_mods = -    [ set_bold_op-    , id_op +allColors :: [Color]+allColors =+    [ black+    , red+    , green+    , yellow+    , blue+    , magenta+    , cyan+    , white+    , brightBlack+    , brightRed+    , brightGreen+    , brightYellow+    , brightBlue+    , brightMagenta+    , brightCyan+    , brightWhite+    ] ++ map Color240 [0..239]++allStyles :: [Style]+allStyles =+    [ standout+    , underline+    , reverseVideo+    , blink+    , dim+    , bold     ] +-- Limit the possible attributes to just a few for now.+possibleAttrMods :: [ AttrOp ]+possibleAttrMods = +    [ idOp +    ] ++ map setForeColorOp allColors+      ++ map setBackColorOp allColors+      ++ map setStyleOp allStyles+ instance Arbitrary Attr where-    arbitrary = elements possible_attr_mods >>= return . flip apply_op def_attr+    arbitrary = elements possibleAttrMods >>= return . flip applyOp defAttr  data DiffAttr = DiffAttr Attr Attr  instance Arbitrary DiffAttr where     arbitrary = do-        op0 <- elements possible_attr_mods-        let possible_attr_mods' = delete op0 possible_attr_mods-        op1 <- elements possible_attr_mods'-        return $ DiffAttr (apply_op op0 def_attr) (apply_op op1 def_attr)+        op0 <- elements possibleAttrMods+        let possibleAttrMods' = delete op0 possibleAttrMods+        op1 <- elements possibleAttrMods'+        return $ DiffAttr (applyOp op0 defAttr) (applyOp op1 defAttr)  data AttrOp = AttrOp String (Attr -> Attr)  instance Eq AttrOp where     AttrOp n0 _ == AttrOp n1 _ = n0 == n1 -set_bold_op = AttrOp "set_bold" (flip with_style bold)-id_op = AttrOp "id" id+setStyleOp s     = AttrOp "set_style"      (flip withStyle s)+setForeColorOp c = AttrOp "set_fore_color" (flip withForeColor c)+setBackColorOp c = AttrOp "set_back_color" (flip withBackColor c)+idOp = AttrOp "id" id -apply_op :: AttrOp -> Attr -> Attr-apply_op (AttrOp _ f) a = f a+applyOp :: AttrOp -> Attr -> Attr+applyOp (AttrOp _ f) a = f a 
− test/Verify/Graphics/Vty/DisplayRegion.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-module Verify.Graphics.Vty.DisplayRegion ( module Verify.Graphics.Vty.DisplayRegion-                                         , module Graphics.Vty.DisplayRegion-                                         , DebugWindow(..)-                                         )-    where--import Graphics.Vty.Debug-import Graphics.Vty.DisplayRegion--import Verify--import Data.Word--data EmptyWindow = EmptyWindow DebugWindow--instance Arbitrary EmptyWindow where-    arbitrary = return $ EmptyWindow (DebugWindow (0 :: Word) (0 :: Word))--instance Show EmptyWindow where-    show (EmptyWindow _) = "EmptyWindow"--instance Arbitrary DebugWindow where-    arbitrary = do-        w <- choose (1,1024)-        h <- choose (1,1024)-        return $ DebugWindow w h-
test/Verify/Graphics/Vty/Image.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DisambiguateRecordFields #-} module Verify.Graphics.Vty.Image ( module Verify.Graphics.Vty.Image@@ -6,85 +7,212 @@     where  import Verify.Graphics.Vty.Attributes-import Graphics.Vty.Debug.Image import Graphics.Vty.Image+import Graphics.Vty.Image.Internal  import Verify -import Data.Word- data UnitImage = UnitImage Char Image  instance Arbitrary UnitImage where     arbitrary = do         SingleColumnChar c <- arbitrary-        return $ UnitImage c (char def_attr c)+        a <- arbitrary+        return $ UnitImage c (char a c)  instance Show UnitImage where     show (UnitImage c _) = "UnitImage " ++ show c -data DefaultImage = DefaultImage Image ImageConstructLog+data DefaultImage = DefaultImage Image  instance Show DefaultImage where-    show (DefaultImage i image_log) -        = "DefaultImage (" ++ show i ++ ") " ++ show (image_width i, image_height i) ++ " " ++ show image_log+    show (DefaultImage i) +        = "DefaultImage (" ++ show i ++ ") " ++ show (imageWidth i, imageHeight i)  instance Arbitrary DefaultImage where     arbitrary = do-        i <- return $ char def_attr 'X' -- elements forward_image_ops >>= return . (\op -> op empty_image)-        return $ DefaultImage i []+        i <- return $ char defAttr 'X'+        return $ DefaultImage i  data SingleRowSingleAttrImage      = SingleRowSingleAttrImage -      { expected_attr :: Attr-      , expected_columns :: Word-      , row_image :: Image+      { expectedAttr :: Attr+      , expectedColumns :: Int+      , rowImage :: Image       }  instance Show SingleRowSingleAttrImage where     show (SingleRowSingleAttrImage attr columns image)          = "SingleRowSingleAttrImage (" ++ show attr ++ ") " ++ show columns ++ " ( " ++ show image ++ " )" +newtype WidthResize = WidthResize (Image -> (Image, Int))++instance Arbitrary WidthResize where+    arbitrary = do+        WidthResize f <- arbitrary+        w <- choose (1,64)+        oneof $ map (return . WidthResize)+            [ \i -> (i, imageWidth i)+            , \i -> (resizeWidth w $ fst $ f i, w)+            , \i -> let i' = fst $ f i in (cropLeft w i', min (imageWidth i') w)+            , \i -> let i' = fst $ f i in (cropRight w i', min (imageWidth i') w)+            ]++newtype HeightResize = HeightResize (Image -> (Image, Int))++instance Arbitrary HeightResize where+    arbitrary = do+        HeightResize f <- arbitrary+        h <- choose (1,64)+        oneof $ map (return . HeightResize)+            [ \i -> (i, imageHeight i)+            , \i -> (resizeHeight h $ fst $ f i, h)+            , \i -> let i' = fst $ f i in (cropTop h i', min (imageHeight i') h)+            , \i -> let i' = fst $ f i in (cropBottom h i', min (imageHeight i') h)+            ]++newtype ImageResize = ImageResize (Image -> (Image, (Int, Int)))++instance Arbitrary ImageResize where+    arbitrary = oneof+        [ return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , return $! ImageResize $! \i -> (i, (imageWidth i, imageHeight i))+        , do+            ImageResize f <- arbitrary+            WidthResize g <- arbitrary+            return $! ImageResize $! \i -> +                let (i0, (_, outHeight)) = f i+                    gI = g i0+                in (fst gI, (snd gI, outHeight))+        , do+            ImageResize f <- arbitrary+            HeightResize g <- arbitrary+            return $! ImageResize $! \i -> +                let (i0, (outWidth, _)) = f i+                    gI = g i0+                in (fst gI, (outWidth, snd gI))+        ]++ instance Arbitrary SingleRowSingleAttrImage where     arbitrary = do         -- The text must contain at least one character. Otherwise the image simplifies to the         -- IdImage which has a height of 0. If this is to represent a single row then the height         -- must be 1-        single_column_row_text <- resize 128 (listOf1 arbitrary)-        attr <- arbitrary-        return $ SingleRowSingleAttrImage -                    attr -                    ( fromIntegral $ length single_column_row_text )-                    ( horiz_cat $ [ char attr c | SingleColumnChar c <- single_column_row_text ] )+        singleColumnRowText <- Verify.resize 16 (listOf1 arbitrary)+        a <- arbitrary+        let outImage = horizCat $ [char a c | SingleColumnChar c <- singleColumnRowText]+            outWidth = length singleColumnRowText+        return $ SingleRowSingleAttrImage a outWidth outImage  data SingleRowTwoAttrImage      = SingleRowTwoAttrImage -    { part_0 :: SingleRowSingleAttrImage-    , part_1 :: SingleRowSingleAttrImage-    , join_image :: Image+    { part0 :: SingleRowSingleAttrImage+    , part1 :: SingleRowSingleAttrImage+    , joinImage :: Image     } deriving Show  instance Arbitrary SingleRowTwoAttrImage where     arbitrary = do         p0 <- arbitrary         p1 <- arbitrary-        return $ SingleRowTwoAttrImage p0 p1 (row_image p0 <|> row_image p1)+        return $ SingleRowTwoAttrImage p0 p1 (rowImage p0 <|> rowImage p1)  data SingleAttrSingleSpanStack = SingleAttrSingleSpanStack -    { stack_image :: Image -    , stack_source_images :: [SingleRowSingleAttrImage]-    , stack_width :: Word-    , stack_height :: Word+    { stackImage :: Image +    , stackSourceImages :: [SingleRowSingleAttrImage]+    , stackWidth :: Int+    , stackHeight :: Int     }     deriving Show  instance Arbitrary SingleAttrSingleSpanStack where     arbitrary = do-        image_list <- resize 128 (listOf1 arbitrary)-        let image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- image_list ]-        return $ SingleAttrSingleSpanStack -                    image -                    image_list -                    ( maximum $ map expected_columns image_list )-                    ( toEnum $ length image_list )+        imageList <- Verify.resize 16 (listOf1 arbitrary)+        return $ mkSingleAttrSingleSpanStack imageList+    shrink s = do+        imageList <- shrink $ stackSourceImages s+        if null imageList+            then []+            else return $ mkSingleAttrSingleSpanStack imageList++mkSingleAttrSingleSpanStack imageList =+    let image = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- imageList ]+    in SingleAttrSingleSpanStack image imageList (maximum $ map expectedColumns imageList)+                                                 (toEnum $ length imageList)++instance Arbitrary Image  where+    arbitrary = oneof+        [ return EmptyImage+        , do+            SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary+            ImageResize f <- Verify.resize 2 arbitrary+            return $! fst $! f stackImage+        , do+            SingleAttrSingleSpanStack {stackImage} <- Verify.resize 8 arbitrary+            ImageResize f <- Verify.resize 2 arbitrary+            return $! fst $! f stackImage+        , do+            i0 <- arbitrary+            i1 <- arbitrary+            let i = i0 <|> i1+            ImageResize f <- Verify.resize 2 arbitrary+            return $! fst $! f i+        , do+            i0 <- arbitrary+            i1 <- arbitrary+            let i = i0 <-> i1+            ImageResize f <- Verify.resize 2 arbitrary+            return $! fst $! f i+        ]+    {-+    shrink i@(HorizJoin {partLeft, partRight}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        !partLeft' <- shrink partLeft+        !partRight' <- shrink partRight+        [i_alt, partLeft' <|> partRight']+    shrink i@(VertJoin {partTop, partBottom}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        !partTop' <- shrink partTop+        !partBottom' <- shrink partBottom+        [i_alt, partTop' <-> partBottom']+    shrink i@(CropRight {croppedImage, outputWidth}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        [i_alt, croppedImage]+    shrink i@(CropLeft {croppedImage, leftSkip, outputWidth}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        [i_alt, croppedImage]+    shrink i@(CropBottom {croppedImage, outputHeight}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        [i_alt, croppedImage]+    shrink i@(CropTop {croppedImage, topSkip, outputHeight}) = do+        let !i_alt = backgroundFill (imageWidth i) (imageHeight i)+        [i_alt, croppedImage]+    shrink i = [emptyImage, backgroundFill (imageWidth i) (imageHeight i)]+    -}++data CropOperation+    = CropFromLeft+    | CropFromRight+    | CropFromTop+    | CropFromBottom+    deriving (Eq, Show)++instance Arbitrary CropOperation where+    arbitrary = oneof $ map return [CropFromLeft, CropFromRight, CropFromTop, CropFromBottom]++data Translation = Translation Image (Int, Int) Image+    deriving (Eq, Show)++instance Arbitrary Translation where+    arbitrary = do+        i <- arbitrary+        x <- arbitrary `suchThat` (> 0)+        y <- arbitrary `suchThat` (> 0)+        let i' = translate x y i+        return $ Translation i (x,y) i' 
+ test/Verify/Graphics/Vty/Output.hs view
@@ -0,0 +1,60 @@+module Verify.Graphics.Vty.Output where++import Graphics.Vty.Output.Mock++import qualified Data.ByteString as BS+import Data.IORef+import qualified Data.String.UTF8 as UTF8++import Test.QuickCheck.Property++-- A list of terminals that should be supported.+-- This started with a list of terminals ubuntu supported. Then those terminals that really could+-- not be supported were removed. Then a few more were pruned until a reasonable looking set was+-- made.+terminalsOfInterest :: [String]+terminalsOfInterest = +    [ "vt100"+    , "vt220"+    , "vt102"+    , "xterm-r5"+    , "xterm-xfree86"+    , "xterm-r6"+    , "xterm-256color"+    , "xterm-vt220"+    , "xterm-debian"+    , "xterm-mono"+    , "xterm-color"+    , "xterm"+    , "mach"+    , "mach-bold"+    , "mach-color"+    , "linux"+    , "ansi"+    , "hurd"+    , "Eterm"+    , "pcansi"+    , "screen-256color"+    , "screen-bce"+    , "screen-s"+    , "screen-w"+    , "screen"+    , "screen-256color-bce"+    , "sun"+    , "rxvt"+    , "rxvt-unicode"+    , "rxvt-basic"+    , "cygwin"+    ]++compareMockOutput :: MockData -> String -> IO Result+compareMockOutput mockData expectedStr = do+    outBytes <- readIORef mockData >>= return . UTF8.toRep+    let expectedBytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expectedStr+    if outBytes /=  expectedBytes+        then return $ failed { reason = "bytes\n" ++ show outBytes+                                      ++ "\nare not the expected bytes\n"+                                      ++ show expectedBytes+                             }+        else return succeeded+
test/Verify/Graphics/Vty/Picture.hs view
@@ -4,47 +4,49 @@                                    )     where +import Verify.Graphics.Vty.Prelude+ import Graphics.Vty.Picture import Graphics.Vty.Debug  import Verify.Graphics.Vty.Attributes import Verify.Graphics.Vty.Image-import Verify.Graphics.Vty.DisplayRegion  import Verify  data DefaultPic = DefaultPic -    { default_pic :: Picture-    , default_win :: DebugWindow -    , default_construct_log :: ImageConstructLog+    { defaultPic :: Picture+    , defaultWin :: MockWindow      }  instance Show DefaultPic where-    show (DefaultPic pic win image_log) -        = "DefaultPic\n\t( " ++ show pic ++ ")\n\t" ++ show win ++ "\n\t" ++ show image_log ++ "\n"+    show (DefaultPic pic win)+        = "DefaultPic\n\t( " ++ show pic ++ ")\n\t" ++ show win ++ "\n"  instance Arbitrary DefaultPic where     arbitrary = do-        DefaultImage image image_construct_events <- arbitrary-        let win = DebugWindow (image_width image) (image_height image)-        return $ DefaultPic (pic_for_image image) +        DefaultImage image <- arbitrary+        let win = MockWindow (imageWidth image) (imageHeight image)+        return $ DefaultPic (picForImage image)                              win -                            image_construct_events  data PicWithBGAttr = PicWithBGAttr -    { with_attr_pic :: Picture-    , with_attr_win :: DebugWindow-    , with_attr_construct_log :: ImageConstructLog-    , with_attr_specified_attr :: Attr+    { withAttrPic :: Picture+    , withAttrWin :: MockWindow+    , withAttrSpecifiedAttr :: Attr     } deriving ( Show )  instance Arbitrary PicWithBGAttr where     arbitrary = do-        DefaultImage image image_construct_events <- arbitrary-        let win = DebugWindow (image_width image) (image_height image)+        DefaultImage image <- arbitrary+        let win = MockWindow (imageWidth image) (imageHeight image)         attr <- arbitrary-        return $ PicWithBGAttr (pic_for_image image) +        return $ PicWithBGAttr (picForImage image)                                 win -                               image_construct_events                                attr         +instance Arbitrary Picture where+    arbitrary = do+        layers <- Verify.resize 20 (listOf1 arbitrary)+        return $ picForLayers layers+
+ test/Verify/Graphics/Vty/Prelude.hs view
@@ -0,0 +1,26 @@+module Verify.Graphics.Vty.Prelude ( module Verify.Graphics.Vty.Prelude+                                   , module Graphics.Vty.Prelude+                                   , MockWindow(..)+                                   )+    where++import Graphics.Vty.Prelude++import Graphics.Vty.Debug++import Verify++data EmptyWindow = EmptyWindow MockWindow++instance Arbitrary EmptyWindow where+    arbitrary = return $ EmptyWindow (MockWindow (0 :: Int) (0 :: Int))++instance Show EmptyWindow where+    show (EmptyWindow _) = "EmptyWindow"++instance Arbitrary MockWindow where+    arbitrary = do+        w <- choose (1,1024)+        h <- choose (1,1024)+        return $ MockWindow w h+
test/Verify/Graphics/Vty/Span.hs view
@@ -9,22 +9,29 @@  import Verify.Graphics.Vty.Picture +import qualified Data.Vector as Vector import Data.Word  import Verify -is_attr_span_op :: SpanOp -> Bool-is_attr_span_op AttributeChange {} = True-is_attr_span_op _                  = False+isAttrSpanOp :: SpanOp -> Bool+isAttrSpanOp TextSpan {} = True+isAttrSpanOp _           = False -verify_all_spans_have_width i spans w-    = case all_spans_have_width spans w of-        True -> succeeded-        False -> failed { reason = "Not all spans contained operations defining exactly " -                                 ++ show w-                                 ++ " columns of output -\n"-                                 ++ show i-                                 ++ "\n->\n"-                                 ++ show spans-                        }+verifyAllSpansHaveWidth i spans w+    = let v = map (\s -> (spanOpsEffectedColumns s /= w, s)) (Vector.toList spans)+      in case any ((== True) . fst) v of+        False -> succeeded+        True -> failed { reason = "Not all spans contained operations defining exactly " +                                ++ show w+                                ++ " columns of output - \n"+                                ++ (concatMap ((++ "\n") . show)) v+                            }++verifyOpsEquality i_ops i_alt_ops =+    if i_ops == i_alt_ops+        then succeeded+        else failed { reason = "ops for alternate image " ++ show i_alt_ops+                               ++ " are not the same as " ++ show i_ops+                    } 
+ test/VerifyConfig.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE QuasiQuotes #-}+module Main where++import Graphics.Vty.Config+import Graphics.Vty.Input.Events++import Control.Applicative+import Control.Exception+import Control.Lens ((^.))+import Control.Monad++import Data.Default+import Data.String.QQ++import Test.Framework+import Test.Framework.Providers.HUnit+import Test.HUnit hiding (Test)++import Text.Printf++exampleConfig :: String+exampleConfig = [s|+-- comments should be ignored.+map _ "\ESC[B" KUp []+askfjla dfasjdflk jasdlkfj asdfj -- lines failing parse should be ignored+map _ "\ESC[1;3B" KDown [MAlt]+map "xterm" "\ESC[1;3B" KDown [MAlt]+map "xterm-256-color" "\ESC[1;3B" KDown [MAlt]+debugLog "/tmp/vty-debug.txt"+|]++exampleConfigConfig :: Config+exampleConfigConfig = Config+    { specifiedEscPeriod = def+    , debugLog = Just "/tmp/vty-debug.txt"+    , inputMap = [ (Nothing, "\ESC[B", EvKey KUp [])+                 , (Nothing, "\ESC[1;3B", EvKey KDown [MAlt])+                 , (Just "xterm", "\ESC[1;3B", EvKey KDown [MAlt])+                 , (Just "xterm-256-color", "\ESC[1;3B", EvKey KDown [MAlt])+                 ]+    }++exampleConfigParses :: IO ()+exampleConfigParses = assertEqual "example config parses as expected"+                                  exampleConfigConfig+                                  (runParseConfig "exampleConfig" exampleConfig)++main :: IO ()+main = defaultMain+    [ testCase "example config parses" $ exampleConfigParses+    ]+
+ test/VerifyCropSpanGeneration.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+module VerifyCropSpanGeneration where++import Verify.Graphics.Vty.Prelude++import Verify.Graphics.Vty.Image+import Verify.Graphics.Vty.Picture+import Verify.Graphics.Vty.Span++import Graphics.Vty.Debug+import Graphics.Vty.PictureToSpans++import Verify++import qualified Data.Vector as Vector ++cropOpDisplayOps :: (Int -> Image -> Image) ->+                    Int -> Image -> (DisplayOps, Image)+cropOpDisplayOps cropOp v i =+    let iOut = cropOp v i+        p = picForImage iOut+        w = MockWindow (imageWidth iOut) (imageHeight iOut)+    in (displayOpsForPic p (regionForWindow w), iOut)++widthCropOutputColumns :: (Int -> Image -> Image) ->+                          SingleAttrSingleSpanStack ->+                          NonNegative Int ->+                          Property+widthCropOutputColumns cropOp s (NonNegative w) = stackWidth s > w ==>+    let (ops, iOut) = cropOpDisplayOps cropOp w (stackImage s)+    in verifyAllSpansHaveWidth iOut ops w++heightCropOutputColumns :: (Int -> Image -> Image) ->+                           SingleAttrSingleSpanStack ->+                           NonNegative Int ->+                           Property+heightCropOutputColumns cropOp s (NonNegative h) = stackHeight s > h ==>+    let (ops, _) = cropOpDisplayOps cropOp h (stackImage s)+    in displayOpsRows ops == h++cropRightOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property+cropRightOutputColumns = widthCropOutputColumns cropRight++cropLeftOutputColumns :: SingleAttrSingleSpanStack -> NonNegative Int -> Property+cropLeftOutputColumns = widthCropOutputColumns cropLeft++cropTopOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property+cropTopOutputRows = heightCropOutputColumns cropTop++cropBottomOutputRows :: SingleAttrSingleSpanStack -> NonNegative Int -> Property+cropBottomOutputRows = heightCropOutputColumns cropBottom++-- TODO: known benign failure.+cropRightAndLeftRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property+cropRightAndLeftRejoinedEquivalence stack = imageWidth (stackImage stack) `mod` 2 == 0 ==>+    let i = stackImage stack+        -- the right part is made by cropping the image from the left.+        iR = cropLeft (imageWidth i `div` 2) i+        -- the left part is made by cropping the image from the right+        iL = cropRight (imageWidth i `div` 2) i+        iAlt = iL <|> iR+        iOps = displayOpsForImage i+        iAltOps = displayOpsForImage iAlt+    in verifyOpsEquality iOps iAltOps++cropTopAndBottomRejoinedEquivalence :: SingleAttrSingleSpanStack -> Property+cropTopAndBottomRejoinedEquivalence stack = imageHeight (stackImage stack) `mod` 2 == 0 ==>+    let i = stackImage stack+        -- the top part is made by cropping the image from the bottom.+        iT = cropBottom (imageHeight i `div` 2) i+        -- the bottom part is made by cropping the image from the top.+        iB = cropTop (imageHeight i `div` 2) i+        iAlt = iT <-> iB+    in displayOpsForImage i == displayOpsForImage iAlt++tests :: IO [Test]+tests = return +    [ verify "cropping from the bottom produces display operations covering the expected rows"+        cropBottomOutputRows+    , verify "cropping from the top produces display operations covering the expected rows"+        cropTopOutputRows+    , verify "cropping from the left produces display operations covering the expected columns"+        cropLeftOutputColumns+    , verify "cropping from the right produces display operations covering the expected columns"+        cropRightOutputColumns+    -- TODO: known benign failure.+    -- , verify "the output of a stack is the same as that stack cropped left & right and joined together"+    --     cropRightAndLeftRejoinedEquivalence+    , verify "the output of a stack is the same as that stack cropped top & bottom and joined together"+        cropTopAndBottomRejoinedEquivalence+    ]+
test/VerifyEmptyImageProps.hs view
@@ -1,14 +1,13 @@-{-# LANGUAGE ScopedTypeVariables #-} module VerifyEmptyImageProps where  import Verify  -- should be exported by Graphics.Vty.Picture-import Graphics.Vty.Picture ( Image, empty_image )+import Graphics.Vty.Picture ( Image, emptyImage )  tests :: IO [Test] tests = do     -- should provide an image type.-    let _ :: Image = empty_image+    let _ :: Image = emptyImage     return [] 
test/VerifyEvalTerminfoCaps.hs view
@@ -1,10 +1,8 @@ {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NamedFieldPuns #-} module VerifyEvalTerminfoCaps where -import Data.Marshalling-+import Blaze.ByteString.Builder.Internal.Write (runWrite, getBound) import Data.Terminfo.Eval  import Data.Terminfo.Parse import Control.DeepSeq@@ -12,6 +10,7 @@ import qualified System.Console.Terminfo as Terminfo  import Verify+import Verify.Graphics.Vty.Output  import Control.Applicative ( (<$>) ) import Control.Exception ( try, SomeException(..) )@@ -21,51 +20,12 @@ import Data.Maybe ( fromJust ) import Data.Word +import Foreign.Marshal.Alloc (mallocBytes)+import Foreign.Ptr (Ptr, minusPtr) import Numeric --- A list of terminals that ubuntu includes a terminfo cap file for. --- Assuming that is a good place to start.-terminals_of_interest = -    [ "wsvt25"-    , "wsvt25m"-    , "vt52"-    , "vt100"-    , "vt220"-    , "vt102"-    , "xterm-r5"-    , "xterm-xfree86"-    , "xterm-r6"-    , "xterm-256color"-    , "xterm-vt220"-    , "xterm-debian"-    , "xterm-mono"-    , "xterm-color"-    , "xterm"-    , "mach"-    , "mach-bold"-    , "mach-color"-    , "linux"-    , "ansi"-    , "hurd"-    , "Eterm"-    , "pcansi"-    , "screen-256color"-    , "screen-bce"-    , "screen-s"-    , "screen-w"-    , "screen"-    , "screen-256color-bce"-    , "sun"-    , "rxvt"-    , "rxvt-unicode"-    , "rxvt-basic"-    , "cygwin"-    , "cons25"-    , "dumb"-    ]- -- If a terminal defines one of the caps then it's expected to be parsable.-caps_of_interest = +capsOfInterest =      [ "cup"     , "sc"     , "rc"@@ -85,45 +45,45 @@     , "sgr0"     ] -from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)  tests :: IO [Test] tests = do-    eval_buffer :: Ptr Word8 <- mallocBytes (1024 * 1024) -- Should be big enough for any termcaps ;-)-    fmap concat $ forM terminals_of_interest $ \term_name -> do-        putStrLn $ "adding tests for terminal: " ++ term_name-        mti <- try $ Terminfo.setupTerm term_name+    evalBuffer :: Ptr Word8 <- mallocBytes (1024 * 1024) -- Should be big enough for any termcaps ;-)+    fmap concat $ forM terminalsOfInterest $ \termName -> do+        putStrLn $ "adding tests for terminal: " ++ termName+        mti <- try $ Terminfo.setupTerm termName         case mti of             Left (_e :: SomeException)                  -> return []             Right ti -> do-                fmap concat $ forM caps_of_interest $ \cap_name -> do-                    case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of-                        Just cap_def -> do-                            putStrLn $ "\tadding test for cap: " ++ cap_name-                            let test_name = term_name ++ "(" ++ cap_name ++ ")"-                            parse_result <- parse_cap_expression cap_def-                            case parse_result of-                                Left error -> return [ verify test_name ( failed { reason = "parse error " ++ show error } ) ]-                                Right !cap_expr -> return [ verify test_name ( verify_eval_cap eval_buffer cap_expr ) ]+                fmap concat $ forM capsOfInterest $ \capName -> do+                    case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of+                        Just capDef -> do+                            putStrLn $ "\tadding test for cap: " ++ capName+                            let testName = termName ++ "(" ++ capName ++ ")"+                            case parseCapExpression capDef of+                                Left error -> return [verify testName (failed {reason = "parse error " ++ show error})]+                                Right !cap_expr -> return [verify testName (verifyEvalCap evalBuffer cap_expr)]                         Nothing      -> do                             return [] -{-# NOINLINE verify_eval_cap #-}-verify_eval_cap :: Ptr Word8 -> CapExpression -> Int -> Property-verify_eval_cap eval_buffer expr !junk_int = do-    forAll (vector 9) $ \input_values -> -        let !byte_count = cap_expression_required_bytes expr input_values+{-# NOINLINE verifyEvalCap #-}+verifyEvalCap :: Ptr Word8 -> CapExpression -> Int -> Property+verifyEvalCap evalBuffer expr !junkInt = do+    forAll (vector 9) $ \inputValues -> +        let write = writeCapExpr expr inputValues+            !byteCount = getBound write         in liftIOResult $ do-            let start_ptr :: Ptr Word8 = eval_buffer-            forM_ [0..100] $ \i -> serialize_cap_expression expr input_values start_ptr-            end_ptr <- serialize_cap_expression expr input_values start_ptr-            case end_ptr `minusPtr` start_ptr of+            let startPtr :: Ptr Word8 = evalBuffer+            forM_ [0..100] $ \i -> runWrite write startPtr+            endPtr <- runWrite write startPtr+            case endPtr `minusPtr` startPtr of                 count | count < 0        ->                              return $ failed { reason = "End pointer before start pointer." }-                      | toEnum count > byte_count -> +                      | toEnum count > byteCount ->                              return $ failed { reason = "End pointer past end of buffer by " -                                                       ++ show (toEnum count - byte_count) +                                                       ++ show (toEnum count - byteCount)                                              }                       | otherwise        ->                              return succeeded
test/VerifyImageOps.hs view
@@ -1,125 +1,174 @@-{-# LANGUAGE ScopedTypeVariables #-} module VerifyImageOps where  import Graphics.Vty.Attributes+import Graphics.Vty.Image.Internal import Verify.Graphics.Vty.Image  import Verify -import Data.Word+import Control.DeepSeq -two_sw_horiz_concat :: SingleColumnChar -> SingleColumnChar -> Bool-two_sw_horiz_concat (SingleColumnChar c1) (SingleColumnChar c2) = -    image_width (char def_attr c1 <|> char def_attr c2) == 2+twoSwHorizConcat :: SingleColumnChar -> SingleColumnChar -> Bool+twoSwHorizConcat (SingleColumnChar c1) (SingleColumnChar c2) = +    imageWidth (char defAttr c1 <|> char defAttr c2) == 2 -many_sw_horiz_concat :: [SingleColumnChar] -> Bool-many_sw_horiz_concat cs = +manySwHorizConcat :: [SingleColumnChar] -> Bool+manySwHorizConcat cs =      let chars = [ char | SingleColumnChar char <- cs ]         l = fromIntegral $ length cs-    in image_width ( horiz_cat $ map (char def_attr) chars ) == l+    in imageWidth ( horizCat $ map (char defAttr) chars ) == l -two_sw_vert_concat :: SingleColumnChar -> SingleColumnChar -> Bool-two_sw_vert_concat (SingleColumnChar c1) (SingleColumnChar c2) = -    image_height (char def_attr c1 <-> char def_attr c2) == 2+twoSwVertConcat :: SingleColumnChar -> SingleColumnChar -> Bool+twoSwVertConcat (SingleColumnChar c1) (SingleColumnChar c2) = +    imageHeight (char defAttr c1 <-> char defAttr c2) == 2 -horiz_concat_sw_assoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool-horiz_concat_sw_assoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) = -    (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2 +horizConcatSwAssoc :: SingleColumnChar -> SingleColumnChar -> SingleColumnChar -> Bool+horizConcatSwAssoc (SingleColumnChar c0) (SingleColumnChar c1) (SingleColumnChar c2) = +    (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2      == -    char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)+    char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2) -two_dw_horiz_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool-two_dw_horiz_concat (DoubleColumnChar c1) (DoubleColumnChar c2) = -    image_width (char def_attr c1 <|> char def_attr c2) == 4+twoDwHorizConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool+twoDwHorizConcat (DoubleColumnChar c1) (DoubleColumnChar c2) = +    imageWidth (char defAttr c1 <|> char defAttr c2) == 4 -many_dw_horiz_concat :: [DoubleColumnChar] -> Bool-many_dw_horiz_concat cs = +manyDwHorizConcat :: [DoubleColumnChar] -> Bool+manyDwHorizConcat cs =      let chars = [ char | DoubleColumnChar char <- cs ]         l = fromIntegral $ length cs-    in image_width ( horiz_cat $ map (char def_attr) chars ) == l * 2+    in imageWidth ( horizCat $ map (char defAttr) chars ) == l * 2 -two_dw_vert_concat :: DoubleColumnChar -> DoubleColumnChar -> Bool-two_dw_vert_concat (DoubleColumnChar c1) (DoubleColumnChar c2) = -    image_height (char def_attr c1 <-> char def_attr c2) == 2+twoDwVertConcat :: DoubleColumnChar -> DoubleColumnChar -> Bool+twoDwVertConcat (DoubleColumnChar c1) (DoubleColumnChar c2) = +    imageHeight (char defAttr c1 <-> char defAttr c2) == 2 -horiz_concat_dw_assoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool-horiz_concat_dw_assoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) = -    (char def_attr c0 <|> char def_attr c1) <|> char def_attr c2 +horizConcatDwAssoc :: DoubleColumnChar -> DoubleColumnChar -> DoubleColumnChar -> Bool+horizConcatDwAssoc (DoubleColumnChar c0) (DoubleColumnChar c1) (DoubleColumnChar c2) = +    (char defAttr c0 <|> char defAttr c1) <|> char defAttr c2      == -    char def_attr c0 <|> (char def_attr c1 <|> char def_attr c2)+    char defAttr c0 <|> (char defAttr c1 <|> char defAttr c2) -vert_contat_single_row :: NonEmptyList SingleRowSingleAttrImage -> Bool-vert_contat_single_row (NonEmpty stack) =-    let expected_height :: Word = fromIntegral $ length stack-        stack_image = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack ]-    in image_height stack_image == expected_height+vertContatSingleRow :: NonEmptyList SingleRowSingleAttrImage -> Bool+vertContatSingleRow (NonEmpty stack) =+    let expectedHeight :: Int = length stack+        stackImage = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack ]+    in imageHeight stackImage == expectedHeight -disjoint_height_horiz_join :: NonEmptyList SingleRowSingleAttrImage +disjointHeightHorizJoin :: NonEmptyList SingleRowSingleAttrImage                                -> NonEmptyList SingleRowSingleAttrImage                               -> Bool-disjoint_height_horiz_join (NonEmpty stack_0) (NonEmpty stack_1) =-    let expected_height :: Word = fromIntegral $ max (length stack_0) (length stack_1)-        stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]-        stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]-    in image_height (stack_image_0 <|> stack_image_1) == expected_height+disjointHeightHorizJoin (NonEmpty stack0) (NonEmpty stack1) =+    let expectedHeight :: Int = max (length stack0) (length stack1)+        stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]+        stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]+    in imageHeight (stackImage0 <|> stackImage1) == expectedHeight  -disjoint_height_horiz_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage +disjointHeightHorizJoinBgFill :: NonEmptyList SingleRowSingleAttrImage                                        -> NonEmptyList SingleRowSingleAttrImage                                       -> Bool-disjoint_height_horiz_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =-    let stack_image_0 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]-        stack_image_1 = vert_cat [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]-        image = stack_image_0 <|> stack_image_1-        expected_height = image_height image+disjointHeightHorizJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =+    let stackImage0 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]+        stackImage1 = vertCat [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]+        image = stackImage0 <|> stackImage1+        expectedHeight = imageHeight image     in case image of-        HorizJoin {}  -> ( expected_height == (image_height $ part_left image) )+        HorizJoin {}  -> ( expectedHeight == (imageHeight $ partLeft image) )                          && -                         ( expected_height == (image_height $ part_right image) )+                         ( expectedHeight == (imageHeight $ partRight image) )         _             -> True -disjoint_width_vert_join :: NonEmptyList SingleRowSingleAttrImage -                            -> NonEmptyList SingleRowSingleAttrImage-                            -> Bool-disjoint_width_vert_join (NonEmpty stack_0) (NonEmpty stack_1) =-    let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)-        stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]-        stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]-        stack_0_image = vert_cat stack_0_images -        stack_1_image = vert_cat stack_1_images -        image = stack_0_image <-> stack_1_image-    in image_width image == expected_width+disjointWidthVertJoin :: NonEmptyList SingleRowSingleAttrImage +                      -> NonEmptyList SingleRowSingleAttrImage+                      -> Bool+disjointWidthVertJoin (NonEmpty stack0) (NonEmpty stack1) =+    let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)+        stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]+        stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]+        stack0Image = vertCat stack0Images +        stack1Image = vertCat stack1Images +        image = stack0Image <-> stack1Image+    in imageWidth image == expectedWidth -disjoint_width_vert_join_bg_fill :: NonEmptyList SingleRowSingleAttrImage +disjointWidthVertJoinBgFill :: NonEmptyList SingleRowSingleAttrImage                              -> NonEmptyList SingleRowSingleAttrImage                             -> Bool-disjoint_width_vert_join_bg_fill (NonEmpty stack_0) (NonEmpty stack_1) =-    let expected_width = maximum $ map image_width (stack_0_images ++ stack_1_images)-        stack_0_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_0 ]-        stack_1_images = [ i | SingleRowSingleAttrImage { row_image = i } <- stack_1 ]-        stack_0_image = vert_cat stack_0_images -        stack_1_image = vert_cat stack_1_images -        image = stack_0_image <-> stack_1_image+disjointWidthVertJoinBgFill (NonEmpty stack0) (NonEmpty stack1) =+    let expectedWidth = maximum $ map imageWidth (stack0Images ++ stack1Images)+        stack0Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack0 ]+        stack1Images = [ i | SingleRowSingleAttrImage { rowImage = i } <- stack1 ]+        stack0Image = vertCat stack0Images +        stack1Image = vertCat stack1Images +        image = stack0Image <-> stack1Image     in case image of-        VertJoin {} -> ( expected_width == (image_width $ part_top image) )+        VertJoin {} -> ( expectedWidth == (imageWidth $ partTop image) )                        &&-                       ( expected_width == (image_width $ part_bottom image) )+                       ( expectedWidth == (imageWidth $ partBottom image) )         _           -> True +translationIsLinearOnOutSize :: Translation -> Bool+translationIsLinearOnOutSize (Translation i (x,y) i') =+    imageWidth i' == imageWidth i + x && imageHeight i' == imageHeight i + y++paddingIsLinearOnOutSize :: Image -> Gen Bool+paddingIsLinearOnOutSize i = do+    l <- offset+    t <- offset+    r <- offset+    b <- offset+    let i' = pad l t r b i+    return $ imageWidth i' == imageWidth i + l + r && imageHeight i' == imageHeight i + t + b+    where offset = choose (1,1024)++cropLeftLimitsWidth :: Image -> Int -> Property+cropLeftLimitsWidth i v = v >= 0 ==>+    v >= imageWidth (cropLeft v i)++cropRightLimitsWidth :: Image -> Int -> Property+cropRightLimitsWidth i v = v >= 0 ==>+    v >= imageWidth (cropRight v i)++cropTopLimitsHeight :: Image -> Int -> Property+cropTopLimitsHeight i v = v >= 0 ==>+    v >= imageHeight (cropTop v i)++cropBottomLimitsHeight :: Image -> Int -> Property+cropBottomLimitsHeight i v = v >= 0 ==>+    v >= imageHeight (cropBottom v i)++-- rediculous tests just to satisfy my desire for nice code coverage :-P+canShowImage :: Image -> Bool+canShowImage i = length (show i) > 0++canRnfImage :: Image -> Bool+canRnfImage i = rnf i == ()++canPpImage :: Image -> Bool+canPpImage i = length (ppImageStructure i) > 0+ tests :: IO [Test] tests = return-    [ verify "two_sw_horiz_concat" two_sw_horiz_concat-    , verify "many_sw_horiz_concat" many_sw_horiz_concat-    , verify "two_sw_vert_concat" two_sw_vert_concat-    , verify "horiz_concat_sw_assoc" horiz_concat_sw_assoc-    , verify "many_dw_horiz_concat" many_dw_horiz_concat-    , verify "two_dw_horiz_concat" two_dw_horiz_concat-    , verify "two_dw_vert_concat" two_dw_vert_concat-    , verify "horiz_concat_dw_assoc" horiz_concat_dw_assoc-    , verify "single row vert concats to correct height" vert_contat_single_row-    , verify "disjoint_height_horiz_join" disjoint_height_horiz_join-    , verify "disjoint_height_horiz_join BG fill" disjoint_height_horiz_join_bg_fill-    , verify "disjoint_width_vert_join" disjoint_width_vert_join-    , verify "disjoint_width_vert_join BG fill" disjoint_width_vert_join_bg_fill+    [ verify "twoSwHorizConcat" twoSwHorizConcat+    , verify "manySwHorizConcat" manySwHorizConcat+    , verify "twoSwVertConcat" twoSwVertConcat+    , verify "horizConcatSwAssoc" horizConcatSwAssoc+    , verify "manyDwHorizConcat" manyDwHorizConcat+    , verify "twoDwHorizConcat" twoDwHorizConcat+    , verify "twoDwVertConcat" twoDwVertConcat+    , verify "horizConcatDwAssoc" horizConcatDwAssoc+    , verify "single row vert concats to correct height" vertContatSingleRow+    , verify "disjointHeightHorizJoin" disjointHeightHorizJoin+    , verify "disjointHeightHorizJoin BG fill" disjointHeightHorizJoinBgFill+    , verify "disjointWidthVertJoin" disjointWidthVertJoin+    , verify "disjointWidthVertJoin BG fill" disjointWidthVertJoinBgFill+    , verify "translation effects output dimensions linearly" translationIsLinearOnOutSize+    , verify "padding effects output dimensions linearly" paddingIsLinearOnOutSize+    , verify "crop left limits width" cropLeftLimitsWidth+    , verify "crop right limits width" cropRightLimitsWidth+    , verify "crop top limits height" cropTopLimitsHeight+    , verify "crop bottom limits height" cropBottomLimitsHeight+    , verify "can show image" canShowImage+    , verify "can rnf image" canRnfImage+    , verify "can pp image" canPpImage     ] 
test/VerifyImageTrans.hs view
@@ -3,30 +3,32 @@  import Verify.Graphics.Vty.Image +import Graphics.Vty.Image.Internal+ import Verify  import Data.Word -is_horiz_text_of_columns :: Image -> Word -> Bool-is_horiz_text_of_columns (HorizText { output_width = in_w }) expected_w = in_w == expected_w-is_horiz_text_of_columns (BGFill { output_width = in_w }) expected_w = in_w == expected_w-is_horiz_text_of_columns _image _expected_w = False+isHorizTextOfColumns :: Image -> Int -> Bool+isHorizTextOfColumns (HorizText { outputWidth = inW }) expectedW = inW == expectedW+isHorizTextOfColumns (BGFill { outputWidth = inW }) expectedW = inW == expectedW+isHorizTextOfColumns _image _expectedW = False -verify_horiz_contat_wo_attr_change_simplifies :: SingleRowSingleAttrImage -> Bool-verify_horiz_contat_wo_attr_change_simplifies (SingleRowSingleAttrImage _attr char_count image) =-    is_horiz_text_of_columns image char_count+verifyHorizContatWoAttrChangeSimplifies :: SingleRowSingleAttrImage -> Bool+verifyHorizContatWoAttrChangeSimplifies (SingleRowSingleAttrImage _attr charCount image) =+    isHorizTextOfColumns image charCount -verify_horiz_contat_w_attr_change_simplifies :: SingleRowTwoAttrImage -> Bool-verify_horiz_contat_w_attr_change_simplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 char_count0 _image0)-                                                                     (SingleRowSingleAttrImage attr1 char_count1 _image1)-                                                                     i+verifyHorizContatWAttrChangeSimplifies :: SingleRowTwoAttrImage -> Bool+verifyHorizContatWAttrChangeSimplifies ( SingleRowTwoAttrImage (SingleRowSingleAttrImage attr0 charCount0 _image0)+                                                               (SingleRowSingleAttrImage attr1 charCount1 _image1)+                                                               i                                              ) -    | char_count0 == 0 || char_count1 == 0 || attr0 == attr1 = is_horiz_text_of_columns i (char_count0 + char_count1)-    | otherwise = False == is_horiz_text_of_columns i (char_count0 + char_count1)+    | charCount0 == 0 || charCount1 == 0 || attr0 == attr1 = isHorizTextOfColumns i (charCount0 + charCount1)+    | otherwise = False == isHorizTextOfColumns i (charCount0 + charCount1)  tests :: IO [Test] tests = return-    [ verify "verify_horiz_contat_wo_attr_change_simplifies" verify_horiz_contat_wo_attr_change_simplifies-    , verify "verify_horiz_contat_w_attr_change_simplifies" verify_horiz_contat_w_attr_change_simplifies+    [ verify "verifyHorizContatWoAttrChangeSimplifies" verifyHorizContatWoAttrChangeSimplifies+    , verify "verifyHorizContatWAttrChangeSimplifies" verifyHorizContatWAttrChangeSimplifies     ] 
test/VerifyInline.hs view
@@ -1,23 +1,31 @@ module VerifyInline where  import Graphics.Vty.Inline-import Graphics.Vty.Terminal+import Graphics.Vty.Output+import Graphics.Vty.Output.TerminfoBased as TerminfoBased +import Verify.Graphics.Vty.Output+ import Verify  import Distribution.TestSuite +import System.IO+ tests :: IO [Test]-tests = return+tests = concat <$> forM terminalsOfInterest (\termName -> return $     [ Test $ TestInstance         { name = "verify vty inline"         , run = do-            t <- terminal_handle-            put_attr_change t $ default_all+            {- disabled because I cannot get useful output out of cabal why this fails.+            nullOut <- openFile "/dev/null" WriteMode+            t <- TerminfoBased.reserveTerminal termName nullOut+            putAttrChange t $ default_all+            releaseTerminal t+            -}             return $ Finished Pass         , tags = []         , options = []         , setOption = \_ _ -> Left "no options supported"         }-    ]-+    ])
+ test/VerifyLayersSpanGeneration.hs view
@@ -0,0 +1,128 @@+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+module VerifyLayersSpanGeneration where++import Verify.Graphics.Vty.Prelude++import Verify.Graphics.Vty.Image+import Verify.Graphics.Vty.Picture+import Verify.Graphics.Vty.Span++import Graphics.Vty.Debug+import Graphics.Vty.PictureToSpans++import Verify++import qualified Data.Vector as Vector ++largerHorizSpanOcclusion :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result+largerHorizSpanOcclusion row0 row1 =+    let i0 = rowImage row0+        i1 = rowImage row1+        (iLarger, iSmaller) = if imageWidth i0 > imageWidth i1 then (i0, i1) else (i1, i0)+        expectedOps = displayOpsForImage iLarger+        p = picForLayers [iLarger, iSmaller]+        ops = displayOpsForPic p (imageWidth iLarger,imageHeight iLarger)+    in verifyOpsEquality expectedOps ops++-- | Two rows stacked vertical is equivalent to the first row rendered as the top layer and the+-- second row rendered as a bottom layer with a background fill where the first row would be.+vertStackLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result+vertStackLayerEquivalence0 row0 row1 =+    let i0 = rowImage row0+        i1 = rowImage row1+        i = i0 <-> i1+        p = picForImage i+        iLower = backgroundFill (imageWidth i0) 1 <-> i1+        pLayered = picForLayers [i0, iLower]+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)+    in verifyOpsEquality expectedOps opsLayered++vertStackLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result+vertStackLayerEquivalence1 row0 row1 =+    let i0 = rowImage row0+        i1 = rowImage row1+        i = i0 <-> i1+        p = picForImage i+        iLower = i0 <-> backgroundFill (imageWidth i1) 1+        iUpper = backgroundFill (imageWidth i0) 1 <-> i1+        pLayered = picForLayers [iUpper, iLower]+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)+    in verifyOpsEquality expectedOps opsLayered++-- | Two rows horiz joined is equivalent to the first row rendered as the top layer and the+-- second row rendered as a bottom layer with a background fill where the first row would be.+horizJoinLayerEquivalence0 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result+horizJoinLayerEquivalence0 row0 row1 =+    let i0 = rowImage row0+        i1 = rowImage row1+        i = i0 <|> i1+        p = picForImage i+        iLower = backgroundFill (imageWidth i0) 1 <|> i1+        pLayered = picForLayers [i0, iLower]+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)+    in verifyOpsEquality expectedOps opsLayered++horizJoinLayerEquivalence1 :: SingleRowSingleAttrImage -> SingleRowSingleAttrImage -> Result+horizJoinLayerEquivalence1 row0 row1 =+    let i0 = rowImage row0+        i1 = rowImage row1+        i = i0 <|> i1+        p = picForImage i+        iLower = i0 <|> backgroundFill (imageWidth i1) 1+        iUpper = backgroundFill (imageWidth i0) 1 <|> i1+        pLayered = picForLayers [iUpper, iLower]+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic pLayered (imageWidth iLower,imageHeight iLower)+    in verifyOpsEquality expectedOps opsLayered++horizJoinAlternate0 :: Result+horizJoinAlternate0 =+    let size = 4+        str0 = replicate size 'a'+        str1 = replicate size 'b'+        i0 = string defAttr str0+        i1 = string defAttr str1+        i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)+        layer0 = horizCat $ replicate size $ i0 <|> backgroundFill size 1+        layer1 = horizCat $ replicate size $ backgroundFill size 1 <|> i1+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic (picForLayers [layer0, layer1])+                                      (imageWidth i,imageHeight i)+    in verifyOpsEquality expectedOps opsLayered++horizJoinAlternate1 :: Result+horizJoinAlternate1 =+    let size = 4+        str0 = replicate size 'a'+        str1 = replicate size 'b'+        i0 = string defAttr str0+        i1 = string defAttr str1+        i = horizCat $ zipWith horizJoin (replicate size i0) (replicate size i1)+        layers = [l | b <- take 4 [0,size*2..], let l = backgroundFill b 1 <|> i0 <|> i1]+        expectedOps = displayOpsForImage i+        opsLayered = displayOpsForPic (picForLayers layers)+                                      (imageWidth i,imageHeight i)+    in verifyOpsEquality expectedOps opsLayered++tests :: IO [Test]+tests = return +    [ verify "a larger horiz span occludes a smaller span on a lower layer"+        largerHorizSpanOcclusion+    , verify "two rows stack vertical equiv to first image layered on top of second with padding (0)"+        vertStackLayerEquivalence0+    , verify "two rows stack vertical equiv to first image layered on top of second with padding (1)"+        vertStackLayerEquivalence1+    -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (0)"+    --     horizJoinLayerEquivalence0+    -- , verify "two rows horiz joined equiv to first image layered on top of second with padding (1)"+    --     horizJoinLayerEquivalence1+    -- , verify "alternating images using joins is the same as alternating images using layers (0)"+    --     horizJoinAlternate0+    -- , verify "alternating images using joins is the same as alternating images using layers (1)"+    --     horizJoinAlternate1+    ]+
− test/VerifyMockTerminal.hs
@@ -1,115 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-module VerifyMockTerminal where--import Verify.Graphics.Vty.DisplayRegion-import Verify.Graphics.Vty.Picture-import Verify.Graphics.Vty.Image-import Verify.Graphics.Vty.Span-import Graphics.Vty.Terminal-import Graphics.Vty.Terminal.Debug--import Graphics.Vty.Debug--import Verify--import qualified Data.ByteString as BS-import Data.IORef-import qualified Data.String.UTF8 as UTF8--import System.IO--unit_image_unit_bounds :: UnitImage -> Property-unit_image_unit_bounds (UnitImage _ i) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion 1 1)-    d <- display_bounds t >>= display_context t-    let pic = pic_for_image i-    output_picture d pic-    return succeeded--unit_image_arb_bounds :: UnitImage -> DebugWindow -> Property-unit_image_arb_bounds (UnitImage _ i) (DebugWindow w h) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion w h)-    d <- display_bounds t >>= display_context t-    let pic = pic_for_image i-    output_picture d pic-    return succeeded--single_T_row :: DebugWindow -> Property-single_T_row (DebugWindow w h) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion w h)-    d <- display_bounds t >>= display_context t-    -- create an image that contains just the character T repeated for a single row-    let i = horiz_cat $ replicate (fromEnum w) (char def_attr 'T')-        pic = (pic_for_image i) { pic_background = Background 'B' def_attr }-    output_picture d pic-    out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep-    -- The UTF8 string that represents the output bytes a single line containing the T string:-    let expected = "HD" ++ "MA" ++ replicate (fromEnum w) 'T'-    -- Followed by h - 1 lines of a change to the background attribute and then the background-    -- character-                   ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')-        expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected-    if out_bytes /=  expected_bytes-        then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }-        else return succeeded-    -many_T_rows :: DebugWindow -> Property-many_T_rows (DebugWindow w h) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion w h)-    d <- display_bounds t >>= display_context t-    -- create an image that contains the character 'T' repeated for all the rows-    let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')-        pic = (pic_for_image i) { pic_background = Background 'B' def_attr }-    output_picture d pic-    out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep-    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an-    -- attribute change. 'A', followed by w 'T's-    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')-        expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected-    if out_bytes /=  expected_bytes-        then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }-        else return succeeded--many_T_rows_cropped_width :: DebugWindow -> Property-many_T_rows_cropped_width (DebugWindow w h) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion w h)-    d <- display_bounds t >>= display_context t-    -- create an image that contains the character 'T' repeated for all the rows-    let i = vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w * 2) (char def_attr 'T')-        pic = (pic_for_image i) { pic_background = Background 'B' def_attr }-    output_picture d pic-    out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep-    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an-    -- attribute change. 'A', followed by w 'T's-    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')-        expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected-    if out_bytes /=  expected_bytes-        then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }-        else return succeeded--many_T_rows_cropped_height :: DebugWindow -> Property-many_T_rows_cropped_height (DebugWindow w h) = liftIOResult $ do-    t <- terminal_instance (DisplayRegion w h)-    d <- display_bounds t >>= display_context t-    -- create an image that contains the character 'T' repeated for all the rows-    let i = vert_cat $ replicate (fromEnum h * 2) $ horiz_cat $ replicate (fromEnum w) (char def_attr 'T')-        pic = (pic_for_image i) { pic_background = Background 'B' def_attr }-    output_picture d pic-    out_bytes <- readIORef (debug_terminal_last_output $ dehandle t) >>= return . UTF8.toRep-    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an-    -- attribute change. 'A', followed by w count 'T's-    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')-        expected_bytes :: BS.ByteString = UTF8.toRep $ UTF8.fromString expected-    if out_bytes /=  expected_bytes-        then return $ failed { reason = "\n" ++ show out_bytes ++ "\n\n" ++ show expected_bytes }-        else return succeeded--tests :: IO [Test]-tests = return [ verify "unit_image_unit_bounds" unit_image_unit_bounds-               , verify "unit_image_arb_bounds" unit_image_arb_bounds-               , verify "single_T_row" single_T_row-               , verify "many_T_rows" many_T_rows-               , verify "many_T_rows_cropped_width" many_T_rows_cropped_width-               , verify "many_T_rows_cropped_height" many_T_rows_cropped_height-               ]-
+ test/VerifyOutput.hs view
@@ -0,0 +1,63 @@+{- We setup the environment to envoke certain terminals of interest.+ - This assumes appropriate definitions exist in the current environment for the terminals of+ - interest.+ -}+module VerifyOutput where+import Verify++import Graphics.Vty++import Verify.Graphics.Vty.Image+import Verify.Graphics.Vty.Output++import Control.Monad++import qualified System.Console.Terminfo as Terminfo+import System.Posix.Env+import System.IO++tests :: IO [Test]+tests = concat <$> forM terminalsOfInterest (\termName -> do+    -- check if that terminfo exists+    -- putStrLn $ "testing end to end for terminal: " ++ termName+    mti <- try $ Terminfo.setupTerm termName+    case mti of+        Left (_ :: SomeException) -> return []+        Right _ -> return [ verify ("verify " ++ termName ++ " could output a picture")+                                   (smokeTestTermNonMac termName)+                          -- this is excessive.+                          , verify ("verify " ++ termName ++ " could output a picture on a Mac.")+                                   (smokeTestTermMac termName)+                          ]+    )++smokeTestTermNonMac :: String -> Image -> Property+smokeTestTermNonMac termName i = liftIOResult $ do+    -- unset the TERM_PROGRAM environment variable if set.+    -- Required to execute regression test for #42 on a mac+    unsetEnv "TERM_PROGRAM"+    smokeTestTerm termName i++smokeTestTermMac :: String -> Image -> Property+smokeTestTermMac termName i = liftIOResult $ do+    setEnv "TERM_PROGRAM" "Apple_Terminal" True+    smokeTestTerm termName i++smokeTestTerm :: String -> Image -> IO Result+smokeTestTerm termName i = do+    nullOut <- openFile "/dev/null" WriteMode+    t <- outputForNameAndIO termName nullOut+    -- putStrLn $ "context color count: " ++ show (contextColorCount t)+    reserveDisplay t+    dc <- displayContext t (100,100)+    -- always show the cursor to produce tests for terminals with no cursor support.+    let pic = (picForImage i) { picCursor = Cursor 0 0 }+    outputPicture dc pic+    setCursorPos t 0 0+    when (supportsCursorVisibility t) $ do+        hideCursor t+        showCursor t+    releaseDisplay t+    releaseTerminal t+    return succeeded+
test/VerifyParseTerminfoCaps.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NamedFieldPuns #-} module VerifyParseTerminfoCaps where @@ -7,60 +6,17 @@ import qualified System.Console.Terminfo as Terminfo  import Verify.Data.Terminfo.Parse+import Verify.Graphics.Vty.Output import Verify -import Control.Applicative ( (<$>) )-import Control.Exception ( try, SomeException(..) )-import Control.Monad ( mapM_, forM, forM_ )- import Data.Maybe ( catMaybes, fromJust ) import Data.Word  import Numeric --- A list of terminals that ubuntu includes a terminfo cap file for. --- Assuming that is a good place to start.-terminals_of_interest = -    [ "wsvt25"-    , "wsvt25m"-    , "vt52"-    , "vt100"-    , "vt220"-    , "vt102"-    , "xterm-r5"-    , "xterm-xfree86"-    , "xterm-r6"-    , "xterm-256color"-    , "xterm-vt220"-    , "xterm-debian"-    , "xterm-mono"-    , "xterm-color"-    , "xterm"-    , "mach"-    , "mach-bold"-    , "mach-color"-    , "linux"-    , "ansi"-    , "hurd"-    , "Eterm"-    , "pcansi"-    , "screen-256color"-    , "screen-bce"-    , "screen-s"-    , "screen-w"-    , "screen"-    , "screen-256color-bce"-    , "sun"-    , "rxvt"-    , "rxvt-unicode"-    , "rxvt-basic"-    , "cygwin"-    , "cons25"-    , "dumb"-    ]- -- If a terminal defines one of the caps then it's expected to be parsable.-caps_of_interest = +-- TODO: reduce duplication with terminfo terminal implementation.+capsOfInterest =      [ "cup"     , "sc"     , "rc"@@ -80,81 +36,66 @@     , "sgr0"     ] -from_capname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)+fromCapname ti name = fromJust $ Terminfo.getCapability ti (Terminfo.tiGetStr name)  tests :: IO [Test] tests = do-    parse_tests <- concat <$> forM terminals_of_interest ( \term_name -> do-        putStrLn $ "testing parsing of caps for terminal: " ++ term_name-        mti <- liftIO $ try $ Terminfo.setupTerm term_name-        case mti of-            Left (_e :: SomeException)-                -> return []-            Right ti -> do-                concat <$> forM caps_of_interest ( \cap_name -> do-                    liftIO $ putStrLn $ "\tparsing cap: " ++ cap_name-                    case Terminfo.getCapability ti (Terminfo.tiGetStr cap_name) of-                        Just cap_def -> do-                            return [ verify ( "\tparse cap " ++ cap_name ++ " -> " ++ show cap_def )-                                            ( verify_parse_cap cap_def $ const (return succeeded)  ) ]-                        Nothing      -> do-                            return []+    parseTests <- concat <$> forM terminalsOfInterest (\termName ->+        liftIO (try $ Terminfo.setupTerm termName)+        >>= either (\(_e :: SomeException) -> return [])+                   (\ti -> concat <$> forM capsOfInterest (\capName -> do+                        let caseName = "\tparsing cap: " ++ capName+                        liftIO $ putStrLn caseName+                        return $ case Terminfo.getCapability ti (Terminfo.tiGetStr capName) of+                            Just capDef -> [verify (caseName ++ " -> " ++ show capDef)+                                                   (verifyParseCap capDef $ const succeeded)]+                            Nothing      -> []                     )+                   )         )-    -- The quickcheck tests-    return $ [ verify "parse_non_paramaterized_caps" non_paramaterized_caps-             , verify "parse cap string with literal %" literal_percent_caps-             , verify "parse cap string with %i op" inc_first_two_caps-             , verify "parse cap string with %pN op" push_param_caps-             ] ++ parse_tests+    return $ [ verify "parse_nonParamaterizedCaps" nonParamaterizedCaps+             , verify "parse cap string with literal %" literalPercentCaps+             , verify "parse cap string with %i op" incFirstTwoCaps+             , verify "parse cap string with %pN op" pushParamCaps+             ] ++ parseTests -verify_parse_cap cap_string on_parse = liftIOResult $ do-    parse_result <- parse_cap_expression cap_string-    case parse_result of-        Left error -> return $ failed { reason = "parse error " ++ show error }-        Right e -> on_parse e+verifyParseCap capString onParse =+    case parseCapExpression capString of+        Left error -> failed { reason = "parse error " ++ show error }+        Right e    -> onParse e -non_paramaterized_caps (NonParamCapString cap) = do-    verify_parse_cap cap $ \e -> -        let expected_count :: Word8 = toEnum $ length cap-            expected_bytes = map (toEnum . fromEnum) cap-            out_bytes = bytes_for_range e 0 expected_count-        in return $ verify_bytes_equal out_bytes expected_bytes+nonParamaterizedCaps (NonParamCapString cap) = do+    verifyParseCap cap $ \e -> +        let expectedBytes = map (toEnum . fromEnum) cap+            outBytes = bytesForRange e 0 (length cap)+        in verifyBytesEqual outBytes expectedBytes -literal_percent_caps (LiteralPercentCap cap_string expected_bytes) = do-    verify_parse_cap cap_string $ \e ->-        let expected_count :: Word8 = toEnum $ length expected_bytes-            out_bytes = collect_bytes e-        in return $ verify_bytes_equal out_bytes expected_bytes+literalPercentCaps (LiteralPercentCap capString expectedBytes) = do+    verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes -inc_first_two_caps (IncFirstTwoCap cap_string expected_bytes) = do-    verify_parse_cap cap_string $ \e ->-        let expected_count :: Word8 = toEnum $ length expected_bytes-            out_bytes = collect_bytes e-        in return $ verify_bytes_equal out_bytes expected_bytes+incFirstTwoCaps (IncFirstTwoCap capString expectedBytes) = do+    verifyParseCap capString $ \e -> verifyBytesEqual (collectBytes e) expectedBytes     -push_param_caps (PushParamCap cap_string expected_param_count expected_bytes) = do-    verify_parse_cap cap_string $ \e ->-        let expected_count :: Word8 = toEnum $ length expected_bytes-            out_bytes = collect_bytes e-            out_param_count = param_count e-        in return $ if out_param_count == expected_param_count-            then verify_bytes_equal out_bytes expected_bytes+pushParamCaps (PushParamCap capString expectedParamCount expectedBytes) = do+    verifyParseCap capString $ \e ->+        let outBytes = collectBytes e+            outParamCount = paramCount e+        in if outParamCount == expectedParamCount+            then verifyBytesEqual outBytes expectedBytes             else failed { reason = "out param count /= expected param count" } -dec_print_param_caps (DecPrintCap cap_string expected_param_count expected_bytes) = do-    verify_parse_cap cap_string $ \e ->-        let expected_count :: Word8 = toEnum $ length expected_bytes-            out_bytes = collect_bytes e-            out_param_count = param_count e-        in return $ if out_param_count == expected_param_count-            then verify_bytes_equal out_bytes expected_bytes+decPrintParamCaps (DecPrintCap capString expectedParamCount expectedBytes) = do+    verifyParseCap capString $ \e ->+        let outBytes = collectBytes e+            outParamCount = paramCount e+        in if outParamCount == expectedParamCount+            then verifyBytesEqual outBytes expectedBytes             else failed { reason = "out param count /= expected param count" } -print_cap ti cap_name = do-    putStrLn $ cap_name ++ ": " ++ show (from_capname ti cap_name)+printCap ti capName = do+    putStrLn $ capName ++ ": " ++ show (fromCapname ti capName) -print_expression ti cap_name = do-    parse_result <- parse_cap_expression $ from_capname ti cap_name-    putStrLn $ cap_name ++ ": " ++ show parse_result+printExpression ti capName = do+    let parseResult = parseCapExpression $ fromCapname ti capName+    putStrLn $ capName ++ ": " ++ show parseResult 
− test/VerifyPictureOps.hs
@@ -1,8 +0,0 @@-module VerifyPictureOps where--import Graphics.Vty.Picture ( translate )--import Verify--tests :: IO [Test]-tests = return []
− test/VerifyPictureToSpan.hs
@@ -1,10 +0,0 @@-module VerifyPictureToSpan where--import Graphics.Vty.Picture-import Graphics.Vty.Span-import Graphics.Vty.PictureToSpans--import Verify--tests :: IO [Test]-tests = return []
+ test/VerifySimpleSpanGeneration.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+module VerifySimpleSpanGeneration where++import Verify.Graphics.Vty.Prelude++import Verify.Graphics.Vty.Image+import Verify.Graphics.Vty.Picture+import Verify.Graphics.Vty.Span++import Graphics.Vty.Debug+import Graphics.Vty.PictureToSpans++import Verify++import qualified Data.Vector as Vector ++unitImageAndZeroWindow0 :: UnitImage -> EmptyWindow -> Bool+unitImageAndZeroWindow0 (UnitImage _ i) (EmptyWindow w) = +    let p = picForImage i+        ops = displayOpsForPic p (regionForWindow w)+    in displayOpsColumns ops == 0 && displayOpsRows ops == 0++unitImageAndZeroWindow1 :: UnitImage -> EmptyWindow -> Bool+unitImageAndZeroWindow1 (UnitImage _ i) (EmptyWindow w) = +    let p = picForImage i+        ops = displayOpsForPic p (regionForWindow w)+    in ( spanOpsEffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )++horizSpanImageAndZeroWindow0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool+horizSpanImageAndZeroWindow0 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) = +    let p = picForImage i+        ops = displayOpsForPic p (regionForWindow w)+    in displayOpsColumns ops == 0 && displayOpsRows ops == 0++horizSpanImageAndZeroWindow1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool+horizSpanImageAndZeroWindow1 (SingleRowSingleAttrImage { rowImage = i }) (EmptyWindow w) = +    let p = picForImage i+        ops = displayOpsForPic p (regionForWindow w)+    in ( spanOpsEffectedRows ops == 0 ) && ( allSpansHaveWidth ops 0 )++horizSpanImageAndEqualWindow0 :: SingleRowSingleAttrImage -> Result+horizSpanImageAndEqualWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =+    let p = picForImage i+        w = MockWindow c 1+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth i ops c++horizSpanImageAndEqualWindow1 :: SingleRowSingleAttrImage -> Bool+horizSpanImageAndEqualWindow1 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =+    let p = picForImage i+        w = MockWindow c 1+        ops = displayOpsForPic p (regionForWindow w)+    in spanOpsEffectedRows ops == 1++horizSpanImageAndLesserWindow0 :: SingleRowSingleAttrImage -> Result+horizSpanImageAndLesserWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =+    let p = picForImage i+        lesserWidth = c `div` 2+        w = MockWindow lesserWidth 1+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth i ops lesserWidth++singleAttrSingleSpanStackCropped0 :: SingleAttrSingleSpanStack -> Result+singleAttrSingleSpanStackCropped0 stack =+    let p = picForImage (stackImage stack)+        w = MockWindow (stackWidth stack `div` 2) (stackHeight stack)+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack `div` 2)++singleAttrSingleSpanStackCropped1 :: SingleAttrSingleSpanStack -> Bool+singleAttrSingleSpanStackCropped1 stack =+    let p = picForImage (stackImage stack)+        expectedRowCount = stackHeight stack `div` 2+        w = MockWindow (stackWidth stack) expectedRowCount+        ops = displayOpsForPic p (regionForWindow w)+        actualRowCount = spanOpsEffectedRows ops+    in expectedRowCount == actualRowCount++singleAttrSingleSpanStackCropped2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result+singleAttrSingleSpanStackCropped2 stack0 stack1 =+    let p = picForImage (stackImage stack0 <|> stackImage stack1)+        w = MockWindow (stackWidth stack0) (imageHeight (picImage p))+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth (picImage p) ops (stackWidth stack0)++singleAttrSingleSpanStackCropped3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool+singleAttrSingleSpanStackCropped3 stack0 stack1 =+    let p = picForImage (stackImage stack0 <|> stackImage stack1)+        w = MockWindow (imageWidth (picImage p))  expectedRowCount+        ops = displayOpsForPic p (regionForWindow w)+        expectedRowCount = imageHeight (picImage p) `div` 2+        actualRowCount = spanOpsEffectedRows ops+    in expectedRowCount == actualRowCount++singleAttrSingleSpanStackCropped4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result+singleAttrSingleSpanStackCropped4 stack0 stack1 =+    let p = picForImage (stackImage stack0 <-> stackImage stack1)+        w = MockWindow expectedWidth (imageHeight (picImage p))+        ops = displayOpsForPic p (regionForWindow w)+        expectedWidth = imageWidth (picImage p) `div` 2+    in verifyAllSpansHaveWidth (picImage p) ops expectedWidth++singleAttrSingleSpanStackCropped5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool+singleAttrSingleSpanStackCropped5 stack0 stack1 =+    let p = picForImage (stackImage stack0 <-> stackImage stack1)+        w = MockWindow (imageWidth (picImage p)) (stackHeight stack0)+        ops = displayOpsForPic p (regionForWindow w)+        expectedRowCount = stackHeight stack0+        actualRowCount = spanOpsEffectedRows ops+    in expectedRowCount == actualRowCount++horizSpanImageAndGreaterWindow0 :: SingleRowSingleAttrImage -> Result+horizSpanImageAndGreaterWindow0 (SingleRowSingleAttrImage { rowImage = i, expectedColumns = c }) =+    let p = picForImage i+        -- SingleRowSingleAttrImage always has width >= 1+        greaterWidth = c * 2+        w = MockWindow greaterWidth 1+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth i ops greaterWidth++arbImageIsCropped :: DefaultImage -> MockWindow -> Bool+arbImageIsCropped (DefaultImage image) win@(MockWindow w h) =+    let pic = picForImage image+        ops = displayOpsForPic pic (regionForWindow win)+    in ( spanOpsEffectedRows ops == h ) && ( allSpansHaveWidth ops w )++spanOpsActuallyFillRows :: DefaultPic -> Bool+spanOpsActuallyFillRows (DefaultPic pic win) =+    let ops = displayOpsForPic pic (regionForWindow win)+        expectedRowCount = regionHeight (regionForWindow win)+        actualRowCount = spanOpsEffectedRows ops+    in expectedRowCount == actualRowCount++spanOpsActuallyFillColumns :: DefaultPic -> Bool+spanOpsActuallyFillColumns (DefaultPic pic win) =+    let ops = displayOpsForPic pic (regionForWindow win)+        expectedColumnCount = regionWidth (regionForWindow win)+    in allSpansHaveWidth ops expectedColumnCount++firstSpanOpSetsAttr :: DefaultPic -> Bool+firstSpanOpSetsAttr DefaultPic { defaultPic = pic, defaultWin = win } = +    let ops = displayOpsForPic pic (regionForWindow win)+    in all ( isAttrSpanOp . Vector.head ) ( Vector.toList ops )++singleAttrSingleSpanStackOpCoverage ::  SingleAttrSingleSpanStack -> Result+singleAttrSingleSpanStackOpCoverage stack =+    let p = picForImage (stackImage stack)+        w = MockWindow (stackWidth stack) (stackHeight stack)+        ops = displayOpsForPic p (regionForWindow w)+    in verifyAllSpansHaveWidth (stackImage stack) ops (stackWidth stack)++imageCoverageMatchesBounds :: Image -> Result+imageCoverageMatchesBounds i =+    let p = picForImage i+        r = (imageWidth i,imageHeight i)+        ops = displayOpsForPic p r+    in verifyAllSpansHaveWidth i ops (imageWidth i)++tests :: IO [Test]+tests = return +    [ verify "unit image is cropped when window size == (0,0) [0]" unitImageAndZeroWindow0+    , verify "unit image is cropped when window size == (0,0) [1]" unitImageAndZeroWindow1+    , verify "horiz span image is cropped when window size == (0,0) [0]" horizSpanImageAndZeroWindow0+    , verify "horiz span image is cropped when window size == (0,0) [1]" horizSpanImageAndZeroWindow1+    , verify "horiz span image is not cropped when window size == size of image [width]" horizSpanImageAndEqualWindow0+    , verify "horiz span image is not cropped when window size == size of image [height]" horizSpanImageAndEqualWindow1+    , verify "horiz span image is not cropped when window size < size of image [width]" horizSpanImageAndLesserWindow0+    , verify "horiz span image is not cropped when window size > size of image [width]" horizSpanImageAndGreaterWindow0+    , verify "first span op is always to set the text attribute" firstSpanOpSetsAttr+    , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"+        singleAttrSingleSpanStackOpCoverage+    , verify "a single attr text span is cropped when window size < size of stack image [width]"+        singleAttrSingleSpanStackCropped0 +    , verify "a single attr text span is cropped when window size < size of stack image [height]"+        singleAttrSingleSpanStackCropped1+    , verify "single attr text span <|> single attr text span display cropped. [width]"+        singleAttrSingleSpanStackCropped2+    , verify "single attr text span <|> single attr text span display cropped. [height]"+        singleAttrSingleSpanStackCropped3+    , verify "single attr text span <-> single attr text span display cropped. [width]"+        singleAttrSingleSpanStackCropped4+    , verify "single attr text span <-> single attr text span display cropped. [height]"+        singleAttrSingleSpanStackCropped5+    , verify "an arbitrary image when rendered to a window of the same size will cover the entire window"+        imageCoverageMatchesBounds+    ]+
− test/VerifySpanOps.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE DisambiguateRecordFields #-}-{-# LANGUAGE NamedFieldPuns #-}-module VerifySpanOps where--import Verify.Graphics.Vty.Picture-import Verify.Graphics.Vty.Image-import Verify.Graphics.Vty.Span-import Verify.Graphics.Vty.DisplayRegion--import Graphics.Vty.Debug--import Verify--import qualified Data.Vector as Vector --unit_image_and_zero_window_0 :: UnitImage -> EmptyWindow -> Bool-unit_image_and_zero_window_0 (UnitImage _ i) (EmptyWindow w) = -    let p = pic_for_image i-        spans = spans_for_pic p (region_for_window w)-    in span_ops_columns spans == 0 && span_ops_rows spans == 0--unit_image_and_zero_window_1 :: UnitImage -> EmptyWindow -> Bool-unit_image_and_zero_window_1 (UnitImage _ i) (EmptyWindow w) = -    let p = pic_for_image i-        spans = spans_for_pic p (region_for_window w)-    in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )--horiz_span_image_and_zero_window_0 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool-horiz_span_image_and_zero_window_0 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) = -    let p = pic_for_image i-        spans = spans_for_pic p (region_for_window w)-    in span_ops_columns spans == 0 && span_ops_rows spans == 0--horiz_span_image_and_zero_window_1 :: SingleRowSingleAttrImage -> EmptyWindow -> Bool-horiz_span_image_and_zero_window_1 (SingleRowSingleAttrImage { row_image = i }) (EmptyWindow w) = -    let p = pic_for_image i-        spans = spans_for_pic p (region_for_window w)-    in ( span_ops_effected_rows spans == 0 ) && ( all_spans_have_width spans 0 )--horiz_span_image_and_equal_window_0 :: SingleRowSingleAttrImage -> Result-horiz_span_image_and_equal_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =-    let p = pic_for_image i-        w = DebugWindow c 1-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width i spans c--horiz_span_image_and_equal_window_1 :: SingleRowSingleAttrImage -> Bool-horiz_span_image_and_equal_window_1 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =-    let p = pic_for_image i-        w = DebugWindow c 1-        spans = spans_for_pic p (region_for_window w)-    in span_ops_effected_rows spans == 1--horiz_span_image_and_lesser_window_0 :: SingleRowSingleAttrImage -> Result-horiz_span_image_and_lesser_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =-    let p = pic_for_image i-        lesser_width = c `div` 2-        w = DebugWindow lesser_width 1-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width i spans lesser_width--single_attr_single_span_stack_cropped_0 :: SingleAttrSingleSpanStack -> Result-single_attr_single_span_stack_cropped_0 stack =-    let p = pic_for_image (stack_image stack)-        w = DebugWindow (stack_width stack `div` 2) (stack_height stack)-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width (stack_image stack) spans (stack_width stack `div` 2)--single_attr_single_span_stack_cropped_1 :: SingleAttrSingleSpanStack -> Bool-single_attr_single_span_stack_cropped_1 stack =-    let p = pic_for_image (stack_image stack)-        expected_row_count = stack_height stack `div` 2-        w = DebugWindow (stack_width stack) expected_row_count-        spans = spans_for_pic p (region_for_window w)-        actual_row_count = span_ops_effected_rows spans-    in expected_row_count == actual_row_count--single_attr_single_span_stack_cropped_2 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result-single_attr_single_span_stack_cropped_2 stack_0 stack_1 =-    let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)-        w = DebugWindow (stack_width stack_0) (image_height (pic_image p))-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width (pic_image p) spans (stack_width stack_0)--single_attr_single_span_stack_cropped_3 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool-single_attr_single_span_stack_cropped_3 stack_0 stack_1 =-    let p = pic_for_image (stack_image stack_0 <|> stack_image stack_1)-        w = DebugWindow (image_width (pic_image p))  expected_row_count-        spans = spans_for_pic p (region_for_window w)-        expected_row_count = image_height (pic_image p) `div` 2-        actual_row_count = span_ops_effected_rows spans-    in expected_row_count == actual_row_count--single_attr_single_span_stack_cropped_4 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Result-single_attr_single_span_stack_cropped_4 stack_0 stack_1 =-    let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)-        w = DebugWindow expected_width (image_height (pic_image p))-        spans = spans_for_pic p (region_for_window w)-        expected_width = image_width (pic_image p) `div` 2-    in verify_all_spans_have_width (pic_image p) spans expected_width--single_attr_single_span_stack_cropped_5 :: SingleAttrSingleSpanStack -> SingleAttrSingleSpanStack -> Bool-single_attr_single_span_stack_cropped_5 stack_0 stack_1 =-    let p = pic_for_image (stack_image stack_0 <-> stack_image stack_1)-        w = DebugWindow (image_width (pic_image p)) (stack_height stack_0)-        spans = spans_for_pic p (region_for_window w)-        expected_row_count = stack_height stack_0-        actual_row_count = span_ops_effected_rows spans-    in expected_row_count == actual_row_count--horiz_span_image_and_greater_window_0 :: SingleRowSingleAttrImage -> Result-horiz_span_image_and_greater_window_0 (SingleRowSingleAttrImage { row_image = i, expected_columns = c }) =-    let p = pic_for_image i-        -- SingleRowSingleAttrImage always has width >= 1-        greater_width = c * 2-        w = DebugWindow greater_width 1-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width i spans greater_width--arb_image_is_cropped :: DefaultImage -> DebugWindow -> Bool-arb_image_is_cropped (DefaultImage image _) win@(DebugWindow w h) =-    let pic = pic_for_image image-        spans = spans_for_pic pic (region_for_window win)-    in ( span_ops_effected_rows spans == h ) && ( all_spans_have_width spans w )--span_ops_actually_fill_rows :: DefaultPic -> Bool-span_ops_actually_fill_rows (DefaultPic pic win _) =-    let spans = spans_for_pic pic (region_for_window win)-        expected_row_count = region_height (region_for_window win)-        actual_row_count = span_ops_effected_rows spans-    in expected_row_count == actual_row_count--span_ops_actually_fill_columns :: DefaultPic -> Bool-span_ops_actually_fill_columns (DefaultPic pic win _) =-    let spans = spans_for_pic pic (region_for_window win)-        expected_column_count = region_width (region_for_window win)-    in all_spans_have_width spans expected_column_count--first_span_op_sets_attr :: DefaultPic -> Bool-first_span_op_sets_attr DefaultPic { default_pic = pic, default_win = win } = -    let spans = spans_for_pic pic (region_for_window win)-    in all ( is_attr_span_op . Vector.head ) ( Vector.toList $ display_ops spans )--single_attr_single_span_stack_op_coverage ::  SingleAttrSingleSpanStack -> Result-single_attr_single_span_stack_op_coverage stack =-    let p = pic_for_image (stack_image stack)-        w = DebugWindow (stack_width stack) (stack_height stack)-        spans = spans_for_pic p (region_for_window w)-    in verify_all_spans_have_width (stack_image stack) spans (stack_width stack)--tests :: IO [Test]-tests = return -    [ verify "unit image is cropped when window size == (0,0) [0]" unit_image_and_zero_window_0-    , verify "unit image is cropped when window size == (0,0) [1]" unit_image_and_zero_window_1-    , verify "horiz span image is cropped when window size == (0,0) [0]" horiz_span_image_and_zero_window_0-    , verify "horiz span image is cropped when window size == (0,0) [1]" horiz_span_image_and_zero_window_1-    , verify "horiz span image is not cropped when window size == size of image [width]" horiz_span_image_and_equal_window_0-    , verify "horiz span image is not cropped when window size == size of image [height]" horiz_span_image_and_equal_window_1-    , verify "horiz span image is not cropped when window size < size of image [width]" horiz_span_image_and_lesser_window_0-    , verify "horiz span image is not cropped when window size > size of image [width]" horiz_span_image_and_greater_window_0-    , verify "arbitrary image is padded or cropped" arb_image_is_cropped-    , verify "The span ops actually define content for all the rows in the output region" span_ops_actually_fill_rows-    , verify "The span ops actually define content for all the columns in the output region" span_ops_actually_fill_columns-    , verify "first span op is always to set the text attribute" first_span_op_sets_attr-    , verify "a stack of single attr text spans should define content for all the columns [output region == size of stack]"-             single_attr_single_span_stack_op_coverage-    , verify "a single attr text span is cropped when window size < size of stack image [width]"-             single_attr_single_span_stack_cropped_0 -    , verify "a single attr text span is cropped when window size < size of stack image [height]"-             single_attr_single_span_stack_cropped_1-    , verify "single attr text span <|> single attr text span cropped. [width]"-             single_attr_single_span_stack_cropped_2-    , verify "single attr text span <|> single attr text span cropped. [height]"-             single_attr_single_span_stack_cropped_3-    , verify "single attr text span <-> single attr text span cropped. [width]"-             single_attr_single_span_stack_cropped_4-    , verify "single attr text span <-> single attr text span cropped. [height]"-             single_attr_single_span_stack_cropped_5-    ]-
+ test/VerifyUsingMockInput.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{- Generate some input bytes and delays between blocks of input bytes. Verify the events produced+ - are as expected.+ -}+module Main where++import Verify.Graphics.Vty.Output++import Graphics.Vty hiding (resize)+import Graphics.Vty.Input.Events+import Graphics.Vty.Input.Loop+import Graphics.Vty.Input.Terminfo++import Control.Applicative+import Control.Concurrent+import Control.Exception+import Control.Lens ((^.))+import Control.Monad++import Data.Default+import Data.IORef+import Data.List (intersperse, reverse, nubBy)++import System.Console.Terminfo+import System.Posix.IO+import System.Posix.Terminal (openPseudoTerminal)+import System.Posix.Types+import System.Timeout++import Test.Framework.Providers.SmallCheck+import Test.Framework+import Test.SmallCheck+import Test.SmallCheck.Series++import Text.Printf++-- processing a block of 16 chars is the largest I can do without taking too long to run the test.+maxBlockSize :: Int+maxBlockSize = 16++maxTableSize :: Int+maxTableSize = 28++forEachOf :: (Show a, Testable m b) => [a] -> (a -> b) -> Property m+forEachOf l = over (generate (\n -> take n l))++data InputEvent+    = Bytes String  -- | input sequence encoded as a string. Regardless, the input is read a byte at a time.+    | Delay Int     -- | microsecond delay+    deriving Show++type InputSpec = [InputEvent]++type ExpectedSpec = [Event]++synthesizeInput :: InputSpec -> Fd -> IO ()+synthesizeInput input outHandle = forM_ input f >> (void $ fdWrite outHandle "\xFFFD")+    where+        f (Bytes str) = void $ fdWrite outHandle str+        f (Delay t) = threadDelay t++minDetectableDelay :: Int+minDetectableDelay = 4000++minTimout :: Int+minTimout = 4000000++testKeyDelay :: Int+testKeyDelay = minDetectableDelay * 4++testEscSampleDelay :: Int+testEscSampleDelay = minDetectableDelay * 2++genEventsUsingIoActions :: Int -> IO () -> IO () -> IO ()+genEventsUsingIoActions maxDuration inputAction outputAction = do+    let maxDuration' = max minTimout maxDuration+    readComplete <- newEmptyMVar+    writeComplete <- newEmptyMVar+    _ <- forkOS $ inputAction `finally` putMVar writeComplete ()+    _ <- forkOS $ outputAction `finally` putMVar readComplete ()+    Just () <- timeout maxDuration' $ takeMVar writeComplete+    Just () <- timeout maxDuration' $ takeMVar readComplete+    return ()++compareEvents :: (Show a1, Show a, Eq a1) => a -> [a1] -> [a1] -> IO Bool+compareEvents inputSpec expectedEvents outEvents = compareEvents' expectedEvents outEvents+    where+        compareEvents' [] []         = return True+        compareEvents' [] outEvents' = do+            printf "extra events %s\n" (show outEvents') :: IO ()+            return False+        compareEvents' expectedEvents' [] = do+            printf "events %s were not produced for input %s\n" (show expectedEvents') (show inputSpec) :: IO ()+            printf "expected events %s\n" (show expectedEvents) :: IO ()+            printf "received events %s\n" (show outEvents) :: IO ()+            return False+        compareEvents' (e : expectedEvents') (o : outEvents')+            | e == o    = compareEvents' expectedEvents' outEvents'+            | otherwise = do+                printf "%s expected not %s for input %s\n" (show e) (show o) (show inputSpec) :: IO ()+                printf "expected events %s\n" (show expectedEvents) :: IO ()+                printf "received events %s\n" (show outEvents) :: IO ()+                return False++assertEventsFromSynInput :: ClassifyMap -> InputSpec -> ExpectedSpec -> IO Bool+assertEventsFromSynInput table inputSpec expectedEvents = do+    let maxDuration = sum [t | Delay t <- inputSpec] + minDetectableDelay+        eventCount = length expectedEvents+    (writeFd, readFd) <- openPseudoTerminal+    (setTermAttr,_) <- attributeControl readFd+    setTermAttr+    input <- initInputForFd def "dummy" table readFd+    eventsRef <- newIORef []+    let writeWaitClose = do+            synthesizeInput inputSpec writeFd+            threadDelay minDetectableDelay+            shutdownInput input+            threadDelay minDetectableDelay+            closeFd writeFd+            closeFd readFd+    -- drain output pipe+    let readEvents = readLoop eventCount+        readLoop 0 = return ()+        readLoop n = do+            e <- readChan $ input^.eventChannel+            modifyIORef eventsRef ((:) e)+            readLoop (n - 1)+    genEventsUsingIoActions maxDuration writeWaitClose readEvents+    outEvents <- reverse <$> readIORef eventsRef+    compareEvents inputSpec expectedEvents outEvents++newtype InputBlocksUsingTable event+    = InputBlocksUsingTable ([(String,event)] -> [(String, event)])++instance Show (InputBlocksUsingTable event) where+    show (InputBlocksUsingTable _g) = "InputBlocksUsingTable"++instance Monad m => Serial m (InputBlocksUsingTable event) where+    series = do+        n :: Int <- localDepth (const maxTableSize) series+        return $ InputBlocksUsingTable $ \raw_table ->+                 let table = reverse $ nubBy (\(s0,_) (s1,_) -> s0 == s1) $ reverse raw_table+                 in concat (take n (selections table))+        where+            selections []     = []+            selections (x:xs) = let z = selections xs in [x] : (z ++ map ((:) x) z)++verifyVisibleSynInputToEvent :: Property IO+verifyVisibleSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do+    let table    = visibleChars+        inputSeq = gen table+        events   = map snd inputSeq+        keydowns = map (Bytes . fst) inputSeq+        input    = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]+    assertEventsFromSynInput universalTable input events++verifyCapsSynInputToEvent :: Property IO+verifyCapsSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->+    forEachOf terminalsOfInterest $ \termName -> monadic $ do+        term <- setupTerm termName+        let table         = capsClassifyMap term keysFromCapsTable+            inputSeq      = gen table+            events        = map snd inputSeq+            keydowns      = map (Bytes . fst) inputSeq+            input         = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]+        assertEventsFromSynInput table input events++verifySpecialSynInputToEvent :: Property IO+verifySpecialSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) -> monadic $ do+    let table         = specialSupportKeys+        inputSeq      = gen table+        events        = map snd inputSeq+        keydowns      = map (Bytes . fst) inputSeq+        input         = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]+    assertEventsFromSynInput universalTable input events++verifyFullSynInputToEvent :: Property IO+verifyFullSynInputToEvent = forAll $ \(InputBlocksUsingTable gen) ->+    forEachOf terminalsOfInterest $ \termName -> monadic $ do+        term <- setupTerm termName+        let table         = classifyMapForTerm termName term+            inputSeq      = gen table+            events        = map snd inputSeq+            keydowns      = map (Bytes . fst) inputSeq+            input         = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]+        assertEventsFromSynInput table input events++verifyFullSynInputToEvent_2x :: Property IO+verifyFullSynInputToEvent_2x = forAll $ \(InputBlocksUsingTable gen) ->+    forEachOf terminalsOfInterest $ \termName -> monadic $ do+        term <- setupTerm termName+        let table         = classifyMapForTerm termName term+            inputSeq      = gen table+            events        = concatMap ((\s -> [s,s]) . snd) inputSeq+            keydowns      = map (Bytes . (\s -> s ++ s) . fst) inputSeq+            input         = intersperse (Delay testKeyDelay) keydowns ++ [Delay testKeyDelay]+        assertEventsFromSynInput table input events++main :: IO ()+main = defaultMain+    [ testProperty "synthesized typing of single visible chars translates to expected events"+        verifyVisibleSynInputToEvent+    , testProperty "synthesized typing of keys from capabilities tables translates to expected events"+        verifyCapsSynInputToEvent+    , testProperty "synthesized typing of hard coded special keys translates to expected events"+        verifySpecialSynInputToEvent+    , testProperty "synthesized typing of any key in the table translates to its paired event"+        verifyFullSynInputToEvent+    , testProperty "synthesized typing of 2x any key in the table translates to 2x paired event"+        verifyFullSynInputToEvent_2x+    ]+
+ test/VerifyUsingMockTerminal.hs view
@@ -0,0 +1,100 @@+module VerifyUsingMockTerminal where++import Verify.Graphics.Vty.Prelude++import Verify.Graphics.Vty.Picture+import Verify.Graphics.Vty.Image+import Verify.Graphics.Vty.Span+import Verify.Graphics.Vty.Output+import Graphics.Vty.Output+import Graphics.Vty.Output.Mock++import Graphics.Vty.Debug++import Verify++import qualified Data.ByteString as BS+import Data.IORef+import qualified Data.String.UTF8 as UTF8++import System.IO++unitImageUnitBounds :: UnitImage -> Property+unitImageUnitBounds (UnitImage _ i) = liftIOResult $ do+    (_,t) <- mockTerminal (1,1)+    dc <- displayBounds t >>= displayContext t+    let pic = picForImage i+    outputPicture dc pic+    return succeeded++unitImageArbBounds :: UnitImage -> MockWindow -> Property+unitImageArbBounds (UnitImage _ i) (MockWindow w h) = liftIOResult $ do+    (_,t) <- mockTerminal (w,h)+    dc <- displayBounds t >>= displayContext t+    let pic = picForImage i+    outputPicture dc pic+    return succeeded++singleTRow :: MockWindow -> Property+singleTRow (MockWindow w h) = liftIOResult $ do+    (mockData,t) <- mockTerminal (w,h)+    dc <- displayBounds t >>= displayContext t+    -- create an image that contains just the character T repeated for a single row+    let i = horizCat $ replicate (fromEnum w) (char defAttr 'T')+        pic = (picForImage i) { picBackground = Background 'B' defAttr }+    outputPicture dc pic+    -- The mock output string that represents the output bytes a single line containing the T+    -- string: Followed by h - 1 lines of a change to the background attribute and then the+    -- background character+    let expected = "HD" ++ "MA" ++ replicate (fromEnum w) 'T'+                 ++ concat (replicate (fromEnum h - 1) $ "MA" ++ replicate (fromEnum w) 'B')+    compareMockOutput mockData expected+    +manyTRows :: MockWindow -> Property+manyTRows (MockWindow w h) = liftIOResult $ do+    (mockData, t) <- mockTerminal (w,h)+    dc <- displayBounds t >>= displayContext t+    -- create an image that contains the character 'T' repeated for all the rows+    let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')+        pic = (picForImage i) { picBackground = Background 'B' defAttr }+    outputPicture dc pic+    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an+    -- attribute change. 'A', followed by w 'T's+    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')+    compareMockOutput mockData expected++manyTRowsCroppedWidth :: MockWindow -> Property+manyTRowsCroppedWidth (MockWindow w h) = liftIOResult $ do+    (mockData,t) <- mockTerminal (w,h)+    dc <- displayBounds t >>= displayContext t+    -- create an image that contains the character 'T' repeated for all the rows+    let i = vertCat $ replicate (fromEnum h) $ horizCat $ replicate (fromEnum w * 2) (char defAttr 'T')+        pic = (picForImage i) { picBackground = Background 'B' defAttr }+    outputPicture dc pic+    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an+    -- attribute change. 'A', followed by w 'T's+    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')+    compareMockOutput mockData expected++manyTRowsCroppedHeight :: MockWindow -> Property+manyTRowsCroppedHeight (MockWindow w h) = liftIOResult $ do+    (mockData,t) <- mockTerminal (w,h)+    dc <- displayBounds t >>= displayContext t+    -- create an image that contains the character 'T' repeated for all the rows+    let i = vertCat $ replicate (fromEnum h * 2) $ horizCat $ replicate (fromEnum w) (char defAttr 'T')+        pic = (picForImage i) { picBackground = Background 'B' defAttr }+    outputPicture dc pic+    -- The UTF8 string that represents the output bytes is h repeats of a move, 'M', followed by an+    -- attribute change. 'A', followed by w count 'T's+    let expected = "HD" ++ concat (replicate (fromEnum h) $ "MA" ++ replicate (fromEnum w) 'T')+    compareMockOutput mockData expected++tests :: IO [Test]+tests = return [ verify "unitImageUnitBounds" unitImageUnitBounds+               , verify "unitImageArbBounds" unitImageArbBounds+               , verify "singleTRow" singleTRow+               , verify "manyTRows" manyTRows+               , verify "manyTRowsCroppedWidth" manyTRowsCroppedWidth+               , verify "manyTRowsCroppedHeight" manyTRowsCroppedHeight+               ]+
test/VerifyUtf8Width.hs view
@@ -1,18 +1,28 @@ module VerifyUtf8Width where import Verify +import Graphics.Text.Width import Graphics.Vty.Attributes import Graphics.Vty.Picture -sw_is_1_column :: SingleColumnChar -> Bool-sw_is_1_column (SingleColumnChar c) = image_width (char def_attr c) == 1+swIs1Column :: SingleColumnChar -> Bool+swIs1Column (SingleColumnChar c) = imageWidth (char defAttr c) == 1 -dw_is_2_column :: DoubleColumnChar -> Bool-dw_is_2_column (DoubleColumnChar c) = image_width (char def_attr c) == 2+dwIs2Column :: DoubleColumnChar -> Bool+dwIs2Column (DoubleColumnChar c) = imageWidth (char defAttr c) == 2 +dcStringIsEven :: NonEmptyList DoubleColumnChar -> Bool+dcStringIsEven (NonEmpty dw_list) =+    even $ safeWcswidth [ c | DoubleColumnChar c <- dw_list ]++safeWcwidthForControlChars :: Bool+safeWcwidthForControlChars = 0 == safeWcwidth '\NUL'+ tests :: IO [Test] tests = return-  [ verify "sw_is_1_column" sw_is_1_column-  , verify "dw_is_2_column" dw_is_2_column+  [ verify "swIs1Column" swIs1Column+  , verify "dwIs2Column" dwIs2Column+  , verify "a string of double characters is an even width" dcStringIsEven+  , verify "safeWcwidth provides a width of 0 for chars without widths" safeWcwidthForControlChars   ] 
− test/interactive_terminal_test.hs
@@ -1,954 +0,0 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ScopedTypeVariables #-}-module Main where--import Graphics.Vty-import Graphics.Vty.Attributes-import Graphics.Vty.Inline-import Graphics.Vty.Picture-import Graphics.Vty.Terminal-import Graphics.Vty.DisplayRegion--import Control.Exception-import Control.Monad--import Data.List ( lookup )-import Data.Maybe ( isJust, fromJust )-import Data.Monoid-import Data.String.QQ-import Data.Word--import Foreign.Marshal.Array --import qualified System.Environment as Env--import System.IO ( hFlush, hPutStr, hPutBuf, stdout )--main = do-    print_intro--output_file_path = "test_results.list"--print_intro = do-    putStr $ [s| -This is an interactive verification program for the terminal input and output-support of the VTY library. This will ask a series of questions about what you-see on screen. The goal is to verify that VTY's output and input support-performs as expected with your terminal.--This program produces a file named -    |] ++ output_file_path ++ [s| -in the current directory that contains the results for each test assertion. This-can  be emailed to coreyoconnor@gmail.com and used by the VTY authors to improve-support for your terminal. No personal information is contained in the report.--Each test follows, more or less, the following format:-    0. A description of the test is printed which will include a detailed-    description of what VTY is going to try and what the expected results are.-    Press return to move on.-    1. The program will produce some output or ask for you to press a key.-    2. You will then be asked to confirm if the behavior matched the provided-    description.  Just pressing enter implies the default response that-    everything was as expected. --All the tests assume the following about the terminal display:-    0. The terminal display will not be resized during a test and is at least 80 -    characters in width. -    1. The terminal display is using a monospaced font for both single width and-    double width characters.-    2. A double width character is displayed with exactly twice the width of a -    single column character. This may require adjusting the font used by the-    terminal. At least, that is the case using xterm. -    3. Fonts are installed, and usable by the terminal, that define glyphs for-    a good range of the unicode characters. Each test involving unicode display-    describes the expected appearance of each glyph. --Thanks for the help! :-D-To exit the test early enter "q" anytime at the following menu screen.--If any test failed then please post an issue to-    https://github.com/coreyoconnor/vty/issues-with the test_results.list file pasted into the issue. The issue summary can-mention the specific tests that failed or just say "interactive terminal test-failure".-|]-    wait_for_return-    results <- do_test_menu 1-    env_attributes <- mapM ( \env_name -> Control.Exception.catch-                                            ( Env.getEnv env_name >>= return . (,) env_name )-                                            ( \ (_ :: SomeException) -> return (env_name, "") )-                           )-                           [ "TERM", "COLORTERM", "LANG", "TERM_PROGRAM", "XTERM_VERSION" ]-    t <- terminal_handle-    let results_txt = show env_attributes ++ "\n" -                      ++ terminal_ID t ++ "\n"-                      ++ show results ++ "\n"-    release_terminal t-    writeFile output_file_path results_txt--wait_for_return = do-    putStr "\n(press return to continue)"-    hFlush stdout-    getLine--test_menu :: [(String, Test)]-test_menu = zip (map show [1..]) all_tests--do_test_menu :: Int -> IO [(String, Bool)]-do_test_menu next_ID -    | next_ID > length all_tests = do-        putStrLn $ "Done! Please email the " ++ output_file_path ++ " file to coreyoconnor@gmail.com"-        return []-    | otherwise = do-        display_test_menu-        putStrLn $ "Press return to start with #" ++ show next_ID ++ "."-        putStrLn "Enter a test number to perform only that test."-        putStrLn "q (or control-C) to quit."-        putStr "> "-        hFlush stdout-        s <- getLine >>= return . filter (/= '\n')-        case s of-            "q" -> return mempty-            "" -> do -                r <- run_test $ show next_ID -                rs <- do_test_menu ( next_ID + 1 )-                return $ r : rs-            i | isJust ( lookup i test_menu ) -> do-                r <- run_test i -                rs <- do_test_menu ( read i + 1 )-                return $ r : rs-        where-            display_test_menu -                = mapM_ display_test_menu' test_menu-            display_test_menu' ( i, t ) -                = putStrLn $ ( if i == show next_ID -                                then "> " -                                else "  "-                             ) ++ i ++ ". " ++ test_name t--run_test :: String -> IO (String, Bool)-run_test i = do-    let t = fromJust $ lookup i test_menu -    print_summary t-    wait_for_return-    test_action t-    r <- confirm_results t-    return (test_ID t, r)--default_success_confirm_results = do-    putStr "\n"-    putStr "[Y/n] "-    hFlush stdout-    r <- getLine-    case r of-        "" -> return True-        "y" -> return True-        "Y" -> return True-        "n" -> return False--data Test = Test-    { test_name :: String-    , test_ID :: String-    , test_action :: IO ()-    , print_summary :: IO ()-    , confirm_results :: IO Bool-    }--all_tests -    = [ reserve_output_test -      , display_bounds_test_0-      , display_bounds_test_1-      , display_bounds_test_2-      , display_bounds_test_3-      , unicode_single_width_0-      , unicode_single_width_1-      , unicode_double_width_0-      , unicode_double_width_1-      , attributes_test_0-      , attributes_test_1-      , attributes_test_2-      , attributes_test_3-      , attributes_test_4-      , attributes_test_5-      , inline_test_0-      , inline_test_1-      , inline_test_2-      , cursor_hide_test_0-      ]--reserve_output_test = Test -    { test_name = "Initialize and reserve terminal output then restore previous state."-    , test_ID = "reserve_output_test"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        putStrLn "Line 1"-        putStrLn "Line 2"-        putStrLn "Line 3"-        putStrLn "Line 4 (press return)"-        hFlush stdout-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared. -    1. Four lines of text should be visible.-    1. The cursor should be visible and at the start of the fifth line.--After return is pressed for the second time this test then:-    * The screen containing the test summary should be restored;-    * The cursor is visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--display_bounds_test_0 = Test-    { test_name = "Verify display bounds are correct test 0: Using spaces."-    , test_ID = "display_bounds_test_0"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        DisplayRegion w h <- display_bounds t-        let row_0 = replicate (fromEnum w) 'X' ++ "\n"-            row_h = replicate (fromEnum w - 1) 'X'-            row_n = "X" ++ replicate (fromEnum w - 2) ' ' ++ "X\n"-            image = row_0 ++ (concat $ replicate (fromEnum h - 2) row_n) ++ row_h-        putStr image-        hFlush stdout-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = display_bounds_test_summary True-    , confirm_results = generic_output_match_confirm-    }--display_bounds_test_1 = Test-    { test_name = "Verify display bounds are correct test 0: Using cursor movement."-    , test_ID = "display_bounds_test_1"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        DisplayRegion w h <- display_bounds t-        set_cursor_pos t 0 0-        let row_0 = replicate (fromEnum w) 'X' ++ "\n"-        putStr row_0-        forM_ [1 .. h - 2] $ \y -> do-            set_cursor_pos t 0 y-            putStr "X"-            hFlush stdout-            set_cursor_pos t (w - 1) y-            putStr "X"-            hFlush stdout-        set_cursor_pos t 0 (h - 1)-        let row_h = replicate (fromEnum w - 1) 'X'-        putStr row_h-        hFlush stdout-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = display_bounds_test_summary True-    , confirm_results = generic_output_match_confirm-    }--display_bounds_test_2 = Test-    { test_name = "Verify display bounds are correct test 0: Using Image ops."-    , test_ID = "display_bounds_test_2"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        bounds@(DisplayRegion w h) <- display_bounds t-        let first_row = horiz_cat $ replicate (fromEnum w) (char def_attr 'X')-            middle_rows = vert_cat $ replicate (fromEnum h - 2) middle_row-            middle_row = (char def_attr 'X') <|> background_fill (w - 2) 1 <|> (char def_attr 'X')-            end_row = first_row-            image = first_row <-> middle_rows <-> end_row-            pic = (pic_for_image image) { pic_cursor = Cursor (w - 1) (h - 1) }-        d <- display_context t bounds-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = display_bounds_test_summary True-    , confirm_results = generic_output_match_confirm-    }--display_bounds_test_3 = Test-    { test_name = "Verify display bounds are correct test 0: Hide cursor; Set cursor pos."-    , test_ID = "display_bounds_test_3"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        DisplayRegion w h <- display_bounds t-        hide_cursor t-        set_cursor_pos t 0 0-        let row_0 = replicate (fromEnum w) 'X'-        putStrLn row_0-        forM_ [1 .. h - 2] $ \y -> do-            set_cursor_pos t 0 y-            putStr "X"-            hFlush stdout-            set_cursor_pos t (w - 1) y-            putStr "X"-            hFlush stdout-        set_cursor_pos t 0 (h - 1)-        let row_h = row_0-        putStr row_h-        hFlush stdout-        getLine-        show_cursor t-        release_display t-        release_terminal t-        return ()-    , print_summary = display_bounds_test_summary False-    , confirm_results = generic_output_match_confirm-    }--display_bounds_test_summary has_cursor = do-    putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-|]-    if has_cursor-        then putStr "    1. The cursor will be visible."-        else putStr "    1. The cursor will NOT be visible."-    putStr [s|-    2. The border of the display will be outlined in Xs. -       So if - and | represented the edge of the terminal window:-         |-------------|-         |XXXXXXXXXXXXX|-         |X           X||]--    if has_cursor-        then putStr $ [s|-         |XXXXXXXXXXXXC| |]-        else putStr $ [s|-         |XXXXXXXXXXXXX| |]--    putStr $ [s|-         |-------------|--        ( Where C is the final position of the cursor. There may be an X drawn-        under the cursor. )-    3. The display will remain in this state until return is pressed again.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]--generic_output_match_confirm = do-    putStr $ [s|-Did the test output match the description?-|]-    default_success_confirm_results---- Explicitely definethe bytes that encode each example text.--- This avoids any issues with how the compiler represents string literals.------ This document is UTF-8 encoded so the UTF-8 string is still included for--- reference------ It's assumed the compiler will at least not barf on UTF-8 encoded text in--- comments ;-)------ txt_0 = ↑↑↓↓←→←→BA--utf8_txt_0 :: [[Word8]]-utf8_txt_0 = [ [ 0xe2 , 0x86 , 0x91 ]-             , [ 0xe2 , 0x86 , 0x91 ]-             , [ 0xe2 , 0x86 , 0x93 ]-             , [ 0xe2 , 0x86 , 0x93 ]-             , [ 0xe2 , 0x86 , 0x90 ]-             , [ 0xe2 , 0x86 , 0x92 ]-             , [ 0xe2 , 0x86 , 0x90 ]-             , [ 0xe2 , 0x86 , 0x92 ]-             , [ 0x42 ]-             , [ 0x41 ]-             ]--iso_10646_txt_0 :: String-iso_10646_txt_0 = map toEnum-    [ 8593 -    , 8593-    , 8595-    , 8595-    , 8592-    , 8594-    , 8592-    , 8594-    , 66-    , 65-    ]--unicode_single_width_0 = Test-    { test_name = "Verify terminal can display unicode single-width characters. (Direct UTF-8)"-    , test_ID = "unicode_single_width_0"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        hide_cursor t-        withArrayLen (concat utf8_txt_0) (flip $ hPutBuf stdout)-        hPutStr stdout "\n"-        hPutStr stdout "0123456789\n"-        hFlush stdout-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = unicode_single_width_summary-    , confirm_results = generic_output_match_confirm-    }--unicode_single_width_1 = Test-    { test_name = "Verify terminal can display unicode single-width characters. (Image ops)"-    , test_ID = "unicode_single_width_1"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = line_0 <-> line_1-            line_0 = iso_10646_string def_attr iso_10646_txt_0-            line_1 = string def_attr "0123456789"-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = unicode_single_width_summary-    , confirm_results = generic_output_match_confirm-    }--unicode_single_width_summary = putStr [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. Two horizontal lines of text will be displayed:-        a. The first will be a sequence of glyphs in UTF-8 encoding. Each glyph-        will occupy one column of space. The order and appearance of the glyphs-        will be:-            | column | appearance    |-            ==========================-            | 0      | up arrow      |-            | 1      | up arrow      |-            | 2      | down arrow    |-            | 3      | down arrow    |-            | 4      | left arrow    |-            | 5      | right arrow   |-            | 6      | left arrow    |-            | 7      | right arrow   |-            | 8      | B             |-            | 9      | A             |-            ( see: http://en.wikipedia.org/wiki/Arrow_(symbol) )-        b. The second will be: 0123456789. --Verify: -    * The far right extent of the glyphs on both lines are equal; -    * The glyphs are as described.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]---- The second example is a unicode string containing double-width glyphs--- 你好吗-utf8_txt_1 :: [[Word8]]-utf8_txt_1 = [ [0xe4,0xbd,0xa0]-             , [0xe5,0xa5,0xbd]-             , [0xe5,0x90,0x97]-             ]--iso_10646_txt_1 :: String-iso_10646_txt_1 = map toEnum [20320,22909,21527]--unicode_double_width_0 = Test-    { test_name = "Verify terminal can display unicode double-width characters. (Direct UTF-8)"-    , test_ID = "unicode_double_width_0"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        hide_cursor t-        withArrayLen (concat utf8_txt_1) (flip $ hPutBuf stdout)-        hPutStr stdout "\n"-        hPutStr stdout "012345\n"-        hFlush stdout-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = unicode_double_width_summary-    , confirm_results = generic_output_match_confirm-    }--unicode_double_width_1 = Test-    { test_name = "Verify terminal can display unicode double-width characters. (Image ops)"-    , test_ID = "unicode_double_width_1"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = line_0 <-> line_1-            line_0 = iso_10646_string def_attr iso_10646_txt_1-            line_1 = string def_attr "012345"-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = unicode_double_width_summary-    , confirm_results = generic_output_match_confirm-    }--unicode_double_width_summary = putStr [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. Two horizontal lines of text will be displayed:-        a. The first will be a sequence of glyphs in UTF-8 encoding. Each glyph-        will occupy two columns of space. The order and appearance of the glyphs-        will be:-            | column | appearance                |-            ======================================-            | 0      | first half of ni3         |-            | 1      | second half of ni3        |-            | 2      | first half of hao3        |-            | 3      | second half of hao3       |-            | 4      | first half of ma          |-            | 5      | second half of ma         |-        b. The second will be: 012345. --Verify: -    * The far right extent of the glyphs on both lines are equal; -    * The glyphs are as described.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]--all_colors = zip [ black, red, green, yellow, blue, magenta, cyan, white ]-                 [ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" ]--all_bright_colors -    = zip [ bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white ]-          [ "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white" ]--attributes_test_0 = Test -    { test_name = "Character attributes: foreground colors."-    , test_ID = "attributes_test_0"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = border <|> column_0 <|> border <|> column_1 <|> border-            column_0 = vert_cat $ map line_with_color all_colors-            border = vert_cat $ replicate (length all_colors) $ string def_attr " | "-            column_1 = vert_cat $ map (string def_attr . snd) all_colors-            line_with_color (c, c_name) = string (def_attr `with_fore_color` c) c_name-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. 9 lines of text in two columns will be drawn. The first column will be a-    name of a standard color (for an 8 color terminal) rendered in that color.-    For instance, one line will be the word "magenta" and that word should be-    rendered in the magenta color. The second column will be the name of a-    standard color rendered with the default attributes.--Verify: -    * In the first column: The foreground color matches the named color.-    * The second column: All text is rendered with the default attributes.-    * The vertical bars used in each line to mark the border of a column are-    lined up.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--attributes_test_1 = Test -    { test_name = "Character attributes: background colors."-    , test_ID = "attributes_test_1"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = border <|> column_0 <|> border <|> column_1 <|> border-            column_0 = vert_cat $ map line_with_color all_colors-            border = vert_cat $ replicate (length all_colors) $ string def_attr " | "-            column_1 = vert_cat $ map (string def_attr . snd) all_colors-            line_with_color (c, c_name) = string (def_attr `with_back_color` c) c_name-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. 9 lines of text in two columns will be drawn. The first column will-    contain be a name of a standard color for an 8 color terminal rendered with-    the default foreground color with a background the named color.  For-    instance, one line will contain be the word "magenta" and the word should-    be rendered in the default foreground color over a magenta background. The-    second column will be the name of a standard color rendered with the default-    attributes.--Verify: -    * The first column: The background color matches the named color.-    * The second column: All text is rendered with the default attributes.-    * The vertical bars used in each line to mark the border of a column are-    lined up.--Note: I haven't decided if, in this case, the background color should extend to-fills added for alignment. Right now the selected background color is only-applied to the background where the word is actually rendered. Since each word-is not of the same length VTY adds background fills to make the width of each-row effectively the same. These added fills are all currently rendered with the-default background pattern.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--attributes_test_2 = Test -    { test_name = "Character attributes: Vivid foreground colors."-    , test_ID = "attributes_test_2"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = horiz_cat [border, column_0, border, column_1, border, column_2, border]-            border = vert_cat $ replicate (length all_colors) $ string def_attr " | "-            column_0 = vert_cat $ map line_with_color_0 all_colors-            column_1 = vert_cat $ map line_with_color_1 all_bright_colors-            column_2 = vert_cat $ map (string def_attr . snd) all_colors-            line_with_color_0 (c, c_name) = string (def_attr `with_fore_color` c) c_name-            line_with_color_1 (c, c_name) = string (def_attr `with_fore_color` c) c_name-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. 9 lines of text in three columns will be drawn:-        a. The first column will be a name of a standard color (for an 8 color-        terminal) rendered with that color as the foreground color.  -        b. The next column will be also be the name of a standard color rendered-        with that color as the foreground color but the shade used should be-        more vivid than the shade used in the first column.    -        c. The final column will be the name of a color rendered with the-        default attributes.--For instance, one line will be the word "magenta" and that word should be-rendered in the magenta color. --I'm not actually sure exactly what "vivid" means in this context. For xterm the-vivid colors are brighter.  --Verify: -    * The first column: The foreground color matches the named color.-    * The second column: The foreground color matches the named color but is-    more vivid than the color used in the first column.  -    * The third column: All text is rendered with the default attributes.-    * The vertical bars used in each line to mark the border of a column are-    lined up.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--attributes_test_3 = Test -    { test_name = "Character attributes: Vivid background colors."-    , test_ID = "attributes_test_3"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = horiz_cat [border, column_0, border, column_1, border, column_2, border]-            border = vert_cat $ replicate (length all_colors) $ string def_attr " | "-            column_0 = vert_cat $ map line_with_color_0 all_colors-            column_1 = vert_cat $ map line_with_color_1 all_bright_colors-            column_2 = vert_cat $ map (string def_attr . snd) all_colors-            line_with_color_0 (c, c_name) = string (def_attr `with_back_color` c) c_name-            line_with_color_1 (c, c_name) = string (def_attr `with_back_color` c) c_name-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. 9 lines of text in three columns will be drawn:-        a. The first column will contain be a name of a standard color for an 8-        color terminal rendered with the default foreground color with a-        background the named color.  -        b. The first column will contain be a name of a standard color for an 8-        color terminal rendered with the default foreground color with the-        background a vivid version of the named color. -        c. The third column will be the name of a standard color rendered with-        the default attributes.-        -For instance, one line will contain be the word "magenta" and the word should-be rendered in the default foreground color over a magenta background. --I'm not actually sure exactly what "vivid" means in this context. For xterm the-vivid colors are brighter.--Verify: -    * The first column: The background color matches the named color.-    * The second column: The background color matches the named color and is-    more vivid than the color used in the first column.  -    * The third column column: All text is rendered with the default attributes.-    * The vertical bars used in each line to mark the border of a column are-    lined up.--Note: I haven't decided if, in this case, the background color should extend to-fills added for alignment. Right now the selected background color is only-applied to the background where the word is actually rendered. Since each word-is not of the same length VTY adds background fills to make the width of each-row effectively the same. These added fills are all currently rendered with the-default background pattern.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--attr_combos = -    [ ( "default", id )-    , ( "bold", flip with_style bold )-    , ( "blink", flip with_style blink )-    , ( "underline", flip with_style underline )-    , ( "bold + blink", flip with_style (bold + blink) )-    , ( "bold + underline", flip with_style (bold + underline) )-    , ( "underline + blink", flip with_style (underline + blink) )-    , ( "bold + blink + underline", flip with_style (bold + blink + underline) )-    ]--attributes_test_4 = Test -    { test_name = "Character attributes: Bold; Blink; Underline."-    , test_ID = "attributes_test_4"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = horiz_cat [border, column_0, border, column_1, border]-            border = vert_cat $ replicate (length attr_combos) $ string def_attr " | "-            column_0 = vert_cat $ map line_with_attrs attr_combos-            column_1 = vert_cat $ map (string def_attr . fst) attr_combos-            line_with_attrs (desc, attr_f) = string (attr_f def_attr) desc-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. 8 rows of text in two columns. -    The rows will contain the following text:-        default-        bold -        blink-        underline-        bold + blink-        bold + underline-        underline + blink-        bold + blink + underline-    The first column will be rendered with the described attributes. The second-    column will be rendered with the default attributes.-        -Verify: -    * The vertical bars used in each line to mark the border of a column are-    lined up.-    * The text in the first column is rendered as described.--After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--attributes_test_5 = Test -    { test_name = "Character attributes: 240 color palette"-    , test_ID = "attributes_test_5"-    , test_action = do-        t <- terminal_handle-        reserve_display t-        let pic = pic_for_image image-            image = vert_cat $ map horiz_cat $ split_color_images color_images-            color_images = map (\i -> string (current_attr `with_back_color` Color240 i) " ") [0..239]-            split_color_images [] = []-            split_color_images is = (take 20 is ++ [string def_attr " "]) : (split_color_images (drop 20 is))-        d <- display_bounds t >>= display_context t-        output_picture d pic-        getLine-        release_display t-        release_terminal t-        return ()-    , print_summary = do-        putStr $ [s|-Once return is pressed:-    0. The screen will be cleared.-    1. The cursor will be hidden.-    2. A 20 character wide and 12 row high block of color squares. This should look like a palette-    of some sort. I'm not exactly sure if all color terminals use the same palette. I doubt it...--Verify: --After return is pressed for the second time:-    0. The screen containing the test summary should be restored.-    1. The cursor should be visible.-|]-    , confirm_results = do-        putStr $ [s|-Did the test output match the description?-|]-        default_success_confirm_results-    }--inline_test_0 = Test-    { test_name = "Verify styled output can be performed without clearing the screen."-    , test_ID = "inline_test_0"-    , test_action = do-        t <- terminal_handle-        putStrLn "line 1."-        put_attr_change t $ back_color red >> apply_style underline-        putStrLn "line 2."-        put_attr_change t $ default_all-        putStrLn "line 3."-        release_terminal t-        return ()-    , print_summary = putStr $ [s|-lines are in order.-The second line "line 2" should have a red background and the text underline.-The third line "line 3" should be drawn in the same style as the first line.-|]--    , confirm_results = generic_output_match_confirm-    }--inline_test_1 = Test-    { test_name = "Verify styled output can be performed without clearing the screen."-    , test_ID = "inline_test_1"-    , test_action = do-        t <- terminal_handle-        putStr "Not styled. "-        put_attr_change t $ back_color red >> apply_style underline-        putStr " Styled! "-        put_attr_change t $ default_all-        putStrLn "Not styled."-        release_terminal t-        return ()-    , print_summary = putStr $ [s|-|]--    , confirm_results = generic_output_match_confirm-    }--inline_test_2 = Test-    { test_name = "Verify styled output can be performed without clearing the screen."-    , test_ID = "inline_test_1"-    , test_action = do-        t <- terminal_handle-        putStr "Not styled. "-        put_attr_change t $ back_color red >> apply_style underline-        putStr " Styled! "-        put_attr_change t $ default_all-        putStr "Not styled.\n"-        release_terminal t-        return ()-    , print_summary = putStr $ [s|-|]-    , confirm_results = generic_output_match_confirm-    }--cursor_hide_test_0 :: Test-cursor_hide_test_0 = Test-    { test_name = "Verify the cursor is hid and re-shown. issue #7"-    , test_ID = "cursor_hide_test_0"-    , test_action = do-        vty <- mkVty-        show_cursor $ terminal vty-        set_cursor_pos (terminal vty) 5 5-        next_event vty-        hide_cursor $ terminal vty-        next_event vty-        shutdown vty-        return ()-    , print_summary = putStr $ [s|-    1. verify the cursor is displayed.-    2. press enter-    3. verify the cursor is hid.-    4. press enter.-    5. the display should return to the state before the test.-|]-    , confirm_results = generic_output_match_confirm-    }-
vty.cabal view
@@ -1,5 +1,5 @@ name:                vty-version:             4.7.5+version:             5.0.0 license:             BSD3 license-file:        LICENSE author:              AUTHORS@@ -14,65 +14,91 @@   Included in the source distribution is a program test/interactive_terminal_test.hs that   demonstrates the various features.    .+  Developers: See the "Graphics.Vty" module.+  .+  Users: See the "Graphics.Vty.Config" module.+  .   If your terminal is not behaving as expected the results of the vty-interactive-terminal-test   executable should be sent to the Vty maintainter to aid in debugging the issue.   .-  Notable infelicities: Sometimes poor efficiency; Assumes UTF-8 character encoding support by the-  terminal;+  Notable infelicities: Assumes UTF-8 character encoding support by the terminal; Poor signal+  handling; Requires terminfo.   .-  Project is hosted on github.com: https://github.com/coreyoconnor/vty+  Project is hosted on github.com: https:\/\/github.com\/coreyoconnor\/vty   .-  git clone git://github.com/coreyoconnor/vty.git+  git clone git:\/\/github.com\/coreyoconnor\/vty.git   .   &#169; 2006-2007 Stefan O'Rear; BSD3 license.   .   &#169; Corey O'Connor; BSD3 license.--- the test suites require >= 1.17.0-cabal-version:        >= 1.14.0-build-type:           Simple-data-files:          README,+cabal-version:       >= 1.18.0+build-type:          Simple+data-files:          README.md,                      TODO,                      AUTHORS,                      CHANGELOG,                      LICENSE+tested-with:         GHC >= 7.6.2  library   default-language:    Haskell2010   build-depends:       base >= 4 && < 5,+                       blaze-builder >= 0.3.3.2 && < 0.4,                        bytestring,                        containers,+                       data-default >= 0.5.3,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,+                       directory,+                       filepath >= 1.0 && < 2.0,+                       lens >= 3.9.0.2 && < 4.2,+                       -- required for nice installation with yi+                       hashable >= 1.2,                        mtl >= 1.1.1.0 && < 2.2,                        parallel >= 2.2 && < 3.3,                        parsec >= 2 && < 4,                        terminfo >= 0.3 && < 0.5,+                       transformers >= 0.3.0.0,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7    exposed-modules:     Graphics.Vty                        Graphics.Vty.Attributes-                       Graphics.Vty.DisplayRegion+                       Graphics.Vty.Config+                       Graphics.Vty.Error                        Graphics.Vty.Image                        Graphics.Vty.Inline-                       Graphics.Vty.LLInput+                       Graphics.Vty.Inline.Unsafe+                       Graphics.Vty.Input+                       Graphics.Vty.Input.Events                        Graphics.Vty.Picture-                       Graphics.Vty.Terminal-                       Codec.Binary.UTF8.Width--  other-modules:       Data.Marshalling+                       Graphics.Vty.Prelude+                       Graphics.Vty.Output+                       Graphics.Text.Width+                       -- the modules below are only meant to be used by the tests.+                       Codec.Binary.UTF8.Debug                        Data.Terminfo.Parse                        Data.Terminfo.Eval-                       Graphics.Vty.Attributes.Color-                       Graphics.Vty.Attributes.Color240+                       Graphics.Vty.Debug                        Graphics.Vty.DisplayAttributes+                       Graphics.Vty.Image.Internal+                       Graphics.Vty.Input.Classify+                       Graphics.Vty.Input.Loop+                       Graphics.Vty.Input.Terminfo+                       Graphics.Vty.PictureToSpans                        Graphics.Vty.Span-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.MacOSX-                       Graphics.Vty.Terminal.XTermColor-                       Graphics.Vty.Terminal.TerminfoBased+                       Graphics.Vty.Output.Mock+                       Graphics.Vty.Output.Interface+                       Graphics.Vty.Output.MacOSX+                       Graphics.Vty.Output.XTermColor+                       Graphics.Vty.Output.TerminfoBased +  other-modules:       Graphics.Vty.Attributes.Color+                       Graphics.Vty.Attributes.Color240+                       Graphics.Vty.Debug.Image+                       Graphics.Vty.Input.Terminfo.ANSIVT+   c-sources:           cbits/gwinsz.c                        cbits/set_term_timing.c                        cbits/mk_wcwidth.c@@ -81,560 +107,500 @@    hs-source-dirs:      src -  ghc-options:         -O2 -funbox-strict-fields -Wall -fno-full-laziness -fspec-constr -fspec-constr-count=10+  default-extensions:  ScopedTypeVariables+                       ForeignFunctionInterface -  ghc-prof-options:    -O2 -funbox-strict-fields -caf-all -Wall -fno-full-laziness -fspec-constr -fspec-constr-count=10+  ghc-options:         -O2 -funbox-strict-fields -threaded -Wall -fspec-constr -fspec-constr-count=10 -  cc-options:          -O2+  ghc-prof-options:    -O2 -funbox-strict-fields -threaded -caf-all -Wall -fspec-constr -fspec-constr-count=10 +  cc-options:          -O2 -fpic++executable vty-demo+  main-is:             Demo.hs++  default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables+  ghc-options:         -threaded++  build-depends:       vty,+                       base >= 4 && < 5,+                       containers,+                       data-default >= 0.5.3,+                       lens,+                       mtl >= 1.1.1.0 && < 2.2+ test-suite verify-attribute-ops   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables   type:                detailed-0.9-  hs-source-dirs:      src test+  hs-source-dirs:      test   test-module:         VerifyAttributeOps-  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-using-mock-terminal   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test -  test-module:         VerifyMockTerminal+  test-module:         VerifyUsingMockTerminal -  other-modules:       Graphics.Vty-                       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Picture-                       Graphics.Vty.Span-                       Graphics.Vty.Terminal-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.Debug-                       Graphics.Vty.Debug-                       Graphics.Vty.Debug.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes-                       Verify.Graphics.Vty.DisplayRegion+                       Verify.Graphics.Vty.Prelude                        Verify.Graphics.Vty.Picture                        Verify.Graphics.Vty.Image                        Verify.Graphics.Vty.Span+                       Verify.Graphics.Vty.Output -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c+  build-depends:       vty,+                       Cabal == 1.20.*,+                       QuickCheck >= 2.4,+                       random == 1.0.*,+                       base >= 4 && < 5,+                       bytestring,+                       containers,+                       deepseq >= 1.1 && < 1.4,+                       mtl >= 1.1.1.0 && < 2.2,+                       text >= 0.11.3,+                       terminfo >= 0.3 && < 0.5,+                       unix,+                       utf8-string >= 0.3 && < 0.4,+                       vector >= 0.7+  +test-suite verify-terminal+  default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables -  include-dirs:        cbits+  type:                detailed-0.9 -  build-depends:       Cabal == 1.17.*,+  hs-source-dirs:      test++  test-module:         VerifyOutput++  other-modules:       Verify+                       Verify.Graphics.Vty.Attributes+                       Verify.Graphics.Vty.Prelude+                       Verify.Graphics.Vty.Picture+                       Verify.Graphics.Vty.Image+                       Verify.Graphics.Vty.Span+                       Verify.Graphics.Vty.Output++  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,                        terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-display-attributes   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyDisplayAttributes -  other-modules:       Graphics.Vty-                       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Picture-                       Graphics.Vty.Span-                       Graphics.Vty.Terminal-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.Debug-                       Graphics.Vty.Debug-                       Graphics.Vty.Debug.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes-                       Verify.Graphics.Vty.DisplayRegion+                       Verify.Graphics.Vty.Prelude                        Verify.Graphics.Vty.Picture                        Verify.Graphics.Vty.Image                        Verify.Graphics.Vty.Span -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c--  include-dirs:        cbits--  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-empty-image-props   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyEmptyImageProps -  other-modules:       Graphics.Vty.Picture-                       Verify+  other-modules:       Verify -  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-eval-terminfo-caps   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyEvalTerminfoCaps -  other-modules:       Data.Terminfo.Parse-                       Data.Terminfo.Eval-                       Data.Marshalling-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify+                       Verify.Graphics.Vty.Output -  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,+                       blaze-builder >= 0.3.3.2 && < 0.4,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,                        terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-image-ops   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyImageOps -  other-modules:       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Picture-                       Graphics.Vty.Span-                       Graphics.Vty.Debug.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes                        Verify.Graphics.Vty.Image -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c--  include-dirs:        cbits--  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-image-trans   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyImageTrans -  other-modules:       Graphics.Vty.Attributes-                       Graphics.Vty.Debug.Image-                       Graphics.Vty.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes                        Verify.Graphics.Vty.Image -  c-sources:           cbits/mk_wcwidth.c--  include-dirs:        cbits--  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-inline   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyInline -  other-modules:       Codec.Binary.UTF8.Width-                       Data.Terminfo.Eval-                       Data.Terminfo.Parse-                       Data.Marshalling-                       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Inline-                       Graphics.Vty.Span-                       Graphics.Vty.Terminal-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.MacOSX-                       Graphics.Vty.Terminal.TerminfoBased-                       Graphics.Vty.Terminal.XTermColor--  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c--  include-dirs:        cbits+  other-modules:       Verify+                       Verify.Graphics.Vty.Output -  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-parse-terminfo-caps   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyParseTerminfoCaps -  other-modules:       Data.Terminfo.Parse-                       Verify+  other-modules:       Verify                        Verify.Data.Terminfo.Parse--  include-dirs:        cbits+                       Verify.Graphics.Vty.Output -  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,                        terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7--test-suite verify-picture-ops+  +test-suite verify-simple-span-generation   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test--  test-module:         VerifyPictureOps+  hs-source-dirs:      test -  other-modules:       Graphics.Vty.Picture-                       Verify+  test-module:         VerifySimpleSpanGeneration -  include-dirs:        cbits+  other-modules:       Verify+                       Verify.Graphics.Vty.Attributes+                       Verify.Graphics.Vty.Prelude+                       Verify.Graphics.Vty.Picture+                       Verify.Graphics.Vty.Image+                       Verify.Graphics.Vty.Span -  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7--test-suite verify-picture-to-span+  +  +test-suite verify-crop-span-generation   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test -  test-module:         VerifyPictureToSpan+  test-module:         VerifyCropSpanGeneration -  other-modules:       Graphics.Vty-                       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Picture-                       Graphics.Vty.Span-                       Graphics.Vty.Terminal-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.Debug-                       Graphics.Vty.Debug-                       Graphics.Vty.Debug.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes-                       Verify.Graphics.Vty.DisplayRegion+                       Verify.Graphics.Vty.Prelude                        Verify.Graphics.Vty.Picture                        Verify.Graphics.Vty.Image                        Verify.Graphics.Vty.Span -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c--  include-dirs:        cbits--  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7+   -test-suite verify-span-ops+test-suite verify-layers-span-generation   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test -  test-module:         VerifySpanOps+  test-module:         VerifyLayersSpanGeneration -  other-modules:       Graphics.Vty-                       Graphics.Vty.Attributes-                       Graphics.Vty.DisplayAttributes-                       Graphics.Vty.DisplayRegion-                       Graphics.Vty.Image-                       Graphics.Vty.Picture-                       Graphics.Vty.Span-                       Graphics.Vty.Terminal-                       Graphics.Vty.Terminal.Generic-                       Graphics.Vty.Terminal.Debug-                       Graphics.Vty.Debug-                       Graphics.Vty.Debug.Image-                       Codec.Binary.UTF8.Width-                       Verify+  other-modules:       Verify                        Verify.Graphics.Vty.Attributes-                       Verify.Graphics.Vty.DisplayRegion+                       Verify.Graphics.Vty.Prelude                        Verify.Graphics.Vty.Picture                        Verify.Graphics.Vty.Image                        Verify.Graphics.Vty.Span -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c--  include-dirs:        cbits--  build-depends:       Cabal == 1.17.*,+  build-depends:       vty,+                       Cabal == 1.20.*,                        QuickCheck >= 2.4,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,-                       terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7  test-suite verify-utf8-width   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables    type:                detailed-0.9 -  hs-source-dirs:      src test+  hs-source-dirs:      test    test-module:         VerifyUtf8Width -  other-modules:       Codec.Binary.UTF8.Width-                       Graphics.Vty.Attributes-                       Graphics.Vty.Image-                       Verify+  other-modules:       Verify -  c-sources:           cbits/mk_wcwidth.c+  build-depends:       vty,+                       Cabal == 1.20.*,+                       QuickCheck >= 2.4,+                       random == 1.0.*,+                       base >= 4 && < 5,+                       bytestring,+                       containers,+                       deepseq >= 1.1 && < 1.4,+                       mtl >= 1.1.1.0 && < 2.2,+                       text >= 0.11.3,+                       unix,+                       utf8-string >= 0.3 && < 0.4,+                       vector >= 0.7+  +test-suite verify-using-mock-input+  default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables -  include-dirs:        cbits+  type:                exitcode-stdio-1.0 -  build-depends:       Cabal == 1.17.*,+  hs-source-dirs:      test++  main-is:             VerifyUsingMockInput.hs++  build-depends:       vty,+                       Cabal == 1.20.*,+                       data-default >= 0.5.3,                        QuickCheck >= 2.4,+                       smallcheck == 1.*,+                       quickcheck-assertions >= 0.1.1,+                       test-framework == 0.8.*,+                       test-framework-smallcheck == 0.2.*,                        random == 1.0.*,                        base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,+                       lens >= 3.9.0.2 && < 5.0,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,                        terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7--executable vty-interactive-terminal-test-  main-is:             interactive_terminal_test.hs+  +  ghc-options:         -threaded -Wall +test-suite verify-config   default-language:    Haskell2010+  default-extensions:  ScopedTypeVariables -  hs-source-dirs:      src test+  type:                exitcode-stdio-1.0 -  c-sources:           cbits/gwinsz.c-                       cbits/set_term_timing.c-                       cbits/mk_wcwidth.c+  hs-source-dirs:      test -  include-dirs:        cbits+  main-is:             VerifyConfig.hs -  build-depends:       base >= 4 && < 5,+  build-depends:       vty,+                       Cabal == 1.20.*,+                       data-default >= 0.5.3,+                       HUnit,+                       QuickCheck >= 2.4,+                       smallcheck == 1.*,+                       quickcheck-assertions >= 0.1.1,+                       test-framework == 0.8.*,+                       test-framework-smallcheck == 0.2.*,+                       test-framework-hunit,+                       random == 1.0.*,+                       base >= 4 && < 5,                        bytestring,                        containers,                        deepseq >= 1.1 && < 1.4,-                       ghc-prim,+                       lens >= 3.9.0.2 && < 5.0,                        mtl >= 1.1.1.0 && < 2.2,-                       parallel >= 2.2 && < 3.3,-                       parsec >= 2 && < 4,                        string-qq,                        terminfo >= 0.3 && < 0.5,+                       text >= 0.11.3,                        unix,                        utf8-string >= 0.3 && < 0.4,                        vector >= 0.7 --- Bench.hs--- Bench2.hs--- BenchRenderChar.hs--- ControlTable.hs--- HereDoc.hs--- Test.hs--- Test2.hs--- Verify.hs--- Verify/Data/Terminfo/Parse.hs--- Verify/Graphics/Vty/Attributes.hs--- Verify/Graphics/Vty/DisplayRegion.hs--- Verify/Graphics/Vty/Image.hs--- Verify/Graphics/Vty/Picture.hs--- Verify/Graphics/Vty/Span.hs--- vty_inline_example.hs--- vty_issue_18.hs--- yi_issue_264.hs+  ghc-options:         -threaded -Wall