|
Packit |
fc89b4 |
-- This file contains code that is common to modules System.Console.ANSI.Unix,
|
|
Packit |
fc89b4 |
-- System.Console.ANSI.Windows and System.Console.ANSI.Windows.Emulator, such as
|
|
Packit |
fc89b4 |
-- type signatures and the definition of functions specific to stdout in terms
|
|
Packit |
fc89b4 |
-- of the corresponding more general functions, inclduding the related Haddock
|
|
Packit |
fc89b4 |
-- documentation.
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
import System.Environment
|
|
Packit |
fc89b4 |
#if !MIN_VERSION_base(4,8,0)
|
|
Packit |
fc89b4 |
import Control.Applicative
|
|
Packit |
fc89b4 |
#endif
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
import Control.Monad (void)
|
|
Packit |
fc89b4 |
import Data.Char (isDigit)
|
|
Packit |
fc89b4 |
import Text.ParserCombinators.ReadP (char, many1, ReadP, satisfy)
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hCursorUp, hCursorDown, hCursorForward, hCursorBackward
|
|
Packit |
fc89b4 |
:: Handle
|
|
Packit |
fc89b4 |
-> Int -- ^ Number of lines or characters to move
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
cursorUp, cursorDown, cursorForward, cursorBackward
|
|
Packit |
fc89b4 |
:: Int -- ^ Number of lines or characters to move
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
cursorUp = hCursorUp stdout
|
|
Packit |
fc89b4 |
cursorDown = hCursorDown stdout
|
|
Packit |
fc89b4 |
cursorForward = hCursorForward stdout
|
|
Packit |
fc89b4 |
cursorBackward = hCursorBackward stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hCursorDownLine, hCursorUpLine :: Handle
|
|
Packit |
fc89b4 |
-> Int -- ^ Number of lines to move
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
cursorDownLine, cursorUpLine :: Int -- ^ Number of lines to move
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
cursorDownLine = hCursorDownLine stdout
|
|
Packit |
fc89b4 |
cursorUpLine = hCursorUpLine stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hSetCursorColumn :: Handle
|
|
Packit |
fc89b4 |
-> Int -- ^ 0-based column to move to
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
setCursorColumn :: Int -- ^ 0-based column to move to
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
setCursorColumn = hSetCursorColumn stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hSetCursorPosition :: Handle
|
|
Packit |
fc89b4 |
-> Int -- ^ 0-based row to move to
|
|
Packit |
fc89b4 |
-> Int -- ^ 0-based column to move to
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
setCursorPosition :: Int -- ^ 0-based row to move to
|
|
Packit |
fc89b4 |
-> Int -- ^ 0-based column to move to
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
setCursorPosition = hSetCursorPosition stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hSaveCursor, hRestoreCursor, hReportCursorPosition :: Handle -> IO ()
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Save the cursor position in memory. The only way to access the saved value
|
|
Packit |
fc89b4 |
-- is with the 'restoreCursor' command.
|
|
Packit |
fc89b4 |
saveCursor :: IO ()
|
|
Packit |
fc89b4 |
-- | Restore the cursor position from memory. There will be no value saved in
|
|
Packit |
fc89b4 |
-- memory until the first use of the 'saveCursor' command.
|
|
Packit |
fc89b4 |
restoreCursor :: IO ()
|
|
Packit |
fc89b4 |
-- | Looking for a way to get the cursors position? See
|
|
Packit |
fc89b4 |
-- 'getCursorPosition'.
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- Emit the cursor position into the console input stream, immediately after
|
|
Packit |
fc89b4 |
-- being recognised on the output stream, as:
|
|
Packit |
fc89b4 |
-- @ESC [ \<cursor row> ; \<cursor column> R@
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- In isolation of 'getReportedCursorPosition' or 'getCursorPosition', this
|
|
Packit |
fc89b4 |
-- function may be of limited use on Windows operating systems because of
|
|
Packit |
fc89b4 |
-- difficulties in obtaining the data emitted into the console input stream.
|
|
Packit |
fc89b4 |
-- The function 'hGetBufNonBlocking' in module "System.IO" does not work on
|
|
Packit |
fc89b4 |
-- Windows. This has been attributed to the lack of non-blocking primatives in
|
|
Packit |
fc89b4 |
-- the operating system (see the GHC bug report #806 at
|
|
Packit |
fc89b4 |
-- <https://ghc.haskell.org/trac/ghc/ticket/806>).
|
|
Packit |
fc89b4 |
reportCursorPosition :: IO ()
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
saveCursor = hSaveCursor stdout
|
|
Packit |
fc89b4 |
restoreCursor = hRestoreCursor stdout
|
|
Packit |
fc89b4 |
reportCursorPosition = hReportCursorPosition stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
hHideCursor, hShowCursor :: Handle
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
hideCursor, showCursor :: IO ()
|
|
Packit |
fc89b4 |
hideCursor = hHideCursor stdout
|
|
Packit |
fc89b4 |
showCursor = hShowCursor stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Set the terminal window title
|
|
Packit |
fc89b4 |
hSetTitle :: Handle
|
|
Packit |
fc89b4 |
-> String -- ^ New title
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
-- | Set the terminal window title
|
|
Packit |
fc89b4 |
setTitle :: String -- ^ New title
|
|
Packit |
fc89b4 |
-> IO ()
|
|
Packit |
fc89b4 |
setTitle = hSetTitle stdout
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Use heuristics to determine whether the functions defined in this
|
|
Packit |
fc89b4 |
-- package will work with a given handle.
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- The current implementation checks that the handle is a terminal, and
|
|
Packit |
fc89b4 |
-- that the @TERM@ environment variable doesn't say @dumb@ (which is what
|
|
Packit |
fc89b4 |
-- Emacs sets for its own terminal).
|
|
Packit |
fc89b4 |
hSupportsANSI :: Handle -> IO Bool
|
|
Packit |
fc89b4 |
-- Borrowed from an HSpec patch by Simon Hengel
|
|
Packit |
fc89b4 |
-- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd)
|
|
Packit |
fc89b4 |
hSupportsANSI h = (&&) <$> hIsTerminalDevice h <*> (not <$> isDumb)
|
|
Packit |
fc89b4 |
where
|
|
Packit |
fc89b4 |
-- cannot use lookupEnv since it only appeared in GHC 7.6
|
|
Packit |
fc89b4 |
isDumb = maybe False (== "dumb") . lookup "TERM" <$> getEnvironment
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Parses the characters emitted by 'reportCursorPosition' into the console
|
|
Packit |
fc89b4 |
-- input stream. Returns the cursor row and column as a tuple.
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- For example, if the characters emitted by 'reportCursorPosition' are in
|
|
Packit |
fc89b4 |
-- 'String' @input@ then the parser could be applied like this:
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- > let result = readP_to_S cursorPosition input
|
|
Packit |
fc89b4 |
-- > case result of
|
|
Packit |
fc89b4 |
-- > [] -> putStrLn $ "Error: could not parse " ++ show input
|
|
Packit |
fc89b4 |
-- > [((row, column), _)] -> putStrLn $ "The cursor was at row " ++ show row
|
|
Packit |
fc89b4 |
-- > ++ " and column" ++ show column ++ "."
|
|
Packit |
fc89b4 |
-- > (_:_) -> putStrLn $ "Error: parse not unique"
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
cursorPosition :: ReadP (Int, Int)
|
|
Packit |
fc89b4 |
cursorPosition = do
|
|
Packit |
fc89b4 |
void $ char '\ESC'
|
|
Packit |
fc89b4 |
void $ char '['
|
|
Packit |
fc89b4 |
row <- decimal -- A non-negative whole decimal number
|
|
Packit |
fc89b4 |
void $ char ';'
|
|
Packit |
fc89b4 |
col <- decimal -- A non-negative whole decimal number
|
|
Packit |
fc89b4 |
void $ char 'R'
|
|
Packit |
fc89b4 |
return (read row, read col)
|
|
Packit |
fc89b4 |
where
|
|
Packit |
fc89b4 |
digit = satisfy isDigit
|
|
Packit |
fc89b4 |
decimal = many1 digit
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Attempts to get the reported cursor position data from the console input
|
|
Packit |
fc89b4 |
-- stream. The function is intended to be called immediately after
|
|
Packit |
fc89b4 |
-- 'reportCursorPosition' (or related functions) have caused characters to be
|
|
Packit |
fc89b4 |
-- emitted into the stream.
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- For example, on a Unix-like operating system:
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- > hSetBuffering stdin NoBuffering -- set no buffering (the contents of the
|
|
Packit |
fc89b4 |
-- > -- buffer will be discarded, so this needs
|
|
Packit |
fc89b4 |
-- > -- to be done before the cursor positon is
|
|
Packit |
fc89b4 |
-- > -- emitted)
|
|
Packit |
fc89b4 |
-- > reportCursorPosition
|
|
Packit |
fc89b4 |
-- > hFlush stdout -- ensure the report cursor position code is sent to the
|
|
Packit |
fc89b4 |
-- > -- operating system
|
|
Packit |
fc89b4 |
-- > input <- getReportedCursorPosition
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- On Windows operating systems, the function is not supported on consoles, such
|
|
Packit |
fc89b4 |
-- as mintty, that are not based on the Win32 console of the Windows API.
|
|
Packit |
fc89b4 |
-- (Command Prompt and PowerShell are based on the Win32 console.)
|
|
Packit |
fc89b4 |
getReportedCursorPosition :: IO String
|
|
Packit |
fc89b4 |
|
|
Packit |
fc89b4 |
-- | Attempts to get the reported cursor position, combining the functions
|
|
Packit |
fc89b4 |
-- 'reportCursorPosition', 'getReportedCursorPosition' and 'cursorPosition'.
|
|
Packit |
fc89b4 |
-- Returns 'Nothing' if any data emitted by 'reportCursorPosition', obtained by
|
|
Packit |
fc89b4 |
-- 'getReportedCursorPosition', cannot be parsed by 'cursorPosition'.
|
|
Packit |
fc89b4 |
--
|
|
Packit |
fc89b4 |
-- On Windows operating systems, the function is not supported on consoles, such
|
|
Packit |
fc89b4 |
-- as mintty, that are not based on the Win32 console of the Windows API.
|
|
Packit |
fc89b4 |
-- (Command Prompt and PowerShell are based on the Win32 console.)
|
|
Packit |
fc89b4 |
getCursorPosition :: IO (Maybe (Int, Int))
|