packages feed

dr-cabal-0.2.0.0: src/DrCabal/Terminal.hs

{- |
Module                  : DrCabal.Terminal
Copyright               : (c) 2022 Dmitrii Kovanikov
SPDX-License-Identifier : MPL-2.0
Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
Stability               : Experimental
Portability             : Portable

Utility functions to work with the terminal output.
-}

module DrCabal.Terminal
    ( getTerminalWidth
    , clearScreen
    , withAlternateBuffer
    ) where

import Colourista.Short (u)
import Control.Exception (bracket)
import System.Console.ANSI (clearFromCursorToScreenEnd, cursorUpLine)

import qualified System.Console.Terminal.Size as Terminal


{- | Get the width of the current terminal.

This function exits the process with the message if it can't get the
width of the current terminal.
-}
getTerminalWidth :: IO Int
getTerminalWidth = Terminal.size >>= \case
    Just (Terminal.Window _height width) -> pure width
    Nothing -> do
        putText $ unlines
            [ "Error getting the terminal width. If you see this error, open an issue"
            , "in the 'dr-cabal' issue tracker and provide as many details as possible"
            , ""
            , "  * " <> u "https://github.com/chshersh/dr-cabal/issues/new"
            ]
        exitFailure

{- | Clears the the @screenHeight@ number of lines in the screen. Pass
the number of lines in the output to clear the entire screen.
-}
clearScreen :: Int -> IO ()
clearScreen screenHeight = do
    -- https://github.com/UnkindPartition/ansi-terminal/issues/141
    when (screenHeight > 0) $
        cursorUpLine screenHeight
    clearFromCursorToScreenEnd

{- | Run action in the alternate buffer and return to the normal
screen after exception or SIGINT.

__NOTE:__ This function always returns to the normal screen after the
action. If you want to print something to the normal screen use the
result of the given action afterwards.
-}
withAlternateBuffer :: IO a -> IO a
withAlternateBuffer action = bracket
    useAlternateScreenBuffer
    (\_ -> useNormalScreenBuffer)
    (\_ -> action)

-- TODO: use functions from 'ansi-terminal'
useAlternateScreenBuffer :: IO ()
useAlternateScreenBuffer = putStrLn "\ESC[?1049h\ESC[H"

-- TODO: use functions from 'ansi-terminal'
useNormalScreenBuffer :: IO ()
useNormalScreenBuffer = putStrLn "\ESC[?1049l"