{-# LANGUAGE NamedFieldPuns, OverloadedStrings #-} module FortyTwo.Utils where import System.Console.ANSI (cursorUpLine, clearFromCursorToScreenEnd) import System.IO (hSetBuffering, hFlush, hSetEcho, hReady, stdin, stdout, BufferMode(..)) import Data.List (findIndex, findIndices, elemIndex, intercalate) import Control.Applicative ((<$>)) import Data.Maybe (fromJust) import FortyTwo.Types(Option(..), Options) import FortyTwo.Constants (emptyString) -- | Disable the stdin stdout output buffering noBuffering :: IO() noBuffering = do hSetBuffering stdin NoBuffering hSetBuffering stdout NoBuffering -- | Enaable the stdin stdout buffering restoreBuffering :: IO() restoreBuffering = do hSetBuffering stdin LineBuffering hSetBuffering stdout LineBuffering -- | Avoid echoing the user input noEcho :: IO () noEcho = hSetEcho stdin False -- | Restore the user input echos restoreEcho :: IO () restoreEcho = hSetEcho stdin True -- | Clear terminal lines from the current cursor position clearLines :: Int -> IO() clearLines l = do -- move up of some lines... cursorUpLine l -- and clear them clearFromCursorToScreenEnd -- | Map a collection with an index map' :: (Int -> a -> b) -> [a] -> [b] map' f = zipWith f [0..] -- | Filter a collection with index filter' :: Eq a => (Int -> a -> Bool) -> [a] -> [a] filter' f xs = [x | x <- xs, f (fromJust (elemIndex x xs)) x] -- | Get the value of any keyboard press getKey :: IO String getKey = reverse <$> getKey' emptyString where getKey' chars = do char <- getChar more <- hReady stdin (if more then getKey' else return) (char:chars) -- | Flush the output buffer flush :: IO() flush = hFlush stdout -- | Get useful informations from the options collection, like minVal, maxVal, activeIndex getOptionsMeta :: Options -> (Int, Int, Maybe Int) getOptionsMeta options = (0, length options - 1, getFocusedOptionIndex options) -- | Get the amount of breaking lines needed to display all the options getOptionsLines :: Options -> Int getOptionsLines = sum . map (length . lines . getOptionValue) -- | Convert a string array to stringsToOptions :: [String] -> Options stringsToOptions options = [ Option { value = o, isFocused = False, isSelected = False } | o <- options ] -- | Give the focus to a single option in the collection focusOption :: Int -> Options -> Options focusOption focusedIndex = map' $ \ i o -> Option { value = getOptionValue o, isSelected = getOptionIsSelected o, isFocused = focusedIndex == i } -- | Normalise the select/multiselect multi lines adding the spaces to format them properly addBreakingLinesSpacing :: String -> String -> String addBreakingLinesSpacing separator value = if null multiLines then value else firstLine ++ "\n" ++ unlines (take (length normalisedLines - 1) normalisedLines) ++ last normalisedLines where values = lines value firstLine = head values multiLines = tail values normalisedLines = map (\text -> separator ++ text) multiLines -- | Toggle the isSelected flag for a single option toggleFocusedOption :: Int -> Options -> Options toggleFocusedOption focusedIndex = map' $ \ i o -> Option { value = getOptionValue o, isFocused = focusedIndex == i, isSelected = if focusedIndex == i then not $ getOptionIsSelected o else getOptionIsSelected o } -- | Print a list to comma separated toCommaSeparatedString :: [String] -> String toCommaSeparatedString = intercalate ", " -- | Get the value of any option getOptionValue :: Option -> String getOptionValue Option { value } = value -- | Get the is focused attribute of any option getOptionIsFocused :: Option -> Bool getOptionIsFocused Option { isFocused } = isFocused -- | Get the is selected attribute of any option getOptionIsSelected :: Option -> Bool getOptionIsSelected Option { isSelected } = isSelected -- | Get the index of the option selected getFocusedOptionIndex :: Options -> Maybe Int getFocusedOptionIndex = findIndex getOptionIsFocused -- | Filter the indexes of the options selected getSelecteOptionsIndexes :: Options -> [Int] getSelecteOptionsIndexes = findIndices getOptionIsSelected