packages feed

ansi-terminal-0.6.3: System/Console/ANSI/Windows/Detect.hs

{-# OPTIONS_HADDOCK hide #-}

module System.Console.ANSI.Windows.Detect
(
    isANSIEnabled
) where

import Control.Exception (SomeException(..), throwIO, try)
import Data.Bits ((.|.))
import System.Console.ANSI.Windows.Foreign (ConsoleException(..), DWORD,
    eNABLE_VIRTUAL_TERMINAL_PROCESSING, getConsoleMode, getStdHandle, HANDLE,
    iNVALID_HANDLE_VALUE, nullHANDLE, setConsoleMode, sTD_OUTPUT_HANDLE)
-- 'lookupEnv' is not available until base-4.6.0.0 (GHC 7.6.1)
import System.Environment.Compat (lookupEnv)
import System.IO.Unsafe (unsafePerformIO)

-- This function assumes that once it is first established whether or not the
-- Windows console is ANSI-enabled, that will not change.
{-# NOINLINE isANSIEnabled #-}
isANSIEnabled :: Bool
isANSIEnabled = unsafePerformIO safeIsANSIEnabled

-- This function takes the following approach. 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 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
    result <- lookupEnv "TERM"
    case result 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, 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 enable virtual terminal processing on the standard
-- output and throws an exception if it cannot.
enableANSIOut :: IO ()
enableANSIOut = do
    hOut <- getValidStdHandle sTD_OUTPUT_HANDLE
    mOut <- getConsoleMode hOut
    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
        else return h