@@ -7,34 +7,56 @@ module Obelisk.CLI.Spinner (withSpinner') where
77import Control.Concurrent (killThread, threadDelay)
88import Control.Monad (forM_, (>=>))
99import Control.Monad.Catch (MonadMask, mask, onException)
10−import Control.Monad.IO.Class (liftIO)
10+import Control.Monad.IO.Class
1111import Control.Monad.Log (MonadLog, logMessage)
1212import Control.Monad.Reader (MonadIO)
13+import Data.IORef
14+import Data.List (delete)
1315import Data.Text (Text)
1416import qualified Data.Text as T
15−import System.Console.ANSI (Color (Cyan, Green, Red), ColorIntensity (Vivid), ConsoleLayer (Foreground),
17+import System.Console.ANSI (Color (Blue, Cyan, Green, Red), ColorIntensity (Vivid), ConsoleLayer (Foreground),
1618 SGR (Reset, SetColor), setSGRCode)
1719
18−import Obelisk.CLI.Logging (LoggingConfig, Output (..), allowUserToMakeLoggingVerbose, forkML)
20+import Obelisk.CLI.Logging (LoggingConfig (..), Output (..), allowUserToMakeLoggingVerbose, forkML)
1921
2022-- | Run an action with a CLI spinner.
2123withSpinner'
2224 :: (MonadIO m, MonadMask m, MonadLog Output m)
2325 => LoggingConfig -> Text -> m a -> m a
24−withSpinner' conf s = bracket' (sequence [run, forkML conf $ allowUserToMakeLoggingVerbose conf enquiryCode]) cleanup . const
26+withSpinner' conf s = bracket' start cleanup . const
2527 where
26− run = forkML conf $ do
27− runSpinner spinner $ \c -> do
28− logMessage $ Output_Overwrite [c, " ", T.unpack s]
28+ start = do
29+ tids <- run
30+ tid <- forkML conf $ allowUserToMakeLoggingVerbose conf enquiryCode
31+ pure $ tid : tids
32+ stack = _loggingConfig_stack conf
33+ run = do
34+ -- Add this log to the spinner stack
35+ wasEmpty <- liftIO $ atomicModifyIORef' stack $ \old -> (s : old, null old)
36+ if wasEmpty
37+ then do -- Fork a thread to manage output of anything on the stack
38+ tid <- forkML conf $ runSpinner spinner $ \c -> do
39+ logs <- liftIO $ concatStack c <$> readIORef stack
40+ logMessage $ Output_Overwrite [logs]
41+ pure [tid]
42+ else pure []
2943 cleanup failed tids = do
3044 liftIO $ mapM_ killThread tids
3145 logMessage Output_ClearLine
3246 let mark = if failed
3347 then withColor Red "✖"
3448 else withColor Green "✔"
35− logMessage $ Output_Overwrite [mark, " ", T.unpack s, "\n"]
49+ -- Delete this log from the spinner stack
50+ logs <- liftIO $ atomicModifyIORef' stack $ \old -> (delete s old, concatStack mark old)
51+ logMessage $ Output_Overwrite [logs, "\n"]
3652 spinner = coloredSpinner defaultSpinnerTheme
3753
54+-- | How nested spinner logs should be displayed
55+concatStack :: String -> [Text] -> String
56+concatStack mark = drop 1 . foldr flatten "" . zip (mark : repeat arrow) . fmap T.unpack
57+ where flatten (m, s) acc = unwords [acc, m, s]
58+ arrow = withColor Blue "⇾"
59+
3860-- | A spinner is simply an infinite list of strings that supplant each other in a delayed loop, creating the
3961-- animation of a "spinner".
4062type Spinner = [String]