packages feed

ansi-terminal-1.0: win/System/Console/ANSI/Internal.hs

{-# LANGUAGE Safe #-}

module System.Console.ANSI.Internal
  ( getReportedCursorPosition
  , getReportedLayerColor
  , hSupportsANSI
  ) where

import Control.Exception ( IOException, catch )
import Data.Maybe ( mapMaybe )
import System.Environment ( lookupEnv )
import System.IO ( Handle, hIsTerminalDevice, hIsWritable, stdin )
import System.Console.ANSI.Types ( ConsoleLayer )

-- Provided by the ansi-terminal package
import System.Console.ANSI.Windows.Foreign
         ( INPUT_RECORD (..), INPUT_RECORD_EVENT (..), KEY_EVENT_RECORD (..)
         , cWcharsToChars, getNumberOfConsoleInputEvents, readConsoleInput
         , unicodeAsciiChar
         )
import System.Console.ANSI.Windows.Win32.MinTTY ( isMinTTYHandle )
import System.Console.ANSI.Windows.Win32.Types ( withHandleToHANDLE )


getReportedCursorPosition :: IO String
getReportedCursorPosition = getReported

getReportedLayerColor :: ConsoleLayer -> IO String
getReportedLayerColor _ = getReported

getReported :: IO String
getReported = catch getReported' getReportedExceptionHandler
 where
  getReported' = withHandleToHANDLE stdin action
   where
    action hdl = do
      n <- getNumberOfConsoleInputEvents hdl
      if n == 0
        then pure ""
        else do
          es <- readConsoleInput hdl n
          pure $ stringFromInputEvents es
    stringFromInputEvents = cWcharsToChars . wCharsFromInputEvents
    wCharsFromInputEvents = mapMaybe wCharFromInputEvent
    wCharFromInputEvent e = if isKeyEvent && isKeyDown
      then Just (unicodeAsciiChar $ keyEventChar keyEventRecord)
      else Nothing
     where
      eventType = inputEventType e
      eventRecord = inputEvent e
      isKeyEvent = eventType == 1
      keyEventRecord = case eventRecord of
        InputKeyEvent keyEventRecord' -> keyEventRecord'
        _ -> error "Unexpected input event, given input event type."
      isKeyDown = keyEventKeyDown keyEventRecord

getReportedExceptionHandler :: IOException -> IO a
getReportedExceptionHandler e = error msg
 where
  msg = "Error: " ++ show e ++ "\nThis error may be avoided by using a " ++
        "console based on the Windows' Console API, such as Command Prompt " ++
        "or PowerShell."

hSupportsANSI :: Handle -> IO Bool
hSupportsANSI h = (&&) <$> hIsWritable h <*> hSupportsANSI'
 where
  hSupportsANSI' = (||) <$> isTDNotDumb <*> isMinTTY
  -- Borrowed from an HSpec patch by Simon Hengel
  -- (https://github.com/hspec/hspec/commit/d932f03317e0e2bd08c85b23903fb8616ae642bd)
  isTDNotDumb = (&&) <$> hIsTerminalDevice h <*> isNotDumb
  isNotDumb = (/= Just "dumb") <$> lookupEnv "TERM"
  isMinTTY = withHandleToHANDLE h isMinTTYHandle