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 +64/−2
- Demo.hs +80/−0
- README +0/−50
- README.md +79/−0
- cbits/gwinsz.c +2/−2
- cbits/set_term_timing.c +5/−6
- dist/build/verify-attribute-opsStub/verify-attribute-opsStub-tmp/verify-attribute-opsStub.hs +5/−0
- dist/build/verify-crop-span-generationStub/verify-crop-span-generationStub-tmp/verify-crop-span-generationStub.hs +5/−0
- dist/build/verify-display-attributesStub/verify-display-attributesStub-tmp/verify-display-attributesStub.hs +5/−0
- dist/build/verify-empty-image-propsStub/verify-empty-image-propsStub-tmp/verify-empty-image-propsStub.hs +5/−0
- dist/build/verify-eval-terminfo-capsStub/verify-eval-terminfo-capsStub-tmp/verify-eval-terminfo-capsStub.hs +5/−0
- dist/build/verify-image-opsStub/verify-image-opsStub-tmp/verify-image-opsStub.hs +5/−0
- dist/build/verify-image-transStub/verify-image-transStub-tmp/verify-image-transStub.hs +5/−0
- dist/build/verify-inlineStub/verify-inlineStub-tmp/verify-inlineStub.hs +5/−0
- dist/build/verify-layers-span-generationStub/verify-layers-span-generationStub-tmp/verify-layers-span-generationStub.hs +5/−0
- dist/build/verify-parse-terminfo-capsStub/verify-parse-terminfo-capsStub-tmp/verify-parse-terminfo-capsStub.hs +5/−0
- dist/build/verify-simple-span-generationStub/verify-simple-span-generationStub-tmp/verify-simple-span-generationStub.hs +5/−0
- dist/build/verify-terminalStub/verify-terminalStub-tmp/verify-terminalStub.hs +5/−0
- dist/build/verify-using-mock-terminalStub/verify-using-mock-terminalStub-tmp/verify-using-mock-terminalStub.hs +5/−0
- dist/build/verify-utf8-widthStub/verify-utf8-widthStub-tmp/verify-utf8-widthStub.hs +5/−0
- src/Codec/Binary/UTF8/Debug.hs +18/−0
- src/Codec/Binary/UTF8/Width.hs +0/−33
- src/Data/Marshalling.hs +0/−30
- src/Data/Terminfo/Eval.hs +84/−203
- src/Data/Terminfo/Parse.hs +184/−187
- src/Graphics/Text/Width.hs +34/−0
- src/Graphics/Vty.hs +138/−108
- src/Graphics/Vty/Attributes.hs +91/−65
- src/Graphics/Vty/Attributes/Color240.hs +4/−4
- src/Graphics/Vty/Config.hs +233/−0
- src/Graphics/Vty/Debug.hs +18/−21
- src/Graphics/Vty/Debug/Image.hs +14/−15
- src/Graphics/Vty/DisplayAttributes.hs +56/−57
- src/Graphics/Vty/DisplayRegion.hs +0/−12
- src/Graphics/Vty/Error.hs +8/−0
- src/Graphics/Vty/Image.hs +235/−308
- src/Graphics/Vty/Image/Internal.hs +293/−0
- src/Graphics/Vty/Inline.hs +54/−46
- src/Graphics/Vty/Inline/Unsafe.hs +42/−0
- src/Graphics/Vty/Input.hs +200/−0
- src/Graphics/Vty/Input/Classify.hs +78/−0
- src/Graphics/Vty/Input/Events.hs +43/−0
- src/Graphics/Vty/Input/Loop.hs +194/−0
- src/Graphics/Vty/Input/Terminfo.hs +184/−0
- src/Graphics/Vty/Input/Terminfo/ANSIVT.hs +87/−0
- src/Graphics/Vty/LLInput.hs +0/−250
- src/Graphics/Vty/Output.hs +140/−0
- src/Graphics/Vty/Output/Interface.hs +256/−0
- src/Graphics/Vty/Output/MacOSX.hs +63/−0
- src/Graphics/Vty/Output/Mock.hs +70/−0
- src/Graphics/Vty/Output/TerminfoBased.hs +483/−0
- src/Graphics/Vty/Output/XTermColor.hs +54/−0
- src/Graphics/Vty/Picture.hs +73/−49
- src/Graphics/Vty/PictureToSpans.hs +344/−0
- src/Graphics/Vty/Prelude.hs +18/−0
- src/Graphics/Vty/Span.hs +101/−317
- src/Graphics/Vty/Terminal.hs +0/−137
- src/Graphics/Vty/Terminal/Debug.hs +0/−121
- src/Graphics/Vty/Terminal/Generic.hs +0/−417
- src/Graphics/Vty/Terminal/MacOSX.hs +0/−102
- src/Graphics/Vty/Terminal/TerminfoBased.hs +0/−437
- src/Graphics/Vty/Terminal/XTermColor.hs +0/−109
- test/Verify.hs +29/−13
- test/Verify/Data/Terminfo/Parse.hs +41/−49
- test/Verify/Graphics/Vty/Attributes.hs +47/−14
- test/Verify/Graphics/Vty/DisplayRegion.hs +0/−28
- test/Verify/Graphics/Vty/Image.hs +161/−33
- test/Verify/Graphics/Vty/Output.hs +60/−0
- test/Verify/Graphics/Vty/Picture.hs +20/−18
- test/Verify/Graphics/Vty/Prelude.hs +26/−0
- test/Verify/Graphics/Vty/Span.hs +20/−13
- test/VerifyConfig.hs +52/−0
- test/VerifyCropSpanGeneration.hs +93/−0
- test/VerifyEmptyImageProps.hs +2/−3
- test/VerifyEvalTerminfoCaps.hs +30/−70
- test/VerifyImageOps.hs +130/−81
- test/VerifyImageTrans.hs +17/−15
- test/VerifyInline.hs +14/−6
- test/VerifyLayersSpanGeneration.hs +128/−0
- test/VerifyMockTerminal.hs +0/−115
- test/VerifyOutput.hs +63/−0
- test/VerifyParseTerminfoCaps.hs +50/−109
- test/VerifyPictureOps.hs +0/−8
- test/VerifyPictureToSpan.hs +0/−10
- test/VerifySimpleSpanGeneration.hs +188/−0
- test/VerifySpanOps.hs +0/−180
- test/VerifyUsingMockInput.hs +213/−0
- test/VerifyUsingMockTerminal.hs +100/−0
- test/VerifyUtf8Width.hs +16/−6
- test/interactive_terminal_test.hs +0/−954
- vty.cabal +251/−285
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 . © 2006-2007 Stefan O'Rear; BSD3 license. . © 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