packages feed

ansi-terminal 0.8.0.4 → 0.8.1

raw patch · 8 files changed

+148/−41 lines, 8 filesdep ~basePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base

API changes (from Hackage documentation)

+ System.Console.ANSI: hSupportsANSIWithoutEmulation :: Handle -> IO (Maybe Bool)

Files

CHANGELOG.md view
@@ -1,11 +1,19 @@ Changes
 =======
 
+Version 0.8.1
+-------------
+
+* Add `hSupportsANSIWithoutEmulation`. On Windows 10, if the handle is identifed
+  as connected to a native terminal ('Command Prompt' or 'PowerShell'), the
+  processing of 'ANSI' control characters will be enabled.
+
 Version 0.8.0.4
 ---------------
 
 * On Windows, `hSupportsANSI` now recognises if the handle is connected to a
   'mintty' terminal.
+* Drop support for GHC versions before GHC 7.0.1 (released November 2010)
 
 Version 0.8.0.3
 ---------------
ansi-terminal.cabal view
@@ -1,5 +1,5 @@ Name:                ansi-terminal
-Version:             0.8.0.4
+Version:             0.8.1
 Cabal-Version:       >= 1.8
 Category:            User Interfaces
 Synopsis:            Simple ANSI terminal support, with Windows compatibility
@@ -36,7 +36,7 @@ 
         Include-Dirs:           src/includes
 
-        Build-Depends:          base >= 4.2.0.0 && < 5
+        Build-Depends:          base >= 4.3.0.0 && < 5
                               , colour
         if os(windows)
                 Build-Depends:          containers >= 0.5.0.0
@@ -64,7 +64,7 @@ Executable ansi-terminal-example
         Hs-Source-Dirs:         app
         Main-Is:                Example.hs
-        Build-Depends:          base >= 4.2.0.0 && < 5
+        Build-Depends:          base >= 4.3.0.0 && < 5
                               , ansi-terminal
         Ghc-Options:            -Wall
         if !flag(example)
src/System/Console/ANSI/Unix.hs view
@@ -11,7 +11,8 @@ import Control.Exception.Base (bracket)
 import System.Environment (getEnvironment)
 import System.IO (BufferMode (..), Handle, hFlush, hGetBuffering, hGetEcho,
-  hIsTerminalDevice, hPutStr, hSetBuffering, hSetEcho, stdin, stdout)
+  hIsTerminalDevice, hIsWritable, hPutStr, hSetBuffering, hSetEcho, stdin,
+  stdout)
 import Text.ParserCombinators.ReadP (readP_to_S)
 
 import System.Console.ANSI.Codes
@@ -71,6 +72,11 @@  where
   -- cannot use lookupEnv since it only appeared in GHC 7.6
   isNotDumb = (/= Just "dumb") . lookup "TERM" <$> getEnvironment
+
+-- hSupportsANSIWithoutEmulation :: Handle -> IO (Maybe Bool)
+-- (See Common-Include.hs for Haddock documentation)
+hSupportsANSIWithoutEmulation h =
+  Just <$> ((&&) <$> hIsWritable h <*> hSupportsANSI h)
 
 -- getReportedCursorPosition :: IO String
 -- (See Common-Include.hs for Haddock documentation)
src/System/Console/ANSI/Windows.hs view
@@ -182,6 +182,10 @@ -- (See Common-Include.hs for Haddock documentation)
 hSupportsANSI = E.hSupportsANSI
 
+-- hSupportsANSIWithoutEmulation :: Handle -> IO (Maybe Bool)
+-- (See Common-Include.hs for Haddock documentation)
+hSupportsANSIWithoutEmulation = E.hSupportsANSIWithoutEmulation
+
 -- getReportedCursorPosition :: IO String
 -- (See Common-Include.hs for Haddock documentation)
 getReportedCursorPosition = E.getReportedCursorPosition
src/System/Console/ANSI/Windows/Detect.hs view
@@ -5,11 +5,17 @@     ANSISupport (..)
   , ConsoleDefaultState (..)
   , aNSISupport
+  , detectHandleSupportsANSI
   ) where
 
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative ((<$>))
+#endif
+
 import Control.Exception (SomeException(..), throwIO, try)
 import Data.Bits ((.&.), (.|.))
-import System.IO (stdout)
+import System.Console.MinTTY (isMinTTYHandle)
+import System.IO (Handle, hIsWritable, stdout)
 import System.IO.Unsafe (unsafePerformIO)
 
 import System.Console.ANSI.Windows.Foreign (ConsoleException(..),
@@ -31,44 +37,77 @@                                  -- the console when that status was determined)
   deriving (Eq, Show)
 
+-- | Terminals on Windows
+data Terminal
+  = NativeANSIEnabled    -- ^ Windows 10 (Command Prompt or PowerShell)
+  | NativeANSIIncapable  -- ^ Versions before Windows 10 (Command Prompt or
+                         -- PowerShell)
+  | Mintty               -- ^ ANSI-enabled
+  | UnknownTerminal
+
 -- | This function assumes that once it is first established whether or not the
 -- Windows console requires emulation, that will not change. If the console
 -- requires emulation, the state of the console is considered to be its default
 -- state.
 {-# NOINLINE aNSISupport #-}
 aNSISupport :: ANSISupport
-aNSISupport = unsafePerformIO $ withHandleToHANDLE stdout aNSISupport'
-
--- | This function first checks if the Windows handle is valid and throws an
--- exception if it is not. It then tries to get a ConHost console mode for
--- that handle. If it can not, it assumes that the handle is ANSI-enabled. If
--- virtual termimal (VT) processing is already enabled, the handle does not
--- require emulation. Otherwise, it trys to enable processing. If it can, the
--- handle is ANSI-enabled. If it can not, emulation will be attempted and the
--- state of the console is considered to be its default state.
-aNSISupport' :: HANDLE -> IO ANSISupport
-aNSISupport' h =
-  if h == iNVALID_HANDLE_VALUE || h == nullHANDLE
-    then throwIO $ ConsoleException 6  -- Invalid handle or no handle
-    else do
-      tryMode <- try (getConsoleMode h) :: IO (Either SomeException DWORD)
-      case tryMode of
-        Left _     -> return Native  -- No ConHost mode
-        Right mode -> if mode .&. eNABLE_VIRTUAL_TERMINAL_PROCESSING /= 0
-          then return Native  -- VT processing already enabled
-          else do
-            let mode' = mode .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING
-            trySetMode <- try (setConsoleMode h mode') :: IO (Either SomeException ())
-            case trySetMode of
-              Left _   -> emulated       -- Can't enable VT processing
-              Right () -> return Native  -- VT processing enabled
+aNSISupport = unsafePerformIO $ withHandleToHANDLE stdout $ withHANDLE
+  (throwIO $ ConsoleException 6)  -- Invalid handle or no handle
+  (\h -> do
+    terminal <- handleToTerminal h
+    case terminal of
+      NativeANSIIncapable -> Emulated <$> consoleDefaultState h
+      _                   -> return Native)
  where
-  emulated = do
+  consoleDefaultState h = do
     info <- getConsoleScreenBufferInfo h
     let attributes = csbi_attributes info
         fgAttributes = attributes .&. fOREGROUND_INTENSE_WHITE
         bgAttributes = attributes .&. bACKGROUND_INTENSE_WHITE
-        consoleDefaultState = ConsoleDefaultState
-          { defaultForegroundAttributes = fgAttributes
-          , defaultBackgroundAttributes = bgAttributes }
-    return $ Emulated consoleDefaultState
+    return ConsoleDefaultState
+      { defaultForegroundAttributes = fgAttributes
+      , defaultBackgroundAttributes = bgAttributes }
+
+-- | This function tests that the handle is writable. If what is attached to the
+-- handle is not recognised as a known terminal, it returns @return Nothing@.
+detectHandleSupportsANSI :: Handle -> IO (Maybe Bool)
+detectHandleSupportsANSI handle = do
+  isWritable <- hIsWritable handle
+  if isWritable
+    then withHandleToHANDLE handle $ withHANDLE
+      (return $ Just False)  -- Invalid handle or no handle
+      (\h -> do
+        terminal <- handleToTerminal h
+        case terminal of
+          NativeANSIIncapable -> return (Just False)
+          UnknownTerminal     -> return Nothing  -- Not sure!
+          _                   -> return (Just True))
+    else return (Just False)  -- Not an output handle
+
+-- | This function assumes that the Windows handle is writable.
+handleToTerminal :: HANDLE -> IO Terminal
+handleToTerminal h = do
+  tryMode <- try (getConsoleMode h) :: IO (Either SomeException DWORD)
+  case tryMode of
+    Left _     -> do  -- No ConHost mode
+      isMinTTY <- isMinTTYHandle h
+      if isMinTTY
+        then return Mintty  -- 'mintty' terminal emulator
+        else return UnknownTerminal  -- Not sure!
+    Right mode -> if mode .&. eNABLE_VIRTUAL_TERMINAL_PROCESSING /= 0
+      then return NativeANSIEnabled  -- VT processing already enabled
+      else do
+        let mode' = mode .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING
+        trySetMode <- try (setConsoleMode h mode')
+          :: IO (Either SomeException ())
+        case trySetMode of
+          Left _   -> return NativeANSIIncapable  -- Can't enable VT processing
+          Right () -> return NativeANSIEnabled  -- VT processing enabled
+
+-- | This function applies another to the Windows handle, if the handle is
+-- valid. If it is invalid, the specified default action is returned.
+withHANDLE :: IO a -> (HANDLE -> IO a) -> HANDLE -> IO a
+withHANDLE invalid action h =
+  if h == iNVALID_HANDLE_VALUE || h == nullHANDLE
+    then invalid  -- Invalid handle or no handle
+    else action h
src/System/Console/ANSI/Windows/Emulator.hs view
@@ -417,14 +417,33 @@ 
 -- hSupportsANSI :: Handle -> IO Bool
 -- (See Common-Include.hs for Haddock documentation)
-hSupportsANSI h = (||) <$> isTDNotDumb <*> isMinTTY
+hSupportsANSI h = (||) <$> isTDNotDumb h <*> isMinTTY
  where
   isMinTTY = withHandleToHANDLE h isMinTTYHandle
+
+-- hSupportsANSIWithoutEmulation :: Handle -> IO (Maybe Bool)
+-- (See Common-Include.hs for Haddock documentation)
+hSupportsANSIWithoutEmulation handle = do
+  supportsANSI <- detectHandleSupportsANSI handle  -- Without reference to the
+                                                   -- environment
+  case supportsANSI of
+    Just isSupported -> return (Just isSupported)
+    Nothing -> do  -- Not sure, based on the handle alone
+      notDumb <- isNotDumb  -- Test the environment
+      if notDumb
+        then return Nothing  -- Still not sure!
+        else return (Just False) -- A dumb terminal
+
 -- Borrowed from an HSpec patch by Simon Hengel
 -- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd)
-  isTDNotDumb = (&&) <$> hIsTerminalDevice h <*> isNotDumb
-  -- cannot use lookupEnv since it only appeared in GHC 7.6
-  isNotDumb = (/= Just "dumb") . lookup "TERM" <$> getEnvironment
+isTDNotDumb :: Handle -> IO Bool
+isTDNotDumb h = (&&) <$> hIsTerminalDevice h <*> isNotDumb
+
+-- Borrowed from an HSpec patch by Simon Hengel
+-- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd)
+isNotDumb :: IO Bool
+-- cannot use lookupEnv since it only appeared in GHC 7.6
+isNotDumb = (/= Just "dumb") . lookup "TERM" <$> getEnvironment
 
 -- getReportedCursorPosition :: IO String
 -- (See Common-Include.hs for Haddock documentation)
src/includes/Common-Include.hs view
@@ -92,7 +92,8 @@ setTitle = hSetTitle stdout
 
 -- | Use heuristics to determine whether the functions defined in this
--- package will work with a given handle.
+-- package will work with a given handle. This function assumes that the handle
+-- is writable (that is, it manages output - see 'hIsWritable').
 --
 -- For Unix-like operating systems, the current implementation checks
 -- that: (1) the handle is a terminal; and (2) a @TERM@
@@ -105,8 +106,37 @@ -- 'hIsTerminalDevice' is used to check if the handle is a
 -- terminal. However, where a non-native Windows terminal (such as \'mintty\')
 -- is implemented using redirection, that function will not identify a
--- handle to the terminal as a terminal.)
+-- handle to the terminal as a terminal.) On Windows 10, if the handle is
+-- identified as connected to a native terminal, this function does /not/ enable
+-- the processing of \'ANSI\' control characters in output (see
+-- 'hSupportsANSIWithoutEmulation').
 hSupportsANSI :: Handle -> IO Bool
+
+-- | Use heuristics to determine whether a given handle will support \'ANSI\'
+-- control characters in output. (On Windows versions before Windows 10, that
+-- means \'support without emulation\'.)
+--
+-- If the handle is not writable (that is, it cannot manage output - see
+-- 'hIsWritable'), then @return (Just False)@ is returned.
+--
+-- On Unix-like operating systems, with one exception, the function is
+-- consistent with 'hSupportsANSI'. The exception is if the handle is not
+-- writable.
+--
+-- On Windows, what is returned will depend on what the handle is connected to
+-- and the version of the operating system. If the handle is identified as
+-- connected to a \'mintty\' terminal, @return (Just True)@ is
+-- returned. If it is identifed as connected to a native terminal, then, on
+-- Windows 10, the processing of \'ANSI\' control characters will be enabled and
+-- @return (Just True)@ returned; and, on versions of Windows before Windows 10,
+-- @return (Just False)@ is returned. Otherwise, if a @TERM@ environment
+-- variable is set to @dumb@, @return (Just False)@ is returned. In all other
+-- cases of a writable handle, @return Nothing@ is returned; this indicates that
+-- the heuristics cannot assist - the handle may be connected to a file or
+-- to another type of terminal.
+--
+-- @since 0.8.1
+hSupportsANSIWithoutEmulation :: Handle -> IO (Maybe Bool)
 
 -- | Parses the characters emitted by 'reportCursorPosition' into the console
 -- input stream. Returns the cursor row and column as a tuple.
src/includes/Exports-Include.hs view
@@ -107,6 +107,7 @@ 
     -- * Checking if handle supports ANSI (not portable: GHC only)
   , hSupportsANSI
+  , hSupportsANSIWithoutEmulation
 
     -- * Getting the cursor position
   , getCursorPosition