ansi-terminal 0.8.0.1 → 0.8.0.2
raw patch · 32 files changed
+2944/−2876 lines, 32 filesdep +ansi-terminaldep −processdep ~basedep ~containersPVP ok
version bump matches the API change (PVP)
Dependencies added: ansi-terminal
Dependencies removed: process
Dependency ranges changed: base, containers
API changes (from Hackage documentation)
Files
- CHANGELOG.md +8/−0
- README.md +104/−88
- System/Console/ANSI.hs +0/−78
- System/Console/ANSI/Codes.hs +0/−188
- System/Console/ANSI/Example.hs +0/−315
- System/Console/ANSI/Types.hs +0/−71
- System/Console/ANSI/Unix.hs +0/−100
- System/Console/ANSI/Windows.hs +0/−187
- System/Console/ANSI/Windows/Detect.hs +0/−123
- System/Console/ANSI/Windows/Emulator.hs +0/−464
- System/Console/ANSI/Windows/Emulator/Codes.hs +0/−94
- System/Console/ANSI/Windows/Foreign.hs +0/−731
- ansi-terminal.cabal +15/−38
- app/Example.hs +315/−0
- includes/Common-Include-Emulator.hs +0/−65
- includes/Common-Include-Enabled.hs +0/−56
- includes/Common-Include.hs +0/−164
- includes/Exports-Include.hs +0/−114
- src/System/Console/ANSI.hs +118/−0
- src/System/Console/ANSI/Codes.hs +183/−0
- src/System/Console/ANSI/Types.hs +71/−0
- src/System/Console/ANSI/Unix.hs +100/−0
- src/System/Console/ANSI/Windows.hs +187/−0
- src/System/Console/ANSI/Windows/Detect.hs +123/−0
- src/System/Console/ANSI/Windows/Emulator.hs +465/−0
- src/System/Console/ANSI/Windows/Emulator/Codes.hs +94/−0
- src/System/Console/ANSI/Windows/Foreign.hs +651/−0
- src/System/Win32/Compat.hs +111/−0
- src/includes/Common-Include-Emulator.hs +65/−0
- src/includes/Common-Include-Enabled.hs +56/−0
- src/includes/Common-Include.hs +164/−0
- src/includes/Exports-Include.hs +114/−0
CHANGELOG.md view
@@ -1,6 +1,14 @@ Changes ======= +Version 0.8.0.2 +--------------- + +* Improve README and Haddock documentation +* On Windows, fix compatability with earlier GHC versions +* Drop support for GHC versions before 6.12.1 (released December 2009) + + Version 0.8.0.1 ---------------
README.md view
@@ -1,88 +1,104 @@-ansi-terminal -============= - -Haskell ANSI Terminal Package For Windows, OS X and Linux - -Description ------------ - -[ANSI](http://en.wikipedia.org/wiki/ANSI_escape_sequences) terminal -support for Haskell, which allows: - -- Cursor movement -- Screen and line clearing -- Color output -- Showing or hiding the cursor -- Changing the console title (though this is not strictly part of - ANSI, it is widely supported in Unix) - -It is compatible with Windows (via an emulation layer) and those Unixes -with ANSI terminals. - -If you like this, you may be interested in -[ansi-wl-pprint](http://github.com/batterseapower/ansi-wl-pprint), which -provides a pretty-printer that can construct strings containing ANSI -colorisation. - -Not all of the ANSI escape codes are provided by this module, but most -(if not all) of the popular and well supported ones are. For a full -list, have a look at the [current version of the -API](http://github.com/feuerbach/ansi-terminal/tree/master/includes/Common-Include.hs). -Each supported escape code or family of codes has a corresponding -function that comes in three variants: - -- A straight `IO` variant that doesn't take a `Handle` and just - applies the ANSI escape code to the terminal attached to stdout -- An `IO` variant similar to above, but which takes a `Handle` to - which the ANSI escape should be applied -- A `String` variant that returns a literal string that should be - included to get the effect of the code. This is the only one of the - three API variants that only works on Unix-like operating systems: - on Windows these strings will always be blank! - -Example -------- - -A full example is -[available](http://github.com/feuerbach/ansi-terminal/tree/master/System/Console/ANSI/Example.hs), -but for a taste of how the library works try the following code: - -``` haskell -import System.Console.ANSI - -main = do - setCursorPosition 5 0 - setTitle "ANSI Terminal Short Example" - - setSGR [ SetConsoleIntensity BoldIntensity - , SetColor Foreground Vivid Red - ] - putStr "Hello" - - setSGR [ SetConsoleIntensity NormalIntensity - , SetColor Foreground Vivid White - , SetColor Background Dull Blue - ] - putStrLn "World!" -``` - - - -Documentation -------------- - -Haddock documentation is [available at -Hackage](http://hackage.haskell.org/packages/archive/ansi-terminal/latest/doc/html/System-Console-ANSI.html). - -Credits -------- - -The library is originally written by [Max Bolingbroke](https://github.com/batterseapower) - -Maintainers ------------ - -[Mike Pilgrem](https://github.com/mpilgrem) and [Roman Cheplyaka](https://github.com/feuerbach) are the primary maintainers. - -[Oliver Charles](https://github.com/ocharles) is the backup maintainer. Please -get in touch with him if the primary maintainers cannot be reached. +ansi-terminal+=============++A Haskell package providing support for 'ANSI' control character sequences for+terminals on Unix-like operating systems and Windows++Description+-----------++['ANSI' terminal escape code](http://en.wikipedia.org/wiki/ANSI_escape_sequences)+support for Haskell, which allows:+- Colored text output, with control over both foreground and background+ colors+- Clearing parts of a line or the screen+- Hiding or showing the cursor+- Moving the cursor around+- Reporting the position of the cursor+- Scrolling the screen up or down+- Changing the title of the terminal++By using emulation, it is compatible with versions of 'Command Prompt' and+'PowerShell' on Windows that did not recognise 'ANSI' escape codes before+Windows 10 version 1511 was released in November 2015.++If you like this, you may be interested in+[ansi-wl-pprint](http://github.com/batterseapower/ansi-wl-pprint), which+provides a pretty-printer that can construct strings containing 'ANSI'+colorisation.++Not all 'ANSI' escape codes are suported by this library but most (if not+all) of the popular ones that are well-supported by terminal software are,+including:+- Select Graphic Rendition mode (colors and other attributes): `setSGR`+- Clearing parts of the screen: `clearFromCursorToScreenEnd`,+ `clearFromCursorToScreenBeginning`, `clearScreen`,+ `clearFromCursorToLineEnd`, `clearFromCursorToLineBeginning` and+ `clearLine`+- Cursor visibility changes: `hideCursor` and `showCursor`+- Cursor movement by character: `cursorUp`, `cursorDown`, `cursorForward` and+ `cursorBackward`+- Cursor movement by line: `cursorUpLine` and `cursorDownLine`+- Directly changing cursor position: `setCursorColumn` and `setCursorPosition`+- Saving, restoring and reporting cursor position: `saveCursor`,+ `restoreCursor` and `reportCursorPosition`+- Scrolling the screen: `scrollPageUp` and `scrollPageDown`+- Changing the title: `setTitle`++Each supported escape code or family of codes has a corresponding+function that comes in three variants:++- A straight `IO` variant that doesn't take a `Handle` and just applies the+ escape code to `stdout` and any terminal attached to it+- An `IO` variant similar to above, but which takes a `Handle` to which the+ escape code should be applied+- A `String` variant that returns a literal string that should be+ included to get the effect of the code. However, on Windows systems where+ emulation has been necessary, these strings will always be blank!++Example+-------++A full example is+[available](http://github.com/feuerbach/ansi-terminal/tree/master/System/Console/ANSI/Example.hs),+but for a taste of how the library works try the following code:++``` haskell+import System.Console.ANSI++main = do+ setCursorPosition 5 0+ setTitle "ANSI Terminal Short Example"++ setSGR [ SetConsoleIntensity BoldIntensity+ , SetColor Foreground Vivid Red+ ]+ putStr "Hello"++ setSGR [ SetConsoleIntensity NormalIntensity+ , SetColor Foreground Vivid White+ , SetColor Background Dull Blue+ ]+ putStrLn "World!"+```++++Documentation+-------------++Haddock documentation is [available at+Hackage](http://hackage.haskell.org/packages/archive/ansi-terminal/latest/doc/html/System-Console-ANSI.html).++Credits+-------++The library is originally written by [Max Bolingbroke](https://github.com/batterseapower)++Maintainers+-----------++[Mike Pilgrem](https://github.com/mpilgrem) and [Roman Cheplyaka](https://github.com/feuerbach) are the primary maintainers.++[Oliver Charles](https://github.com/ocharles) is the backup maintainer. Please+get in touch with him if the primary maintainers cannot be reached.
− System/Console/ANSI.hs
@@ -1,78 +0,0 @@--- | Provides ANSI terminal support for ANSI terminal software running on a --- Unix-like operating system or on a Windows operating system (where supported) --- or on other Windows operating systems where the terminal in use is not --- ANSI-enabled. --- --- The ANSI escape codes are described at <http://en.wikipedia.org/wiki/ANSI_escape_code> --- and provide a rich range of functionality for terminal control, which --- includes: --- --- * Colored text output, with control over both foreground and background --- colors --- --- * Hiding or showing the cursor --- --- * Moving the cursor around --- --- * Clearing parts of the screen --- --- The most frequently used parts of this ANSI command set are exposed with a --- platform independent interface by this module. Every function exported comes --- in three flavours: --- --- * Vanilla: has an @IO ()@ type and doesn't take a @Handle@. This just --- outputs the ANSI command directly on to the terminal corresponding to --- stdout. Commands issued like this should work as you expect on both --- Windows and Unix. --- --- * Chocolate: has an @IO ()@ type but takes a @Handle@. This outputs the --- ANSI command on the terminal corresponding to the supplied handle. --- Commands issued like this should also work as you expect on both Windows --- and Unix. --- --- * Strawberry: has a @String@ type and just consists of an escape code which --- can be added to any other bit of text before being output. The use of --- these codes is generally discouraged because they will not work on Windows --- operating systems where the terminal in use is not ANSI-enabled (such as --- those before Windows 10 Threshold 2). On versions of Windows where the --- terminal in use is not ANSI-enabled, these codes will always be the empty --- string, so it is possible to use them portably for e.g. coloring console --- output on the understanding that you will only see colors if you are --- running on an operating system that is Unix-like or is a version of --- Windows where the terminal in use is ANSI-enabled. --- --- Example: --- --- > -- Set colors and write some text in those colors. --- > sgrExample :: IO () --- > sgrExample = do --- > setSGR [SetColor Foreground Vivid Red] --- > setSGR [SetColor Background Vivid Blue] --- > putStr "Red-On-Blue" --- > setSGR [Reset] --- > putStr "White-On-Black" --- --- For many more examples, see the project's extensive --- <https://raw.githubusercontent.com/feuerbach/ansi-terminal/master/System/Console/ANSI/Example.hs Example.hs> file. -#if defined(WINDOWS) -module System.Console.ANSI - ( - module System.Console.ANSI.Windows - ) where - -import System.Console.ANSI.Windows - -#elif defined(UNIX) - -module System.Console.ANSI - ( - module System.Console.ANSI.Unix - ) where - -import System.Console.ANSI.Unix - -#else - -#error Unsupported platform for the ansi-terminal package - -#endif
− System/Console/ANSI/Codes.hs
@@ -1,188 +0,0 @@--- | This module exports functions that return 'String' values containing codes --- in accordance with: (1) standard ECMA-48 Control Functions for Coded --- Character Sets (5th edition, 1991); or (2) in the case of 'saveCursorCode', --- 'restoreCursorCode', 'reportCursorPositionCode' and 'setTitleCode', the XTerm --- control sequence. --- --- The reference used for the codes in this module was --- <http://en.wikipedia.org/wiki/ANSI_escape_sequences>. --- --- The module "System.Console.ANSI" exports functions with the same names as --- those in this module. On some versions of Windows, the terminal in use may --- not be ANSI-capable. When that is the case, the same-named functions exported --- by module "System.Console.ANSI" return \"\", for the reasons set out in the --- documentation of that module. --- --- Consequently, if module "System.Console.ANSI" is also imported, this module --- is intended to be imported qualified, to avoid name clashes with those --- functions. For example: --- --- > import qualified System.Console.ANSI.Codes as ANSI --- -module System.Console.ANSI.Codes - ( - -- * Basic data types - module System.Console.ANSI.Types - - -- * Cursor movement by character - , cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode - - -- * Cursor movement by line - , cursorUpLineCode, cursorDownLineCode - - -- * Directly changing cursor position - , setCursorColumnCode, setCursorPositionCode - - -- * Saving, restoring and reporting cursor position - , saveCursorCode, restoreCursorCode, reportCursorPositionCode - - -- * Clearing parts of the screen - , clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode - , clearScreenCode, clearFromCursorToLineEndCode - , clearFromCursorToLineBeginningCode, clearLineCode - - -- * Scrolling the screen - , scrollPageUpCode, scrollPageDownCode - - -- * Select Graphic Rendition mode: colors and other whizzy stuff - , setSGRCode - - -- * Cursor visibilty changes - , hideCursorCode, showCursorCode - - -- * Changing the title - -- | Thanks to Brandon S. Allbery and Curt Sampson for pointing me in the - -- right direction on xterm title setting on haskell-cafe. The "0" - -- signifies that both the title and "icon" text should be set: i.e. the - -- text for the window in the Start bar (or similar) as well as that in - -- the actual window title. This is chosen for consistent behaviour - -- between Unixes and Windows. - , setTitleCode - - -- * Utilities - , colorToCode, csi, sgrToCode - ) where - -import Data.List (intersperse) - -import Data.Colour.SRGB (toSRGB24, RGB (..)) - -import System.Console.ANSI.Types - --- | 'csi' @parameters controlFunction@, where @parameters@ is a list of 'Int', --- returns the control sequence comprising the control function CONTROL --- SEQUENCE INTRODUCER (CSI) followed by the parameter(s) (separated by \';\') --- and ending with the @controlFunction@ character(s) that identifies the --- control function. -csi :: [Int] -- ^ List of parameters for the control sequence - -> String -- ^ Character(s) that identify the control function - -> String -csi args code = "\ESC[" ++ concat (intersperse ";" (map show args)) ++ code - --- | 'colorToCode' @color@ returns the 0-based index of the color (one of the --- eight colors in the standard). -colorToCode :: Color -> Int -colorToCode color = case color of - Black -> 0 - Red -> 1 - Green -> 2 - Yellow -> 3 - Blue -> 4 - Magenta -> 5 - Cyan -> 6 - White -> 7 - --- | 'sgrToCode' @sgr@ returns the parameter of the SELECT GRAPHIC RENDITION --- (SGR) aspect identified by @sgr@. -sgrToCode :: SGR -- ^ The SGR aspect - -> [Int] -sgrToCode sgr = case sgr of - Reset -> [0] - SetConsoleIntensity intensity -> case intensity of - BoldIntensity -> [1] - FaintIntensity -> [2] - NormalIntensity -> [22] - SetItalicized True -> [3] - SetItalicized False -> [23] - SetUnderlining underlining -> case underlining of - SingleUnderline -> [4] - DoubleUnderline -> [21] - NoUnderline -> [24] - SetBlinkSpeed blink_speed -> case blink_speed of - SlowBlink -> [5] - RapidBlink -> [6] - NoBlink -> [25] - SetVisible False -> [8] - SetVisible True -> [28] - SetSwapForegroundBackground True -> [7] - SetSwapForegroundBackground False -> [27] - SetColor Foreground Dull color -> [30 + colorToCode color] - SetColor Foreground Vivid color -> [90 + colorToCode color] - SetColor Background Dull color -> [40 + colorToCode color] - SetColor Background Vivid color -> [100 + colorToCode color] - SetRGBColor Foreground color -> [38, 2] ++ toRGB color - SetRGBColor Background color -> [48, 2] ++ toRGB color - where - toRGB color = let RGB r g b = toSRGB24 color - in map fromIntegral [r, g, b] - -cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode - :: Int -- ^ Number of lines or characters to move - -> String -cursorUpCode n = csi [n] "A" -cursorDownCode n = csi [n] "B" -cursorForwardCode n = csi [n] "C" -cursorBackwardCode n = csi [n] "D" - -cursorDownLineCode, cursorUpLineCode :: Int -- ^ Number of lines to move - -> String -cursorDownLineCode n = csi [n] "E" -cursorUpLineCode n = csi [n] "F" - -setCursorColumnCode :: Int -- ^ 0-based column to move to - -> String -setCursorColumnCode n = csi [n + 1] "G" - -setCursorPositionCode :: Int -- ^ 0-based row to move to - -> Int -- ^ 0-based column to move to - -> String -setCursorPositionCode n m = csi [n + 1, m + 1] "H" - -saveCursorCode, restoreCursorCode, reportCursorPositionCode :: String -saveCursorCode = "\ESC7" -restoreCursorCode = "\ESC8" -reportCursorPositionCode = csi [] "6n" - -clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode, - clearScreenCode :: String -clearFromCursorToLineEndCode, clearFromCursorToLineBeginningCode, - clearLineCode :: String - -clearFromCursorToScreenEndCode = csi [0] "J" -clearFromCursorToScreenBeginningCode = csi [1] "J" -clearScreenCode = csi [2] "J" -clearFromCursorToLineEndCode = csi [0] "K" -clearFromCursorToLineBeginningCode = csi [1] "K" -clearLineCode = csi [2] "K" - -scrollPageUpCode, scrollPageDownCode :: Int -- ^ Number of lines to scroll by - -> String -scrollPageUpCode n = csi [n] "S" -scrollPageDownCode n = csi [n] "T" - -setSGRCode :: [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied - -- left to right. - -> String -setSGRCode sgrs = csi (concatMap sgrToCode sgrs) "m" - -hideCursorCode, showCursorCode :: String -hideCursorCode = csi [] "?25l" -showCursorCode = csi [] "?25h" - - --- | XTerm control sequence to set the Icon Name and Window Title. -setTitleCode :: String -- ^ New Icon Name and Window Title - -> String -setTitleCode title = "\ESC]0;" ++ filter (/= '\007') title ++ "\007"
− System/Console/ANSI/Example.hs
@@ -1,315 +0,0 @@-module Main - ( - main - ) where - -import Control.Concurrent (threadDelay) -import Control.Monad (forM_) -import System.IO (hFlush, stdout) - -import System.Console.ANSI - -examples :: [IO ()] -examples = [ cursorMovementExample - , lineChangeExample - , setCursorPositionExample - , saveRestoreCursorExample - , clearExample - , scrollExample - , sgrExample - , cursorVisibilityExample - , titleExample - , getCursorPositionExample - ] - -main :: IO () -main = mapM_ (\example -> resetScreen >> example) examples - --- Annex D to Standard ECMA-48 (5th Ed, 1991) identifies that the representation --- of an erased state is implementation-dependent. There may or may not be a --- distinction between a character position in the erased state and one imaging --- SPACE. Consequently, to reset the screen, the default graphic rendition must --- be selected (setSGR [Reset]) before all character positions are put into the --- erased state (clearScreen). -resetScreen :: IO () -resetScreen = setSGR [Reset] >> clearScreen >> setCursorPosition 0 0 - -pause :: IO () -pause = do - hFlush stdout - -- 1 second pause - threadDelay 1000000 - -cursorMovementExample :: IO () -cursorMovementExample = do - putStrLn "Line One" - putStr "Line Two" - pause - -- Line One - -- Line Two - - cursorUp 1 - putStr " - Extras" - pause - -- Line One - Extras - -- Line Two - - cursorBackward 2 - putStr "zz" - pause - -- Line One - Extrzz - -- Line Two - - cursorForward 2 - putStr "- And More" - pause - -- Line One - Extrzz - And More - -- Line Two - - cursorDown 1 - putStr "Disconnected" - pause - -- Line One - Extrzz - And More - -- Line Two Disconnected - -lineChangeExample :: IO () -lineChangeExample = do - putStrLn "Line One" - putStr "Line Two" - pause - -- Line One - -- Line Two - - cursorUpLine 1 - putStr "New Line One" - pause - -- New Line One - -- Line Two - - cursorDownLine 1 - putStr "New Line Two" - pause - -- New Line One - -- New Line Two - -setCursorPositionExample :: IO () -setCursorPositionExample = do - putStrLn "Line One" - putStrLn "Line Two" - pause - -- Line One - -- Line Two - - setCursorPosition 0 5 - putStr "Foo" - pause - -- Line Foo - -- Line Two - - setCursorPosition 1 5 - putStr "Bar" - pause - -- Line Foo - -- Line Bar - - setCursorColumn 1 - putStr "oaf" - pause - -- Line Foo - -- Loaf Bar - -saveRestoreCursorExample :: IO () -saveRestoreCursorExample = do - putStr "Start sentence ..." - pause - -- Start sentence ... - - saveCursor - setCursorPosition 2 3 - putStr "SPLASH!" - pause - -- Start sentence ... - -- - -- SPLASH! - - restoreCursor - putStr " end sentence, uninterrupted." - pause - -- Start sentence ... end sentence, uninterrupted - -- - -- SPLASH! - -clearExample :: IO () -clearExample = do - putStrLn "Line One" - putStrLn "Line Two" - pause - -- Line One - -- Line Two - - setCursorPosition 0 4 - clearFromCursorToScreenEnd - pause - -- Line - - resetScreen - putStrLn "Line One" - putStrLn "Line Two" - pause - -- Line One - -- Line Two - - setCursorPosition 1 4 - clearFromCursorToScreenBeginning - pause - -- - -- Two - - resetScreen - putStrLn "Line One" - putStrLn "Line Two" - pause - -- Line One - -- Line Two - - setCursorPosition 0 4 - clearFromCursorToLineEnd - pause - -- Line - -- Line Two - - setCursorPosition 1 4 - clearFromCursorToLineBeginning - pause - -- Line - -- Two - - clearLine - pause - -- Line - - clearScreen - pause - -- - -scrollExample :: IO () -scrollExample = do - putStrLn "Line One" - putStrLn "Line Two" - putStrLn "Line Three" - pause - -- Line One - -- Line Two - -- Line Three - - scrollPageDown 2 - pause - -- - -- - -- Line One - -- Line Two - -- Line Three - - scrollPageUp 3 - pause - -- Line Two - -- Line Three - -sgrExample :: IO () -sgrExample = do - let colors = enumFromTo minBound maxBound :: [Color] - forM_ [Foreground, Background] $ \layer -> do - forM_ [Dull, Vivid] $ \intensity -> do - resetScreen - forM_ colors $ \color -> do - setSGR [Reset] - setSGR [SetColor layer intensity color] - putStrLn (show color) - pause - -- All the colors, 4 times in sequence - - let named_styles = [ (SetConsoleIntensity BoldIntensity, "Bold") - , (SetConsoleIntensity FaintIntensity, "Faint") - , (SetConsoleIntensity NormalIntensity, "Normal") - , (SetItalicized True, "Italic") - , (SetItalicized False, "No Italics") - , (SetUnderlining SingleUnderline, "Single Underline") - , (SetUnderlining DoubleUnderline, "Double Underline") - , (SetUnderlining NoUnderline, "No Underline") - , (SetBlinkSpeed SlowBlink, "Slow Blink") - , (SetBlinkSpeed RapidBlink, "Rapid Blink") - , (SetBlinkSpeed NoBlink, "No Blink") - , (SetVisible False, "Conceal") - , (SetVisible True, "Reveal") - ] - forM_ named_styles $ \(style, name) -> do - resetScreen - setSGR [style] - putStrLn name - pause - -- Text describing a style displayed in that style in sequence - - setSGR [SetColor Foreground Vivid Red] - setSGR [SetColor Background Vivid Blue] - - clearScreen >> setCursorPosition 0 0 - setSGR [SetSwapForegroundBackground False] - putStr "Red-On-Blue" - pause - -- Red-On-Blue - - clearScreen >> setCursorPosition 0 0 - setSGR [SetSwapForegroundBackground True] - putStr "Blue-On-Red" - pause - -- Blue-On-Red - -cursorVisibilityExample :: IO () -cursorVisibilityExample = do - putStr "Cursor Demo" - pause - -- Cursor Demo| - - hideCursor - pause - -- Cursor Demo - - showCursor - pause - -- Cursor Demo| - -titleExample :: IO () -titleExample = do - putStr "Title Demo" - pause - -- ~/foo/ - ansi-terminal-ex - 83x70 - ------------------------------------ - -- Title Demo - - setTitle "Yup, I'm a new title!" - pause - -- Yup, I'm a new title! - ansi-terminal-ex - 83x70 - --------------------------------------------------- - -- Title Demo - -getCursorPositionExample :: IO () -getCursorPositionExample = do - putStrLn " 11111111112222222222" - putStrLn "12345678901234567890123456789" - putStr "Report cursor position here:" - pause - -- 11111111112222222222 - -- 12345678901234567890123456789 - -- Report cursor position here:| - result <- getCursorPosition - putStrLn " (3rd row, 29th column) to stdin, as CSI 3 ; 29 R.\n" - case result of - Just (row, col) -> putStrLn $ "The cursor was at row number " ++ - show row ++ " and column number " ++ show col ++ ".\n" - Nothing -> putStrLn "Error: unable to get the cursor position\n" - pause - -- 11111111112222222222 - -- 12345678901234567890123456789 - -- Report cursor position here: (3rd row, 29th column) to stdin, as CSI 3 ; 29 R. - -- - -- The cursor was at row number 3 and column number 29.
− System/Console/ANSI/Types.hs
@@ -1,71 +0,0 @@--- | Types used to represent SELECT GRAPHIC RENDITION (SGR) aspects. -module System.Console.ANSI.Types - ( - SGR (..) - , ConsoleLayer (..) - , Color (..) - , ColorIntensity (..) - , ConsoleIntensity (..) - , Underlining (..) - , BlinkSpeed (..) - ) where - -import Data.Ix (Ix) - -import Data.Colour (Colour) - --- | ANSI colors: come in various intensities, which are controlled by --- 'ColorIntensity' -data Color = Black - | Red - | Green - | Yellow - | Blue - | Magenta - | Cyan - | White - deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) - --- | ANSI colors come in two intensities -data ColorIntensity = Dull - | Vivid - deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) - --- | ANSI colors can be set on two different layers -data ConsoleLayer = Foreground - | Background - deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) - --- | ANSI blink speeds: values other than 'NoBlink' are not widely supported -data BlinkSpeed = SlowBlink -- ^ Less than 150 blinks per minute - | RapidBlink -- ^ More than 150 blinks per minute - | NoBlink - deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) - --- | ANSI text underlining -data Underlining = SingleUnderline - | DoubleUnderline -- ^ Not widely supported - | NoUnderline - deriving (Eq, Ord, Bounded ,Enum, Show, Read, Ix) - --- | ANSI general console intensity: usually treated as setting the font style --- (e.g. 'BoldIntensity' causes text to be bold) -data ConsoleIntensity = BoldIntensity - | FaintIntensity -- ^ Not widely supported: sometimes - -- treated as concealing text - | NormalIntensity - deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) - --- | ANSI Select Graphic Rendition command -data SGR = Reset - | SetConsoleIntensity !ConsoleIntensity - | SetItalicized !Bool -- ^ Not widely supported: sometimes treated as - -- swapping foreground and background - | SetUnderlining !Underlining - | SetBlinkSpeed !BlinkSpeed - | SetVisible !Bool -- ^ Not widely supported - | SetSwapForegroundBackground !Bool - | SetColor !ConsoleLayer !ColorIntensity !Color - | SetRGBColor !ConsoleLayer !(Colour Float) -- ^ Supported from Windows 10 - -- Creators Update - deriving (Eq, Show, Read)
− System/Console/ANSI/Unix.hs
@@ -1,100 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} - -module System.Console.ANSI.Unix - ( --- This file contains code that is common to modules --- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module --- exports and the associated Haddock documentation. -#include "Exports-Include.hs" - ) where - -import Control.Exception.Base (bracket) -import System.IO (BufferMode (..), Handle, hFlush, hGetBuffering, hGetEcho, - hIsTerminalDevice, hPutStr, hSetBuffering, hSetEcho, stdin, stdout) -import Text.ParserCombinators.ReadP (readP_to_S) - -import System.Console.ANSI.Codes -import System.Console.ANSI.Types - --- This file contains code that is common to modules System.Console.ANSI.Unix, --- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as --- type signatures and the definition of functions specific to stdout in terms --- of the corresponding more general functions, inclduding the related Haddock --- documentation. -#include "Common-Include.hs" --- This file contains code that is common save that different code is required --- in the case of the module System.Console.ANSI.Windows.Emulator (see the file --- Common-Include-Emulator.hs in respect of the latter). -#include "Common-Include-Enabled.hs" - -hCursorUp h n = hPutStr h $ cursorUpCode n -hCursorDown h n = hPutStr h $ cursorDownCode n -hCursorForward h n = hPutStr h $ cursorForwardCode n -hCursorBackward h n = hPutStr h $ cursorBackwardCode n - -hCursorDownLine h n = hPutStr h $ cursorDownLineCode n -hCursorUpLine h n = hPutStr h $ cursorUpLineCode n - -hSetCursorColumn h n = hPutStr h $ setCursorColumnCode n -hSetCursorPosition h n m = hPutStr h $ setCursorPositionCode n m - -hSaveCursor h = hPutStr h saveCursorCode -hRestoreCursor h = hPutStr h restoreCursorCode -hReportCursorPosition h = hPutStr h reportCursorPositionCode - -hClearFromCursorToScreenEnd h = hPutStr h clearFromCursorToScreenEndCode -hClearFromCursorToScreenBeginning h - = hPutStr h clearFromCursorToScreenBeginningCode -hClearScreen h = hPutStr h clearScreenCode - -hClearFromCursorToLineEnd h = hPutStr h clearFromCursorToLineEndCode -hClearFromCursorToLineBeginning h = hPutStr h clearFromCursorToLineBeginningCode -hClearLine h = hPutStr h clearLineCode - -hScrollPageUp h n = hPutStr h $ scrollPageUpCode n -hScrollPageDown h n = hPutStr h $ scrollPageDownCode n - -hSetSGR h sgrs = hPutStr h $ setSGRCode sgrs - -hHideCursor h = hPutStr h hideCursorCode -hShowCursor h = hPutStr h showCursorCode - -hSetTitle h title = hPutStr h $ setTitleCode title - --- getReportedCursorPosition :: IO String --- (See Common-Include.hs for Haddock documentation) -getReportedCursorPosition = bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do - hSetEcho stdin False -- Turn echo off - get - where - get = do - c <- getChar - if c == '\ESC' - then get' [c] - else return [c] -- If the first character is not the expected \ESC then - -- give up. This provides a modicom of protection against - -- unexpected data in the input stream. - get' s = do - c <- getChar - if c /= 'R' - then get' (c:s) -- Continue building the list, until the expected 'R' - -- character is obtained. Build the list in reverse order, - -- in order to avoid O(n^2) complexity. - else return $ reverse (c:s) -- Reverse the order of the built list. - --- getCursorPosition :: IO (Maybe (Int, Int)) --- (See Common-Include.hs for Haddock documentation) -getCursorPosition = do - input <- bracket (hGetBuffering stdin) (hSetBuffering stdin) $ \_ -> do - hSetBuffering stdin NoBuffering -- set no buffering (the contents of the - -- buffer will be discarded, so this needs - -- to be done before the cursor positon is - -- emitted) - reportCursorPosition - hFlush stdout -- ensure the report cursor position code is sent to the - -- operating system - getReportedCursorPosition - case readP_to_S cursorPosition input of - [] -> return Nothing - [((row, col),_)] -> return $ Just (row, col) - (_:_) -> return Nothing
− System/Console/ANSI/Windows.hs
@@ -1,187 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} - -module System.Console.ANSI.Windows - ( --- This file contains code that is common to modules --- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module --- exports and the associated Haddock documentation. -#include "Exports-Include.hs" - ) where - -import System.IO (Handle, hIsTerminalDevice, stdout) - -import System.Console.ANSI.Types -import qualified System.Console.ANSI.Unix as U -import System.Console.ANSI.Windows.Detect (ANSIEnabledStatus (..), - ConsoleDefaultState (..), isANSIEnabled) -import qualified System.Console.ANSI.Windows.Emulator as E - --- This file contains code that is common to modules System.Console.ANSI.Unix, --- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as --- type signatures and the definition of functions specific to stdout in terms --- of the corresponding more general functions, inclduding the related Haddock --- documentation. -#include "Common-Include.hs" --- This file contains code that is common save that different code is required --- in the case of the module System.Console.ANSI.Windows.Emulator (see the file --- Common-Include-Emulator.hs in respect of the latter). -#include "Common-Include-Enabled.hs" - --- | A helper function which returns the native or emulated version, depending --- on `isANSIEnabled`. -nativeOrEmulated :: a -> a -> a -nativeOrEmulated native emulated = case isANSIEnabled of - ANSIEnabled -> native - NotANSIEnabled _ -> emulated - --- | A helper function which returns the native or emulated version, depending --- on `isANSIEnabled`, where the emulator uses the default console state. -nativeOrEmulatedWithDefault :: a -> (ConsoleDefaultState -> a) -> a -nativeOrEmulatedWithDefault native emulated = case isANSIEnabled of - ANSIEnabled -> native - NotANSIEnabled def -> emulated def - - --- * Cursor movement by character -hCursorUp = nativeOrEmulated U.hCursorUp E.hCursorUp -hCursorDown = nativeOrEmulated U.hCursorDown E.hCursorDown -hCursorForward = nativeOrEmulated U.hCursorForward E.hCursorForward -hCursorBackward = nativeOrEmulated U.hCursorBackward E.hCursorBackward - -cursorUpCode :: Int -> String -cursorUpCode = nativeOrEmulated U.cursorUpCode E.cursorUpCode - -cursorDownCode :: Int -> String -cursorDownCode = nativeOrEmulated U.cursorDownCode E.cursorDownCode - -cursorForwardCode :: Int -> String -cursorForwardCode = nativeOrEmulated U.cursorForwardCode E.cursorForwardCode - -cursorBackwardCode :: Int -> String -cursorBackwardCode = nativeOrEmulated U.cursorBackwardCode E.cursorBackwardCode - --- * Cursor movement by line -hCursorUpLine = nativeOrEmulated U.hCursorUpLine E.hCursorUpLine -hCursorDownLine = nativeOrEmulated U.hCursorDownLine E.hCursorDownLine - -cursorUpLineCode :: Int -> String -cursorUpLineCode = nativeOrEmulated U.cursorUpLineCode E.cursorUpLineCode - -cursorDownLineCode :: Int -> String -cursorDownLineCode = nativeOrEmulated U.cursorDownLineCode E.cursorDownLineCode - --- * Directly changing cursor position -hSetCursorColumn = nativeOrEmulated U.hSetCursorColumn E.hSetCursorColumn - -setCursorColumnCode :: Int -> String -setCursorColumnCode = nativeOrEmulated - U.setCursorColumnCode E.setCursorColumnCode - -hSetCursorPosition = nativeOrEmulated U.hSetCursorPosition E.hSetCursorPosition - -setCursorPositionCode :: Int -> Int -> String -setCursorPositionCode = nativeOrEmulated - U.setCursorPositionCode E.setCursorPositionCode - --- * Saving, restoring and reporting cursor position -hSaveCursor = nativeOrEmulated U.hSaveCursor E.hSaveCursor -hRestoreCursor = nativeOrEmulated U.hRestoreCursor E.hRestoreCursor -hReportCursorPosition = nativeOrEmulated - U.hReportCursorPosition E.hReportCursorPosition - -saveCursorCode :: String -saveCursorCode = nativeOrEmulated U.saveCursorCode E.saveCursorCode - -restoreCursorCode :: String -restoreCursorCode = nativeOrEmulated U.restoreCursorCode E.restoreCursorCode - -reportCursorPositionCode :: String -reportCursorPositionCode = nativeOrEmulated - U.reportCursorPositionCode E.reportCursorPositionCode - --- * Clearing parts of the screen -hClearFromCursorToScreenEnd = nativeOrEmulatedWithDefault - U.hClearFromCursorToScreenEnd E.hClearFromCursorToScreenEnd -hClearFromCursorToScreenBeginning = nativeOrEmulatedWithDefault - U.hClearFromCursorToScreenBeginning E.hClearFromCursorToScreenBeginning -hClearScreen = nativeOrEmulatedWithDefault U.hClearScreen E.hClearScreen - -clearFromCursorToScreenEndCode :: String -clearFromCursorToScreenEndCode = nativeOrEmulated - U.clearFromCursorToScreenEndCode E.clearFromCursorToScreenEndCode - -clearFromCursorToScreenBeginningCode :: String -clearFromCursorToScreenBeginningCode = nativeOrEmulated - U.clearFromCursorToScreenBeginningCode E.clearFromCursorToScreenBeginningCode - -clearScreenCode :: String -clearScreenCode = nativeOrEmulated U.clearScreenCode E.clearScreenCode - -hClearFromCursorToLineEnd = nativeOrEmulatedWithDefault - U.hClearFromCursorToLineEnd E.hClearFromCursorToLineEnd -hClearFromCursorToLineBeginning = nativeOrEmulatedWithDefault - U.hClearFromCursorToLineBeginning E.hClearFromCursorToLineBeginning -hClearLine = nativeOrEmulatedWithDefault U.hClearLine E.hClearLine - -clearFromCursorToLineEndCode :: String -clearFromCursorToLineEndCode = nativeOrEmulated - U.clearFromCursorToLineEndCode E.clearFromCursorToLineEndCode - -clearFromCursorToLineBeginningCode :: String -clearFromCursorToLineBeginningCode = nativeOrEmulated - U.clearFromCursorToLineBeginningCode E.clearFromCursorToLineBeginningCode - -clearLineCode :: String -clearLineCode = nativeOrEmulated U.clearLineCode E.clearLineCode - --- * Scrolling the screen -hScrollPageUp = nativeOrEmulatedWithDefault U.hScrollPageUp E.hScrollPageUp -hScrollPageDown = nativeOrEmulatedWithDefault U.hScrollPageDown E.hScrollPageDown - -scrollPageUpCode :: Int -> String -scrollPageUpCode = nativeOrEmulated U.scrollPageUpCode E.scrollPageUpCode - -scrollPageDownCode :: Int -> String -scrollPageDownCode = nativeOrEmulated U.scrollPageDownCode E.scrollPageDownCode - --- * Select Graphic Rendition mode: colors and other whizzy stuff --- --- The following SGR codes are NOT implemented by Windows 10 Threshold 2: --- 2 SetConsoleIntensity FaintIntensity --- 3 SetItalicized True --- 5 SetBlinkSpeed SlowBlink --- 6 SetBlinkSpeed RapidBlink --- 8 SetVisible False --- 21 SetUnderlining DoubleUnderline --- 23 SetItalicized False --- 25 SetBlinkSpeed NoBlink --- 28 SetVisible True - -hSetSGR = nativeOrEmulatedWithDefault U.hSetSGR E.hSetSGR - -setSGRCode :: [SGR] -> String -setSGRCode = nativeOrEmulated U.setSGRCode E.setSGRCode - --- * Cursor visibilty changes -hHideCursor = nativeOrEmulated U.hHideCursor E.hHideCursor -hShowCursor = nativeOrEmulated U.hShowCursor E.hShowCursor - -hideCursorCode :: String -hideCursorCode = nativeOrEmulated U.hideCursorCode E.hideCursorCode - -showCursorCode :: String -showCursorCode = nativeOrEmulated U.showCursorCode E.showCursorCode - --- * Changing the title -hSetTitle = nativeOrEmulated U.hSetTitle E.hSetTitle - -setTitleCode :: String -> String -setTitleCode = nativeOrEmulated U.setTitleCode E.setTitleCode - --- getReportedCursorPosition :: IO String --- (See Common-Include.hs for Haddock documentation) -getReportedCursorPosition = E.getReportedCursorPosition - --- getCursorPosition :: IO (Maybe (Int, Int)) --- (See Common-Include.hs for Haddock documentation) -getCursorPosition = E.getCursorPosition
− System/Console/ANSI/Windows/Detect.hs
@@ -1,123 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} - -module System.Console.ANSI.Windows.Detect - ( - ANSIEnabledStatus (..) - , ConsoleDefaultState (..) - , isANSIEnabled - ) where - -import Control.Exception (SomeException(..), onException, throwIO, try) -import Data.Bits ((.&.), (.|.)) --- 'lookupEnv' is not available until base-4.6.0.0 (GHC 7.6.1) -import System.Environment.Compat (lookupEnv) -import System.IO.Unsafe (unsafePerformIO) - -import System.Console.ANSI.Windows.Foreign (bACKGROUND_INTENSE_WHITE, - ConsoleException(..), CONSOLE_SCREEN_BUFFER_INFO (..), DWORD, - eNABLE_VIRTUAL_TERMINAL_PROCESSING, fOREGROUND_INTENSE_WHITE, - getConsoleMode, getConsoleScreenBufferInfo, getStdHandle, HANDLE, - iNVALID_HANDLE_VALUE, nullHANDLE, setConsoleMode, sTD_OUTPUT_HANDLE, WORD) - --- | The default state of the console -data ConsoleDefaultState = ConsoleDefaultState - { defaultForegroundAttributes :: WORD -- ^ Foreground attributes - , defaultBackgroundAttributes :: WORD -- ^ Background attributes - } deriving (Eq, Show) - --- | The status of the console as regards it being ANSI-enabled or not --- ANSI-enabled. -data ANSIEnabledStatus - = ANSIEnabled -- ^ ANSI-enabled - | NotANSIEnabled ConsoleDefaultState -- ^ Not ANSI-enabled (including the - -- state of the console when that status - -- was determined) - deriving (Eq, Show) - --- | This function assumes that once it is first established whether or not the --- Windows console is ANSI-enabled, that will not change. If the console is not --- ANSI-enabled, the state of the console is considered to be its default state. -{-# NOINLINE isANSIEnabled #-} -isANSIEnabled :: ANSIEnabledStatus -isANSIEnabled = unsafePerformIO $ do - e <- safeIsANSIEnabled - if e - then return ANSIEnabled - else do - hOut <- getValidStdHandle sTD_OUTPUT_HANDLE - info <- getConsoleScreenBufferInfo hOut - let attributes = csbi_attributes info - fgAttributes = attributes .&. fOREGROUND_INTENSE_WHITE - bgAttributes = attributes .&. bACKGROUND_INTENSE_WHITE - consoleDefaultState = ConsoleDefaultState - { defaultForegroundAttributes = fgAttributes - , defaultBackgroundAttributes = bgAttributes } - return $ NotANSIEnabled consoleDefaultState - --- This function takes the following approach. If the environment variable --- APPVEYOR exists and is set to 'True', it assumes the code is running in the --- Appveyor build environment and that the build console is ANSI-enabled. --- Otherwise, if the environment variable TERM exists and is not set to 'dumb' --- or 'msys' (see below), it assumes the console is ANSI-enabled. Otherwise, it --- tries to get a valid standard output handle. If that does not fail, it --- tried to get a ConHost console mode. If that fails, it assumes that the --- handle is ANSI-enabled. Otherwise, it tries to enable virtual terminal --- processing. If that fails, it assumes the console is not ANSI-enabled. --- --- In Git Shell, if Command Prompt or PowerShell are used, the environment --- variable TERM is set to 'msys'. If 'Git Bash' (mintty) is used, TERM is set --- to 'xterm' (by default). -safeIsANSIEnabled :: IO Bool -safeIsANSIEnabled = do - appveyor <- lookupEnv "APPVEYOR" - case appveyor of - Just "True" -> return True - _ -> do - term <- lookupEnv "TERM" - case term of - Just "dumb" -> return False - Just "msys" -> doesEnableANSIOutSucceed - Just _ -> return True - Nothing -> doesEnableANSIOutSucceed - --- This function returns whether or not an attempt to enable virtual terminal --- processing succeeded, or was deemed to succeed, in the IO monad. -doesEnableANSIOutSucceed :: IO Bool -doesEnableANSIOutSucceed = do - result <- try enableANSIOut :: IO (Either SomeException ()) - case result of - Left _ -> return False - Right () -> return True - --- This function tries to get a valid handle on the standard output and throws --- an exception if it cannot. It then tries to get a ConHost console mode for --- that handle. If it can not, it assumes that the standard output handle is --- ANSI-enabled. If it can, it tries to enable virtual terminal processing and --- throws an exception if it can not. -enableANSIOut :: IO () -enableANSIOut = do - hOut <- getValidStdHandle sTD_OUTPUT_HANDLE - result <- conHostConsoleMode hOut - case result of - Nothing -> return () -- assume hOut is ANSI-enabled - Just mOut -> do - let mOut' = mOut .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING - setConsoleMode hOut mOut' - --- This function tries to get a valid standard handle and throws an exception if --- it cannot. -getValidStdHandle :: DWORD -> IO HANDLE -getValidStdHandle nStdHandle = do - h <- getStdHandle nStdHandle - if h == iNVALID_HANDLE_VALUE || h == nullHANDLE - then throwIO $ ConsoleException 6 -- Invalid handle or no handle - else return h - --- The function tries to get a ConHost console mode from a handle that is --- assumed to be a valid standard handle (see getValidStdHandle) -conHostConsoleMode :: HANDLE -> IO (Maybe DWORD) -conHostConsoleMode h = do - result <- try (getConsoleMode h) :: IO (Either SomeException DWORD) - case result of - Left _ -> return Nothing - Right mode -> return (Just mode)
− System/Console/ANSI/Windows/Emulator.hs
@@ -1,464 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} - -module System.Console.ANSI.Windows.Emulator - ( -#include "Exports-Include.hs" - ) where - -import Control.Exception (catch, catchJust, IOException) -import Control.Monad (forM_, unless) -import Data.Bits ((.&.), (.|.), complement, shiftL, shiftR) -import Data.IORef (IORef, newIORef, readIORef, writeIORef) -import Data.List (foldl', minimumBy) -import Data.Maybe (mapMaybe) -import qualified Data.Map.Strict as Map (Map, empty, insert, lookup) -import System.IO (Handle, hFlush, hIsTerminalDevice, stdin, stdout) -import System.IO.Unsafe (unsafePerformIO) -import Text.ParserCombinators.ReadP (readP_to_S) - -import Data.Colour (Colour) -import Data.Colour.Names (black, blue, cyan, green, grey, lime, magenta, maroon, - navy, olive, purple, red, silver, teal, white, yellow) -import Data.Colour.SRGB (RGB (..), toSRGB) - -import System.Console.ANSI.Types -import qualified System.Console.ANSI.Unix as Unix -import System.Console.ANSI.Windows.Detect -import System.Console.ANSI.Windows.Emulator.Codes -import System.Console.ANSI.Windows.Foreign - --- This file contains code that is common to modules System.Console.ANSI.Unix, --- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as --- type signatures and the definition of functions specific to stdout in terms --- of the corresponding more general functions, inclduding the related Haddock --- documentation. -#include "Common-Include.hs" --- This file contains code that is required in the case of the module --- System.Console.ANSI.Windows.Emulator and differs from the common code in --- file Common-Include-Enabled.hs. -#include "Common-Include-Emulator.hs" - -withHandle :: Handle -> (HANDLE -> IO a) -> IO a -withHandle handle action = do - -- It's VERY IMPORTANT that we flush before issuing any sort of Windows API - -- call to change the console because on Windows the arrival of - -- API-initiated state changes is not necessarily synchronised with that of - -- the text they are attempting to modify. - hFlush handle - withHandleToHANDLE handle action - --- Unfortunately, the emulator is not perfect. In particular, it has a tendency --- to die with exceptions about invalid handles when it is used with certain --- Windows consoles (e.g. mintty, terminator, or cygwin sshd). --- --- This happens because in those environments the stdout family of handles are --- not actually associated with a real console. --- --- My observation is that every time I've seen this in practice, the handle we --- have instead of the actual console handle is there so that the terminal --- supports ANSI escape codes. So 99% of the time, the correct thing to do is --- just to fall back on the Unix module to output the ANSI codes and hope for --- the best. -emulatorFallback :: IO a -> IO a -> IO a -emulatorFallback fallback first_try - = catchJust invalidHandle first_try (const fallback) - where - invalidHandle (ConsoleException 6) = Just () -- 6 is the Windows error code - -- for invalid handles - invalidHandle (_) = Nothing - -adjustCursorPosition :: HANDLE - -> (SHORT -> SHORT -> SHORT) - -> (SHORT -> SHORT -> SHORT) - -> IO () -adjustCursorPosition handle change_x change_y = do - screen_buffer_info <- getConsoleScreenBufferInfo handle - let window = csbi_window screen_buffer_info - (COORD x y) = csbi_cursor_position screen_buffer_info - cursor_pos'= COORD (change_x (rect_left window) x) - (change_y (rect_top window) y) - setConsoleCursorPosition handle cursor_pos' - -hCursorUp h n - = emulatorFallback (Unix.hCursorUp h n) $ withHandle h $ - \handle -> adjustCursorPosition handle (\_ x -> x) - (\_ y -> y - fromIntegral n) -hCursorDown h n - = emulatorFallback (Unix.hCursorDown h n) $ withHandle h $ - \handle -> adjustCursorPosition handle (\_ x -> x) - (\_ y -> y + fromIntegral n) -hCursorForward h n - = emulatorFallback (Unix.hCursorForward h n) $ withHandle h $ - \handle -> adjustCursorPosition handle (\_ x -> x + fromIntegral n) - (\_ y -> y) -hCursorBackward h n - = emulatorFallback (Unix.hCursorBackward h n) $ withHandle h $ - \handle -> adjustCursorPosition handle (\_ x -> x - fromIntegral n) - (\_ y -> y) - -adjustLine :: HANDLE -> (SHORT -> SHORT -> SHORT) -> IO () -adjustLine handle change_y - = adjustCursorPosition handle (\window_left _ -> window_left) change_y - -hCursorDownLine h n - = emulatorFallback (Unix.hCursorDownLine h n) $ withHandle h $ - \handle -> adjustLine handle (\_ y -> y + fromIntegral n) - -hCursorUpLine h n - = emulatorFallback (Unix.hCursorUpLine h n) $ withHandle h $ - \handle -> adjustLine handle (\_ y -> y - fromIntegral n) - -hSetCursorColumn h x - = emulatorFallback (Unix.hSetCursorColumn h x) $ withHandle h $ - \handle -> adjustCursorPosition handle - (\window_left _ -> window_left + fromIntegral x) (\_ y -> y) - -hSetCursorPosition h y x - = emulatorFallback (Unix.hSetCursorPosition h y x) $ withHandle h $ - \handle -> adjustCursorPosition handle - (\window_left _ -> window_left + fromIntegral x) - (\window_top _ -> window_top + fromIntegral y) - -clearChar :: WCHAR -clearChar = charToWCHAR ' ' - --- | The 'clear' attribute is equated with the default background attributes. -clearAttribute :: ConsoleDefaultState -> WORD -clearAttribute = defaultBackgroundAttributes - -hClearScreenFraction - :: ConsoleDefaultState - -> HANDLE - -> (SMALL_RECT -> COORD -> (SHORT, SHORT, SHORT, SHORT, SHORT, SHORT)) - -> IO () -hClearScreenFraction cds handle fraction_finder = do - screen_buffer_info <- getConsoleScreenBufferInfo handle - let window = csbi_window screen_buffer_info - cursor_pos = csbi_cursor_position screen_buffer_info - (left, top, right, bottom, start_x, end_x) - = fraction_finder window cursor_pos - mapM_ (fill_line left top right bottom start_x end_x) [top .. bottom] - where - fill_line left top right bottom start_x end_x y = do - let left' = if y == top then start_x else left - right' = if y == bottom then end_x else right - fill_cursor_pos = COORD left' y - fill_length = fromIntegral $ right' - left' + 1 - _ <- fillConsoleOutputCharacter handle clearChar fill_length fill_cursor_pos - fillConsoleOutputAttribute handle (clearAttribute cds) fill_length - fill_cursor_pos - -hClearFromCursorToScreenEnd cds h - = emulatorFallback (Unix.hClearFromCursorToScreenEnd h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window cursor_pos = (left, top, right, bottom, start_x, right) - where - SMALL_RECT (COORD left _) (COORD right bottom) = window - COORD start_x top = cursor_pos - -hClearFromCursorToScreenBeginning cds h - = emulatorFallback (Unix.hClearFromCursorToScreenBeginning h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window cursor_pos = (left, top, right, bottom, left, end_x) - where - SMALL_RECT (COORD left top) (COORD right _) = window - COORD end_x bottom = cursor_pos - -hClearScreen cds h - = emulatorFallback (Unix.hClearScreen h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window _ = (left, top, right, bottom, left, right) - where - SMALL_RECT (COORD left top) (COORD right bottom) = window - -hClearFromCursorToLineEnd cds h - = emulatorFallback (Unix.hClearFromCursorToLineEnd h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window cursor_pos = (left, y, right, y, start_x, right) - where - SMALL_RECT (COORD left _) (COORD right _) = window - COORD start_x y = cursor_pos - -hClearFromCursorToLineBeginning cds h - = emulatorFallback (Unix.hClearFromCursorToLineBeginning h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window cursor_pos = (left, y, right, y, left, end_x) - where - SMALL_RECT (COORD left _) (COORD right _) = window - COORD end_x y = cursor_pos - -hClearLine cds h - = emulatorFallback (Unix.hClearLine h) $ withHandle h $ - \handle -> hClearScreenFraction cds handle go - where - go window cursor_pos = (left, y, right, y, left, right) - where - SMALL_RECT (COORD left _) (COORD right _) = window - COORD _ y = cursor_pos - -hScrollPage - :: ConsoleDefaultState -- ^ The default console state - -> HANDLE - -> Int - -> IO () -hScrollPage cds handle new_origin_y = do - screen_buffer_info <- getConsoleScreenBufferInfo handle - let fill = CHAR_INFO clearChar (clearAttribute cds) - window = csbi_window screen_buffer_info - origin = COORD (rect_left window) - (rect_top window + fromIntegral new_origin_y) - scrollConsoleScreenBuffer handle window Nothing origin fill - -hScrollPageUp cds h n - = emulatorFallback (Unix.hScrollPageUp h n) $ withHandle h $ - \handle -> hScrollPage cds handle (negate n) - -hScrollPageDown cds h n - = emulatorFallback (Unix.hScrollPageDown h n) $ withHandle h $ - \handle -> hScrollPage cds handle n - -{-# INLINE applyANSIColorToAttribute #-} -applyANSIColorToAttribute :: WORD -> WORD -> WORD -> Color -> WORD -> WORD -applyANSIColorToAttribute rED gREEN bLUE color attribute = case color of - Black -> attribute' - Red -> attribute' .|. rED - Green -> attribute' .|. gREEN - Yellow -> attribute' .|. rED .|. gREEN - Blue -> attribute' .|. bLUE - Magenta -> attribute' .|. rED .|. bLUE - Cyan -> attribute' .|. gREEN .|. bLUE - White -> attribute' .|. wHITE - where - wHITE = rED .|. gREEN .|. bLUE - attribute' = attribute .&. (complement wHITE) - -applyForegroundANSIColorToAttribute, applyBackgroundANSIColorToAttribute - :: Color - -> WORD - -> WORD -applyForegroundANSIColorToAttribute - = applyANSIColorToAttribute fOREGROUND_RED fOREGROUND_GREEN fOREGROUND_BLUE -applyBackgroundANSIColorToAttribute - = applyANSIColorToAttribute bACKGROUND_RED bACKGROUND_GREEN bACKGROUND_BLUE - -swapForegroundBackgroundColors :: WORD -> WORD -swapForegroundBackgroundColors attribute - = clean_attribute .|. foreground_attribute' .|. background_attribute' - where - foreground_attribute = attribute .&. fOREGROUND_INTENSE_WHITE - background_attribute = attribute .&. bACKGROUND_INTENSE_WHITE - clean_attribute = attribute .&. complement - (fOREGROUND_INTENSE_WHITE .|. bACKGROUND_INTENSE_WHITE) - foreground_attribute' = background_attribute `shiftR` 4 - background_attribute' = foreground_attribute `shiftL` 4 - -applyANSISGRToAttribute :: WORD -> SGR -> WORD -> WORD -applyANSISGRToAttribute def sgr attribute = case sgr of - Reset -> def - SetConsoleIntensity intensity -> case intensity of - BoldIntensity -> attribute .|. iNTENSITY - FaintIntensity -> attribute .&. (complement iNTENSITY) -- Not supported - NormalIntensity -> attribute .&. (complement iNTENSITY) - SetItalicized _ -> attribute -- Not supported - SetUnderlining underlining -> case underlining of - NoUnderline -> attribute .&. (complement cOMMON_LVB_UNDERSCORE) - _ -> attribute .|. cOMMON_LVB_UNDERSCORE -- Not supported, since - -- cOMMON_LVB_UNDERSCORE seems to have no effect - SetBlinkSpeed _ -> attribute -- Not supported - SetVisible _ -> attribute -- Not supported - -- The cOMMON_LVB_REVERSE_VIDEO doesn't actually appear to have any affect - -- on the colors being displayed, so the emulator just uses it to carry - -- information and implements the color-swapping behaviour itself. Bit of a - -- hack, I guess :-) - SetSwapForegroundBackground True -> - -- Check if the color-swapping flag is already set - if attribute .&. cOMMON_LVB_REVERSE_VIDEO /= 0 - then attribute - else swapForegroundBackgroundColors attribute .|. cOMMON_LVB_REVERSE_VIDEO - SetSwapForegroundBackground False -> - -- Check if the color-swapping flag is already not set - if attribute .&. cOMMON_LVB_REVERSE_VIDEO == 0 - then attribute - else swapForegroundBackgroundColors attribute .&. - (complement cOMMON_LVB_REVERSE_VIDEO) - SetColor Foreground Dull color -> applyForegroundANSIColorToAttribute color - (attribute .&. (complement fOREGROUND_INTENSITY)) - SetColor Foreground Vivid color -> applyForegroundANSIColorToAttribute color - (attribute .|. fOREGROUND_INTENSITY) - SetColor Background Dull color -> applyBackgroundANSIColorToAttribute color - (attribute .&. (complement bACKGROUND_INTENSITY)) - SetColor Background Vivid color -> applyBackgroundANSIColorToAttribute color - (attribute .|. bACKGROUND_INTENSITY) - SetRGBColor Foreground color -> - let (colorIntensity, aNSIColor) = toANSIColor color - attribute' = case colorIntensity of - Dull -> attribute .&. complement fOREGROUND_INTENSITY - Vivid -> attribute .|. fOREGROUND_INTENSITY - in applyForegroundANSIColorToAttribute aNSIColor attribute' - SetRGBColor Background color -> - let (colorIntensity, aNSIColor) = toANSIColor color - attribute' = case colorIntensity of - Dull -> attribute .&. complement bACKGROUND_INTENSITY - Vivid -> attribute .|. bACKGROUND_INTENSITY - in applyBackgroundANSIColorToAttribute aNSIColor attribute' - where - iNTENSITY = fOREGROUND_INTENSITY - -hSetSGR cds h sgr - = emulatorFallback (Unix.hSetSGR h sgr) $ withHandle h $ \handle -> do - screen_buffer_info <- getConsoleScreenBufferInfo handle - let attribute = csbi_attributes screen_buffer_info - def = defaultForegroundAttributes cds - .|. defaultBackgroundAttributes cds - attribute' = foldl' (flip $ applyANSISGRToAttribute def) attribute - -- make [] equivalent to [Reset], as documented - (if null sgr then [Reset] else sgr) - setConsoleTextAttribute handle attribute' - -hChangeCursorVisibility :: HANDLE -> Bool -> IO () -hChangeCursorVisibility handle cursor_visible = do - cursor_info <- getConsoleCursorInfo handle - setConsoleCursorInfo handle - (cursor_info { cci_cursor_visible = cursor_visible }) - -hHideCursor h - = emulatorFallback (Unix.hHideCursor h) $ withHandle h $ - \handle -> hChangeCursorVisibility handle False -hShowCursor h - = emulatorFallback (Unix.hShowCursor h) $ withHandle h $ - \handle -> hChangeCursorVisibility handle True - --- Windows only supports setting the terminal title on a process-wide basis, so --- for now we will assume that that is what the user intended. This will fail if --- they are sending the command over e.g. a network link... but that's not --- really what I'm designing for. -hSetTitle h title - = emulatorFallback (Unix.hSetTitle h title) $ - withTString title $ setConsoleTitle - -cursorPositionRef :: IORef (Map.Map HANDLE COORD) -{-# NOINLINE cursorPositionRef #-} -cursorPositionRef = unsafePerformIO $ newIORef Map.empty - -hSaveCursor h - = emulatorFallback (Unix.hSaveCursor h) $ withHandle h $ \handle -> do - screen_buffer_info <- getConsoleScreenBufferInfo handle - m <- readIORef cursorPositionRef - writeIORef cursorPositionRef - (Map.insert handle (csbi_cursor_position screen_buffer_info) m) - -hRestoreCursor h - = emulatorFallback (Unix.hRestoreCursor h) $ withHandle h $ \handle -> do - m <- readIORef cursorPositionRef - let result = Map.lookup handle m - forM_ result (setConsoleCursorPosition handle) - -hReportCursorPosition h - = emulatorFallback (Unix.hReportCursorPosition h) $ withHandle h $ - \handle -> do - result <- getConsoleScreenBufferInfo handle - let (COORD cx cy) = csbi_cursor_position result - window = csbi_window result - x = cx - rect_left window + 1 - y = cy - rect_top window + 1 - hIn <- getStdHandle sTD_INPUT_HANDLE - _ <- writeConsoleInput hIn $ keyPresses $ - "\ESC[" ++ show y ++ ";" ++ show x ++ "R" - return () - -keyPress :: Char -> [INPUT_RECORD] -keyPress c = [keyDown, keyUp] - where - keyDown = key True - keyUp = key False - c' = UnicodeAsciiChar $ charToWCHAR c - key isDown = INPUT_RECORD kEY_EVENT $ - InputKeyEvent (KEY_EVENT_RECORD isDown 1 0 0 c' 0) - -keyPresses :: String -> [INPUT_RECORD] -keyPresses = concatMap keyPress - -aNSIColors :: [((ColorIntensity, Color), Colour Float)] -aNSIColors = [ ((Dull, Black), black) - , ((Dull, Blue), navy) - , ((Dull, Green), green) - , ((Dull, Cyan), teal) - , ((Dull, Red), maroon) - , ((Dull, Magenta), purple) - , ((Dull, Yellow), olive) - , ((Dull, White), silver) - , ((Vivid, Black), grey) - , ((Vivid, Blue), blue) - , ((Vivid, Green), lime) - , ((Vivid, Cyan), cyan) - , ((Vivid, Red), red) - , ((Vivid, Magenta), magenta) - , ((Vivid, Yellow), yellow) - , ((Vivid, White), white) ] - -toANSIColor :: Colour Float -> (ColorIntensity, Color) -toANSIColor color = fst $ minimumBy order aNSIColors - where - RGB r g b = toSRGB color - order (_, c1) (_, c2) = compare (dist c1) (dist c2) - dist c = let RGB r' g' b' = toSRGB c - dr = r' - r - dg = g' - g - db = b' - b - in dr * dr + dg * dg + db * db - --- getReportedCursorPosition :: IO String --- (See Common-Include.hs for Haddock documentation) -getReportedCursorPosition - = catch getReportedCursorPosition' getCPExceptionHandler - where - getReportedCursorPosition' = withHandleToHANDLE stdin action - where - action hdl = do - n <- getNumberOfConsoleInputEvents hdl - if n == 0 - then return "" - else do - es <- readConsoleInput hdl n - return $ stringFromInputEvents es - stringFromInputEvents = cWcharsToChars . wCharsFromInputEvents - wCharsFromInputEvents = mapMaybe wCharFromInputEvent - wCharFromInputEvent e = if isKeyDownEvent - then Just (unicodeAsciiChar $ keyEventChar keyEventRecord) - else Nothing - where - eventType = inputEventType e - InputKeyEvent keyEventRecord = inputEvent e - isKeyDown = keyEventKeyDown keyEventRecord - isKeyDownEvent = eventType == 1 && isKeyDown - --- getCursorPosition :: IO (Maybe (Int, Int)) --- (See Common-Include.hs for Haddock documentation) -getCursorPosition = catch getCursorPosition' getCPExceptionHandler - where - getCursorPosition' = do - withHandleToHANDLE stdin flush -- Flush the console input buffer - reportCursorPosition - hFlush stdout -- ensure the report cursor position code is sent to the - -- operating system - input <- getReportedCursorPosition - case readP_to_S cursorPosition input of - [] -> return Nothing - [((row, col),_)] -> return $ Just (row, col) - (_:_) -> return Nothing - where - flush hdl = do - n <- getNumberOfConsoleInputEvents hdl - unless (n == 0) (void $ readConsoleInput hdl n) - -getCPExceptionHandler :: IOException -> IO a -getCPExceptionHandler e = error msg - where - msg = "Error: " ++ show e ++ "\nThis error may be avoided by using a " ++ - "console based on the Win32 console of the Windows API, such as " ++ - "Command Prompt or PowerShell."
− System/Console/ANSI/Windows/Emulator/Codes.hs
@@ -1,94 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} - -module System.Console.ANSI.Windows.Emulator.Codes - ( - -- * Cursor movement by character - cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode - - -- * Cursor movement by line - , cursorUpLineCode, cursorDownLineCode - - -- * Directly changing cursor position - , setCursorColumnCode, setCursorPositionCode - - -- * Saving, restoring and reporting cursor position - , saveCursorCode, restoreCursorCode, reportCursorPositionCode - - -- * Clearing parts of the screen - , clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode - , clearScreenCode, clearFromCursorToLineEndCode - , clearFromCursorToLineBeginningCode, clearLineCode - - -- * Scrolling the screen - , scrollPageUpCode, scrollPageDownCode - - -- * Select Graphic Rendition mode: colors and other whizzy stuff - , setSGRCode - - -- * Cursor visibilty changes - , hideCursorCode, showCursorCode - - -- * Changing the title - , setTitleCode - ) where - -import System.Console.ANSI.Types - -cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode - :: Int -- ^ Number of lines or characters to move - -> String -cursorUpCode _ = "" -cursorDownCode _ = "" -cursorForwardCode _ = "" -cursorBackwardCode _ = "" - -cursorDownLineCode, cursorUpLineCode :: Int -- ^ Number of lines to move - -> String -cursorDownLineCode _ = "" -cursorUpLineCode _ = "" - -setCursorColumnCode :: Int -- ^ 0-based column to move to - -> String -setCursorColumnCode _ = "" - -setCursorPositionCode :: Int -- ^ 0-based row to move to - -> Int -- ^ 0-based column to move to - -> String -setCursorPositionCode _ _ = "" - -saveCursorCode, restoreCursorCode, reportCursorPositionCode :: String -saveCursorCode = "" -restoreCursorCode = "" -reportCursorPositionCode = "" - -clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode, - clearScreenCode :: String -clearFromCursorToLineEndCode, clearFromCursorToLineBeginningCode, - clearLineCode :: String - -clearFromCursorToScreenEndCode = "" -clearFromCursorToScreenBeginningCode = "" -clearScreenCode = "" -clearFromCursorToLineEndCode = "" -clearFromCursorToLineBeginningCode = "" -clearLineCode = "" - -scrollPageUpCode, scrollPageDownCode :: Int -- ^ Number of lines to scroll by - -> String -scrollPageUpCode _ = "" -scrollPageDownCode _ = "" - -setSGRCode :: [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied - -- left to right. - -> String -setSGRCode _ = "" - -hideCursorCode, showCursorCode :: String -hideCursorCode = "" -showCursorCode = "" - -setTitleCode :: String -- ^ New title - -> String -setTitleCode _ = ""
− System/Console/ANSI/Windows/Foreign.hs
@@ -1,731 +0,0 @@-{-# OPTIONS_HADDOCK hide #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE DeriveDataTypeable #-} - --- | "System.Win32.Console" is really very impoverished, so I have had to do all --- the FFI myself. -module System.Console.ANSI.Windows.Foreign - ( - -- Re-exports from Win32.Types - BOOL, WORD, DWORD, WCHAR, HANDLE, iNVALID_HANDLE_VALUE, nullHANDLE, SHORT, - - -- 'Re-exports from System.Win32.Console.Extra' - INPUT_RECORD (..), INPUT_RECORD_EVENT (..), kEY_EVENT, - KEY_EVENT_RECORD (..), UNICODE_ASCII_CHAR (..), writeConsoleInput, - getNumberOfConsoleInputEvents, readConsoleInput, - - charToWCHAR, cWcharsToChars, - - COORD(..), SMALL_RECT(..), rect_top, rect_bottom, rect_left, rect_right, - rect_width, rect_height, CONSOLE_CURSOR_INFO(..), - CONSOLE_SCREEN_BUFFER_INFO(..), CHAR_INFO(..), - - sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE, sTD_ERROR_HANDLE, - - eNABLE_VIRTUAL_TERMINAL_INPUT, eNABLE_VIRTUAL_TERMINAL_PROCESSING, - - fOREGROUND_BLUE, fOREGROUND_GREEN, fOREGROUND_RED, fOREGROUND_INTENSITY, - fOREGROUND_WHITE, fOREGROUND_INTENSE_WHITE, - bACKGROUND_BLUE, bACKGROUND_GREEN, bACKGROUND_RED, bACKGROUND_INTENSITY, - bACKGROUND_WHITE, bACKGROUND_INTENSE_WHITE, - cOMMON_LVB_REVERSE_VIDEO, cOMMON_LVB_UNDERSCORE, - - getStdHandle, - getConsoleScreenBufferInfo, - getConsoleCursorInfo, - getConsoleMode, - - setConsoleTextAttribute, - setConsoleCursorPosition, - setConsoleCursorInfo, - setConsoleTitle, - setConsoleMode, - - fillConsoleOutputAttribute, - fillConsoleOutputCharacter, - scrollConsoleScreenBuffer, - - withTString, withHandleToHANDLE, - - ConsoleException (..) - ) where - -import Control.Exception (Exception, throw) -import Data.Bits ((.|.), shiftL) -import Data.Char (chr, ord) -import Data.Typeable (Typeable) -import Foreign.C.Types (CInt (..), CWchar (..)) --- Some Windows types missing from System.Win32 prior to version 2.5.0.0 -#if !MIN_VERSION_Win32(2,5,0) -import Foreign.C.Types (CShort (..)) -#endif -import Foreign.Marshal (alloca, allocaArray, maybeWith, peekArray, with, - withArrayLen) -import Foreign.Ptr (Ptr, castPtr, plusPtr) -import Foreign.Storable (Storable (..)) - -#if !MIN_VERSION_Win32(2,5,1) -import Control.Concurrent.MVar (readMVar) -import Control.Exception (bracket) -import Foreign.StablePtr (StablePtr, freeStablePtr, newStablePtr) -#if __GLASGOW_HASKELL__ >= 612 -import Data.Typeable (cast) -import GHC.IO.FD (FD(..)) -- A wrapper around an Int32 -import GHC.IO.Handle.Types (Handle (..), Handle__ (..)) -#else -import GHC.IOBase (Handle (..), Handle__ (..)) -import qualified GHC.IOBase as IOBase (FD) -- Just an Int32 -#endif -#endif - -import System.Win32.Types (BOOL, DWORD, ErrCode, HANDLE, LPCTSTR, LPDWORD, - TCHAR, UINT, WORD, failIfFalse_, getLastError, iNVALID_HANDLE_VALUE, - nullHANDLE, withTString) --- Missing from System.Win32.Types prior to version 2.5.0.0 -#if MIN_VERSION_Win32(2,5,0) -import System.Win32.Types (SHORT) -#endif --- withHandleToHANDLE added to System.Win32.Types from version 2.5.1.0 -#if MIN_VERSION_Win32(2,5,1) -import System.Win32.Types (withHandleToHANDLE) -#endif - -#if defined(i386_HOST_ARCH) -#define WINDOWS_CCONV stdcall -#elif defined(x86_64_HOST_ARCH) -#define WINDOWS_CCONV ccall -#else -#error Unknown mingw32 arch -#endif - --- Missing from System.Win32.Types prior to version 2.5.0.0 -#if !MIN_VERSION_Win32(2,5,0) -type SHORT = CShort -#endif -type WCHAR = CWchar - -charToWCHAR :: Char -> WCHAR -charToWCHAR char = fromIntegral (ord char) - --- This is a FFI hack. Some of the API calls take a Coord, but that isn't a --- built-in FFI type so I can't use it directly. Instead, I use UNPACKED_COORD --- and marshal COORDs into this manually. Note that we CAN'T just use two SHORTs --- directly because they get expanded to 4 bytes each instead of just boing 2 --- lots of 2 bytes by the stdcall convention, so linking fails. -type UNPACKED_COORD = CInt - --- Field packing order determined experimentally: I couldn't immediately find a --- specification for Windows struct layout anywhere. -unpackCOORD :: COORD -> UNPACKED_COORD -unpackCOORD (COORD x y) - = (fromIntegral y) `shiftL` (sizeOf x * 8) .|. (fromIntegral x) - - -peekAndOffset :: Storable a => Ptr a -> IO (a, Ptr b) -peekAndOffset ptr = do - item <- peek ptr - return (item, ptr `plusPtr` sizeOf item) - -pokeAndOffset :: Storable a => Ptr a -> a -> IO (Ptr b) -pokeAndOffset ptr item = do - poke ptr item - return (ptr `plusPtr` sizeOf item) - -data COORD = COORD - { coord_x :: SHORT - , coord_y :: SHORT - } deriving (Read, Eq) - -instance Show COORD where - show (COORD x y) = "(" ++ show x ++ ", " ++ show y ++ ")" - -instance Storable COORD where - sizeOf ~(COORD x y) = sizeOf x + sizeOf y - alignment ~(COORD x _) = alignment x - peek ptr = do - let ptr' = castPtr ptr :: Ptr SHORT - x <- peekElemOff ptr' 0 - y <- peekElemOff ptr' 1 - return (COORD x y) - poke ptr (COORD x y) = do - let ptr' = castPtr ptr :: Ptr SHORT - pokeElemOff ptr' 0 x - pokeElemOff ptr' 1 y - -data SMALL_RECT = SMALL_RECT - { rect_top_left :: COORD - , rect_bottom_right :: COORD - } - -rect_top, rect_left, rect_bottom, rect_right :: SMALL_RECT -> SHORT -rect_top = coord_y . rect_top_left -rect_left = coord_x . rect_top_left -rect_bottom = coord_y . rect_bottom_right -rect_right = coord_x . rect_bottom_right - -rect_width, rect_height :: SMALL_RECT -> SHORT -rect_width rect = rect_right rect - rect_left rect + 1 -rect_height rect = rect_bottom rect - rect_top rect + 1 - -instance Show SMALL_RECT where - show (SMALL_RECT tl br) = show tl ++ "-" ++ show br - -instance Storable SMALL_RECT where - sizeOf ~(SMALL_RECT tl br) = sizeOf tl + sizeOf br - alignment ~(SMALL_RECT tl _) = alignment tl - peek ptr = do - let ptr' = castPtr ptr :: Ptr COORD - tl <- peekElemOff ptr' 0 - br <- peekElemOff ptr' 1 - return (SMALL_RECT tl br) - poke ptr (SMALL_RECT tl br) = do - let ptr' = castPtr ptr :: Ptr COORD - pokeElemOff ptr' 0 tl - pokeElemOff ptr' 1 br - -data CONSOLE_CURSOR_INFO = CONSOLE_CURSOR_INFO - { cci_cursor_size :: DWORD - , cci_cursor_visible :: BOOL - } deriving (Show) - -instance Storable CONSOLE_CURSOR_INFO where - sizeOf ~(CONSOLE_CURSOR_INFO size visible) = sizeOf size + sizeOf visible - alignment ~(CONSOLE_CURSOR_INFO size _) = alignment size - peek ptr = do - (size, ptr') <- peekAndOffset (castPtr ptr) - visible <- peek ptr' - return (CONSOLE_CURSOR_INFO size visible) - poke ptr (CONSOLE_CURSOR_INFO size visible) = do - ptr' <- pokeAndOffset (castPtr ptr) size - poke ptr' visible - -data CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO - { csbi_size :: COORD - , csbi_cursor_position :: COORD - , csbi_attributes :: WORD - , csbi_window :: SMALL_RECT - , csbi_maximum_window_size :: COORD - } deriving (Show) - -instance Storable CONSOLE_SCREEN_BUFFER_INFO where - sizeOf ~(CONSOLE_SCREEN_BUFFER_INFO - size cursor_position attributes window maximum_window_size) - = sizeOf size + sizeOf cursor_position + sizeOf attributes + sizeOf window - + sizeOf maximum_window_size - alignment ~(CONSOLE_SCREEN_BUFFER_INFO size _ _ _ _) = alignment size - peek ptr = do - (size, ptr1) <- peekAndOffset (castPtr ptr) - (cursor_position, ptr2) <- peekAndOffset ptr1 - (attributes, ptr3) <- peekAndOffset ptr2 - (window, ptr4) <- peekAndOffset ptr3 - maximum_window_size <- peek ptr4 - return (CONSOLE_SCREEN_BUFFER_INFO - size cursor_position attributes window maximum_window_size) - poke ptr (CONSOLE_SCREEN_BUFFER_INFO - size cursor_position attributes window maximum_window_size) - = do - ptr1 <- pokeAndOffset (castPtr ptr) size - ptr2 <- pokeAndOffset ptr1 cursor_position - ptr3 <- pokeAndOffset ptr2 attributes - ptr4 <- pokeAndOffset ptr3 window - poke ptr4 maximum_window_size - -data CHAR_INFO = CHAR_INFO - { ci_char :: WCHAR - , ci_attributes :: WORD - } deriving (Show) - -instance Storable CHAR_INFO where - sizeOf ~(CHAR_INFO char attributes) = sizeOf char + sizeOf attributes - alignment ~(CHAR_INFO char _) = alignment char - peek ptr = do - (char, ptr') <- peekAndOffset (castPtr ptr) - attributes <- peek ptr' - return (CHAR_INFO char attributes) - poke ptr (CHAR_INFO char attributes) = do - ptr' <- pokeAndOffset (castPtr ptr) char - poke ptr' attributes - -eNABLE_VIRTUAL_TERMINAL_INPUT, eNABLE_VIRTUAL_TERMINAL_PROCESSING :: DWORD -sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE, sTD_ERROR_HANDLE :: DWORD -eNABLE_VIRTUAL_TERMINAL_INPUT = 512 -eNABLE_VIRTUAL_TERMINAL_PROCESSING = 4 -sTD_INPUT_HANDLE = 0xFFFFFFF6 -- minus 10 -sTD_OUTPUT_HANDLE = 0xFFFFFFF5 -- minus 11 -sTD_ERROR_HANDLE = 0xFFFFFFF4 -- minus 12 - -fOREGROUND_BLUE, fOREGROUND_GREEN, fOREGROUND_RED, fOREGROUND_INTENSITY, - bACKGROUND_BLUE, bACKGROUND_GREEN, bACKGROUND_RED, bACKGROUND_INTENSITY, - cOMMON_LVB_REVERSE_VIDEO, cOMMON_LVB_UNDERSCORE :: WORD -fOREGROUND_BLUE = 0x1 -fOREGROUND_GREEN = 0x2 -fOREGROUND_RED = 0x4 -fOREGROUND_INTENSITY = 0x8 -bACKGROUND_BLUE = 0x10 -bACKGROUND_GREEN = 0x20 -bACKGROUND_RED = 0x40 -bACKGROUND_INTENSITY = 0x80 -cOMMON_LVB_REVERSE_VIDEO = 0x4000 -cOMMON_LVB_UNDERSCORE = 0x8000 - -fOREGROUND_WHITE, bACKGROUND_WHITE, fOREGROUND_INTENSE_WHITE, - bACKGROUND_INTENSE_WHITE :: WORD -fOREGROUND_WHITE = fOREGROUND_RED .|. fOREGROUND_GREEN .|. fOREGROUND_BLUE -bACKGROUND_WHITE = bACKGROUND_RED .|. bACKGROUND_GREEN .|. bACKGROUND_BLUE -fOREGROUND_INTENSE_WHITE = fOREGROUND_WHITE .|. fOREGROUND_INTENSITY -bACKGROUND_INTENSE_WHITE = bACKGROUND_WHITE .|. bACKGROUND_INTENSITY - -kEY_EVENT, mOUSE_EVENT, wINDOW_BUFFER_SIZE_EVENT, mENU_EVENT, - fOCUS_EVENT :: WORD -kEY_EVENT = 1 -mOUSE_EVENT = 2 -wINDOW_BUFFER_SIZE_EVENT = 4 -mENU_EVENT = 8 -fOCUS_EVENT = 16 - -foreign import WINDOWS_CCONV unsafe "windows.h GetStdHandle" - getStdHandle :: DWORD -> IO HANDLE -foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleScreenBufferInfo" - cGetConsoleScreenBufferInfo :: HANDLE - -> Ptr CONSOLE_SCREEN_BUFFER_INFO - -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleCursorInfo" - cGetConsoleCursorInfo :: HANDLE -> Ptr CONSOLE_CURSOR_INFO -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleMode" - cGetConsoleMode :: HANDLE -> Ptr DWORD -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleTextAttribute" - cSetConsoleTextAttribute :: HANDLE -> WORD -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleCursorPosition" - cSetConsoleCursorPosition :: HANDLE -> UNPACKED_COORD -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleCursorInfo" - cSetConsoleCursorInfo :: HANDLE -> Ptr CONSOLE_CURSOR_INFO -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleTitleW" - cSetConsoleTitle :: LPCTSTR -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleMode" - cSetConsoleMode :: HANDLE -> DWORD -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h FillConsoleOutputAttribute" - cFillConsoleOutputAttribute :: HANDLE - -> WORD - -> DWORD - -> UNPACKED_COORD - -> Ptr DWORD - -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h FillConsoleOutputCharacterW" - cFillConsoleOutputCharacter :: HANDLE - -> TCHAR - -> DWORD - -> UNPACKED_COORD - -> Ptr DWORD - -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h ScrollConsoleScreenBufferW" - cScrollConsoleScreenBuffer :: HANDLE - -> Ptr SMALL_RECT - -> Ptr SMALL_RECT - -> UNPACKED_COORD - -> Ptr CHAR_INFO - -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h WriteConsoleInputW" - cWriteConsoleInput :: HANDLE - -> Ptr INPUT_RECORD - -> DWORD - -> LPDWORD - -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h GetNumberOfConsoleInputEvents" - cGetNumberOfConsoleInputEvents :: HANDLE -> Ptr DWORD -> IO BOOL -foreign import WINDOWS_CCONV unsafe "windows.h ReadConsoleInputW" - cReadConsoleInput :: HANDLE - -> Ptr INPUT_RECORD - -> DWORD - -> LPDWORD - -> IO BOOL - -data ConsoleException = ConsoleException !ErrCode deriving (Eq, Typeable) - -instance Show ConsoleException where - show (ConsoleException 6) = - "A fatal error has occurred.\n\n" ++ - "An attempt has been made to send console virtual terminal sequences\n" ++ - "(ANSI codes) to an output that has not been recognised as an\n" ++ - "ANSI-capable terminal and also cannot be emulated as an ANSI-enabled\n" ++ - "terminal (emulation needs a ConHost-based terminal, such as Command\n" ++ - "Prompt or PowerShell). That may occur, for example, if output has\n" ++ - "been redirected to a file.\n\n" ++ - "If that is unexpected, please post an issue at:\n" ++ - "https://github.com/feuerbach/ansi-terminal/issues\n" - show (ConsoleException errCode) = "ConsoleException " ++ show errCode - -instance Exception ConsoleException - -throwIfFalse :: IO Bool -> IO () -throwIfFalse action = do - succeeded <- action - if not succeeded - then getLastError >>= throw . ConsoleException -- TODO: Check if last error - -- is zero for some instructable reason (?) - else return () - -getConsoleScreenBufferInfo :: HANDLE -> IO CONSOLE_SCREEN_BUFFER_INFO -getConsoleScreenBufferInfo handle - = alloca $ \ptr_console_screen_buffer_info -> do - throwIfFalse $ - cGetConsoleScreenBufferInfo handle ptr_console_screen_buffer_info - peek ptr_console_screen_buffer_info - -getConsoleCursorInfo :: HANDLE -> IO CONSOLE_CURSOR_INFO -getConsoleCursorInfo handle = alloca $ \ptr_console_cursor_info -> do - throwIfFalse $ cGetConsoleCursorInfo handle ptr_console_cursor_info - peek ptr_console_cursor_info - -getConsoleMode :: HANDLE -> IO DWORD -getConsoleMode handle = alloca $ \ptr_mode -> do - throwIfFalse $ cGetConsoleMode handle ptr_mode - peek ptr_mode - -setConsoleTextAttribute :: HANDLE -> WORD -> IO () -setConsoleTextAttribute handle attributes - = throwIfFalse $ cSetConsoleTextAttribute handle attributes - -setConsoleCursorPosition :: HANDLE -> COORD -> IO () -setConsoleCursorPosition handle cursor_position - = throwIfFalse $ cSetConsoleCursorPosition handle - (unpackCOORD cursor_position) - -setConsoleCursorInfo :: HANDLE -> CONSOLE_CURSOR_INFO -> IO () -setConsoleCursorInfo handle console_cursor_info - = with console_cursor_info $ \ptr_console_cursor_info -> do - throwIfFalse $ cSetConsoleCursorInfo handle ptr_console_cursor_info - -setConsoleTitle :: LPCTSTR -> IO () -setConsoleTitle title = throwIfFalse $ cSetConsoleTitle title - -setConsoleMode :: HANDLE -> DWORD -> IO () -setConsoleMode handle attributes - = throwIfFalse $ cSetConsoleMode handle attributes - -fillConsoleOutputAttribute :: HANDLE -> WORD -> DWORD -> COORD -> IO DWORD -fillConsoleOutputAttribute handle attribute fill_length write_origin - = alloca $ \ptr_chars_written -> do - throwIfFalse $ cFillConsoleOutputAttribute handle attribute - fill_length (unpackCOORD write_origin) ptr_chars_written - peek ptr_chars_written - -fillConsoleOutputCharacter :: HANDLE -> TCHAR -> DWORD -> COORD -> IO DWORD -fillConsoleOutputCharacter handle char fill_length write_origin - = alloca $ \ptr_chars_written -> do - throwIfFalse $ cFillConsoleOutputCharacter handle char fill_length - (unpackCOORD write_origin) ptr_chars_written - peek ptr_chars_written - -scrollConsoleScreenBuffer :: HANDLE - -> SMALL_RECT - -> Maybe SMALL_RECT - -> COORD - -> CHAR_INFO - -> IO () -scrollConsoleScreenBuffer - handle scroll_rectangle mb_clip_rectangle destination_origin fill - = with scroll_rectangle $ \ptr_scroll_rectangle -> - maybeWith with mb_clip_rectangle $ \ptr_clip_rectangle -> - with fill $ \ptr_fill -> - throwIfFalse $ cScrollConsoleScreenBuffer handle ptr_scroll_rectangle - ptr_clip_rectangle (unpackCOORD destination_origin) ptr_fill - --- withHandleToHANDLE added to System.Win32.Types from version 2.5.1.0 -#if !MIN_VERSION_Win32(2,5,1) --- | This bit is all highly dubious. The problem is that we want to output ANSI --- to arbitrary Handles rather than forcing people to use stdout. However, the --- Windows ANSI emulator needs a Windows HANDLE to work it's magic, so we need --- to be able to extract one of those from the Haskell Handle. --- --- This code accomplishes this, albeit at the cost of only being compatible with --- GHC. withHandleToHANDLE was added in Win32-2.5.1.0 -withHandleToHANDLE :: Handle -> (HANDLE -> IO a) -> IO a -withHandleToHANDLE haskell_handle action = - -- Create a stable pointer to the Handle. This prevents the garbage collector - -- getting to it while we are doing horrible manipulations with it, and hence - -- stops it being finalized (and closed). - withStablePtr haskell_handle $ const $ do - -- Grab the write handle variable from the Handle - let write_handle_mvar = case haskell_handle of - FileHandle _ handle_mvar -> handle_mvar - DuplexHandle _ _ handle_mvar -> handle_mvar -- This is "write" MVar, - -- we could also take the "read" one - - -- Get the FD from the algebraic data type -#if __GLASGOW_HASKELL__ < 612 - fd <- fmap haFD $ readMVar write_handle_mvar -#else - -- readMVar write_handle_mvar >>= \(Handle__ { haDevice = dev }) -> - -- print (typeOf dev) - Just fd <- fmap (\(Handle__ { haDevice = dev }) -> - fmap fdFD (cast dev)) $ readMVar write_handle_mvar -#endif - - -- Finally, turn that (C-land) FD into a HANDLE using msvcrt - windows_handle <- cget_osfhandle fd - - -- Do what the user originally wanted - action windows_handle - --- This essential function comes from the C runtime system. It is certainly --- provided by msvcrt, and also seems to be provided by the mingw C library - --- hurrah! -#if __GLASGOW_HASKELL__ >= 612 -foreign import WINDOWS_CCONV unsafe "_get_osfhandle" - cget_osfhandle :: CInt -> IO HANDLE -#else -foreign import WINDOWS_CCONV unsafe "_get_osfhandle" - cget_osfhandle :: IOBase.FD -> IO HANDLE -#endif - --- withStablePtr was added in Win32-2.5.1.0 -withStablePtr :: a -> (StablePtr a -> IO b) -> IO b -withStablePtr value = bracket (newStablePtr value) freeStablePtr -#endif - --- The following is based on module System.Win32.Console.Extra from package --- Win32-console, cut down for the WCHAR version of writeConsoleInput. - -writeConsoleInput :: HANDLE -> [INPUT_RECORD] -> IO DWORD -writeConsoleInput hdl evs - = writeConsoleInputWith hdl $ \act -> - withArrayLen evs $ \len ptr -> - act (ptr, toEnum len) - -writeConsoleInputWith :: HANDLE - -> InputHandler (Ptr INPUT_RECORD, DWORD) - -> IO DWORD -writeConsoleInputWith hdl withBuffer - = returnWith_ $ \ptrN -> - withBuffer $ \(ptrBuf, len) -> - failIfFalse_ "WriteConsoleInputW" $ cWriteConsoleInput hdl ptrBuf len ptrN - -returnWith_ :: Storable a => (Ptr a -> IO b) -> IO a -returnWith_ act = alloca $ \ptr -> act ptr >> peek ptr - -type InputHandler i = forall a. (i -> IO a) -> IO a - -{- -typedef union _UNICODE_ASCII_CHAR { - WCHAR UnicodeChar; - CHAR AsciiChar; -} UNICODE_ASCII_CHAR; --} -newtype UNICODE_ASCII_CHAR = UnicodeAsciiChar - { unicodeAsciiChar :: WCHAR - } deriving (Show, Read, Eq) - -instance Storable UNICODE_ASCII_CHAR where - sizeOf _ = 2 - alignment _ = 2 - peek ptr = UnicodeAsciiChar <$> (`peekByteOff` 0) ptr - poke ptr val = case val of - UnicodeAsciiChar c -> (`pokeByteOff` 0) ptr c - -{- -typedef struct _KEY_EVENT_RECORD { - BOOL bKeyDown; - WORD wRepeatCount; - WORD wVirtualKeyCode; - WORD wVirtualScanCode; - union { - WCHAR UnicodeChar; - CHAR AsciiChar; - } uChar; - DWORD dwControlKeyState; -} -#ifdef __GNUC__ -/* gcc's alignment is not what win32 expects */ - PACKED -#endif -KEY_EVENT_RECORD; --} -data KEY_EVENT_RECORD = KEY_EVENT_RECORD - { keyEventKeyDown :: BOOL - , keyEventRepeatCount :: WORD - , keyEventVirtualKeyCode :: WORD - , keyEventVirtualScanCode :: WORD - , keyEventChar :: UNICODE_ASCII_CHAR - , keyEventControlKeystate :: DWORD - } deriving (Show, Read, Eq) - -instance Storable KEY_EVENT_RECORD where - sizeOf _ = 16 - alignment _ = 4 - peek ptr = KEY_EVENT_RECORD <$> (`peekByteOff` 0) ptr - <*> (`peekByteOff` 4) ptr - <*> (`peekByteOff` 6) ptr - <*> (`peekByteOff` 8) ptr - <*> (`peekByteOff` 10) ptr - <*> (`peekByteOff` 12) ptr - poke ptr val = do - (`pokeByteOff` 0) ptr $ keyEventKeyDown val - (`pokeByteOff` 4) ptr $ keyEventRepeatCount val - (`pokeByteOff` 6) ptr $ keyEventVirtualKeyCode val - (`pokeByteOff` 8) ptr $ keyEventVirtualScanCode val - (`pokeByteOff` 10) ptr $ keyEventChar val - (`pokeByteOff` 12) ptr $ keyEventControlKeystate val - -{- -typedef struct _MOUSE_EVENT_RECORD { - COORD dwMousePosition; - DWORD dwButtonState; - DWORD dwControlKeyState; - DWORD dwEventFlags; -} MOUSE_EVENT_RECORD; --} -data MOUSE_EVENT_RECORD = MOUSE_EVENT_RECORD - { mousePosition :: COORD - , mouseButtonState :: DWORD - , mouseControlKeyState :: DWORD - , mouseEventFlags :: DWORD - } deriving (Show, Read, Eq) - -instance Storable MOUSE_EVENT_RECORD where - sizeOf _ = 16 - alignment _ = 4 - peek ptr = MOUSE_EVENT_RECORD <$> (`peekByteOff` 0) ptr - <*> (`peekByteOff` 4) ptr - <*> (`peekByteOff` 8) ptr - <*> (`peekByteOff` 12) ptr - poke ptr val = do - (`pokeByteOff` 0) ptr $ mousePosition val - (`pokeByteOff` 4) ptr $ mouseButtonState val - (`pokeByteOff` 8) ptr $ mouseControlKeyState val - (`pokeByteOff` 12) ptr $ mouseEventFlags val - -{- -typedef struct _WINDOW_BUFFER_SIZE_RECORD { - COORD dwSize; -} WINDOW_BUFFER_SIZE_RECORD; --} -data WINDOW_BUFFER_SIZE_RECORD = WINDOW_BUFFER_SIZE_RECORD - { bufSizeNew :: COORD - } deriving (Show, Read, Eq) - -instance Storable WINDOW_BUFFER_SIZE_RECORD where - sizeOf _ = 4 - alignment _ = 4 - peek ptr = WINDOW_BUFFER_SIZE_RECORD <$> (`peekByteOff` 0) ptr - poke ptr val = (`pokeByteOff` 0) ptr $ bufSizeNew val - -{- -typedef struct _MENU_EVENT_RECORD { - UINT dwCommandId; -} MENU_EVENT_RECORD,*PMENU_EVENT_RECORD; --} -data MENU_EVENT_RECORD = MENU_EVENT_RECORD - { menuCommandId :: UINT - } deriving (Show, Read, Eq) - -instance Storable MENU_EVENT_RECORD where - sizeOf _ = 4 - alignment _ = 4 - peek ptr = MENU_EVENT_RECORD <$> (`peekByteOff` 0) ptr - poke ptr val = (`pokeByteOff` 0) ptr $ menuCommandId val - -{- -typedef struct _FOCUS_EVENT_RECORD { BOOL bSetFocus; } FOCUS_EVENT_RECORD; --} -data FOCUS_EVENT_RECORD = FOCUS_EVENT_RECORD - { focusSetFocus :: BOOL - } deriving (Show, Read, Eq) - -instance Storable FOCUS_EVENT_RECORD where - sizeOf _ = 4 - alignment _ = 4 - peek ptr = FOCUS_EVENT_RECORD <$> (`peekByteOff` 0) ptr - poke ptr val = (`pokeByteOff` 0) ptr $ focusSetFocus val - -data INPUT_RECORD_EVENT - = InputKeyEvent KEY_EVENT_RECORD - | InputMouseEvent MOUSE_EVENT_RECORD - | InputWindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD - | InputMenuEvent MENU_EVENT_RECORD - | InputFocusEvent FOCUS_EVENT_RECORD - deriving (Show, Read, Eq) - -{- -typedef struct _INPUT_RECORD { - WORD EventType; - union { - KEY_EVENT_RECORD KeyEvent; - MOUSE_EVENT_RECORD MouseEvent; - WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; - MENU_EVENT_RECORD MenuEvent; - FOCUS_EVENT_RECORD FocusEvent; - } Event; -} INPUT_RECORD,*PINPUT_RECORD; --} -data INPUT_RECORD = INPUT_RECORD - { inputEventType :: WORD - , inputEvent :: INPUT_RECORD_EVENT - } deriving (Show, Read, Eq) - -instance Storable INPUT_RECORD where - sizeOf _ = 20 - alignment _ = 4 - peek ptr = do - evType <- (`peekByteOff` 0) ptr - event <- case evType of - _ | evType == kEY_EVENT - -> InputKeyEvent <$> (`peekByteOff` 4) ptr - _ | evType == mOUSE_EVENT - -> InputMouseEvent <$> (`peekByteOff` 4) ptr - _ | evType == wINDOW_BUFFER_SIZE_EVENT - -> InputWindowBufferSizeEvent <$> (`peekByteOff` 4) ptr - _ | evType == mENU_EVENT - -> InputMenuEvent <$> (`peekByteOff` 4) ptr - _ | evType == fOCUS_EVENT - -> InputFocusEvent <$> (`peekByteOff` 4) ptr - _ -> error $ "peek (INPUT_RECORD): Unknown event type " ++ - show evType - return $ INPUT_RECORD evType event - poke ptr val = do - (`pokeByteOff` 0) ptr $ inputEventType val - case inputEvent val of - InputKeyEvent ev -> (`pokeByteOff` 4) ptr ev - InputMouseEvent ev -> (`pokeByteOff` 4) ptr ev - InputWindowBufferSizeEvent ev -> (`pokeByteOff` 4) ptr ev - InputMenuEvent ev -> (`pokeByteOff` 4) ptr ev - InputFocusEvent ev -> (`pokeByteOff` 4) ptr ev - --- The following is based on module System.Win32.Console.Extra from package --- Win32-console. - -getNumberOfConsoleInputEvents :: HANDLE -> IO DWORD -getNumberOfConsoleInputEvents hdl = - returnWith_ $ \ptrN -> - failIfFalse_ "GetNumberOfConsoleInputEvents" $ - cGetNumberOfConsoleInputEvents hdl ptrN - --- The following is based on module System.Win32.Console.Extra from package --- Win32-console, cut down for the WCHAR version of readConsoleInput. - -readConsoleInput :: HANDLE -> DWORD -> IO [INPUT_RECORD] -readConsoleInput hdl len - = readConsoleInputWith hdl len $ \(ptr, n) -> peekArray (fromEnum n) ptr - -readConsoleInputWith :: HANDLE - -> DWORD - -> OutputHandler (Ptr INPUT_RECORD, DWORD) -readConsoleInputWith hdl len handler = - allocaArray (fromEnum len) $ \ptrBuf -> - alloca $ \ptrN -> do - failIfFalse_ "ReadConsoleInputW" $ - cReadConsoleInput hdl ptrBuf len ptrN - n <- peek ptrN - handler (ptrBuf, n) - -type OutputHandler o = forall a. (o -> IO a) -> IO a - --- Replicated from module Foreign.C.String in package base because that module --- does not export the function. -cWcharsToChars :: [CWchar] -> [Char] -cWcharsToChars = map chr . fromUTF16 . map fromIntegral - where - fromUTF16 (c1:c2:wcs) - | 0xd800 <= c1 && c1 <= 0xdbff && 0xdc00 <= c2 && c2 <= 0xdfff = - ((c1 - 0xd800)*0x400 + (c2 - 0xdc00) + 0x10000) : fromUTF16 wcs - fromUTF16 (c:wcs) = c : fromUTF16 wcs - fromUTF16 [] = []
ansi-terminal.cabal view
@@ -1,6 +1,6 @@ Name: ansi-terminal -Version: 0.8.0.1 -Cabal-Version: >= 1.6 +Version: 0.8.0.2 +Cabal-Version: >= 1.8 Category: User Interfaces Synopsis: Simple ANSI terminal support, with Windows compatibility Description: ANSI terminal support for Haskell: allows cursor movement, @@ -13,10 +13,10 @@ Homepage: https://github.com/feuerbach/ansi-terminal Build-Type: Simple -Extra-Source-Files: includes/Common-Include.hs - includes/Common-Include-Emulator.hs - includes/Common-Include-Enabled.hs - includes/Exports-Include.hs +Extra-Source-Files: src/includes/Common-Include.hs + src/includes/Common-Include-Emulator.hs + src/includes/Common-Include-Enabled.hs + src/includes/Exports-Include.hs CHANGELOG.md README.md @@ -29,19 +29,19 @@ Default: False Library + Hs-Source-Dirs: src Exposed-Modules: System.Console.ANSI System.Console.ANSI.Types System.Console.ANSI.Codes - Include-Dirs: includes + Include-Dirs: src/includes - Build-Depends: base >= 4 && < 5 + Build-Depends: base >= 4.2.0.0 && < 5 , colour if os(windows) Build-Depends: base-compat >= 0.9.1 - , containers + , containers >= 0.5.0.0 , Win32 >= 2.0 - , process Cpp-Options: -DWINDOWS Other-Modules: System.Console.ANSI.Windows System.Console.ANSI.Windows.Detect @@ -50,6 +50,7 @@ System.Console.ANSI.Windows.Foreign -- NB: used for fallback by the emulator System.Console.ANSI.Unix + System.Win32.Compat else -- We assume any non-Windows platform is Unix Cpp-Options: -DUNIX @@ -61,34 +62,10 @@ Ghc-Options: -Wall Executable ansi-terminal-example - Main-Is: System/Console/ANSI/Example.hs - - Include-Dirs: includes - - Other-Modules: System.Console.ANSI - System.Console.ANSI.Codes - System.Console.ANSI.Types - System.Console.ANSI.Unix - - if os(windows) - Build-Depends: base-compat >= 0.9.1 - , containers - , Win32 >= 2.0 - Cpp-Options: -DWINDOWS - Other-Modules: System.Console.ANSI.Windows - System.Console.ANSI.Windows.Detect - System.Console.ANSI.Windows.Emulator - System.Console.ANSI.Windows.Emulator.Codes - System.Console.ANSI.Windows.Foreign - else - -- We assume any non-Windows platform is Unix - Cpp-Options: -DUNIX - - Build-Depends: base >= 4 && < 5 - Extensions: CPP - ForeignFunctionInterface - + Hs-Source-Dirs: app + Main-Is: Example.hs + Build-Depends: base >= 4.2.0.0 && < 5 + , ansi-terminal Ghc-Options: -Wall - if !flag(example) Buildable: False
+ app/Example.hs view
@@ -0,0 +1,315 @@+module Main + ( + main + ) where + +import Control.Concurrent (threadDelay) +import Control.Monad (forM_) +import System.IO (hFlush, stdout) + +import System.Console.ANSI + +examples :: [IO ()] +examples = [ cursorMovementExample + , lineChangeExample + , setCursorPositionExample + , saveRestoreCursorExample + , clearExample + , scrollExample + , sgrExample + , cursorVisibilityExample + , titleExample + , getCursorPositionExample + ] + +main :: IO () +main = mapM_ (\example -> resetScreen >> example) examples + +-- Annex D to Standard ECMA-48 (5th Ed, 1991) identifies that the representation +-- of an erased state is implementation-dependent. There may or may not be a +-- distinction between a character position in the erased state and one imaging +-- SPACE. Consequently, to reset the screen, the default graphic rendition must +-- be selected (setSGR [Reset]) before all character positions are put into the +-- erased state (clearScreen). +resetScreen :: IO () +resetScreen = setSGR [Reset] >> clearScreen >> setCursorPosition 0 0 + +pause :: IO () +pause = do + hFlush stdout + -- 1 second pause + threadDelay 1000000 + +cursorMovementExample :: IO () +cursorMovementExample = do + putStrLn "Line One" + putStr "Line Two" + pause + -- Line One + -- Line Two + + cursorUp 1 + putStr " - Extras" + pause + -- Line One - Extras + -- Line Two + + cursorBackward 2 + putStr "zz" + pause + -- Line One - Extrzz + -- Line Two + + cursorForward 2 + putStr "- And More" + pause + -- Line One - Extrzz - And More + -- Line Two + + cursorDown 1 + putStr "Disconnected" + pause + -- Line One - Extrzz - And More + -- Line Two Disconnected + +lineChangeExample :: IO () +lineChangeExample = do + putStrLn "Line One" + putStr "Line Two" + pause + -- Line One + -- Line Two + + cursorUpLine 1 + putStr "New Line One" + pause + -- New Line One + -- Line Two + + cursorDownLine 1 + putStr "New Line Two" + pause + -- New Line One + -- New Line Two + +setCursorPositionExample :: IO () +setCursorPositionExample = do + putStrLn "Line One" + putStrLn "Line Two" + pause + -- Line One + -- Line Two + + setCursorPosition 0 5 + putStr "Foo" + pause + -- Line Foo + -- Line Two + + setCursorPosition 1 5 + putStr "Bar" + pause + -- Line Foo + -- Line Bar + + setCursorColumn 1 + putStr "oaf" + pause + -- Line Foo + -- Loaf Bar + +saveRestoreCursorExample :: IO () +saveRestoreCursorExample = do + putStr "Start sentence ..." + pause + -- Start sentence ... + + saveCursor + setCursorPosition 2 3 + putStr "SPLASH!" + pause + -- Start sentence ... + -- + -- SPLASH! + + restoreCursor + putStr " end sentence, uninterrupted." + pause + -- Start sentence ... end sentence, uninterrupted + -- + -- SPLASH! + +clearExample :: IO () +clearExample = do + putStrLn "Line One" + putStrLn "Line Two" + pause + -- Line One + -- Line Two + + setCursorPosition 0 4 + clearFromCursorToScreenEnd + pause + -- Line + + resetScreen + putStrLn "Line One" + putStrLn "Line Two" + pause + -- Line One + -- Line Two + + setCursorPosition 1 4 + clearFromCursorToScreenBeginning + pause + -- + -- Two + + resetScreen + putStrLn "Line One" + putStrLn "Line Two" + pause + -- Line One + -- Line Two + + setCursorPosition 0 4 + clearFromCursorToLineEnd + pause + -- Line + -- Line Two + + setCursorPosition 1 4 + clearFromCursorToLineBeginning + pause + -- Line + -- Two + + clearLine + pause + -- Line + + clearScreen + pause + -- + +scrollExample :: IO () +scrollExample = do + putStrLn "Line One" + putStrLn "Line Two" + putStrLn "Line Three" + pause + -- Line One + -- Line Two + -- Line Three + + scrollPageDown 2 + pause + -- + -- + -- Line One + -- Line Two + -- Line Three + + scrollPageUp 3 + pause + -- Line Two + -- Line Three + +sgrExample :: IO () +sgrExample = do + let colors = enumFromTo minBound maxBound :: [Color] + forM_ [Foreground, Background] $ \layer -> do + forM_ [Dull, Vivid] $ \intensity -> do + resetScreen + forM_ colors $ \color -> do + setSGR [Reset] + setSGR [SetColor layer intensity color] + putStrLn (show color) + pause + -- All the colors, 4 times in sequence + + let named_styles = [ (SetConsoleIntensity BoldIntensity, "Bold") + , (SetConsoleIntensity FaintIntensity, "Faint") + , (SetConsoleIntensity NormalIntensity, "Normal") + , (SetItalicized True, "Italic") + , (SetItalicized False, "No Italics") + , (SetUnderlining SingleUnderline, "Single Underline") + , (SetUnderlining DoubleUnderline, "Double Underline") + , (SetUnderlining NoUnderline, "No Underline") + , (SetBlinkSpeed SlowBlink, "Slow Blink") + , (SetBlinkSpeed RapidBlink, "Rapid Blink") + , (SetBlinkSpeed NoBlink, "No Blink") + , (SetVisible False, "Conceal") + , (SetVisible True, "Reveal") + ] + forM_ named_styles $ \(style, name) -> do + resetScreen + setSGR [style] + putStrLn name + pause + -- Text describing a style displayed in that style in sequence + + setSGR [SetColor Foreground Vivid Red] + setSGR [SetColor Background Vivid Blue] + + clearScreen >> setCursorPosition 0 0 + setSGR [SetSwapForegroundBackground False] + putStr "Red-On-Blue" + pause + -- Red-On-Blue + + clearScreen >> setCursorPosition 0 0 + setSGR [SetSwapForegroundBackground True] + putStr "Blue-On-Red" + pause + -- Blue-On-Red + +cursorVisibilityExample :: IO () +cursorVisibilityExample = do + putStr "Cursor Demo" + pause + -- Cursor Demo| + + hideCursor + pause + -- Cursor Demo + + showCursor + pause + -- Cursor Demo| + +titleExample :: IO () +titleExample = do + putStr "Title Demo" + pause + -- ~/foo/ - ansi-terminal-ex - 83x70 + ------------------------------------ + -- Title Demo + + setTitle "Yup, I'm a new title!" + pause + -- Yup, I'm a new title! - ansi-terminal-ex - 83x70 + --------------------------------------------------- + -- Title Demo + +getCursorPositionExample :: IO () +getCursorPositionExample = do + putStrLn " 11111111112222222222" + putStrLn "12345678901234567890123456789" + putStr "Report cursor position here:" + pause + -- 11111111112222222222 + -- 12345678901234567890123456789 + -- Report cursor position here:| + result <- getCursorPosition + putStrLn " (3rd row, 29th column) to stdin, as CSI 3 ; 29 R.\n" + case result of + Just (row, col) -> putStrLn $ "The cursor was at row number " ++ + show row ++ " and column number " ++ show col ++ ".\n" + Nothing -> putStrLn "Error: unable to get the cursor position\n" + pause + -- 11111111112222222222 + -- 12345678901234567890123456789 + -- Report cursor position here: (3rd row, 29th column) to stdin, as CSI 3 ; 29 R. + -- + -- The cursor was at row number 3 and column number 29.
− includes/Common-Include-Emulator.hs
@@ -1,65 +0,0 @@--- This file contains code that is required in the case of the module --- System.Console.ANSI.Windows.Emulator and differs from the common code in --- file Common-Include-Enabled.hs. - --- | Set the Select Graphic Rendition mode -hSetSGR - :: ConsoleDefaultState -- ^ The default console state - -> Handle - -> [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied left to - -- right. - -> IO () - --- | Set the Select Graphic Rendition mode -setSGR - :: ConsoleDefaultState -- ^ The default console state - -> [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied left to - -- right. - -> IO () -setSGR def = hSetSGR def stdout - -hClearFromCursorToScreenEnd, hClearFromCursorToScreenBeginning, hClearScreen - :: ConsoleDefaultState -- ^ The default console state - -> Handle - -> IO () - -clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen - :: ConsoleDefaultState -- ^ The default console state - -> IO () -clearFromCursorToScreenEnd def = hClearFromCursorToScreenEnd def stdout -clearFromCursorToScreenBeginning def - = hClearFromCursorToScreenBeginning def stdout -clearScreen def = hClearScreen def stdout - -hClearFromCursorToLineEnd, hClearFromCursorToLineBeginning, hClearLine - :: ConsoleDefaultState -- ^ The default console state - -> Handle - -> IO () - -clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine - :: ConsoleDefaultState -- ^ The default console state - -> IO () -clearFromCursorToLineEnd def = hClearFromCursorToLineEnd def stdout -clearFromCursorToLineBeginning def = hClearFromCursorToLineBeginning def stdout -clearLine def = hClearLine def stdout - --- | Scroll the displayed information up or down the terminal: not widely --- supported -hScrollPageUp, hScrollPageDown - :: ConsoleDefaultState -- ^ The default console state - -> Handle - -> Int -- ^ Number of lines to scroll by - -> IO () - --- | Scroll the displayed information up or down the terminal: not widely --- supported -scrollPageUp, scrollPageDown - :: ConsoleDefaultState -- ^ The default console state - -> Int -- ^ Number of lines to scroll by - -> IO () -scrollPageUp def = hScrollPageUp def stdout -scrollPageDown def = hScrollPageDown def stdout
− includes/Common-Include-Enabled.hs
@@ -1,56 +0,0 @@--- This file contains code that is common save that different code is required --- in the case of the module System.Console.ANSI.Windows.Emulator (see the file --- Common-Include-Emulator.hs in respect of the latter). - --- | Set the Select Graphic Rendition mode -hSetSGR - :: Handle - -> [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied left to - -- right. - -> IO () - --- | Set the Select Graphic Rendition mode -setSGR - :: [SGR] -- ^ Commands: these will typically be applied on top of the - -- current console SGR mode. An empty list of commands is - -- equivalent to the list @[Reset]@. Commands are applied left to - -- right. - -> IO () -setSGR = hSetSGR stdout - -hClearFromCursorToScreenEnd, hClearFromCursorToScreenBeginning, hClearScreen - :: Handle - -> IO () - -clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen - :: IO () -clearFromCursorToScreenEnd = hClearFromCursorToScreenEnd stdout -clearFromCursorToScreenBeginning = hClearFromCursorToScreenBeginning stdout -clearScreen = hClearScreen stdout - -hClearFromCursorToLineEnd, hClearFromCursorToLineBeginning, hClearLine - :: Handle - -> IO () - -clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine - :: IO () -clearFromCursorToLineEnd = hClearFromCursorToLineEnd stdout -clearFromCursorToLineBeginning = hClearFromCursorToLineBeginning stdout -clearLine = hClearLine stdout - --- | Scroll the displayed information up or down the terminal: not widely --- supported -hScrollPageUp, hScrollPageDown - :: Handle - -> Int -- ^ Number of lines to scroll by - -> IO () - --- | Scroll the displayed information up or down the terminal: not widely --- supported -scrollPageUp, scrollPageDown - :: Int -- ^ Number of lines to scroll by - -> IO () -scrollPageUp = hScrollPageUp stdout -scrollPageDown = hScrollPageDown stdout
− includes/Common-Include.hs
@@ -1,164 +0,0 @@--- This file contains code that is common to modules System.Console.ANSI.Unix, --- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as --- type signatures and the definition of functions specific to stdout in terms --- of the corresponding more general functions, inclduding the related Haddock --- documentation. - -import System.Environment -#if !MIN_VERSION_base(4,8,0) -import Control.Applicative -#endif - -import Control.Monad (void) -import Data.Char (isDigit) -import Text.ParserCombinators.ReadP (char, many1, ReadP, satisfy) - -hCursorUp, hCursorDown, hCursorForward, hCursorBackward - :: Handle - -> Int -- ^ Number of lines or characters to move - -> IO () -cursorUp, cursorDown, cursorForward, cursorBackward - :: Int -- ^ Number of lines or characters to move - -> IO () -cursorUp = hCursorUp stdout -cursorDown = hCursorDown stdout -cursorForward = hCursorForward stdout -cursorBackward = hCursorBackward stdout - -hCursorDownLine, hCursorUpLine :: Handle - -> Int -- ^ Number of lines to move - -> IO () -cursorDownLine, cursorUpLine :: Int -- ^ Number of lines to move - -> IO () -cursorDownLine = hCursorDownLine stdout -cursorUpLine = hCursorUpLine stdout - -hSetCursorColumn :: Handle - -> Int -- ^ 0-based column to move to - -> IO () -setCursorColumn :: Int -- ^ 0-based column to move to - -> IO () -setCursorColumn = hSetCursorColumn stdout - -hSetCursorPosition :: Handle - -> Int -- ^ 0-based row to move to - -> Int -- ^ 0-based column to move to - -> IO () -setCursorPosition :: Int -- ^ 0-based row to move to - -> Int -- ^ 0-based column to move to - -> IO () -setCursorPosition = hSetCursorPosition stdout - -hSaveCursor, hRestoreCursor, hReportCursorPosition :: Handle -> IO () - --- | Save the cursor position in memory. The only way to access the saved value --- is with the 'restoreCursor' command. -saveCursor :: IO () --- | Restore the cursor position from memory. There will be no value saved in --- memory until the first use of the 'saveCursor' command. -restoreCursor :: IO () --- | Looking for a way to get the cursors position? See --- 'getCursorPosition'. --- --- Emit the cursor position into the console input stream, immediately after --- being recognised on the output stream, as: --- @ESC [ \<cursor row> ; \<cursor column> R@ --- --- In isolation of 'getReportedCursorPosition' or 'getCursorPosition', this --- function may be of limited use on Windows operating systems because of --- difficulties in obtaining the data emitted into the console input stream. --- The function 'hGetBufNonBlocking' in module "System.IO" does not work on --- Windows. This has been attributed to the lack of non-blocking primatives in --- the operating system (see the GHC bug report #806 at --- <https://ghc.haskell.org/trac/ghc/ticket/806>). -reportCursorPosition :: IO () - -saveCursor = hSaveCursor stdout -restoreCursor = hRestoreCursor stdout -reportCursorPosition = hReportCursorPosition stdout - -hHideCursor, hShowCursor :: Handle - -> IO () -hideCursor, showCursor :: IO () -hideCursor = hHideCursor stdout -showCursor = hShowCursor stdout - --- | Set the terminal window title -hSetTitle :: Handle - -> String -- ^ New title - -> IO () --- | Set the terminal window title -setTitle :: String -- ^ New title - -> IO () -setTitle = hSetTitle stdout - --- | Use heuristics to determine whether the functions defined in this --- package will work with a given handle. --- --- The current implementation checks that the handle is a terminal, and --- that the @TERM@ environment variable doesn't say @dumb@ (which is what --- Emacs sets for its own terminal). -hSupportsANSI :: Handle -> IO Bool --- Borrowed from an HSpec patch by Simon Hengel --- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd) -hSupportsANSI h = (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb) - where - -- cannot use lookupEnv since it only appeared in GHC 7.6 - isDumb = maybe False (== "dumb") . lookup "TERM" <$> getEnvironment - --- | Parses the characters emitted by 'reportCursorPosition' into the console --- input stream. Returns the cursor row and column as a tuple. --- --- For example, if the characters emitted by 'reportCursorPosition' are in --- 'String' @input@ then the parser could be applied like this: --- --- > let result = readP_to_S cursorPosition input --- > case result of --- > [] -> putStrLn $ "Error: could not parse " ++ show input --- > [((row, column), _)] -> putStrLn $ "The cursor was at row " ++ show row --- > ++ " and column" ++ show column ++ "." --- > (_:_) -> putStrLn $ "Error: parse not unique" --- -cursorPosition :: ReadP (Int, Int) -cursorPosition = do - void $ char '\ESC' - void $ char '[' - row <- decimal -- A non-negative whole decimal number - void $ char ';' - col <- decimal -- A non-negative whole decimal number - void $ char 'R' - return (read row, read col) - where - digit = satisfy isDigit - decimal = many1 digit - --- | Attempts to get the reported cursor position data from the console input --- stream. The function is intended to be called immediately after --- 'reportCursorPosition' (or related functions) have caused characters to be --- emitted into the stream. --- --- For example, on a Unix-like operating system: --- --- > hSetBuffering stdin NoBuffering -- set no buffering (the contents of the --- > -- buffer will be discarded, so this needs --- > -- to be done before the cursor positon is --- > -- emitted) --- > reportCursorPosition --- > hFlush stdout -- ensure the report cursor position code is sent to the --- > -- operating system --- > input <- getReportedCursorPosition --- --- On Windows operating systems, the function is not supported on consoles, such --- as mintty, that are not based on the Win32 console of the Windows API. --- (Command Prompt and PowerShell are based on the Win32 console.) -getReportedCursorPosition :: IO String - --- | Attempts to get the reported cursor position, combining the functions --- 'reportCursorPosition', 'getReportedCursorPosition' and 'cursorPosition'. --- Returns 'Nothing' if any data emitted by 'reportCursorPosition', obtained by --- 'getReportedCursorPosition', cannot be parsed by 'cursorPosition'. --- --- On Windows operating systems, the function is not supported on consoles, such --- as mintty, that are not based on the Win32 console of the Windows API. --- (Command Prompt and PowerShell are based on the Win32 console.) -getCursorPosition :: IO (Maybe (Int, Int))
− includes/Exports-Include.hs
@@ -1,114 +0,0 @@--- This file contains code that is common to modules --- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module --- exports and the associated Haddock documentation. - - -- * Basic data types - module System.Console.ANSI.Types - - -- * Cursor movement by character - , cursorUp - , cursorDown - , cursorForward - , cursorBackward - , hCursorUp - , hCursorDown - , hCursorForward - , hCursorBackward - , cursorUpCode - , cursorDownCode - , cursorForwardCode - , cursorBackwardCode - - -- * Cursor movement by line - -- | The difference between movements \"by character\" and \"by line\" is - -- that @*Line@ functions additionally move the cursor to the start of the - -- line, while functions like @cursorUp@ and @cursorDown@ keep the column - -- the same. - -- - -- Also keep in mind that @*Line@ functions are not as portable. See - -- <https://github.com/feuerbach/ansi-terminal/issues/10> for the details. - , cursorUpLine - , cursorDownLine - , hCursorUpLine - , hCursorDownLine - , cursorUpLineCode - , cursorDownLineCode - - -- * Directly changing cursor position - , setCursorColumn - , hSetCursorColumn - , setCursorColumnCode - - , setCursorPosition - , hSetCursorPosition - , setCursorPositionCode - - -- * Saving, restoring and reporting cursor position - , saveCursor - , hSaveCursor - , saveCursorCode - - , restoreCursor - , hRestoreCursor - , restoreCursorCode - - , reportCursorPosition - , hReportCursorPosition - , reportCursorPositionCode - - -- * Clearing parts of the screen - -- | Note that these functions only clear parts of the screen. They do not move the - -- cursor. - , clearFromCursorToScreenEnd - , clearFromCursorToScreenBeginning - , clearScreen - , hClearFromCursorToScreenEnd - , hClearFromCursorToScreenBeginning - , hClearScreen - , clearFromCursorToScreenEndCode - , clearFromCursorToScreenBeginningCode - , clearScreenCode - - , clearFromCursorToLineEnd - , clearFromCursorToLineBeginning - , clearLine - , hClearFromCursorToLineEnd - , hClearFromCursorToLineBeginning - , hClearLine - , clearFromCursorToLineEndCode - , clearFromCursorToLineBeginningCode - , clearLineCode - - -- * Scrolling the screen - , scrollPageUp - , scrollPageDown - , hScrollPageUp - , hScrollPageDown - , scrollPageUpCode - , scrollPageDownCode - - -- * Select Graphic Rendition mode: colors and other whizzy stuff - , setSGR - , hSetSGR - , setSGRCode - - -- * Cursor visibilty changes - , hideCursor - , showCursor - , hHideCursor - , hShowCursor - , hideCursorCode - , showCursorCode - - -- * Changing the title - , setTitle - , hSetTitle - , setTitleCode - - -- * Checking if handle supports ANSI - , hSupportsANSI - - -- * Getting the cursor position - , getCursorPosition - , getReportedCursorPosition - , cursorPosition
+ src/System/Console/ANSI.hs view
@@ -0,0 +1,118 @@+-- | Through this module, this library provides platform-independent support +-- for control character sequences following the \'ANSI\' standards (see further +-- below) for terminal software that supports those sequences, running on a +-- Unix-like operating system or Windows. +-- +-- The sequences of control characters (also referred to as \'escape\' sequences +-- or codes) provide a rich range of functionality for terminal control, which +-- includes: +-- +-- * Colored text output, with control over both foreground and background +-- colors +-- +-- * Clearing parts of a line or the screen +-- +-- * Hiding or showing the cursor +-- +-- * Moving the cursor around +-- +-- * Reporting the position of the cursor +-- +-- * Scrolling the screen up or down +-- +-- * Changing the title of the terminal +-- +-- The native terminal software on Windows is \'Command Prompt\' or +-- \`PowerShell\`. Before Windows 10 version 1511 (known as the \'November +-- [2015] Update\' or \'Threshold 2\') that software did not support such +-- control sequences. For that software, this library also provides support for +-- such sequences by using emulation. +-- +-- Terminal software other than the native software exists for Windows. One +-- example is the \'mintty\' terminal emulator for \'Cygwin\', \'MSYS\' or +-- \'MSYS2\', and dervied projects, and for \'WSL\' (Windows Subsystem for +-- Linux). On Windows, this library makes use of the existence of, and content +-- of, the @TERM@ environment variable to detect the use of non-native terminal +-- software. If you are using a native terminal on Windows (outside of WSL) and +-- this library is not working as you expect, check that the @TERM@ variable +-- does not exist. +-- +-- The \'ANSI\' standards refer to (1) standard ECMA-48 \`Control Functions for +-- Coded Character Sets\' (5th edition, 1991); (2) extensions in ITU-T +-- Recommendation (previously CCITT Recommendation) T.416 (03/93) \'Information +-- Technology – Open Document Architecture (ODA) and Interchange Format: +-- Character Content Architectures\` (also published as ISO/IEC International +-- Standard 8613-6); and (3) further extensions used by \'XTerm\', a terminal +-- emulator for the X Window System. The escape codes are described in a +-- Wikipedia article at <http://en.wikipedia.org/wiki/ANSI_escape_code> and +-- those codes supported on current versions of Windows at +-- <https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences>. +-- +-- The whole of the \'ANSI\' standards are not supported by this library but +-- most (if not all) of the parts that are popular and well-supported by +-- terminal software are supported. Every function exported by this module comes +-- in three variants, namely: +-- +-- * A variant that has an @IO ()@ type and doesn't take a @Handle@ (for +-- example, @clearScreen :: IO ()@). This variant just outputs the \`ANSI\` +-- command directly to the standard output channel ('stdout') and any +-- terminal corresponding to it. Commands issued like this should work as you +-- expect on both Unix-like operating systems and Windows. +-- +-- * An \'@h@...\' variant that has an @IO ()@ type but takes a @Handle@ (for +-- example, @hClearScreen :: Handle -> IO ()@). This variant outputs the +-- \`ANSI\` command to the supplied handle and any terminal corresponding to +-- it. Commands issued like this should also work as you expect on both +-- Unix-like operating systems and Windows. +-- +-- * A \'...@Code@\' variant that has a @String@ type (for example, +-- @clearScreenCode :: String@). This variant outputs the sequence of control +-- characters as a 'String', which can be added to any other bit of text +-- before being output. The use of these codes is generally discouraged +-- because they will not work on legacy versions of Windows where the +-- terminal in use is not ANSI-enabled (see further above). On Windows, where +-- emulation has been necessary, these variants will always output the empty +-- string. That is done so that it is possible to use them portably; for +-- example, coloring console output on the understanding that you will see +-- colors only if you are running on a Unix-like operating system or a +-- version of Windows where emulation has not been necessary. If the control +-- characters are always required, see module "System.Console.ANSI.Codes". +-- +-- Example: +-- +-- > module Main where +-- > +-- > import System.Console.ANSI +-- > +-- > -- Set colors and write some text in those colors. +-- > main = do +-- > setSGR [SetColor Foreground Vivid Red] +-- > setSGR [SetColor Background Vivid Blue] +-- > putStrLn "Red-On-Blue" +-- > setSGR [Reset] -- Reset to default colour scheme +-- > putStrLn "Default colors." +-- +-- For many more examples, see the project's extensive +-- <https://raw.githubusercontent.com/feuerbach/ansi-terminal/master/System/Console/ANSI/Example.hs Example.hs> file. +#if defined(WINDOWS) +module System.Console.ANSI + ( + module System.Console.ANSI.Windows + ) where + +import System.Console.ANSI.Windows + +#elif defined(UNIX) + +module System.Console.ANSI + ( + module System.Console.ANSI.Unix + ) where + +import System.Console.ANSI.Unix + +#else + +#error Unsupported platform for the ansi-terminal package + +#endif
+ src/System/Console/ANSI/Codes.hs view
@@ -0,0 +1,183 @@+-- | This module exports functions that return 'String' values containing codes +-- in accordance with the \'ANSI\' standards for control character sequences +-- described in the documentation of module "System.Console.ANSI". +-- +-- The module "System.Console.ANSI" exports functions with the same names as +-- those in this module. On some versions of Windows, the terminal in use may +-- not be ANSI-capable. When that is the case, the same-named functions exported +-- by module "System.Console.ANSI" return \"\", for the reasons set out in the +-- documentation of that module. +-- +-- Consequently, if module "System.Console.ANSI" is also imported, this module +-- is intended to be imported qualified, to avoid name clashes with those +-- functions. For example: +-- +-- > import qualified System.Console.ANSI.Codes as ANSI +-- +module System.Console.ANSI.Codes + ( + -- * Basic data types + module System.Console.ANSI.Types + + -- * Cursor movement by character + , cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode + + -- * Cursor movement by line + , cursorUpLineCode, cursorDownLineCode + + -- * Directly changing cursor position + , setCursorColumnCode, setCursorPositionCode + + -- * Saving, restoring and reporting cursor position + , saveCursorCode, restoreCursorCode, reportCursorPositionCode + + -- * Clearing parts of the screen + , clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode + , clearScreenCode, clearFromCursorToLineEndCode + , clearFromCursorToLineBeginningCode, clearLineCode + + -- * Scrolling the screen + , scrollPageUpCode, scrollPageDownCode + + -- * Select Graphic Rendition mode: colors and other whizzy stuff + , setSGRCode + + -- * Cursor visibilty changes + , hideCursorCode, showCursorCode + + -- * Changing the title + -- | Thanks to Brandon S. Allbery and Curt Sampson for pointing me in the + -- right direction on xterm title setting on haskell-cafe. The "0" + -- signifies that both the title and "icon" text should be set: i.e. the + -- text for the window in the Start bar (or similar) as well as that in + -- the actual window title. This is chosen for consistent behaviour + -- between Unixes and Windows. + , setTitleCode + + -- * Utilities + , colorToCode, csi, sgrToCode + ) where + +import Data.List (intersperse) + +import Data.Colour.SRGB (toSRGB24, RGB (..)) + +import System.Console.ANSI.Types + +-- | 'csi' @parameters controlFunction@, where @parameters@ is a list of 'Int', +-- returns the control sequence comprising the control function CONTROL +-- SEQUENCE INTRODUCER (CSI) followed by the parameter(s) (separated by \';\') +-- and ending with the @controlFunction@ character(s) that identifies the +-- control function. +csi :: [Int] -- ^ List of parameters for the control sequence + -> String -- ^ Character(s) that identify the control function + -> String +csi args code = "\ESC[" ++ concat (intersperse ";" (map show args)) ++ code + +-- | 'colorToCode' @color@ returns the 0-based index of the color (one of the +-- eight colors in the standard). +colorToCode :: Color -> Int +colorToCode color = case color of + Black -> 0 + Red -> 1 + Green -> 2 + Yellow -> 3 + Blue -> 4 + Magenta -> 5 + Cyan -> 6 + White -> 7 + +-- | 'sgrToCode' @sgr@ returns the parameter of the SELECT GRAPHIC RENDITION +-- (SGR) aspect identified by @sgr@. +sgrToCode :: SGR -- ^ The SGR aspect + -> [Int] +sgrToCode sgr = case sgr of + Reset -> [0] + SetConsoleIntensity intensity -> case intensity of + BoldIntensity -> [1] + FaintIntensity -> [2] + NormalIntensity -> [22] + SetItalicized True -> [3] + SetItalicized False -> [23] + SetUnderlining underlining -> case underlining of + SingleUnderline -> [4] + DoubleUnderline -> [21] + NoUnderline -> [24] + SetBlinkSpeed blink_speed -> case blink_speed of + SlowBlink -> [5] + RapidBlink -> [6] + NoBlink -> [25] + SetVisible False -> [8] + SetVisible True -> [28] + SetSwapForegroundBackground True -> [7] + SetSwapForegroundBackground False -> [27] + SetColor Foreground Dull color -> [30 + colorToCode color] + SetColor Foreground Vivid color -> [90 + colorToCode color] + SetColor Background Dull color -> [40 + colorToCode color] + SetColor Background Vivid color -> [100 + colorToCode color] + SetRGBColor Foreground color -> [38, 2] ++ toRGB color + SetRGBColor Background color -> [48, 2] ++ toRGB color + where + toRGB color = let RGB r g b = toSRGB24 color + in map fromIntegral [r, g, b] + +cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode + :: Int -- ^ Number of lines or characters to move + -> String +cursorUpCode n = csi [n] "A" +cursorDownCode n = csi [n] "B" +cursorForwardCode n = csi [n] "C" +cursorBackwardCode n = csi [n] "D" + +cursorDownLineCode, cursorUpLineCode :: Int -- ^ Number of lines to move + -> String +cursorDownLineCode n = csi [n] "E" +cursorUpLineCode n = csi [n] "F" + +setCursorColumnCode :: Int -- ^ 0-based column to move to + -> String +setCursorColumnCode n = csi [n + 1] "G" + +setCursorPositionCode :: Int -- ^ 0-based row to move to + -> Int -- ^ 0-based column to move to + -> String +setCursorPositionCode n m = csi [n + 1, m + 1] "H" + +saveCursorCode, restoreCursorCode, reportCursorPositionCode :: String +saveCursorCode = "\ESC7" +restoreCursorCode = "\ESC8" +reportCursorPositionCode = csi [] "6n" + +clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode, + clearScreenCode :: String +clearFromCursorToLineEndCode, clearFromCursorToLineBeginningCode, + clearLineCode :: String + +clearFromCursorToScreenEndCode = csi [0] "J" +clearFromCursorToScreenBeginningCode = csi [1] "J" +clearScreenCode = csi [2] "J" +clearFromCursorToLineEndCode = csi [0] "K" +clearFromCursorToLineBeginningCode = csi [1] "K" +clearLineCode = csi [2] "K" + +scrollPageUpCode, scrollPageDownCode :: Int -- ^ Number of lines to scroll by + -> String +scrollPageUpCode n = csi [n] "S" +scrollPageDownCode n = csi [n] "T" + +setSGRCode :: [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied + -- left to right. + -> String +setSGRCode sgrs = csi (concatMap sgrToCode sgrs) "m" + +hideCursorCode, showCursorCode :: String +hideCursorCode = csi [] "?25l" +showCursorCode = csi [] "?25h" + + +-- | XTerm control sequence to set the Icon Name and Window Title. +setTitleCode :: String -- ^ New Icon Name and Window Title + -> String +setTitleCode title = "\ESC]0;" ++ filter (/= '\007') title ++ "\007"
+ src/System/Console/ANSI/Types.hs view
@@ -0,0 +1,71 @@+-- | Types used to represent SELECT GRAPHIC RENDITION (SGR) aspects. +module System.Console.ANSI.Types + ( + SGR (..) + , ConsoleLayer (..) + , Color (..) + , ColorIntensity (..) + , ConsoleIntensity (..) + , Underlining (..) + , BlinkSpeed (..) + ) where + +import Data.Ix (Ix) + +import Data.Colour (Colour) + +-- | ANSI colors: come in various intensities, which are controlled by +-- 'ColorIntensity' +data Color = Black + | Red + | Green + | Yellow + | Blue + | Magenta + | Cyan + | White + deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) + +-- | ANSI colors come in two intensities +data ColorIntensity = Dull + | Vivid + deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) + +-- | ANSI colors can be set on two different layers +data ConsoleLayer = Foreground + | Background + deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) + +-- | ANSI blink speeds: values other than 'NoBlink' are not widely supported +data BlinkSpeed = SlowBlink -- ^ Less than 150 blinks per minute + | RapidBlink -- ^ More than 150 blinks per minute + | NoBlink + deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) + +-- | ANSI text underlining +data Underlining = SingleUnderline + | DoubleUnderline -- ^ Not widely supported + | NoUnderline + deriving (Eq, Ord, Bounded ,Enum, Show, Read, Ix) + +-- | ANSI general console intensity: usually treated as setting the font style +-- (e.g. 'BoldIntensity' causes text to be bold) +data ConsoleIntensity = BoldIntensity + | FaintIntensity -- ^ Not widely supported: sometimes + -- treated as concealing text + | NormalIntensity + deriving (Eq, Ord, Bounded, Enum, Show, Read, Ix) + +-- | ANSI Select Graphic Rendition command +data SGR = Reset + | SetConsoleIntensity !ConsoleIntensity + | SetItalicized !Bool -- ^ Not widely supported: sometimes treated as + -- swapping foreground and background + | SetUnderlining !Underlining + | SetBlinkSpeed !BlinkSpeed + | SetVisible !Bool -- ^ Not widely supported + | SetSwapForegroundBackground !Bool + | SetColor !ConsoleLayer !ColorIntensity !Color + | SetRGBColor !ConsoleLayer !(Colour Float) -- ^ Supported from Windows 10 + -- Creators Update + deriving (Eq, Show, Read)
+ src/System/Console/ANSI/Unix.hs view
@@ -0,0 +1,100 @@+{-# OPTIONS_HADDOCK hide #-} + +module System.Console.ANSI.Unix + ( +-- This file contains code that is common to modules +-- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module +-- exports and the associated Haddock documentation. +#include "Exports-Include.hs" + ) where + +import Control.Exception.Base (bracket) +import System.IO (BufferMode (..), Handle, hFlush, hGetBuffering, hGetEcho, + hIsTerminalDevice, hPutStr, hSetBuffering, hSetEcho, stdin, stdout) +import Text.ParserCombinators.ReadP (readP_to_S) + +import System.Console.ANSI.Codes +import System.Console.ANSI.Types + +-- This file contains code that is common to modules System.Console.ANSI.Unix, +-- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as +-- type signatures and the definition of functions specific to stdout in terms +-- of the corresponding more general functions, inclduding the related Haddock +-- documentation. +#include "Common-Include.hs" +-- This file contains code that is common save that different code is required +-- in the case of the module System.Console.ANSI.Windows.Emulator (see the file +-- Common-Include-Emulator.hs in respect of the latter). +#include "Common-Include-Enabled.hs" + +hCursorUp h n = hPutStr h $ cursorUpCode n +hCursorDown h n = hPutStr h $ cursorDownCode n +hCursorForward h n = hPutStr h $ cursorForwardCode n +hCursorBackward h n = hPutStr h $ cursorBackwardCode n + +hCursorDownLine h n = hPutStr h $ cursorDownLineCode n +hCursorUpLine h n = hPutStr h $ cursorUpLineCode n + +hSetCursorColumn h n = hPutStr h $ setCursorColumnCode n +hSetCursorPosition h n m = hPutStr h $ setCursorPositionCode n m + +hSaveCursor h = hPutStr h saveCursorCode +hRestoreCursor h = hPutStr h restoreCursorCode +hReportCursorPosition h = hPutStr h reportCursorPositionCode + +hClearFromCursorToScreenEnd h = hPutStr h clearFromCursorToScreenEndCode +hClearFromCursorToScreenBeginning h + = hPutStr h clearFromCursorToScreenBeginningCode +hClearScreen h = hPutStr h clearScreenCode + +hClearFromCursorToLineEnd h = hPutStr h clearFromCursorToLineEndCode +hClearFromCursorToLineBeginning h = hPutStr h clearFromCursorToLineBeginningCode +hClearLine h = hPutStr h clearLineCode + +hScrollPageUp h n = hPutStr h $ scrollPageUpCode n +hScrollPageDown h n = hPutStr h $ scrollPageDownCode n + +hSetSGR h sgrs = hPutStr h $ setSGRCode sgrs + +hHideCursor h = hPutStr h hideCursorCode +hShowCursor h = hPutStr h showCursorCode + +hSetTitle h title = hPutStr h $ setTitleCode title + +-- getReportedCursorPosition :: IO String +-- (See Common-Include.hs for Haddock documentation) +getReportedCursorPosition = bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do + hSetEcho stdin False -- Turn echo off + get + where + get = do + c <- getChar + if c == '\ESC' + then get' [c] + else return [c] -- If the first character is not the expected \ESC then + -- give up. This provides a modicom of protection against + -- unexpected data in the input stream. + get' s = do + c <- getChar + if c /= 'R' + then get' (c:s) -- Continue building the list, until the expected 'R' + -- character is obtained. Build the list in reverse order, + -- in order to avoid O(n^2) complexity. + else return $ reverse (c:s) -- Reverse the order of the built list. + +-- getCursorPosition :: IO (Maybe (Int, Int)) +-- (See Common-Include.hs for Haddock documentation) +getCursorPosition = do + input <- bracket (hGetBuffering stdin) (hSetBuffering stdin) $ \_ -> do + hSetBuffering stdin NoBuffering -- set no buffering (the contents of the + -- buffer will be discarded, so this needs + -- to be done before the cursor positon is + -- emitted) + reportCursorPosition + hFlush stdout -- ensure the report cursor position code is sent to the + -- operating system + getReportedCursorPosition + case readP_to_S cursorPosition input of + [] -> return Nothing + [((row, col),_)] -> return $ Just (row, col) + (_:_) -> return Nothing
+ src/System/Console/ANSI/Windows.hs view
@@ -0,0 +1,187 @@+{-# OPTIONS_HADDOCK hide #-} + +module System.Console.ANSI.Windows + ( +-- This file contains code that is common to modules +-- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module +-- exports and the associated Haddock documentation. +#include "Exports-Include.hs" + ) where + +import System.IO (Handle, hIsTerminalDevice, stdout) + +import System.Console.ANSI.Types +import qualified System.Console.ANSI.Unix as U +import System.Console.ANSI.Windows.Detect (ANSIEnabledStatus (..), + ConsoleDefaultState (..), isANSIEnabled) +import qualified System.Console.ANSI.Windows.Emulator as E + +-- This file contains code that is common to modules System.Console.ANSI.Unix, +-- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as +-- type signatures and the definition of functions specific to stdout in terms +-- of the corresponding more general functions, inclduding the related Haddock +-- documentation. +#include "Common-Include.hs" +-- This file contains code that is common save that different code is required +-- in the case of the module System.Console.ANSI.Windows.Emulator (see the file +-- Common-Include-Emulator.hs in respect of the latter). +#include "Common-Include-Enabled.hs" + +-- | A helper function which returns the native or emulated version, depending +-- on `isANSIEnabled`. +nativeOrEmulated :: a -> a -> a +nativeOrEmulated native emulated = case isANSIEnabled of + ANSIEnabled -> native + NotANSIEnabled _ -> emulated + +-- | A helper function which returns the native or emulated version, depending +-- on `isANSIEnabled`, where the emulator uses the default console state. +nativeOrEmulatedWithDefault :: a -> (ConsoleDefaultState -> a) -> a +nativeOrEmulatedWithDefault native emulated = case isANSIEnabled of + ANSIEnabled -> native + NotANSIEnabled def -> emulated def + + +-- * Cursor movement by character +hCursorUp = nativeOrEmulated U.hCursorUp E.hCursorUp +hCursorDown = nativeOrEmulated U.hCursorDown E.hCursorDown +hCursorForward = nativeOrEmulated U.hCursorForward E.hCursorForward +hCursorBackward = nativeOrEmulated U.hCursorBackward E.hCursorBackward + +cursorUpCode :: Int -> String +cursorUpCode = nativeOrEmulated U.cursorUpCode E.cursorUpCode + +cursorDownCode :: Int -> String +cursorDownCode = nativeOrEmulated U.cursorDownCode E.cursorDownCode + +cursorForwardCode :: Int -> String +cursorForwardCode = nativeOrEmulated U.cursorForwardCode E.cursorForwardCode + +cursorBackwardCode :: Int -> String +cursorBackwardCode = nativeOrEmulated U.cursorBackwardCode E.cursorBackwardCode + +-- * Cursor movement by line +hCursorUpLine = nativeOrEmulated U.hCursorUpLine E.hCursorUpLine +hCursorDownLine = nativeOrEmulated U.hCursorDownLine E.hCursorDownLine + +cursorUpLineCode :: Int -> String +cursorUpLineCode = nativeOrEmulated U.cursorUpLineCode E.cursorUpLineCode + +cursorDownLineCode :: Int -> String +cursorDownLineCode = nativeOrEmulated U.cursorDownLineCode E.cursorDownLineCode + +-- * Directly changing cursor position +hSetCursorColumn = nativeOrEmulated U.hSetCursorColumn E.hSetCursorColumn + +setCursorColumnCode :: Int -> String +setCursorColumnCode = nativeOrEmulated + U.setCursorColumnCode E.setCursorColumnCode + +hSetCursorPosition = nativeOrEmulated U.hSetCursorPosition E.hSetCursorPosition + +setCursorPositionCode :: Int -> Int -> String +setCursorPositionCode = nativeOrEmulated + U.setCursorPositionCode E.setCursorPositionCode + +-- * Saving, restoring and reporting cursor position +hSaveCursor = nativeOrEmulated U.hSaveCursor E.hSaveCursor +hRestoreCursor = nativeOrEmulated U.hRestoreCursor E.hRestoreCursor +hReportCursorPosition = nativeOrEmulated + U.hReportCursorPosition E.hReportCursorPosition + +saveCursorCode :: String +saveCursorCode = nativeOrEmulated U.saveCursorCode E.saveCursorCode + +restoreCursorCode :: String +restoreCursorCode = nativeOrEmulated U.restoreCursorCode E.restoreCursorCode + +reportCursorPositionCode :: String +reportCursorPositionCode = nativeOrEmulated + U.reportCursorPositionCode E.reportCursorPositionCode + +-- * Clearing parts of the screen +hClearFromCursorToScreenEnd = nativeOrEmulatedWithDefault + U.hClearFromCursorToScreenEnd E.hClearFromCursorToScreenEnd +hClearFromCursorToScreenBeginning = nativeOrEmulatedWithDefault + U.hClearFromCursorToScreenBeginning E.hClearFromCursorToScreenBeginning +hClearScreen = nativeOrEmulatedWithDefault U.hClearScreen E.hClearScreen + +clearFromCursorToScreenEndCode :: String +clearFromCursorToScreenEndCode = nativeOrEmulated + U.clearFromCursorToScreenEndCode E.clearFromCursorToScreenEndCode + +clearFromCursorToScreenBeginningCode :: String +clearFromCursorToScreenBeginningCode = nativeOrEmulated + U.clearFromCursorToScreenBeginningCode E.clearFromCursorToScreenBeginningCode + +clearScreenCode :: String +clearScreenCode = nativeOrEmulated U.clearScreenCode E.clearScreenCode + +hClearFromCursorToLineEnd = nativeOrEmulatedWithDefault + U.hClearFromCursorToLineEnd E.hClearFromCursorToLineEnd +hClearFromCursorToLineBeginning = nativeOrEmulatedWithDefault + U.hClearFromCursorToLineBeginning E.hClearFromCursorToLineBeginning +hClearLine = nativeOrEmulatedWithDefault U.hClearLine E.hClearLine + +clearFromCursorToLineEndCode :: String +clearFromCursorToLineEndCode = nativeOrEmulated + U.clearFromCursorToLineEndCode E.clearFromCursorToLineEndCode + +clearFromCursorToLineBeginningCode :: String +clearFromCursorToLineBeginningCode = nativeOrEmulated + U.clearFromCursorToLineBeginningCode E.clearFromCursorToLineBeginningCode + +clearLineCode :: String +clearLineCode = nativeOrEmulated U.clearLineCode E.clearLineCode + +-- * Scrolling the screen +hScrollPageUp = nativeOrEmulatedWithDefault U.hScrollPageUp E.hScrollPageUp +hScrollPageDown = nativeOrEmulatedWithDefault U.hScrollPageDown E.hScrollPageDown + +scrollPageUpCode :: Int -> String +scrollPageUpCode = nativeOrEmulated U.scrollPageUpCode E.scrollPageUpCode + +scrollPageDownCode :: Int -> String +scrollPageDownCode = nativeOrEmulated U.scrollPageDownCode E.scrollPageDownCode + +-- * Select Graphic Rendition mode: colors and other whizzy stuff +-- +-- The following SGR codes are NOT implemented by Windows 10 Threshold 2: +-- 2 SetConsoleIntensity FaintIntensity +-- 3 SetItalicized True +-- 5 SetBlinkSpeed SlowBlink +-- 6 SetBlinkSpeed RapidBlink +-- 8 SetVisible False +-- 21 SetUnderlining DoubleUnderline +-- 23 SetItalicized False +-- 25 SetBlinkSpeed NoBlink +-- 28 SetVisible True + +hSetSGR = nativeOrEmulatedWithDefault U.hSetSGR E.hSetSGR + +setSGRCode :: [SGR] -> String +setSGRCode = nativeOrEmulated U.setSGRCode E.setSGRCode + +-- * Cursor visibilty changes +hHideCursor = nativeOrEmulated U.hHideCursor E.hHideCursor +hShowCursor = nativeOrEmulated U.hShowCursor E.hShowCursor + +hideCursorCode :: String +hideCursorCode = nativeOrEmulated U.hideCursorCode E.hideCursorCode + +showCursorCode :: String +showCursorCode = nativeOrEmulated U.showCursorCode E.showCursorCode + +-- * Changing the title +hSetTitle = nativeOrEmulated U.hSetTitle E.hSetTitle + +setTitleCode :: String -> String +setTitleCode = nativeOrEmulated U.setTitleCode E.setTitleCode + +-- getReportedCursorPosition :: IO String +-- (See Common-Include.hs for Haddock documentation) +getReportedCursorPosition = E.getReportedCursorPosition + +-- getCursorPosition :: IO (Maybe (Int, Int)) +-- (See Common-Include.hs for Haddock documentation) +getCursorPosition = E.getCursorPosition
+ src/System/Console/ANSI/Windows/Detect.hs view
@@ -0,0 +1,123 @@+{-# OPTIONS_HADDOCK hide #-} + +module System.Console.ANSI.Windows.Detect + ( + ANSIEnabledStatus (..) + , ConsoleDefaultState (..) + , isANSIEnabled + ) where + +import Control.Exception (SomeException(..), throwIO, try) +import Data.Bits ((.&.), (.|.)) +-- 'lookupEnv' is not available until base-4.6.0.0 (GHC 7.6.1) +import System.Environment.Compat (lookupEnv) +import System.IO.Unsafe (unsafePerformIO) + +import System.Console.ANSI.Windows.Foreign (bACKGROUND_INTENSE_WHITE, + ConsoleException(..), CONSOLE_SCREEN_BUFFER_INFO (..), DWORD, + eNABLE_VIRTUAL_TERMINAL_PROCESSING, fOREGROUND_INTENSE_WHITE, + getConsoleMode, getConsoleScreenBufferInfo, getStdHandle, HANDLE, + iNVALID_HANDLE_VALUE, nullHANDLE, setConsoleMode, sTD_OUTPUT_HANDLE, WORD) + +-- | The default state of the console +data ConsoleDefaultState = ConsoleDefaultState + { defaultForegroundAttributes :: WORD -- ^ Foreground attributes + , defaultBackgroundAttributes :: WORD -- ^ Background attributes + } deriving (Eq, Show) + +-- | The status of the console as regards it being ANSI-enabled or not +-- ANSI-enabled. +data ANSIEnabledStatus + = ANSIEnabled -- ^ ANSI-enabled + | NotANSIEnabled ConsoleDefaultState -- ^ Not ANSI-enabled (including the + -- state of the console when that status + -- was determined) + deriving (Eq, Show) + +-- | This function assumes that once it is first established whether or not the +-- Windows console is ANSI-enabled, that will not change. If the console is not +-- ANSI-enabled, the state of the console is considered to be its default state. +{-# NOINLINE isANSIEnabled #-} +isANSIEnabled :: ANSIEnabledStatus +isANSIEnabled = unsafePerformIO $ do + e <- safeIsANSIEnabled + if e + then return ANSIEnabled + else do + hOut <- getValidStdHandle sTD_OUTPUT_HANDLE + info <- getConsoleScreenBufferInfo hOut + let attributes = csbi_attributes info + fgAttributes = attributes .&. fOREGROUND_INTENSE_WHITE + bgAttributes = attributes .&. bACKGROUND_INTENSE_WHITE + consoleDefaultState = ConsoleDefaultState + { defaultForegroundAttributes = fgAttributes + , defaultBackgroundAttributes = bgAttributes } + return $ NotANSIEnabled consoleDefaultState + +-- This function takes the following approach. If the environment variable +-- APPVEYOR exists and is set to 'True', it assumes the code is running in the +-- Appveyor build environment and that the build console is ANSI-enabled. +-- Otherwise, if the environment variable TERM exists and is not set to 'dumb' +-- or 'msys' (see below), it assumes the console is ANSI-enabled. Otherwise, it +-- tries to get a valid standard output handle. If that does not fail, it +-- tried to get a ConHost console mode. If that fails, it assumes that the +-- handle is ANSI-enabled. Otherwise, it tries to enable virtual terminal +-- processing. If that fails, it assumes the console is not ANSI-enabled. +-- +-- In Git Shell, if Command Prompt or PowerShell are used, the environment +-- variable TERM is set to 'msys'. If 'Git Bash' (mintty) is used, TERM is set +-- to 'xterm' (by default). +safeIsANSIEnabled :: IO Bool +safeIsANSIEnabled = do + appveyor <- lookupEnv "APPVEYOR" + case appveyor of + Just "True" -> return True + _ -> do + term <- lookupEnv "TERM" + case term of + Just "dumb" -> return False + Just "msys" -> doesEnableANSIOutSucceed + Just _ -> return True + Nothing -> doesEnableANSIOutSucceed + +-- This function returns whether or not an attempt to enable virtual terminal +-- processing succeeded, or was deemed to succeed, in the IO monad. +doesEnableANSIOutSucceed :: IO Bool +doesEnableANSIOutSucceed = do + result <- try enableANSIOut :: IO (Either SomeException ()) + case result of + Left _ -> return False + Right () -> return True + +-- This function tries to get a valid handle on the standard output and throws +-- an exception if it cannot. It then tries to get a ConHost console mode for +-- that handle. If it can not, it assumes that the standard output handle is +-- ANSI-enabled. If it can, it tries to enable virtual terminal processing and +-- throws an exception if it can not. +enableANSIOut :: IO () +enableANSIOut = do + hOut <- getValidStdHandle sTD_OUTPUT_HANDLE + result <- conHostConsoleMode hOut + case result of + Nothing -> return () -- assume hOut is ANSI-enabled + Just mOut -> do + let mOut' = mOut .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING + setConsoleMode hOut mOut' + +-- This function tries to get a valid standard handle and throws an exception if +-- it cannot. +getValidStdHandle :: DWORD -> IO HANDLE +getValidStdHandle nStdHandle = do + h <- getStdHandle nStdHandle + if h == iNVALID_HANDLE_VALUE || h == nullHANDLE + then throwIO $ ConsoleException 6 -- Invalid handle or no handle + else return h + +-- The function tries to get a ConHost console mode from a handle that is +-- assumed to be a valid standard handle (see getValidStdHandle) +conHostConsoleMode :: HANDLE -> IO (Maybe DWORD) +conHostConsoleMode h = do + result <- try (getConsoleMode h) :: IO (Either SomeException DWORD) + case result of + Left _ -> return Nothing + Right mode -> return (Just mode)
+ src/System/Console/ANSI/Windows/Emulator.hs view
@@ -0,0 +1,465 @@+{-# OPTIONS_HADDOCK hide #-} + +module System.Console.ANSI.Windows.Emulator + ( +#include "Exports-Include.hs" + ) where + +import Control.Exception (catchJust, IOException) +import qualified Control.Exception as CE (catch) +import Control.Monad (unless) +import Data.Bits ((.&.), (.|.), complement, shiftL, shiftR) +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import Data.List (foldl', minimumBy) +import Data.Maybe (mapMaybe) +import qualified Data.Map.Strict as Map (Map, empty, insert, lookup) +import System.IO (Handle, hFlush, hIsTerminalDevice, stdin, stdout) +import System.IO.Unsafe (unsafePerformIO) +import Text.ParserCombinators.ReadP (readP_to_S) + +import Data.Colour (Colour) +import Data.Colour.Names (black, blue, cyan, green, grey, lime, magenta, maroon, + navy, olive, purple, red, silver, teal, white, yellow) +import Data.Colour.SRGB (RGB (..), toSRGB) + +import System.Console.ANSI.Types +import qualified System.Console.ANSI.Unix as Unix +import System.Console.ANSI.Windows.Detect +import System.Console.ANSI.Windows.Emulator.Codes +import System.Console.ANSI.Windows.Foreign + +-- This file contains code that is common to modules System.Console.ANSI.Unix, +-- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as +-- type signatures and the definition of functions specific to stdout in terms +-- of the corresponding more general functions, inclduding the related Haddock +-- documentation. +#include "Common-Include.hs" +-- This file contains code that is required in the case of the module +-- System.Console.ANSI.Windows.Emulator and differs from the common code in +-- file Common-Include-Enabled.hs. +#include "Common-Include-Emulator.hs" + +withHandle :: Handle -> (HANDLE -> IO a) -> IO a +withHandle handle action = do + -- It's VERY IMPORTANT that we flush before issuing any sort of Windows API + -- call to change the console because on Windows the arrival of + -- API-initiated state changes is not necessarily synchronised with that of + -- the text they are attempting to modify. + hFlush handle + withHandleToHANDLE handle action + +-- Unfortunately, the emulator is not perfect. In particular, it has a tendency +-- to die with exceptions about invalid handles when it is used with certain +-- Windows consoles (e.g. mintty, terminator, or cygwin sshd). +-- +-- This happens because in those environments the stdout family of handles are +-- not actually associated with a real console. +-- +-- My observation is that every time I've seen this in practice, the handle we +-- have instead of the actual console handle is there so that the terminal +-- supports ANSI escape codes. So 99% of the time, the correct thing to do is +-- just to fall back on the Unix module to output the ANSI codes and hope for +-- the best. +emulatorFallback :: IO a -> IO a -> IO a +emulatorFallback fallback first_try + = catchJust invalidHandle first_try (const fallback) + where + invalidHandle (ConsoleException 6) = Just () -- 6 is the Windows error code + -- for invalid handles + invalidHandle (_) = Nothing + +adjustCursorPosition :: HANDLE + -> (SHORT -> SHORT -> SHORT) + -> (SHORT -> SHORT -> SHORT) + -> IO () +adjustCursorPosition handle change_x change_y = do + screen_buffer_info <- getConsoleScreenBufferInfo handle + let window = csbi_window screen_buffer_info + (COORD x y) = csbi_cursor_position screen_buffer_info + cursor_pos'= COORD (change_x (rect_left window) x) + (change_y (rect_top window) y) + setConsoleCursorPosition handle cursor_pos' + +hCursorUp h n + = emulatorFallback (Unix.hCursorUp h n) $ withHandle h $ + \handle -> adjustCursorPosition handle (\_ x -> x) + (\_ y -> y - fromIntegral n) +hCursorDown h n + = emulatorFallback (Unix.hCursorDown h n) $ withHandle h $ + \handle -> adjustCursorPosition handle (\_ x -> x) + (\_ y -> y + fromIntegral n) +hCursorForward h n + = emulatorFallback (Unix.hCursorForward h n) $ withHandle h $ + \handle -> adjustCursorPosition handle (\_ x -> x + fromIntegral n) + (\_ y -> y) +hCursorBackward h n + = emulatorFallback (Unix.hCursorBackward h n) $ withHandle h $ + \handle -> adjustCursorPosition handle (\_ x -> x - fromIntegral n) + (\_ y -> y) + +adjustLine :: HANDLE -> (SHORT -> SHORT -> SHORT) -> IO () +adjustLine handle change_y + = adjustCursorPosition handle (\window_left _ -> window_left) change_y + +hCursorDownLine h n + = emulatorFallback (Unix.hCursorDownLine h n) $ withHandle h $ + \handle -> adjustLine handle (\_ y -> y + fromIntegral n) + +hCursorUpLine h n + = emulatorFallback (Unix.hCursorUpLine h n) $ withHandle h $ + \handle -> adjustLine handle (\_ y -> y - fromIntegral n) + +hSetCursorColumn h x + = emulatorFallback (Unix.hSetCursorColumn h x) $ withHandle h $ + \handle -> adjustCursorPosition handle + (\window_left _ -> window_left + fromIntegral x) (\_ y -> y) + +hSetCursorPosition h y x + = emulatorFallback (Unix.hSetCursorPosition h y x) $ withHandle h $ + \handle -> adjustCursorPosition handle + (\window_left _ -> window_left + fromIntegral x) + (\window_top _ -> window_top + fromIntegral y) + +clearChar :: WCHAR +clearChar = charToWCHAR ' ' + +-- | The 'clear' attribute is equated with the default background attributes. +clearAttribute :: ConsoleDefaultState -> WORD +clearAttribute = defaultBackgroundAttributes + +hClearScreenFraction + :: ConsoleDefaultState + -> HANDLE + -> (SMALL_RECT -> COORD -> (SHORT, SHORT, SHORT, SHORT, SHORT, SHORT)) + -> IO () +hClearScreenFraction cds handle fraction_finder = do + screen_buffer_info <- getConsoleScreenBufferInfo handle + let window = csbi_window screen_buffer_info + cursor_pos = csbi_cursor_position screen_buffer_info + (left, top, right, bottom, start_x, end_x) + = fraction_finder window cursor_pos + mapM_ (fill_line left top right bottom start_x end_x) [top .. bottom] + where + fill_line left top right bottom start_x end_x y = do + let left' = if y == top then start_x else left + right' = if y == bottom then end_x else right + fill_cursor_pos = COORD left' y + fill_length = fromIntegral $ right' - left' + 1 + _ <- fillConsoleOutputCharacter handle clearChar fill_length fill_cursor_pos + fillConsoleOutputAttribute handle (clearAttribute cds) fill_length + fill_cursor_pos + +hClearFromCursorToScreenEnd cds h + = emulatorFallback (Unix.hClearFromCursorToScreenEnd h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window cursor_pos = (left, top, right, bottom, start_x, right) + where + SMALL_RECT (COORD left _) (COORD right bottom) = window + COORD start_x top = cursor_pos + +hClearFromCursorToScreenBeginning cds h + = emulatorFallback (Unix.hClearFromCursorToScreenBeginning h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window cursor_pos = (left, top, right, bottom, left, end_x) + where + SMALL_RECT (COORD left top) (COORD right _) = window + COORD end_x bottom = cursor_pos + +hClearScreen cds h + = emulatorFallback (Unix.hClearScreen h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window _ = (left, top, right, bottom, left, right) + where + SMALL_RECT (COORD left top) (COORD right bottom) = window + +hClearFromCursorToLineEnd cds h + = emulatorFallback (Unix.hClearFromCursorToLineEnd h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window cursor_pos = (left, y, right, y, start_x, right) + where + SMALL_RECT (COORD left _) (COORD right _) = window + COORD start_x y = cursor_pos + +hClearFromCursorToLineBeginning cds h + = emulatorFallback (Unix.hClearFromCursorToLineBeginning h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window cursor_pos = (left, y, right, y, left, end_x) + where + SMALL_RECT (COORD left _) (COORD right _) = window + COORD end_x y = cursor_pos + +hClearLine cds h + = emulatorFallback (Unix.hClearLine h) $ withHandle h $ + \handle -> hClearScreenFraction cds handle go + where + go window cursor_pos = (left, y, right, y, left, right) + where + SMALL_RECT (COORD left _) (COORD right _) = window + COORD _ y = cursor_pos + +hScrollPage + :: ConsoleDefaultState -- ^ The default console state + -> HANDLE + -> Int + -> IO () +hScrollPage cds handle new_origin_y = do + screen_buffer_info <- getConsoleScreenBufferInfo handle + let fill = CHAR_INFO clearChar (clearAttribute cds) + window = csbi_window screen_buffer_info + origin = COORD (rect_left window) + (rect_top window + fromIntegral new_origin_y) + scrollConsoleScreenBuffer handle window Nothing origin fill + +hScrollPageUp cds h n + = emulatorFallback (Unix.hScrollPageUp h n) $ withHandle h $ + \handle -> hScrollPage cds handle (negate n) + +hScrollPageDown cds h n + = emulatorFallback (Unix.hScrollPageDown h n) $ withHandle h $ + \handle -> hScrollPage cds handle n + +{-# INLINE applyANSIColorToAttribute #-} +applyANSIColorToAttribute :: WORD -> WORD -> WORD -> Color -> WORD -> WORD +applyANSIColorToAttribute rED gREEN bLUE color attribute = case color of + Black -> attribute' + Red -> attribute' .|. rED + Green -> attribute' .|. gREEN + Yellow -> attribute' .|. rED .|. gREEN + Blue -> attribute' .|. bLUE + Magenta -> attribute' .|. rED .|. bLUE + Cyan -> attribute' .|. gREEN .|. bLUE + White -> attribute' .|. wHITE + where + wHITE = rED .|. gREEN .|. bLUE + attribute' = attribute .&. (complement wHITE) + +applyForegroundANSIColorToAttribute, applyBackgroundANSIColorToAttribute + :: Color + -> WORD + -> WORD +applyForegroundANSIColorToAttribute + = applyANSIColorToAttribute fOREGROUND_RED fOREGROUND_GREEN fOREGROUND_BLUE +applyBackgroundANSIColorToAttribute + = applyANSIColorToAttribute bACKGROUND_RED bACKGROUND_GREEN bACKGROUND_BLUE + +swapForegroundBackgroundColors :: WORD -> WORD +swapForegroundBackgroundColors attribute + = clean_attribute .|. foreground_attribute' .|. background_attribute' + where + foreground_attribute = attribute .&. fOREGROUND_INTENSE_WHITE + background_attribute = attribute .&. bACKGROUND_INTENSE_WHITE + clean_attribute = attribute .&. complement + (fOREGROUND_INTENSE_WHITE .|. bACKGROUND_INTENSE_WHITE) + foreground_attribute' = background_attribute `shiftR` 4 + background_attribute' = foreground_attribute `shiftL` 4 + +applyANSISGRToAttribute :: WORD -> SGR -> WORD -> WORD +applyANSISGRToAttribute def sgr attribute = case sgr of + Reset -> def + SetConsoleIntensity intensity -> case intensity of + BoldIntensity -> attribute .|. iNTENSITY + FaintIntensity -> attribute .&. (complement iNTENSITY) -- Not supported + NormalIntensity -> attribute .&. (complement iNTENSITY) + SetItalicized _ -> attribute -- Not supported + SetUnderlining underlining -> case underlining of + NoUnderline -> attribute .&. (complement cOMMON_LVB_UNDERSCORE) + _ -> attribute .|. cOMMON_LVB_UNDERSCORE -- Not supported, since + -- cOMMON_LVB_UNDERSCORE seems to have no effect + SetBlinkSpeed _ -> attribute -- Not supported + SetVisible _ -> attribute -- Not supported + -- The cOMMON_LVB_REVERSE_VIDEO doesn't actually appear to have any affect + -- on the colors being displayed, so the emulator just uses it to carry + -- information and implements the color-swapping behaviour itself. Bit of a + -- hack, I guess :-) + SetSwapForegroundBackground True -> + -- Check if the color-swapping flag is already set + if attribute .&. cOMMON_LVB_REVERSE_VIDEO /= 0 + then attribute + else swapForegroundBackgroundColors attribute .|. cOMMON_LVB_REVERSE_VIDEO + SetSwapForegroundBackground False -> + -- Check if the color-swapping flag is already not set + if attribute .&. cOMMON_LVB_REVERSE_VIDEO == 0 + then attribute + else swapForegroundBackgroundColors attribute .&. + (complement cOMMON_LVB_REVERSE_VIDEO) + SetColor Foreground Dull color -> applyForegroundANSIColorToAttribute color + (attribute .&. (complement fOREGROUND_INTENSITY)) + SetColor Foreground Vivid color -> applyForegroundANSIColorToAttribute color + (attribute .|. fOREGROUND_INTENSITY) + SetColor Background Dull color -> applyBackgroundANSIColorToAttribute color + (attribute .&. (complement bACKGROUND_INTENSITY)) + SetColor Background Vivid color -> applyBackgroundANSIColorToAttribute color + (attribute .|. bACKGROUND_INTENSITY) + SetRGBColor Foreground color -> + let (colorIntensity, aNSIColor) = toANSIColor color + attribute' = case colorIntensity of + Dull -> attribute .&. complement fOREGROUND_INTENSITY + Vivid -> attribute .|. fOREGROUND_INTENSITY + in applyForegroundANSIColorToAttribute aNSIColor attribute' + SetRGBColor Background color -> + let (colorIntensity, aNSIColor) = toANSIColor color + attribute' = case colorIntensity of + Dull -> attribute .&. complement bACKGROUND_INTENSITY + Vivid -> attribute .|. bACKGROUND_INTENSITY + in applyBackgroundANSIColorToAttribute aNSIColor attribute' + where + iNTENSITY = fOREGROUND_INTENSITY + +hSetSGR cds h sgr + = emulatorFallback (Unix.hSetSGR h sgr) $ withHandle h $ \handle -> do + screen_buffer_info <- getConsoleScreenBufferInfo handle + let attribute = csbi_attributes screen_buffer_info + def = defaultForegroundAttributes cds + .|. defaultBackgroundAttributes cds + attribute' = foldl' (flip $ applyANSISGRToAttribute def) attribute + -- make [] equivalent to [Reset], as documented + (if null sgr then [Reset] else sgr) + setConsoleTextAttribute handle attribute' + +hChangeCursorVisibility :: HANDLE -> Bool -> IO () +hChangeCursorVisibility handle cursor_visible = do + cursor_info <- getConsoleCursorInfo handle + setConsoleCursorInfo handle + (cursor_info { cci_cursor_visible = cursor_visible }) + +hHideCursor h + = emulatorFallback (Unix.hHideCursor h) $ withHandle h $ + \handle -> hChangeCursorVisibility handle False +hShowCursor h + = emulatorFallback (Unix.hShowCursor h) $ withHandle h $ + \handle -> hChangeCursorVisibility handle True + +-- Windows only supports setting the terminal title on a process-wide basis, so +-- for now we will assume that that is what the user intended. This will fail if +-- they are sending the command over e.g. a network link... but that's not +-- really what I'm designing for. +hSetTitle h title + = emulatorFallback (Unix.hSetTitle h title) $ + withTString title $ setConsoleTitle + +cursorPositionRef :: IORef (Map.Map HANDLE COORD) +{-# NOINLINE cursorPositionRef #-} +cursorPositionRef = unsafePerformIO $ newIORef Map.empty + +hSaveCursor h + = emulatorFallback (Unix.hSaveCursor h) $ withHandle h $ \handle -> do + screen_buffer_info <- getConsoleScreenBufferInfo handle + m <- readIORef cursorPositionRef + writeIORef cursorPositionRef + (Map.insert handle (csbi_cursor_position screen_buffer_info) m) + +hRestoreCursor h + = emulatorFallback (Unix.hRestoreCursor h) $ withHandle h $ \handle -> do + m <- readIORef cursorPositionRef + let result = Map.lookup handle m + maybe (return ()) (setConsoleCursorPosition handle) result + +hReportCursorPosition h + = emulatorFallback (Unix.hReportCursorPosition h) $ withHandle h $ + \handle -> do + result <- getConsoleScreenBufferInfo handle + let (COORD cx cy) = csbi_cursor_position result + window = csbi_window result + x = cx - rect_left window + 1 + y = cy - rect_top window + 1 + hIn <- getStdHandle sTD_INPUT_HANDLE + _ <- writeConsoleInput hIn $ keyPresses $ + "\ESC[" ++ show y ++ ";" ++ show x ++ "R" + return () + +keyPress :: Char -> [INPUT_RECORD] +keyPress c = [keyDown, keyUp] + where + keyDown = key True + keyUp = key False + c' = UnicodeAsciiChar $ charToWCHAR c + key isDown = INPUT_RECORD kEY_EVENT $ + InputKeyEvent (KEY_EVENT_RECORD isDown 1 0 0 c' 0) + +keyPresses :: String -> [INPUT_RECORD] +keyPresses = concatMap keyPress + +aNSIColors :: [((ColorIntensity, Color), Colour Float)] +aNSIColors = [ ((Dull, Black), black) + , ((Dull, Blue), navy) + , ((Dull, Green), green) + , ((Dull, Cyan), teal) + , ((Dull, Red), maroon) + , ((Dull, Magenta), purple) + , ((Dull, Yellow), olive) + , ((Dull, White), silver) + , ((Vivid, Black), grey) + , ((Vivid, Blue), blue) + , ((Vivid, Green), lime) + , ((Vivid, Cyan), cyan) + , ((Vivid, Red), red) + , ((Vivid, Magenta), magenta) + , ((Vivid, Yellow), yellow) + , ((Vivid, White), white) ] + +toANSIColor :: Colour Float -> (ColorIntensity, Color) +toANSIColor color = fst $ minimumBy order aNSIColors + where + RGB r g b = toSRGB color + order (_, c1) (_, c2) = compare (dist c1) (dist c2) + dist c = let RGB r' g' b' = toSRGB c + dr = r' - r + dg = g' - g + db = b' - b + in dr * dr + dg * dg + db * db + +-- getReportedCursorPosition :: IO String +-- (See Common-Include.hs for Haddock documentation) +getReportedCursorPosition + = CE.catch getReportedCursorPosition' getCPExceptionHandler + where + getReportedCursorPosition' = withHandleToHANDLE stdin action + where + action hdl = do + n <- getNumberOfConsoleInputEvents hdl + if n == 0 + then return "" + else do + es <- readConsoleInput hdl n + return $ stringFromInputEvents es + stringFromInputEvents = cWcharsToChars . wCharsFromInputEvents + wCharsFromInputEvents = mapMaybe wCharFromInputEvent + wCharFromInputEvent e = if isKeyDownEvent + then Just (unicodeAsciiChar $ keyEventChar keyEventRecord) + else Nothing + where + eventType = inputEventType e + InputKeyEvent keyEventRecord = inputEvent e + isKeyDown = keyEventKeyDown keyEventRecord + isKeyDownEvent = eventType == 1 && isKeyDown + +-- getCursorPosition :: IO (Maybe (Int, Int)) +-- (See Common-Include.hs for Haddock documentation) +getCursorPosition = CE.catch getCursorPosition' getCPExceptionHandler + where + getCursorPosition' = do + withHandleToHANDLE stdin flush -- Flush the console input buffer + reportCursorPosition + hFlush stdout -- ensure the report cursor position code is sent to the + -- operating system + input <- getReportedCursorPosition + case readP_to_S cursorPosition input of + [] -> return Nothing + [((row, col),_)] -> return $ Just (row, col) + (_:_) -> return Nothing + where + flush hdl = do + n <- getNumberOfConsoleInputEvents hdl + unless (n == 0) (void $ readConsoleInput hdl n) + +getCPExceptionHandler :: IOException -> IO a +getCPExceptionHandler e = error msg + where + msg = "Error: " ++ show e ++ "\nThis error may be avoided by using a " ++ + "console based on the Win32 console of the Windows API, such as " ++ + "Command Prompt or PowerShell."
+ src/System/Console/ANSI/Windows/Emulator/Codes.hs view
@@ -0,0 +1,94 @@+{-# OPTIONS_HADDOCK hide #-} + +module System.Console.ANSI.Windows.Emulator.Codes + ( + -- * Cursor movement by character + cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode + + -- * Cursor movement by line + , cursorUpLineCode, cursorDownLineCode + + -- * Directly changing cursor position + , setCursorColumnCode, setCursorPositionCode + + -- * Saving, restoring and reporting cursor position + , saveCursorCode, restoreCursorCode, reportCursorPositionCode + + -- * Clearing parts of the screen + , clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode + , clearScreenCode, clearFromCursorToLineEndCode + , clearFromCursorToLineBeginningCode, clearLineCode + + -- * Scrolling the screen + , scrollPageUpCode, scrollPageDownCode + + -- * Select Graphic Rendition mode: colors and other whizzy stuff + , setSGRCode + + -- * Cursor visibilty changes + , hideCursorCode, showCursorCode + + -- * Changing the title + , setTitleCode + ) where + +import System.Console.ANSI.Types + +cursorUpCode, cursorDownCode, cursorForwardCode, cursorBackwardCode + :: Int -- ^ Number of lines or characters to move + -> String +cursorUpCode _ = "" +cursorDownCode _ = "" +cursorForwardCode _ = "" +cursorBackwardCode _ = "" + +cursorDownLineCode, cursorUpLineCode :: Int -- ^ Number of lines to move + -> String +cursorDownLineCode _ = "" +cursorUpLineCode _ = "" + +setCursorColumnCode :: Int -- ^ 0-based column to move to + -> String +setCursorColumnCode _ = "" + +setCursorPositionCode :: Int -- ^ 0-based row to move to + -> Int -- ^ 0-based column to move to + -> String +setCursorPositionCode _ _ = "" + +saveCursorCode, restoreCursorCode, reportCursorPositionCode :: String +saveCursorCode = "" +restoreCursorCode = "" +reportCursorPositionCode = "" + +clearFromCursorToScreenEndCode, clearFromCursorToScreenBeginningCode, + clearScreenCode :: String +clearFromCursorToLineEndCode, clearFromCursorToLineBeginningCode, + clearLineCode :: String + +clearFromCursorToScreenEndCode = "" +clearFromCursorToScreenBeginningCode = "" +clearScreenCode = "" +clearFromCursorToLineEndCode = "" +clearFromCursorToLineBeginningCode = "" +clearLineCode = "" + +scrollPageUpCode, scrollPageDownCode :: Int -- ^ Number of lines to scroll by + -> String +scrollPageUpCode _ = "" +scrollPageDownCode _ = "" + +setSGRCode :: [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied + -- left to right. + -> String +setSGRCode _ = "" + +hideCursorCode, showCursorCode :: String +hideCursorCode = "" +showCursorCode = "" + +setTitleCode :: String -- ^ New title + -> String +setTitleCode _ = ""
+ src/System/Console/ANSI/Windows/Foreign.hs view
@@ -0,0 +1,651 @@+{-# OPTIONS_HADDOCK hide #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE DeriveDataTypeable #-} + +-- | "System.Win32.Console" is really very impoverished, so I have had to do all +-- the FFI myself. +module System.Console.ANSI.Windows.Foreign + ( + -- Re-exports from Win32.Types + BOOL, WORD, DWORD, WCHAR, HANDLE, iNVALID_HANDLE_VALUE, nullHANDLE, SHORT, + + -- 'Re-exports from System.Win32.Console.Extra' + INPUT_RECORD (..), INPUT_RECORD_EVENT (..), kEY_EVENT, + KEY_EVENT_RECORD (..), UNICODE_ASCII_CHAR (..), writeConsoleInput, + getNumberOfConsoleInputEvents, readConsoleInput, + + charToWCHAR, cWcharsToChars, + + COORD(..), SMALL_RECT(..), rect_top, rect_bottom, rect_left, rect_right, + rect_width, rect_height, CONSOLE_CURSOR_INFO(..), + CONSOLE_SCREEN_BUFFER_INFO(..), CHAR_INFO(..), + + sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE, sTD_ERROR_HANDLE, + + eNABLE_VIRTUAL_TERMINAL_INPUT, eNABLE_VIRTUAL_TERMINAL_PROCESSING, + + fOREGROUND_BLUE, fOREGROUND_GREEN, fOREGROUND_RED, fOREGROUND_INTENSITY, + fOREGROUND_WHITE, fOREGROUND_INTENSE_WHITE, + bACKGROUND_BLUE, bACKGROUND_GREEN, bACKGROUND_RED, bACKGROUND_INTENSITY, + bACKGROUND_WHITE, bACKGROUND_INTENSE_WHITE, + cOMMON_LVB_REVERSE_VIDEO, cOMMON_LVB_UNDERSCORE, + + getStdHandle, + getConsoleScreenBufferInfo, + getConsoleCursorInfo, + getConsoleMode, + + setConsoleTextAttribute, + setConsoleCursorPosition, + setConsoleCursorInfo, + setConsoleTitle, + setConsoleMode, + + fillConsoleOutputAttribute, + fillConsoleOutputCharacter, + scrollConsoleScreenBuffer, + + withTString, withHandleToHANDLE, + + ConsoleException (..) + ) where + +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative ((<$>), (<*>)) +#endif +import Control.Exception (Exception, throw) +import Data.Bits ((.|.), shiftL) +import Data.Char (chr, ord) +import Data.Typeable (Typeable) +import Foreign.C.Types (CInt (..), CWchar (..)) +import Foreign.Marshal (alloca, allocaArray, maybeWith, peekArray, with, + withArrayLen) +import Foreign.Ptr (Ptr, castPtr, plusPtr) +import Foreign.Storable (Storable (..)) +-- `SHORT` and `withHandleToHANDLE` are not both available before Win32-2.5.1.0 +import System.Win32.Compat (BOOL, DWORD, ErrCode, HANDLE, LPCTSTR, LPDWORD, + SHORT, TCHAR, UINT, WORD, failIfFalse_, getLastError, iNVALID_HANDLE_VALUE, + nullHANDLE, withHandleToHANDLE, withTString) + +#if defined(i386_HOST_ARCH) +#define WINDOWS_CCONV stdcall +#elif defined(x86_64_HOST_ARCH) +#define WINDOWS_CCONV ccall +#else +#error Unknown mingw32 arch +#endif + +type WCHAR = CWchar + +charToWCHAR :: Char -> WCHAR +charToWCHAR char = fromIntegral (ord char) + +-- This is a FFI hack. Some of the API calls take a Coord, but that isn't a +-- built-in FFI type so I can't use it directly. Instead, I use UNPACKED_COORD +-- and marshal COORDs into this manually. Note that we CAN'T just use two SHORTs +-- directly because they get expanded to 4 bytes each instead of just boing 2 +-- lots of 2 bytes by the stdcall convention, so linking fails. +type UNPACKED_COORD = CInt + +-- Field packing order determined experimentally: I couldn't immediately find a +-- specification for Windows struct layout anywhere. +unpackCOORD :: COORD -> UNPACKED_COORD +unpackCOORD (COORD x y) + = (fromIntegral y) `shiftL` (sizeOf x * 8) .|. (fromIntegral x) + + +peekAndOffset :: Storable a => Ptr a -> IO (a, Ptr b) +peekAndOffset ptr = do + item <- peek ptr + return (item, ptr `plusPtr` sizeOf item) + +pokeAndOffset :: Storable a => Ptr a -> a -> IO (Ptr b) +pokeAndOffset ptr item = do + poke ptr item + return (ptr `plusPtr` sizeOf item) + +data COORD = COORD + { coord_x :: SHORT + , coord_y :: SHORT + } deriving (Read, Eq) + +instance Show COORD where + show (COORD x y) = "(" ++ show x ++ ", " ++ show y ++ ")" + +instance Storable COORD where + sizeOf ~(COORD x y) = sizeOf x + sizeOf y + alignment ~(COORD x _) = alignment x + peek ptr = do + let ptr' = castPtr ptr :: Ptr SHORT + x <- peekElemOff ptr' 0 + y <- peekElemOff ptr' 1 + return (COORD x y) + poke ptr (COORD x y) = do + let ptr' = castPtr ptr :: Ptr SHORT + pokeElemOff ptr' 0 x + pokeElemOff ptr' 1 y + +data SMALL_RECT = SMALL_RECT + { rect_top_left :: COORD + , rect_bottom_right :: COORD + } + +rect_top, rect_left, rect_bottom, rect_right :: SMALL_RECT -> SHORT +rect_top = coord_y . rect_top_left +rect_left = coord_x . rect_top_left +rect_bottom = coord_y . rect_bottom_right +rect_right = coord_x . rect_bottom_right + +rect_width, rect_height :: SMALL_RECT -> SHORT +rect_width rect = rect_right rect - rect_left rect + 1 +rect_height rect = rect_bottom rect - rect_top rect + 1 + +instance Show SMALL_RECT where + show (SMALL_RECT tl br) = show tl ++ "-" ++ show br + +instance Storable SMALL_RECT where + sizeOf ~(SMALL_RECT tl br) = sizeOf tl + sizeOf br + alignment ~(SMALL_RECT tl _) = alignment tl + peek ptr = do + let ptr' = castPtr ptr :: Ptr COORD + tl <- peekElemOff ptr' 0 + br <- peekElemOff ptr' 1 + return (SMALL_RECT tl br) + poke ptr (SMALL_RECT tl br) = do + let ptr' = castPtr ptr :: Ptr COORD + pokeElemOff ptr' 0 tl + pokeElemOff ptr' 1 br + +data CONSOLE_CURSOR_INFO = CONSOLE_CURSOR_INFO + { cci_cursor_size :: DWORD + , cci_cursor_visible :: BOOL + } deriving (Show) + +instance Storable CONSOLE_CURSOR_INFO where + sizeOf ~(CONSOLE_CURSOR_INFO size visible) = sizeOf size + sizeOf visible + alignment ~(CONSOLE_CURSOR_INFO size _) = alignment size + peek ptr = do + (size, ptr') <- peekAndOffset (castPtr ptr) + visible <- peek ptr' + return (CONSOLE_CURSOR_INFO size visible) + poke ptr (CONSOLE_CURSOR_INFO size visible) = do + ptr' <- pokeAndOffset (castPtr ptr) size + poke ptr' visible + +data CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO + { csbi_size :: COORD + , csbi_cursor_position :: COORD + , csbi_attributes :: WORD + , csbi_window :: SMALL_RECT + , csbi_maximum_window_size :: COORD + } deriving (Show) + +instance Storable CONSOLE_SCREEN_BUFFER_INFO where + sizeOf ~(CONSOLE_SCREEN_BUFFER_INFO + size cursor_position attributes window maximum_window_size) + = sizeOf size + sizeOf cursor_position + sizeOf attributes + sizeOf window + + sizeOf maximum_window_size + alignment ~(CONSOLE_SCREEN_BUFFER_INFO size _ _ _ _) = alignment size + peek ptr = do + (size, ptr1) <- peekAndOffset (castPtr ptr) + (cursor_position, ptr2) <- peekAndOffset ptr1 + (attributes, ptr3) <- peekAndOffset ptr2 + (window, ptr4) <- peekAndOffset ptr3 + maximum_window_size <- peek ptr4 + return (CONSOLE_SCREEN_BUFFER_INFO + size cursor_position attributes window maximum_window_size) + poke ptr (CONSOLE_SCREEN_BUFFER_INFO + size cursor_position attributes window maximum_window_size) + = do + ptr1 <- pokeAndOffset (castPtr ptr) size + ptr2 <- pokeAndOffset ptr1 cursor_position + ptr3 <- pokeAndOffset ptr2 attributes + ptr4 <- pokeAndOffset ptr3 window + poke ptr4 maximum_window_size + +data CHAR_INFO = CHAR_INFO + { ci_char :: WCHAR + , ci_attributes :: WORD + } deriving (Show) + +instance Storable CHAR_INFO where + sizeOf ~(CHAR_INFO char attributes) = sizeOf char + sizeOf attributes + alignment ~(CHAR_INFO char _) = alignment char + peek ptr = do + (char, ptr') <- peekAndOffset (castPtr ptr) + attributes <- peek ptr' + return (CHAR_INFO char attributes) + poke ptr (CHAR_INFO char attributes) = do + ptr' <- pokeAndOffset (castPtr ptr) char + poke ptr' attributes + +eNABLE_VIRTUAL_TERMINAL_INPUT, eNABLE_VIRTUAL_TERMINAL_PROCESSING :: DWORD +sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE, sTD_ERROR_HANDLE :: DWORD +eNABLE_VIRTUAL_TERMINAL_INPUT = 512 +eNABLE_VIRTUAL_TERMINAL_PROCESSING = 4 +sTD_INPUT_HANDLE = 0xFFFFFFF6 -- minus 10 +sTD_OUTPUT_HANDLE = 0xFFFFFFF5 -- minus 11 +sTD_ERROR_HANDLE = 0xFFFFFFF4 -- minus 12 + +fOREGROUND_BLUE, fOREGROUND_GREEN, fOREGROUND_RED, fOREGROUND_INTENSITY, + bACKGROUND_BLUE, bACKGROUND_GREEN, bACKGROUND_RED, bACKGROUND_INTENSITY, + cOMMON_LVB_REVERSE_VIDEO, cOMMON_LVB_UNDERSCORE :: WORD +fOREGROUND_BLUE = 0x1 +fOREGROUND_GREEN = 0x2 +fOREGROUND_RED = 0x4 +fOREGROUND_INTENSITY = 0x8 +bACKGROUND_BLUE = 0x10 +bACKGROUND_GREEN = 0x20 +bACKGROUND_RED = 0x40 +bACKGROUND_INTENSITY = 0x80 +cOMMON_LVB_REVERSE_VIDEO = 0x4000 +cOMMON_LVB_UNDERSCORE = 0x8000 + +fOREGROUND_WHITE, bACKGROUND_WHITE, fOREGROUND_INTENSE_WHITE, + bACKGROUND_INTENSE_WHITE :: WORD +fOREGROUND_WHITE = fOREGROUND_RED .|. fOREGROUND_GREEN .|. fOREGROUND_BLUE +bACKGROUND_WHITE = bACKGROUND_RED .|. bACKGROUND_GREEN .|. bACKGROUND_BLUE +fOREGROUND_INTENSE_WHITE = fOREGROUND_WHITE .|. fOREGROUND_INTENSITY +bACKGROUND_INTENSE_WHITE = bACKGROUND_WHITE .|. bACKGROUND_INTENSITY + +kEY_EVENT, mOUSE_EVENT, wINDOW_BUFFER_SIZE_EVENT, mENU_EVENT, + fOCUS_EVENT :: WORD +kEY_EVENT = 1 +mOUSE_EVENT = 2 +wINDOW_BUFFER_SIZE_EVENT = 4 +mENU_EVENT = 8 +fOCUS_EVENT = 16 + +foreign import WINDOWS_CCONV unsafe "windows.h GetStdHandle" + getStdHandle :: DWORD -> IO HANDLE +foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleScreenBufferInfo" + cGetConsoleScreenBufferInfo :: HANDLE + -> Ptr CONSOLE_SCREEN_BUFFER_INFO + -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleCursorInfo" + cGetConsoleCursorInfo :: HANDLE -> Ptr CONSOLE_CURSOR_INFO -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h GetConsoleMode" + cGetConsoleMode :: HANDLE -> Ptr DWORD -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleTextAttribute" + cSetConsoleTextAttribute :: HANDLE -> WORD -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleCursorPosition" + cSetConsoleCursorPosition :: HANDLE -> UNPACKED_COORD -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleCursorInfo" + cSetConsoleCursorInfo :: HANDLE -> Ptr CONSOLE_CURSOR_INFO -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleTitleW" + cSetConsoleTitle :: LPCTSTR -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h SetConsoleMode" + cSetConsoleMode :: HANDLE -> DWORD -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h FillConsoleOutputAttribute" + cFillConsoleOutputAttribute :: HANDLE + -> WORD + -> DWORD + -> UNPACKED_COORD + -> Ptr DWORD + -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h FillConsoleOutputCharacterW" + cFillConsoleOutputCharacter :: HANDLE + -> TCHAR + -> DWORD + -> UNPACKED_COORD + -> Ptr DWORD + -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h ScrollConsoleScreenBufferW" + cScrollConsoleScreenBuffer :: HANDLE + -> Ptr SMALL_RECT + -> Ptr SMALL_RECT + -> UNPACKED_COORD + -> Ptr CHAR_INFO + -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h WriteConsoleInputW" + cWriteConsoleInput :: HANDLE + -> Ptr INPUT_RECORD + -> DWORD + -> LPDWORD + -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h GetNumberOfConsoleInputEvents" + cGetNumberOfConsoleInputEvents :: HANDLE -> Ptr DWORD -> IO BOOL +foreign import WINDOWS_CCONV unsafe "windows.h ReadConsoleInputW" + cReadConsoleInput :: HANDLE + -> Ptr INPUT_RECORD + -> DWORD + -> LPDWORD + -> IO BOOL + +data ConsoleException = ConsoleException !ErrCode deriving (Eq, Typeable) + +instance Show ConsoleException where + show (ConsoleException 6) = + "A fatal error has occurred.\n\n" ++ + "An attempt has been made to send console virtual terminal sequences\n" ++ + "(ANSI codes) to an output that has not been recognised as an\n" ++ + "ANSI-capable terminal and also cannot be emulated as an ANSI-enabled\n" ++ + "terminal (emulation needs a ConHost-based terminal, such as Command\n" ++ + "Prompt or PowerShell). That may occur, for example, if output has\n" ++ + "been redirected to a file.\n\n" ++ + "If that is unexpected, please post an issue at:\n" ++ + "https://github.com/feuerbach/ansi-terminal/issues\n" + show (ConsoleException errCode) = "ConsoleException " ++ show errCode + +instance Exception ConsoleException + +throwIfFalse :: IO Bool -> IO () +throwIfFalse action = do + succeeded <- action + if not succeeded + then getLastError >>= throw . ConsoleException -- TODO: Check if last error + -- is zero for some instructable reason (?) + else return () + +getConsoleScreenBufferInfo :: HANDLE -> IO CONSOLE_SCREEN_BUFFER_INFO +getConsoleScreenBufferInfo handle + = alloca $ \ptr_console_screen_buffer_info -> do + throwIfFalse $ + cGetConsoleScreenBufferInfo handle ptr_console_screen_buffer_info + peek ptr_console_screen_buffer_info + +getConsoleCursorInfo :: HANDLE -> IO CONSOLE_CURSOR_INFO +getConsoleCursorInfo handle = alloca $ \ptr_console_cursor_info -> do + throwIfFalse $ cGetConsoleCursorInfo handle ptr_console_cursor_info + peek ptr_console_cursor_info + +getConsoleMode :: HANDLE -> IO DWORD +getConsoleMode handle = alloca $ \ptr_mode -> do + throwIfFalse $ cGetConsoleMode handle ptr_mode + peek ptr_mode + +setConsoleTextAttribute :: HANDLE -> WORD -> IO () +setConsoleTextAttribute handle attributes + = throwIfFalse $ cSetConsoleTextAttribute handle attributes + +setConsoleCursorPosition :: HANDLE -> COORD -> IO () +setConsoleCursorPosition handle cursor_position + = throwIfFalse $ cSetConsoleCursorPosition handle + (unpackCOORD cursor_position) + +setConsoleCursorInfo :: HANDLE -> CONSOLE_CURSOR_INFO -> IO () +setConsoleCursorInfo handle console_cursor_info + = with console_cursor_info $ \ptr_console_cursor_info -> do + throwIfFalse $ cSetConsoleCursorInfo handle ptr_console_cursor_info + +setConsoleTitle :: LPCTSTR -> IO () +setConsoleTitle title = throwIfFalse $ cSetConsoleTitle title + +setConsoleMode :: HANDLE -> DWORD -> IO () +setConsoleMode handle attributes + = throwIfFalse $ cSetConsoleMode handle attributes + +fillConsoleOutputAttribute :: HANDLE -> WORD -> DWORD -> COORD -> IO DWORD +fillConsoleOutputAttribute handle attribute fill_length write_origin + = alloca $ \ptr_chars_written -> do + throwIfFalse $ cFillConsoleOutputAttribute handle attribute + fill_length (unpackCOORD write_origin) ptr_chars_written + peek ptr_chars_written + +fillConsoleOutputCharacter :: HANDLE -> TCHAR -> DWORD -> COORD -> IO DWORD +fillConsoleOutputCharacter handle char fill_length write_origin + = alloca $ \ptr_chars_written -> do + throwIfFalse $ cFillConsoleOutputCharacter handle char fill_length + (unpackCOORD write_origin) ptr_chars_written + peek ptr_chars_written + +scrollConsoleScreenBuffer :: HANDLE + -> SMALL_RECT + -> Maybe SMALL_RECT + -> COORD + -> CHAR_INFO + -> IO () +scrollConsoleScreenBuffer + handle scroll_rectangle mb_clip_rectangle destination_origin fill + = with scroll_rectangle $ \ptr_scroll_rectangle -> + maybeWith with mb_clip_rectangle $ \ptr_clip_rectangle -> + with fill $ \ptr_fill -> + throwIfFalse $ cScrollConsoleScreenBuffer handle ptr_scroll_rectangle + ptr_clip_rectangle (unpackCOORD destination_origin) ptr_fill + +-- The following is based on module System.Win32.Console.Extra from package +-- Win32-console, cut down for the WCHAR version of writeConsoleInput. + +writeConsoleInput :: HANDLE -> [INPUT_RECORD] -> IO DWORD +writeConsoleInput hdl evs + = writeConsoleInputWith hdl $ \act -> + withArrayLen evs $ \len ptr -> + act (ptr, toEnum len) + +writeConsoleInputWith :: HANDLE + -> InputHandler (Ptr INPUT_RECORD, DWORD) + -> IO DWORD +writeConsoleInputWith hdl withBuffer + = returnWith_ $ \ptrN -> + withBuffer $ \(ptrBuf, len) -> + failIfFalse_ "WriteConsoleInputW" $ cWriteConsoleInput hdl ptrBuf len ptrN + +returnWith_ :: Storable a => (Ptr a -> IO b) -> IO a +returnWith_ act = alloca $ \ptr -> act ptr >> peek ptr + +type InputHandler i = forall a. (i -> IO a) -> IO a + +{- +typedef union _UNICODE_ASCII_CHAR { + WCHAR UnicodeChar; + CHAR AsciiChar; +} UNICODE_ASCII_CHAR; +-} +newtype UNICODE_ASCII_CHAR = UnicodeAsciiChar + { unicodeAsciiChar :: WCHAR + } deriving (Show, Read, Eq) + +instance Storable UNICODE_ASCII_CHAR where + sizeOf _ = 2 + alignment _ = 2 + peek ptr = UnicodeAsciiChar <$> (`peekByteOff` 0) ptr + poke ptr val = case val of + UnicodeAsciiChar c -> (`pokeByteOff` 0) ptr c + +{- +typedef struct _KEY_EVENT_RECORD { + BOOL bKeyDown; + WORD wRepeatCount; + WORD wVirtualKeyCode; + WORD wVirtualScanCode; + union { + WCHAR UnicodeChar; + CHAR AsciiChar; + } uChar; + DWORD dwControlKeyState; +} +#ifdef __GNUC__ +/* gcc's alignment is not what win32 expects */ + PACKED +#endif +KEY_EVENT_RECORD; +-} +data KEY_EVENT_RECORD = KEY_EVENT_RECORD + { keyEventKeyDown :: BOOL + , keyEventRepeatCount :: WORD + , keyEventVirtualKeyCode :: WORD + , keyEventVirtualScanCode :: WORD + , keyEventChar :: UNICODE_ASCII_CHAR + , keyEventControlKeystate :: DWORD + } deriving (Show, Read, Eq) + +instance Storable KEY_EVENT_RECORD where + sizeOf _ = 16 + alignment _ = 4 + peek ptr = KEY_EVENT_RECORD <$> (`peekByteOff` 0) ptr + <*> (`peekByteOff` 4) ptr + <*> (`peekByteOff` 6) ptr + <*> (`peekByteOff` 8) ptr + <*> (`peekByteOff` 10) ptr + <*> (`peekByteOff` 12) ptr + poke ptr val = do + (`pokeByteOff` 0) ptr $ keyEventKeyDown val + (`pokeByteOff` 4) ptr $ keyEventRepeatCount val + (`pokeByteOff` 6) ptr $ keyEventVirtualKeyCode val + (`pokeByteOff` 8) ptr $ keyEventVirtualScanCode val + (`pokeByteOff` 10) ptr $ keyEventChar val + (`pokeByteOff` 12) ptr $ keyEventControlKeystate val + +{- +typedef struct _MOUSE_EVENT_RECORD { + COORD dwMousePosition; + DWORD dwButtonState; + DWORD dwControlKeyState; + DWORD dwEventFlags; +} MOUSE_EVENT_RECORD; +-} +data MOUSE_EVENT_RECORD = MOUSE_EVENT_RECORD + { mousePosition :: COORD + , mouseButtonState :: DWORD + , mouseControlKeyState :: DWORD + , mouseEventFlags :: DWORD + } deriving (Show, Read, Eq) + +instance Storable MOUSE_EVENT_RECORD where + sizeOf _ = 16 + alignment _ = 4 + peek ptr = MOUSE_EVENT_RECORD <$> (`peekByteOff` 0) ptr + <*> (`peekByteOff` 4) ptr + <*> (`peekByteOff` 8) ptr + <*> (`peekByteOff` 12) ptr + poke ptr val = do + (`pokeByteOff` 0) ptr $ mousePosition val + (`pokeByteOff` 4) ptr $ mouseButtonState val + (`pokeByteOff` 8) ptr $ mouseControlKeyState val + (`pokeByteOff` 12) ptr $ mouseEventFlags val + +{- +typedef struct _WINDOW_BUFFER_SIZE_RECORD { + COORD dwSize; +} WINDOW_BUFFER_SIZE_RECORD; +-} +data WINDOW_BUFFER_SIZE_RECORD = WINDOW_BUFFER_SIZE_RECORD + { bufSizeNew :: COORD + } deriving (Show, Read, Eq) + +instance Storable WINDOW_BUFFER_SIZE_RECORD where + sizeOf _ = 4 + alignment _ = 4 + peek ptr = WINDOW_BUFFER_SIZE_RECORD <$> (`peekByteOff` 0) ptr + poke ptr val = (`pokeByteOff` 0) ptr $ bufSizeNew val + +{- +typedef struct _MENU_EVENT_RECORD { + UINT dwCommandId; +} MENU_EVENT_RECORD,*PMENU_EVENT_RECORD; +-} +data MENU_EVENT_RECORD = MENU_EVENT_RECORD + { menuCommandId :: UINT + } deriving (Show, Read, Eq) + +instance Storable MENU_EVENT_RECORD where + sizeOf _ = 4 + alignment _ = 4 + peek ptr = MENU_EVENT_RECORD <$> (`peekByteOff` 0) ptr + poke ptr val = (`pokeByteOff` 0) ptr $ menuCommandId val + +{- +typedef struct _FOCUS_EVENT_RECORD { BOOL bSetFocus; } FOCUS_EVENT_RECORD; +-} +data FOCUS_EVENT_RECORD = FOCUS_EVENT_RECORD + { focusSetFocus :: BOOL + } deriving (Show, Read, Eq) + +instance Storable FOCUS_EVENT_RECORD where + sizeOf _ = 4 + alignment _ = 4 + peek ptr = FOCUS_EVENT_RECORD <$> (`peekByteOff` 0) ptr + poke ptr val = (`pokeByteOff` 0) ptr $ focusSetFocus val + +data INPUT_RECORD_EVENT + = InputKeyEvent KEY_EVENT_RECORD + | InputMouseEvent MOUSE_EVENT_RECORD + | InputWindowBufferSizeEvent WINDOW_BUFFER_SIZE_RECORD + | InputMenuEvent MENU_EVENT_RECORD + | InputFocusEvent FOCUS_EVENT_RECORD + deriving (Show, Read, Eq) + +{- +typedef struct _INPUT_RECORD { + WORD EventType; + union { + KEY_EVENT_RECORD KeyEvent; + MOUSE_EVENT_RECORD MouseEvent; + WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; + MENU_EVENT_RECORD MenuEvent; + FOCUS_EVENT_RECORD FocusEvent; + } Event; +} INPUT_RECORD,*PINPUT_RECORD; +-} +data INPUT_RECORD = INPUT_RECORD + { inputEventType :: WORD + , inputEvent :: INPUT_RECORD_EVENT + } deriving (Show, Read, Eq) + +instance Storable INPUT_RECORD where + sizeOf _ = 20 + alignment _ = 4 + peek ptr = do + evType <- (`peekByteOff` 0) ptr + event <- case evType of + _ | evType == kEY_EVENT + -> InputKeyEvent <$> (`peekByteOff` 4) ptr + _ | evType == mOUSE_EVENT + -> InputMouseEvent <$> (`peekByteOff` 4) ptr + _ | evType == wINDOW_BUFFER_SIZE_EVENT + -> InputWindowBufferSizeEvent <$> (`peekByteOff` 4) ptr + _ | evType == mENU_EVENT + -> InputMenuEvent <$> (`peekByteOff` 4) ptr + _ | evType == fOCUS_EVENT + -> InputFocusEvent <$> (`peekByteOff` 4) ptr + _ -> error $ "peek (INPUT_RECORD): Unknown event type " ++ + show evType + return $ INPUT_RECORD evType event + poke ptr val = do + (`pokeByteOff` 0) ptr $ inputEventType val + case inputEvent val of + InputKeyEvent ev -> (`pokeByteOff` 4) ptr ev + InputMouseEvent ev -> (`pokeByteOff` 4) ptr ev + InputWindowBufferSizeEvent ev -> (`pokeByteOff` 4) ptr ev + InputMenuEvent ev -> (`pokeByteOff` 4) ptr ev + InputFocusEvent ev -> (`pokeByteOff` 4) ptr ev + +-- The following is based on module System.Win32.Console.Extra from package +-- Win32-console. + +getNumberOfConsoleInputEvents :: HANDLE -> IO DWORD +getNumberOfConsoleInputEvents hdl = + returnWith_ $ \ptrN -> + failIfFalse_ "GetNumberOfConsoleInputEvents" $ + cGetNumberOfConsoleInputEvents hdl ptrN + +-- The following is based on module System.Win32.Console.Extra from package +-- Win32-console, cut down for the WCHAR version of readConsoleInput. + +readConsoleInput :: HANDLE -> DWORD -> IO [INPUT_RECORD] +readConsoleInput hdl len + = readConsoleInputWith hdl len $ \(ptr, n) -> peekArray (fromEnum n) ptr + +readConsoleInputWith :: HANDLE + -> DWORD + -> OutputHandler (Ptr INPUT_RECORD, DWORD) +readConsoleInputWith hdl len handler = + allocaArray (fromEnum len) $ \ptrBuf -> + alloca $ \ptrN -> do + failIfFalse_ "ReadConsoleInputW" $ + cReadConsoleInput hdl ptrBuf len ptrN + n <- peek ptrN + handler (ptrBuf, n) + +type OutputHandler o = forall a. (o -> IO a) -> IO a + +-- Replicated from module Foreign.C.String in package base because that module +-- does not export the function. +cWcharsToChars :: [CWchar] -> [Char] +cWcharsToChars = map chr . fromUTF16 . map fromIntegral + where + fromUTF16 (c1:c2:wcs) + | 0xd800 <= c1 && c1 <= 0xdbff && 0xdc00 <= c2 && c2 <= 0xdfff = + ((c1 - 0xd800)*0x400 + (c2 - 0xdc00) + 0x10000) : fromUTF16 wcs + fromUTF16 (c:wcs) = c : fromUTF16 wcs + fromUTF16 [] = []
+ src/System/Win32/Compat.hs view
@@ -0,0 +1,111 @@+{-# OPTIONS_HADDOCK hide #-} + +{-| The Win32 library ships with GHC. Win32-2.1 first shipped with GHC 6.6 +(released October 2006). Win32-2.5.4.1 first shipped with GHC 8.2.1 +(released July 2017), replacing Win32-2.3.1.1. + +The ansi-terminal library makes use of functionality in Win32-2.1 and other +functionality first added to Win32-2.5.0.0 or Win32-2.5.1.0 (from ansi-terminal +itself). + +This module provides functions available in those later versions of Win32 to a +wider range of compilers, reducing the use of CPP pragmas in other modules. +-} +module System.Win32.Compat + ( + BOOL + , DWORD + , ErrCode + , HANDLE + , LPCTSTR + , LPDWORD + , SHORT -- from Win32-2.5.0.0 + , TCHAR + , UINT + , WORD + , failIfFalse_ + , getLastError + , iNVALID_HANDLE_VALUE + , nullHANDLE + , withHandleToHANDLE -- from Win32-2.5.1.0 + , withTString + ) where + +#if !MIN_VERSION_Win32(2,5,0) +import Foreign.C.Types (CShort (..)) +#endif + +#if !MIN_VERSION_Win32(2,5,1) +import Control.Concurrent.MVar (readMVar) +import Control.Exception (bracket) +import Foreign.C.Types (CInt (..)) +import Foreign.StablePtr (StablePtr, freeStablePtr, newStablePtr) +import Data.Typeable (cast) +import GHC.IO.FD (FD(..)) -- A wrapper around an Int32 +import GHC.IO.Handle.Types (Handle (..), Handle__ (..)) +#endif + +import System.Win32.Types (BOOL, DWORD, ErrCode, HANDLE, LPCTSTR, LPDWORD, + TCHAR, UINT, WORD, failIfFalse_, getLastError, iNVALID_HANDLE_VALUE, + nullHANDLE, withTString) + +#if MIN_VERSION_Win32(2,5,0) +import System.Win32.Types (SHORT) +#endif + +#if MIN_VERSION_Win32(2,5,1) +import System.Win32.Types (withHandleToHANDLE) +#endif + +#if !MIN_VERSION_Win32(2,5,0) +type SHORT = CShort +#endif + +#if !MIN_VERSION_Win32(2,5,1) + +#if defined(i386_HOST_ARCH) +#define WINDOWS_CCONV stdcall +#elif defined(x86_64_HOST_ARCH) +#define WINDOWS_CCONV ccall +#else +#error Unknown mingw32 arch +#endif + +-- | This bit is all highly dubious. The problem is that we want to output ANSI +-- to arbitrary Handles rather than forcing people to use stdout. However, the +-- Windows ANSI emulator needs a Windows HANDLE to work it's magic, so we need +-- to be able to extract one of those from the Haskell Handle. +-- +-- This code accomplishes this, albeit at the cost of only being compatible with +-- GHC. +withHandleToHANDLE :: Handle -> (HANDLE -> IO a) -> IO a +withHandleToHANDLE haskell_handle action = + -- Create a stable pointer to the Handle. This prevents the garbage collector + -- getting to it while we are doing horrible manipulations with it, and hence + -- stops it being finalized (and closed). + withStablePtr haskell_handle $ const $ do + -- Grab the write handle variable from the Handle + let write_handle_mvar = case haskell_handle of + FileHandle _ handle_mvar -> handle_mvar + DuplexHandle _ _ handle_mvar -> handle_mvar -- This is "write" MVar, + -- we could also take the "read" one + + -- Get the FD from the algebraic data type + Just fd <- fmap (\(Handle__ { haDevice = dev }) -> + fmap fdFD (cast dev)) $ readMVar write_handle_mvar + + -- Finally, turn that (C-land) FD into a HANDLE using msvcrt + windows_handle <- cget_osfhandle fd + + -- Do what the user originally wanted + action windows_handle + +-- This essential function comes from the C runtime system. It is certainly +-- provided by msvcrt, and also seems to be provided by the mingw C library - +-- hurrah! +foreign import WINDOWS_CCONV unsafe "_get_osfhandle" + cget_osfhandle :: CInt -> IO HANDLE + +withStablePtr :: a -> (StablePtr a -> IO b) -> IO b +withStablePtr value = bracket (newStablePtr value) freeStablePtr +#endif
+ src/includes/Common-Include-Emulator.hs view
@@ -0,0 +1,65 @@+-- This file contains code that is required in the case of the module +-- System.Console.ANSI.Windows.Emulator and differs from the common code in +-- file Common-Include-Enabled.hs. + +-- | Set the Select Graphic Rendition mode +hSetSGR + :: ConsoleDefaultState -- ^ The default console state + -> Handle + -> [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied left to + -- right. + -> IO () + +-- | Set the Select Graphic Rendition mode +setSGR + :: ConsoleDefaultState -- ^ The default console state + -> [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied left to + -- right. + -> IO () +setSGR def = hSetSGR def stdout + +hClearFromCursorToScreenEnd, hClearFromCursorToScreenBeginning, hClearScreen + :: ConsoleDefaultState -- ^ The default console state + -> Handle + -> IO () + +clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen + :: ConsoleDefaultState -- ^ The default console state + -> IO () +clearFromCursorToScreenEnd def = hClearFromCursorToScreenEnd def stdout +clearFromCursorToScreenBeginning def + = hClearFromCursorToScreenBeginning def stdout +clearScreen def = hClearScreen def stdout + +hClearFromCursorToLineEnd, hClearFromCursorToLineBeginning, hClearLine + :: ConsoleDefaultState -- ^ The default console state + -> Handle + -> IO () + +clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine + :: ConsoleDefaultState -- ^ The default console state + -> IO () +clearFromCursorToLineEnd def = hClearFromCursorToLineEnd def stdout +clearFromCursorToLineBeginning def = hClearFromCursorToLineBeginning def stdout +clearLine def = hClearLine def stdout + +-- | Scroll the displayed information up or down the terminal: not widely +-- supported +hScrollPageUp, hScrollPageDown + :: ConsoleDefaultState -- ^ The default console state + -> Handle + -> Int -- ^ Number of lines to scroll by + -> IO () + +-- | Scroll the displayed information up or down the terminal: not widely +-- supported +scrollPageUp, scrollPageDown + :: ConsoleDefaultState -- ^ The default console state + -> Int -- ^ Number of lines to scroll by + -> IO () +scrollPageUp def = hScrollPageUp def stdout +scrollPageDown def = hScrollPageDown def stdout
+ src/includes/Common-Include-Enabled.hs view
@@ -0,0 +1,56 @@+-- This file contains code that is common save that different code is required +-- in the case of the module System.Console.ANSI.Windows.Emulator (see the file +-- Common-Include-Emulator.hs in respect of the latter). + +-- | Set the Select Graphic Rendition mode +hSetSGR + :: Handle + -> [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied left to + -- right. + -> IO () + +-- | Set the Select Graphic Rendition mode +setSGR + :: [SGR] -- ^ Commands: these will typically be applied on top of the + -- current console SGR mode. An empty list of commands is + -- equivalent to the list @[Reset]@. Commands are applied left to + -- right. + -> IO () +setSGR = hSetSGR stdout + +hClearFromCursorToScreenEnd, hClearFromCursorToScreenBeginning, hClearScreen + :: Handle + -> IO () + +clearFromCursorToScreenEnd, clearFromCursorToScreenBeginning, clearScreen + :: IO () +clearFromCursorToScreenEnd = hClearFromCursorToScreenEnd stdout +clearFromCursorToScreenBeginning = hClearFromCursorToScreenBeginning stdout +clearScreen = hClearScreen stdout + +hClearFromCursorToLineEnd, hClearFromCursorToLineBeginning, hClearLine + :: Handle + -> IO () + +clearFromCursorToLineEnd, clearFromCursorToLineBeginning, clearLine + :: IO () +clearFromCursorToLineEnd = hClearFromCursorToLineEnd stdout +clearFromCursorToLineBeginning = hClearFromCursorToLineBeginning stdout +clearLine = hClearLine stdout + +-- | Scroll the displayed information up or down the terminal: not widely +-- supported +hScrollPageUp, hScrollPageDown + :: Handle + -> Int -- ^ Number of lines to scroll by + -> IO () + +-- | Scroll the displayed information up or down the terminal: not widely +-- supported +scrollPageUp, scrollPageDown + :: Int -- ^ Number of lines to scroll by + -> IO () +scrollPageUp = hScrollPageUp stdout +scrollPageDown = hScrollPageDown stdout
+ src/includes/Common-Include.hs view
@@ -0,0 +1,164 @@+-- This file contains code that is common to modules System.Console.ANSI.Unix, +-- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as +-- type signatures and the definition of functions specific to stdout in terms +-- of the corresponding more general functions, inclduding the related Haddock +-- documentation. + +import System.Environment +#if !MIN_VERSION_base(4,8,0) +import Control.Applicative +#endif + +import Control.Monad (void) +import Data.Char (isDigit) +import Text.ParserCombinators.ReadP (char, many1, ReadP, satisfy) + +hCursorUp, hCursorDown, hCursorForward, hCursorBackward + :: Handle + -> Int -- ^ Number of lines or characters to move + -> IO () +cursorUp, cursorDown, cursorForward, cursorBackward + :: Int -- ^ Number of lines or characters to move + -> IO () +cursorUp = hCursorUp stdout +cursorDown = hCursorDown stdout +cursorForward = hCursorForward stdout +cursorBackward = hCursorBackward stdout + +hCursorDownLine, hCursorUpLine :: Handle + -> Int -- ^ Number of lines to move + -> IO () +cursorDownLine, cursorUpLine :: Int -- ^ Number of lines to move + -> IO () +cursorDownLine = hCursorDownLine stdout +cursorUpLine = hCursorUpLine stdout + +hSetCursorColumn :: Handle + -> Int -- ^ 0-based column to move to + -> IO () +setCursorColumn :: Int -- ^ 0-based column to move to + -> IO () +setCursorColumn = hSetCursorColumn stdout + +hSetCursorPosition :: Handle + -> Int -- ^ 0-based row to move to + -> Int -- ^ 0-based column to move to + -> IO () +setCursorPosition :: Int -- ^ 0-based row to move to + -> Int -- ^ 0-based column to move to + -> IO () +setCursorPosition = hSetCursorPosition stdout + +hSaveCursor, hRestoreCursor, hReportCursorPosition :: Handle -> IO () + +-- | Save the cursor position in memory. The only way to access the saved value +-- is with the 'restoreCursor' command. +saveCursor :: IO () +-- | Restore the cursor position from memory. There will be no value saved in +-- memory until the first use of the 'saveCursor' command. +restoreCursor :: IO () +-- | Looking for a way to get the cursors position? See +-- 'getCursorPosition'. +-- +-- Emit the cursor position into the console input stream, immediately after +-- being recognised on the output stream, as: +-- @ESC [ \<cursor row> ; \<cursor column> R@ +-- +-- In isolation of 'getReportedCursorPosition' or 'getCursorPosition', this +-- function may be of limited use on Windows operating systems because of +-- difficulties in obtaining the data emitted into the console input stream. +-- The function 'hGetBufNonBlocking' in module "System.IO" does not work on +-- Windows. This has been attributed to the lack of non-blocking primatives in +-- the operating system (see the GHC bug report #806 at +-- <https://ghc.haskell.org/trac/ghc/ticket/806>). +reportCursorPosition :: IO () + +saveCursor = hSaveCursor stdout +restoreCursor = hRestoreCursor stdout +reportCursorPosition = hReportCursorPosition stdout + +hHideCursor, hShowCursor :: Handle + -> IO () +hideCursor, showCursor :: IO () +hideCursor = hHideCursor stdout +showCursor = hShowCursor stdout + +-- | Set the terminal window title +hSetTitle :: Handle + -> String -- ^ New title + -> IO () +-- | Set the terminal window title +setTitle :: String -- ^ New title + -> IO () +setTitle = hSetTitle stdout + +-- | Use heuristics to determine whether the functions defined in this +-- package will work with a given handle. +-- +-- The current implementation checks that the handle is a terminal, and +-- that the @TERM@ environment variable doesn't say @dumb@ (which is what +-- Emacs sets for its own terminal). +hSupportsANSI :: Handle -> IO Bool +-- Borrowed from an HSpec patch by Simon Hengel +-- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd) +hSupportsANSI h = (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb) + where + -- cannot use lookupEnv since it only appeared in GHC 7.6 + isDumb = maybe False (== "dumb") . lookup "TERM" <$> getEnvironment + +-- | Parses the characters emitted by 'reportCursorPosition' into the console +-- input stream. Returns the cursor row and column as a tuple. +-- +-- For example, if the characters emitted by 'reportCursorPosition' are in +-- 'String' @input@ then the parser could be applied like this: +-- +-- > let result = readP_to_S cursorPosition input +-- > case result of +-- > [] -> putStrLn $ "Error: could not parse " ++ show input +-- > [((row, column), _)] -> putStrLn $ "The cursor was at row " ++ show row +-- > ++ " and column" ++ show column ++ "." +-- > (_:_) -> putStrLn $ "Error: parse not unique" +-- +cursorPosition :: ReadP (Int, Int) +cursorPosition = do + void $ char '\ESC' + void $ char '[' + row <- decimal -- A non-negative whole decimal number + void $ char ';' + col <- decimal -- A non-negative whole decimal number + void $ char 'R' + return (read row, read col) + where + digit = satisfy isDigit + decimal = many1 digit + +-- | Attempts to get the reported cursor position data from the console input +-- stream. The function is intended to be called immediately after +-- 'reportCursorPosition' (or related functions) have caused characters to be +-- emitted into the stream. +-- +-- For example, on a Unix-like operating system: +-- +-- > hSetBuffering stdin NoBuffering -- set no buffering (the contents of the +-- > -- buffer will be discarded, so this needs +-- > -- to be done before the cursor positon is +-- > -- emitted) +-- > reportCursorPosition +-- > hFlush stdout -- ensure the report cursor position code is sent to the +-- > -- operating system +-- > input <- getReportedCursorPosition +-- +-- On Windows operating systems, the function is not supported on consoles, such +-- as mintty, that are not based on the Win32 console of the Windows API. +-- (Command Prompt and PowerShell are based on the Win32 console.) +getReportedCursorPosition :: IO String + +-- | Attempts to get the reported cursor position, combining the functions +-- 'reportCursorPosition', 'getReportedCursorPosition' and 'cursorPosition'. +-- Returns 'Nothing' if any data emitted by 'reportCursorPosition', obtained by +-- 'getReportedCursorPosition', cannot be parsed by 'cursorPosition'. +-- +-- On Windows operating systems, the function is not supported on consoles, such +-- as mintty, that are not based on the Win32 console of the Windows API. +-- (Command Prompt and PowerShell are based on the Win32 console.) +getCursorPosition :: IO (Maybe (Int, Int))
+ src/includes/Exports-Include.hs view
@@ -0,0 +1,114 @@+-- This file contains code that is common to modules +-- System.Console.ANSI.Unix and System.Console.ANSI.Windows, namely the module +-- exports and the associated Haddock documentation. + + -- * Basic data types + module System.Console.ANSI.Types + + -- * Cursor movement by character + , cursorUp + , cursorDown + , cursorForward + , cursorBackward + , hCursorUp + , hCursorDown + , hCursorForward + , hCursorBackward + , cursorUpCode + , cursorDownCode + , cursorForwardCode + , cursorBackwardCode + + -- * Cursor movement by line + -- | The difference between movements \"by character\" and \"by line\" is + -- that @*Line@ functions additionally move the cursor to the start of the + -- line, while functions like @cursorUp@ and @cursorDown@ keep the column + -- the same. + -- + -- Also keep in mind that @*Line@ functions are not as portable. See + -- <https://github.com/feuerbach/ansi-terminal/issues/10> for the details. + , cursorUpLine + , cursorDownLine + , hCursorUpLine + , hCursorDownLine + , cursorUpLineCode + , cursorDownLineCode + + -- * Directly changing cursor position + , setCursorColumn + , hSetCursorColumn + , setCursorColumnCode + + , setCursorPosition + , hSetCursorPosition + , setCursorPositionCode + + -- * Saving, restoring and reporting cursor position + , saveCursor + , hSaveCursor + , saveCursorCode + + , restoreCursor + , hRestoreCursor + , restoreCursorCode + + , reportCursorPosition + , hReportCursorPosition + , reportCursorPositionCode + + -- * Clearing parts of the screen + -- | Note that these functions only clear parts of the screen. They do not move the + -- cursor. + , clearFromCursorToScreenEnd + , clearFromCursorToScreenBeginning + , clearScreen + , hClearFromCursorToScreenEnd + , hClearFromCursorToScreenBeginning + , hClearScreen + , clearFromCursorToScreenEndCode + , clearFromCursorToScreenBeginningCode + , clearScreenCode + + , clearFromCursorToLineEnd + , clearFromCursorToLineBeginning + , clearLine + , hClearFromCursorToLineEnd + , hClearFromCursorToLineBeginning + , hClearLine + , clearFromCursorToLineEndCode + , clearFromCursorToLineBeginningCode + , clearLineCode + + -- * Scrolling the screen + , scrollPageUp + , scrollPageDown + , hScrollPageUp + , hScrollPageDown + , scrollPageUpCode + , scrollPageDownCode + + -- * Select Graphic Rendition mode: colors and other whizzy stuff + , setSGR + , hSetSGR + , setSGRCode + + -- * Cursor visibilty changes + , hideCursor + , showCursor + , hHideCursor + , hShowCursor + , hideCursorCode + , showCursorCode + + -- * Changing the title + , setTitle + , hSetTitle + , setTitleCode + + -- * Checking if handle supports ANSI + , hSupportsANSI + + -- * Getting the cursor position + , getCursorPosition + , getReportedCursorPosition + , cursorPosition