    {-# LANGUAGE NamedFieldPuns #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Actions.TopicSpace
    -- Description :  Turns your workspaces into a more topic oriented system.
    -- Copyright   :  (c) Nicolas Pouillard
    -- License     :  BSD-style (see LICENSE)
    --
    -- Maintainer  :  Nicolas Pouillard <nicolas.pouillard@gmail.com>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- Turns your workspaces into a more topic oriented system.
    -----------------------------------------------------------------------------

    module XMonad.Actions.TopicSpace
      (
      -- * Overview
      -- $overview

      -- * Usage
      -- $usage

      -- * Types for Building Topics
        Topic
      , Dir
      , TopicConfig(..)
      , TopicItem(..)

        -- * Managing 'TopicItem's
      , topicNames
      , tiActions
      , tiDirs
      , noAction
      , inHome

        -- * Switching and Shifting Topics
      , switchTopic
      , switchNthLastFocused
      , switchNthLastFocusedByScreen
      , switchNthLastFocusedExclude
      , shiftNthLastFocused

        -- * Topic Actions
      , topicActionWithPrompt
      , topicAction
      , currentTopicAction

        -- * Getting the Topic History
      , getLastFocusedTopics
      , workspaceHistory
      , workspaceHistoryByScreen

        -- * Modifying the Topic History
      , setLastFocusedTopic
      , reverseLastFocusedTopics

        -- * History hooks
      , workspaceHistoryHook
      , workspaceHistoryHookExclude

        -- * Pretty Printing
      , pprWindowSet

        -- * Utility
      , currentTopicDir
      , checkTopicConfig
      , (>*>)
      )
    where

    import XMonad
    import XMonad.Prelude

    import qualified Data.Map.Strict           as M
    import qualified XMonad.Hooks.StatusBar.PP as SBPP
    import qualified XMonad.StackSet           as W

    import Data.Map (Map)

    import XMonad.Prompt (XPConfig)
    import XMonad.Prompt.Workspace (workspacePrompt)

    import XMonad.Hooks.StatusBar.PP (PP(ppHidden, ppVisible))
    import XMonad.Hooks.UrgencyHook (readUrgents)
    import XMonad.Hooks.WorkspaceHistory
        ( workspaceHistory
        , workspaceHistoryByScreen
        , workspaceHistoryHook
        , workspaceHistoryHookExclude
        , workspaceHistoryModify
        )

    -- $overview
    -- This module allows to organize your workspaces on a precise topic basis.  So
    -- instead of having a workspace called `work' you can setup one workspace per
    -- task.  Here we call these workspaces, topics. The great thing with
    -- topics is that one can attach a directory that makes sense to each
    -- particular topic.  One can also attach an action which will be triggered
    -- when switching to a topic that does not have any windows in it.  So you can
    -- attach your mail client to the mail topic, some terminals in the right
    -- directory to the xmonad topic... This package also provides a nice way to
    -- display your topics in an historical way using a custom `pprWindowSet'
    -- function. You can also easily switch to recent topics using this history
    -- of last focused topics.
    --
    -- A blog post highlighting some features of this module can be found
    -- <https://tony-zorman.com/posts/topic-space/2022-09-11-topic-spaces.html here>.

    -- $usage
    -- You can use this module with the following in your @xmonad.hs@:
    --
    -- > import qualified Data.Map.Strict as M
    -- > import qualified XMonad.StackSet as W
    -- >
    -- > import XMonad.Actions.TopicSpace
    -- > import XMonad.Util.EZConfig    -- for the keybindings
    -- > import XMonad.Prompt.Workspace -- if you want to use the prompt
    --
    -- You will then have to
    --
    --   * Define a new 'TopicConfig' via 'TopicItem's
    --
    --   * Add the appropriate keybindings
    --
    --   * Replace the @workspaces@ field in your 'XConfig' with a list of
    --     your topics names
    --
    --   * Optionally, if you want to use the history features, add
    --     'workspaceHistoryHook' from "XMonad.Hooks.WorkspaceHistory"
    --     (re-exported by this module) or an equivalent function to your
    --     @logHook@.  See the documentation of
    --     "XMonad.Hooks.WorkspaceHistory" for further details
    --
    -- Let us go through a full example together.
    --
    -- A 'TopicItem' consists of three things: the name of the topic, its
    -- root directory, and the action associated to it—to be executed if the
    -- topic is empty or the action is forced via a keybinding.
    --
    -- We start by specifying our chosen topics as a list of such
    -- 'TopicItem's:
    --
    -- > topicItems :: [TopicItem]
    -- > topicItems =
    -- >   [ inHome   "1:WEB"              (spawn "firefox")
    -- >   , noAction "2"      "."
    -- >   , noAction "3:VID"  "videos"
    -- >   , TI       "4:VPN"  "openvpn"   (spawn "urxvt -e randomVPN.sh")
    -- >   , inHome   "5:IM"               (spawn "signal" *> spawn "telegram")
    -- >   , inHome   "6:IRC"              (spawn "urxvt -e weechat")
    -- >   , TI       "dts"    ".dotfiles" spawnShell
    -- >   , TI       "xm-con" "hs/xm-con" (spawnShell *> spawnShellIn "hs/xm")
    -- >   ]
    --
    -- Then we just need to put together our topic config:
    --
    -- > myTopicConfig :: TopicConfig
    -- > myTopicConfig = def
    -- >   { topicDirs          = tiDirs    topicItems
    -- >   , topicActions       = tiActions topicItems
    -- >   , defaultTopicAction = const (pure ()) -- by default, do nothing
    -- >   , defaultTopic       = "1:WEB"         -- fallback
    -- >   }
    --
    -- Above, we have used the `spawnShell` and `spawnShellIn` helper
    -- functions; here they are:
    --
    -- > spawnShell :: X ()
    -- > spawnShell = currentTopicDir myTopicConfig >>= spawnShellIn
    -- >
    -- > spawnShellIn :: Dir -> X ()
    -- > spawnShellIn dir = spawn $ "alacritty --working-directory " ++ dir
    --
    -- Next, we define some other other useful helper functions.  It is
    -- rather common to have a lot of topics—much more than available keys!
    -- In a situation like that, it's very convenient to switch topics with
    -- a prompt; the following use of 'workspacePrompt' does exactly that.
    --
    -- > goto :: Topic -> X ()
    -- > goto = switchTopic myTopicConfig
    -- >
    -- > promptedGoto :: X ()
    -- > promptedGoto = workspacePrompt def goto
    -- >
    -- > promptedShift :: X ()
    -- > promptedShift = workspacePrompt def $ windows . W.shift
    -- >
    -- > -- Toggle between the two most recently used topics, but keep
    -- > -- screens separate.  This needs @workspaceHistoryHook@.
    -- > toggleTopic :: X ()
    -- > toggleTopic = switchNthLastFocusedByScreen myTopicConfig 1
    --
    -- Hopefully you've gotten a general feeling of how to define these kind of
    -- small helper functions using what's provided in this module.
    --
    -- Adding the appropriate keybindings works as it normally would.  Here,
    -- we'll use "XMonad.Util.EZConfig" syntax:
    --
    -- > myKeys :: [(String, X ())]
    -- > myKeys =
    -- >   [ ("M-n"        , spawnShell)
    -- >   , ("M-a"        , currentTopicAction myTopicConfig)
    -- >   , ("M-g"        , promptedGoto)
    -- >   , ("M-S-g"      , promptedShift)
    -- >   , ("M-S-<Space>", toggleTopic)
    -- >   ]
    -- >   ++
    -- >   -- The following does two things:
    -- >   --   1. Switch topics (no modifier)
    -- >   --   2. Move focused window to topic N (shift modifier)
    -- >   [ ("M-" ++ m ++ k, f i)
    -- >   | (i, k) <- zip (topicNames topicItems) (map show [1 .. 9 :: Int])
    -- >   , (f, m) <- [(goto, ""), (windows . W.shift, "S-")]
    -- >   ]
    --
    -- This makes @M-1@ to @M-9@ switch to the first nine topics that we
    -- have specified in @topicItems@.
    --
    -- You can also switch to the nine last-used topics instead:
    --
    -- >   [ ("M-" ++ show i, switchNthLastFocused myTopicConfig i)
    -- >   | i <- [1 .. 9]
    -- >   ]
    --
    -- We can now put the whole configuration together with the following:
    --
    -- > main :: IO ()
    -- > main = xmonad $ def
    -- >   { workspaces = topicNames topicItems
    -- >   }
    -- >  `additionalKeysP` myKeys

    -- | An alias for @flip replicateM_@
    (>*>) :: Monad m => m a -> Int -> m ()
    >*> :: forall (m :: * -> *) a. Monad m => m a -> Int -> m ()
    (>*>) = (Int -> m a -> m ()) -> m a -> Int -> m ()
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Int -> m a -> m ()
    forall (m :: * -> *) a. Applicative m => Int -> m a -> m ()
    replicateM_
    infix >*>

    -- | 'Topic' is just an alias for 'WorkspaceId'
    type Topic = WorkspaceId

    -- | 'Dir' is just an alias for 'FilePath', but should point to a directory.
    type Dir = FilePath

    -- | Here is the topic space configuration area.
    data TopicConfig = TopicConfig { TopicConfig -> Map Topic Topic
    topicDirs          :: Map Topic Dir
                                     -- ^ This mapping associates a directory to each topic.
                                   , TopicConfig -> Map Topic (X ())
    topicActions       :: Map Topic (X ())
                                     -- ^ This mapping associates an action to trigger when
                                     -- switching to a given topic which workspace is empty.
                                   , TopicConfig -> Topic -> X ()
    defaultTopicAction :: Topic -> X ()
                                     -- ^ This is the default topic action.
                                   , TopicConfig -> Topic
    defaultTopic       :: Topic
                                     -- ^ This is the default (= fallback) topic.
                                   , TopicConfig -> Int
    maxTopicHistory    :: Int
                                     -- ^ This specifies the maximum depth of the topic history;
                                     -- usually 10 is a good default since we can bind all of
                                     -- them using numeric keypad.
                                   }
    {-# DEPRECATED maxTopicHistory "This field will be removed in the future; history is now handled by XMonad.Hooks.WorkspaceHistory" #-}

    instance Default TopicConfig where
      def :: TopicConfig
    def            = TopicConfig { topicDirs :: Map Topic Topic
    topicDirs = Map Topic Topic
    forall k a. Map k a
    M.empty
                                   , topicActions :: Map Topic (X ())
    topicActions = Map Topic (X ())
    forall k a. Map k a
    M.empty
                                   , defaultTopicAction :: Topic -> X ()
    defaultTopicAction = X () -> Topic -> X ()
    forall a b. a -> b -> a
    const (X XConf
    forall r (m :: * -> *). MonadReader r m => m r
    ask X XConf -> (XConf -> X ()) -> X ()
    forall a b. X a -> (a -> X b) -> X b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Topic -> X ()
    forall (m :: * -> *). MonadIO m => Topic -> m ()
    spawn (Topic -> X ()) -> (XConf -> Topic) -> XConf -> X ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XConfig Layout -> Topic
    forall (l :: * -> *). XConfig l -> Topic
    terminal (XConfig Layout -> Topic)
    -> (XConf -> XConfig Layout) -> XConf -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XConf -> XConfig Layout
    config)
                                   , defaultTopic :: Topic
    defaultTopic = Topic
    "1"
                                   , maxTopicHistory :: Int
    maxTopicHistory = Int
    10
                                   }

    -- | Return the (possibly empty) list of last focused topics.
    getLastFocusedTopics :: X [Topic]
    getLastFocusedTopics :: X [Topic]
    getLastFocusedTopics = X [Topic]
    workspaceHistory
    {-# DEPRECATED getLastFocusedTopics "Use XMonad.Hooks.WorkspaceHistory.workspaceHistory (re-exported by this module) instead" #-}

    -- | Given a 'TopicConfig', a topic, and a predicate to select topics that one
    -- wants to keep, this function will cons the topic in front of the list of
    -- last focused topics and filter it according to the predicate.  Note that we
    -- prune the list in case that its length exceeds 'maxTopicHistory'.
    setLastFocusedTopic :: TopicConfig -> Topic -> (Topic -> Bool) -> X ()
    setLastFocusedTopic :: TopicConfig -> Topic -> (Topic -> Bool) -> X ()
    setLastFocusedTopic TopicConfig
    tc Topic
    w Topic -> Bool
    predicate = do
      ScreenId
    sid <- (XState -> ScreenId) -> X ScreenId
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets ((XState -> ScreenId) -> X ScreenId)
    -> (XState -> ScreenId) -> X ScreenId
    forall a b. (a -> b) -> a -> b
    $ Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> ScreenId
    forall i l a sid sd. Screen i l a sid sd -> sid
    W.screen (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> ScreenId)
    -> (XState
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> ScreenId
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> (XState
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset
      ([(ScreenId, Topic)] -> [(ScreenId, Topic)]) -> X ()
    workspaceHistoryModify (([(ScreenId, Topic)] -> [(ScreenId, Topic)]) -> X ())
    -> ([(ScreenId, Topic)] -> [(ScreenId, Topic)]) -> X ()
    forall a b. (a -> b) -> a -> b
    $
        Int -> [(ScreenId, Topic)] -> [(ScreenId, Topic)]
    forall a. Int -> [a] -> [a]
    take (TopicConfig -> Int
    maxTopicHistory TopicConfig
    tc) ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> [(ScreenId, Topic)]
    -> [(ScreenId, Topic)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(ScreenId, Topic)] -> [(ScreenId, Topic)]
    forall a. Eq a => [a] -> [a]
    nub ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> [(ScreenId, Topic)]
    -> [(ScreenId, Topic)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((ScreenId, Topic) -> Bool)
    -> [(ScreenId, Topic)] -> [(ScreenId, Topic)]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Topic -> Bool
    predicate (Topic -> Bool)
    -> ((ScreenId, Topic) -> Topic) -> (ScreenId, Topic) -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (ScreenId, Topic) -> Topic
    forall a b. (a, b) -> b
    snd) ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> ([(ScreenId, Topic)] -> [(ScreenId, Topic)])
    -> [(ScreenId, Topic)]
    -> [(ScreenId, Topic)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((ScreenId
    sid, Topic
    w) (ScreenId, Topic) -> [(ScreenId, Topic)] -> [(ScreenId, Topic)]
    forall a. a -> [a] -> [a]
    :)
    {-# DEPRECATED setLastFocusedTopic "Use XMonad.Hooks.WorkspaceHistory instead" #-}

    -- | Reverse the list of "last focused topics"
    reverseLastFocusedTopics :: X ()
    reverseLastFocusedTopics :: X ()
    reverseLastFocusedTopics = ([(ScreenId, Topic)] -> [(ScreenId, Topic)]) -> X ()
    workspaceHistoryModify [(ScreenId, Topic)] -> [(ScreenId, Topic)]
    forall a. [a] -> [a]
    reverse

    -- | This function is a variant of 'SBPP.pprWindowSet' which takes a topic
    -- configuration and a pretty-printing record 'PP'. It will show the list of
    -- topics sorted historically and highlight topics with urgent windows.
    pprWindowSet :: TopicConfig -> PP -> X String
    pprWindowSet :: TopicConfig -> PP -> X Topic
    pprWindowSet TopicConfig
    tg PP
    pp = do
      StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    winset <- (XState
     -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> X (StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset
      [Window]
    urgents <- X [Window]
    readUrgents
      let empty_workspaces :: [Topic]
    empty_workspaces = (Workspace Topic (Layout Window) Window -> Topic)
    -> [Workspace Topic (Layout Window) Window] -> [Topic]
    forall a b. (a -> b) -> [a] -> [b]
    map Workspace Topic (Layout Window) Window -> Topic
    forall i l a. Workspace i l a -> i
    W.tag ([Workspace Topic (Layout Window) Window] -> [Topic])
    -> [Workspace Topic (Layout Window) Window] -> [Topic]
    forall a b. (a -> b) -> a -> b
    $ (Workspace Topic (Layout Window) Window -> Bool)
    -> [Workspace Topic (Layout Window) Window]
    -> [Workspace Topic (Layout Window) Window]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Maybe (Stack Window) -> Bool
    forall a. Maybe a -> Bool
    isNothing (Maybe (Stack Window) -> Bool)
    -> (Workspace Topic (Layout Window) Window -> Maybe (Stack Window))
    -> Workspace Topic (Layout Window) Window
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Workspace Topic (Layout Window) Window -> Maybe (Stack Window)
    forall i l a. Workspace i l a -> Maybe (Stack a)
    W.stack) ([Workspace Topic (Layout Window) Window]
     -> [Workspace Topic (Layout Window) Window])
    -> [Workspace Topic (Layout Window) Window]
    -> [Workspace Topic (Layout Window) Window]
    forall a b. (a -> b) -> a -> b
    $ StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> [Workspace Topic (Layout Window) Window]
    forall i l a s sd. StackSet i l a s sd -> [Workspace i l a]
    W.workspaces StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    winset
          maxDepth :: Int
    maxDepth = TopicConfig -> Int
    maxTopicHistory TopicConfig
    tg
      TopicConfig -> Topic -> (Topic -> Bool) -> X ()
    setLastFocusedTopic TopicConfig
    tg
                          (Workspace Topic (Layout Window) Window -> Topic
    forall i l a. Workspace i l a -> i
    W.tag (Workspace Topic (Layout Window) Window -> Topic)
    -> (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
        -> Workspace Topic (Layout Window) Window)
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> Workspace Topic (Layout Window) Window
    forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
    W.workspace (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> Workspace Topic (Layout Window) Window)
    -> (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Workspace Topic (Layout Window) Window
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Topic)
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Topic
    forall a b. (a -> b) -> a -> b
    $ StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    winset)
                          (Topic -> [Topic] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` [Topic]
    empty_workspaces)
      [Topic]
    lastWs <- X [Topic]
    workspaceHistory
      let depth :: Topic -> Int
    depth Topic
    topic = Maybe Int -> Int
    forall a. HasCallStack => Maybe a -> a
    fromJust (Maybe Int -> Int) -> Maybe Int -> Int
    forall a b. (a -> b) -> a -> b
    $ Topic -> [Topic] -> Maybe Int
    forall a. Eq a => a -> [a] -> Maybe Int
    elemIndex Topic
    topic ([Topic]
    lastWs [Topic] -> [Topic] -> [Topic]
    forall a. [a] -> [a] -> [a]
    ++ [Topic
    topic])
          add_depth :: (PP -> Topic -> c) -> Topic -> c
    add_depth PP -> Topic -> c
    proj Topic
    topic = PP -> Topic -> c
    proj PP
    pp (Topic -> c) -> (Topic -> Topic) -> Topic -> c
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (((Topic
    topicTopic -> Topic -> Topic
    forall a. [a] -> [a] -> [a]
    ++Topic
    ":")Topic -> Topic -> Topic
    forall a. [a] -> [a] -> [a]
    ++) (Topic -> Topic) -> (Int -> Topic) -> Int -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> Topic
    forall a. Show a => a -> Topic
    show) (Int -> Topic) -> (Topic -> Int) -> Topic -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Topic -> Int
    depth (Topic -> c) -> Topic -> c
    forall a b. (a -> b) -> a -> b
    $ Topic
    topic
          pp' :: PP
    pp' = PP
    pp { ppHidden = add_depth ppHidden, ppVisible = add_depth ppVisible }
          sortWindows :: [Workspace Topic l a] -> [Workspace Topic l a]
    sortWindows = Int -> [Workspace Topic l a] -> [Workspace Topic l a]
    forall a. Int -> [a] -> [a]
    take Int
    maxDepth ([Workspace Topic l a] -> [Workspace Topic l a])
    -> ([Workspace Topic l a] -> [Workspace Topic l a])
    -> [Workspace Topic l a]
    -> [Workspace Topic l a]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Workspace Topic l a -> Int)
    -> [Workspace Topic l a] -> [Workspace Topic l a]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (Topic -> Int
    depth (Topic -> Int)
    -> (Workspace Topic l a -> Topic) -> Workspace Topic l a -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Workspace Topic l a -> Topic
    forall i l a. Workspace i l a -> i
    W.tag)
      Topic -> X Topic
    forall a. a -> X a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Topic -> X Topic) -> Topic -> X Topic
    forall a b. (a -> b) -> a -> b
    $ ([Workspace Topic (Layout Window) Window]
     -> [Workspace Topic (Layout Window) Window])
    -> [Window]
    -> PP
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Topic
    SBPP.pprWindowSet [Workspace Topic (Layout Window) Window]
    -> [Workspace Topic (Layout Window) Window]
    forall {l} {a}. [Workspace Topic l a] -> [Workspace Topic l a]
    sortWindows [Window]
    urgents PP
    pp' StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    winset

    -- | Given a prompt configuration and a topic configuration, trigger the action associated with
    -- the topic given in prompt.
    topicActionWithPrompt :: XPConfig -> TopicConfig -> X ()
    topicActionWithPrompt :: XPConfig -> TopicConfig -> X ()
    topicActionWithPrompt XPConfig
    xp TopicConfig
    tg = XPConfig -> (Topic -> X ()) -> X ()
    workspacePrompt XPConfig
    xp ((X () -> X () -> X ())
    -> (Topic -> X ()) -> (Topic -> X ()) -> Topic -> X ()
    forall a b c.
    (a -> b -> c) -> (Topic -> a) -> (Topic -> b) -> Topic -> c
    forall (f :: * -> *) a b c.
    Applicative f =>
    (a -> b -> c) -> f a -> f b -> f c
    liftA2 X () -> X () -> X ()
    forall a b. X a -> X b -> X b
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    (>>) (TopicConfig -> Topic -> X ()
    switchTopic TopicConfig
    tg) (TopicConfig -> Topic -> X ()
    topicAction TopicConfig
    tg))

    -- | Given a configuration and a topic, trigger the action associated with the given topic.
    topicAction :: TopicConfig -> Topic -> X ()
    topicAction :: TopicConfig -> Topic -> X ()
    topicAction TopicConfig
    tg Topic
    topic = X () -> Maybe (X ()) -> X ()
    forall a. a -> Maybe a -> a
    fromMaybe (TopicConfig -> Topic -> X ()
    defaultTopicAction TopicConfig
    tg Topic
    topic) (Maybe (X ()) -> X ()) -> Maybe (X ()) -> X ()
    forall a b. (a -> b) -> a -> b
    $ Topic -> Map Topic (X ()) -> Maybe (X ())
    forall k a. Ord k => k -> Map k a -> Maybe a
    M.lookup Topic
    topic (Map Topic (X ()) -> Maybe (X ()))
    -> Map Topic (X ()) -> Maybe (X ())
    forall a b. (a -> b) -> a -> b
    $ TopicConfig -> Map Topic (X ())
    topicActions TopicConfig
    tg

    -- | Trigger the action associated with the current topic.
    currentTopicAction :: TopicConfig -> X ()
    currentTopicAction :: TopicConfig -> X ()
    currentTopicAction TopicConfig
    tg = TopicConfig -> Topic -> X ()
    topicAction TopicConfig
    tg (Topic -> X ()) -> X Topic -> X ()
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< (XState -> Topic) -> X Topic
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets (Workspace Topic (Layout Window) Window -> Topic
    forall i l a. Workspace i l a -> i
    W.tag (Workspace Topic (Layout Window) Window -> Topic)
    -> (XState -> Workspace Topic (Layout Window) Window)
    -> XState
    -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> Workspace Topic (Layout Window) Window
    forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
    W.workspace (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> Workspace Topic (Layout Window) Window)
    -> (XState
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Workspace Topic (Layout Window) Window
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> (XState
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset)

    -- | Switch to the given topic.
    switchTopic :: TopicConfig -> Topic -> X ()
    switchTopic :: TopicConfig -> Topic -> X ()
    switchTopic TopicConfig
    tc Topic
    topic = do
      -- Switch to topic and add it to the last seen topics
      (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> X ()
    windows ((StackSet Topic (Layout Window) Window ScreenId ScreenDetail
      -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
     -> X ())
    -> (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> X ()
    forall a b. (a -> b) -> a -> b
    $ Topic
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    forall s i l a sd.
    (Eq s, Eq i) =>
    i -> StackSet i l a s sd -> StackSet i l a s sd
    W.greedyView Topic
    topic

      -- If applicable, execute the topic action
      [Window]
    wins <- (XState -> [Window]) -> X [Window]
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets (Maybe (Stack Window) -> [Window]
    forall a. Maybe (Stack a) -> [a]
    W.integrate' (Maybe (Stack Window) -> [Window])
    -> (XState -> Maybe (Stack Window)) -> XState -> [Window]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Workspace Topic (Layout Window) Window -> Maybe (Stack Window)
    forall i l a. Workspace i l a -> Maybe (Stack a)
    W.stack (Workspace Topic (Layout Window) Window -> Maybe (Stack Window))
    -> (XState -> Workspace Topic (Layout Window) Window)
    -> XState
    -> Maybe (Stack Window)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> Workspace Topic (Layout Window) Window
    forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
    W.workspace (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> Workspace Topic (Layout Window) Window)
    -> (XState
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Workspace Topic (Layout Window) Window
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> (XState
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset)
      Bool -> X () -> X ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when ([Window] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Window]
    wins) (X () -> X ()) -> X () -> X ()
    forall a b. (a -> b) -> a -> b
    $ TopicConfig -> Topic -> X ()
    topicAction TopicConfig
    tc Topic
    topic

    -- | Switch to the Nth last focused topic or fall back to the 'defaultTopic'.
    switchNthLastFocused :: TopicConfig -> Int -> X ()
    switchNthLastFocused :: TopicConfig -> Int -> X ()
    switchNthLastFocused = [Topic] -> TopicConfig -> Int -> X ()
    switchNthLastFocusedExclude []

    -- | Like 'switchNthLastFocused', but also filter out certain topics.
    switchNthLastFocusedExclude :: [Topic] -> TopicConfig -> Int -> X ()
    switchNthLastFocusedExclude :: [Topic] -> TopicConfig -> Int -> X ()
    switchNthLastFocusedExclude [Topic]
    excludes TopicConfig
    tc Int
    depth = do
      [Topic]
    lastWs <- (Topic -> Bool) -> [Topic] -> [Topic]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Topic -> [Topic] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` [Topic]
    excludes) ([Topic] -> [Topic]) -> X [Topic] -> X [Topic]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> X [Topic]
    workspaceHistory
      TopicConfig -> Topic -> X ()
    switchTopic TopicConfig
    tc (Topic -> X ()) -> Topic -> X ()
    forall a b. (a -> b) -> a -> b
    $ ([Topic]
    lastWs [Topic] -> [Topic] -> [Topic]
    forall a. [a] -> [a] -> [a]
    ++ Topic -> [Topic]
    forall a. a -> [a]
    repeat (TopicConfig -> Topic
    defaultTopic TopicConfig
    tc)) [Topic] -> Int -> Topic
    forall a. HasCallStack => [a] -> Int -> a
    !! Int
    depth

    -- | Like 'switchNthLastFocused', but only consider topics that used to
    -- be on the current screen.
    --
    -- For example, the following function allows one to toggle between the
    -- currently focused and the last used topic, while treating different
    -- screens completely independently from one another.
    --
    -- > toggleTopicScreen = switchNthLastFocusedByScreen myTopicConfig 1
    switchNthLastFocusedByScreen :: TopicConfig -> Int -> X ()
    switchNthLastFocusedByScreen :: TopicConfig -> Int -> X ()
    switchNthLastFocusedByScreen TopicConfig
    tc Int
    depth = do
      ScreenId
    sid <- (XState -> ScreenId) -> X ScreenId
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets ((XState -> ScreenId) -> X ScreenId)
    -> (XState -> ScreenId) -> X ScreenId
    forall a b. (a -> b) -> a -> b
    $ Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> ScreenId
    forall i l a sid sd. Screen i l a sid sd -> sid
    W.screen (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> ScreenId)
    -> (XState
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> ScreenId
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> (XState
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset
      [Topic]
    sws <- [Topic] -> Maybe [Topic] -> [Topic]
    forall a. a -> Maybe a -> a
    fromMaybe []
           (Maybe [Topic] -> [Topic])
    -> ([(ScreenId, [Topic])] -> Maybe [Topic])
    -> [(ScreenId, [Topic])]
    -> [Topic]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[Topic]] -> Maybe [Topic]
    forall a. [a] -> Maybe a
    listToMaybe
           ([[Topic]] -> Maybe [Topic])
    -> ([(ScreenId, [Topic])] -> [[Topic]])
    -> [(ScreenId, [Topic])]
    -> Maybe [Topic]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((ScreenId, [Topic]) -> [Topic])
    -> [(ScreenId, [Topic])] -> [[Topic]]
    forall a b. (a -> b) -> [a] -> [b]
    map (ScreenId, [Topic]) -> [Topic]
    forall a b. (a, b) -> b
    snd
           ([(ScreenId, [Topic])] -> [[Topic]])
    -> ([(ScreenId, [Topic])] -> [(ScreenId, [Topic])])
    -> [(ScreenId, [Topic])]
    -> [[Topic]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((ScreenId, [Topic]) -> Bool)
    -> [(ScreenId, [Topic])] -> [(ScreenId, [Topic])]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((ScreenId -> ScreenId -> Bool
    forall a. Eq a => a -> a -> Bool
    == ScreenId
    sid) (ScreenId -> Bool)
    -> ((ScreenId, [Topic]) -> ScreenId) -> (ScreenId, [Topic]) -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (ScreenId, [Topic]) -> ScreenId
    forall a b. (a, b) -> a
    fst)
         ([(ScreenId, [Topic])] -> [Topic])
    -> X [(ScreenId, [Topic])] -> X [Topic]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> X [(ScreenId, [Topic])]
    workspaceHistoryByScreen
      TopicConfig -> Topic -> X ()
    switchTopic TopicConfig
    tc (Topic -> X ()) -> Topic -> X ()
    forall a b. (a -> b) -> a -> b
    $ ([Topic]
    sws [Topic] -> [Topic] -> [Topic]
    forall a. [a] -> [a] -> [a]
    ++ Topic -> [Topic]
    forall a. a -> [a]
    repeat (TopicConfig -> Topic
    defaultTopic TopicConfig
    tc)) [Topic] -> Int -> Topic
    forall a. HasCallStack => [a] -> Int -> a
    !! Int
    depth

    -- | Shift the focused window to the Nth last focused topic, or fall back to doing nothing.
    shiftNthLastFocused :: Int -> X ()
    shiftNthLastFocused :: Int -> X ()
    shiftNthLastFocused Int
    n = do
      Maybe Topic
    ws <- ([Topic] -> Maybe Topic) -> X [Topic] -> X (Maybe Topic)
    forall a b. (a -> b) -> X a -> X b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ([Topic] -> Maybe Topic
    forall a. [a] -> Maybe a
    listToMaybe ([Topic] -> Maybe Topic)
    -> ([Topic] -> [Topic]) -> [Topic] -> Maybe Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> [Topic] -> [Topic]
    forall a. Int -> [a] -> [a]
    drop Int
    n) X [Topic]
    workspaceHistory
      Maybe Topic -> (Topic -> X ()) -> X ()
    forall (m :: * -> *) a. Monad m => Maybe a -> (a -> m ()) -> m ()
    whenJust Maybe Topic
    ws ((Topic -> X ()) -> X ()) -> (Topic -> X ()) -> X ()
    forall a b. (a -> b) -> a -> b
    $ (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> X ()
    windows ((StackSet Topic (Layout Window) Window ScreenId ScreenDetail
      -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
     -> X ())
    -> (Topic
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> Topic
    -> X ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Topic
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    forall a s i l sd.
    (Ord a, Eq s, Eq i) =>
    i -> StackSet i l a s sd -> StackSet i l a s sd
    W.shift

    -- | Return the directory associated with the current topic, or return the empty
    -- string if the topic could not be found.
    currentTopicDir :: TopicConfig -> X FilePath
    currentTopicDir :: TopicConfig -> X Topic
    currentTopicDir TopicConfig
    tg = do
      Topic
    topic <- (XState -> Topic) -> X Topic
    forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
    gets (Workspace Topic (Layout Window) Window -> Topic
    forall i l a. Workspace i l a -> i
    W.tag (Workspace Topic (Layout Window) Window -> Topic)
    -> (XState -> Workspace Topic (Layout Window) Window)
    -> XState
    -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Screen Topic (Layout Window) Window ScreenId ScreenDetail
    -> Workspace Topic (Layout Window) Window
    forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
    W.workspace (Screen Topic (Layout Window) Window ScreenId ScreenDetail
     -> Workspace Topic (Layout Window) Window)
    -> (XState
        -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Workspace Topic (Layout Window) Window
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
    W.current (StackSet Topic (Layout Window) Window ScreenId ScreenDetail
     -> Screen Topic (Layout Window) Window ScreenId ScreenDetail)
    -> (XState
        -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail)
    -> XState
    -> Screen Topic (Layout Window) Window ScreenId ScreenDetail
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . XState
    -> StackSet Topic (Layout Window) Window ScreenId ScreenDetail
    windowset)
      Topic -> X Topic
    forall a. a -> X a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Topic -> X Topic)
    -> (Map Topic Topic -> Topic) -> Map Topic Topic -> X Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Topic -> Maybe Topic -> Topic
    forall a. a -> Maybe a -> a
    fromMaybe Topic
    "" (Maybe Topic -> Topic)
    -> (Map Topic Topic -> Maybe Topic) -> Map Topic Topic -> Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Topic -> Map Topic Topic -> Maybe Topic
    forall k a. Ord k => k -> Map k a -> Maybe a
    M.lookup Topic
    topic (Map Topic Topic -> X Topic) -> Map Topic Topic -> X Topic
    forall a b. (a -> b) -> a -> b
    $ TopicConfig -> Map Topic Topic
    topicDirs TopicConfig
    tg

    -- | Check the given topic configuration for duplicate or undefined topics.
    checkTopicConfig :: [Topic] -> TopicConfig -> IO ()
    checkTopicConfig :: [Topic] -> TopicConfig -> IO ()
    checkTopicConfig [Topic]
    tags TopicConfig
    tg = do
      -- tags <- gets $ map W.tag . workspaces . windowset

      let
        seenTopics :: [Topic]
    seenTopics = [Topic] -> [Topic]
    forall a. Eq a => [a] -> [a]
    nub ([Topic] -> [Topic]) -> [Topic] -> [Topic]
    forall a b. (a -> b) -> a -> b
    $ [Topic] -> [Topic]
    forall a. Ord a => [a] -> [a]
    sort ([Topic] -> [Topic]) -> [Topic] -> [Topic]
    forall a b. (a -> b) -> a -> b
    $ Map Topic Topic -> [Topic]
    forall k a. Map k a -> [k]
    M.keys (TopicConfig -> Map Topic Topic
    topicDirs TopicConfig
    tg) [Topic] -> [Topic] -> [Topic]
    forall a. [a] -> [a] -> [a]
    ++ Map Topic (X ()) -> [Topic]
    forall k a. Map k a -> [k]
    M.keys (TopicConfig -> Map Topic (X ())
    topicActions TopicConfig
    tg)
        dups :: [Topic]
    dups       = [Topic]
    tags [Topic] -> [Topic] -> [Topic]
    forall a. Eq a => [a] -> [a] -> [a]
    \\ [Topic] -> [Topic]
    forall a. Eq a => [a] -> [a]
    nub [Topic]
    tags
        diffTopic :: [Topic]
    diffTopic  = [Topic]
    seenTopics [Topic] -> [Topic] -> [Topic]
    forall a. Eq a => [a] -> [a] -> [a]
    \\ [Topic] -> [Topic]
    forall a. Ord a => [a] -> [a]
    sort [Topic]
    tags
        check :: t a -> Topic -> f ()
    check t a
    lst Topic
    msg = Bool -> f () -> f ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    unless (t a -> Bool
    forall a. t a -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null t a
    lst) (f () -> f ()) -> f () -> f ()
    forall a b. (a -> b) -> a -> b
    $ Topic -> f ()
    forall (m :: * -> *). MonadIO m => Topic -> m ()
    xmessage (Topic -> f ()) -> Topic -> f ()
    forall a b. (a -> b) -> a -> b
    $ Topic
    msg Topic -> Topic -> Topic
    forall a. [a] -> [a] -> [a]
    ++ Topic
    " (tags): " Topic -> Topic -> Topic
    forall a. [a] -> [a] -> [a]
    ++ t a -> Topic
    forall a. Show a => a -> Topic
    show t a
    lst

      [Topic] -> Topic -> IO ()
    forall {f :: * -> *} {t :: * -> *} {a}.
    (Foldable t, MonadIO f, Show (t a)) =>
    t a -> Topic -> f ()
    check [Topic]
    diffTopic Topic
    "Seen but missing topics/workspaces"
      [Topic] -> Topic -> IO ()
    forall {f :: * -> *} {t :: * -> *} {a}.
    (Foldable t, MonadIO f, Show (t a)) =>
    t a -> Topic -> f ()
    check [Topic]
    dups      Topic
    "Duplicate topics/workspaces"

    -- | Convenience type for specifying topics.
    data TopicItem = TI
      { TopicItem -> Topic
    tiName   :: !Topic  -- ^ 'Topic' ≡ 'String'
      , TopicItem -> Topic
    tiDir    :: !Dir    -- ^ Directory associated with topic; 'Dir' ≡ 'String'
      , TopicItem -> X ()
    tiAction :: !(X ()) -- ^ Startup hook when topic is empty
      }

    -- | Extract the names from a given list of 'TopicItem's.
    topicNames :: [TopicItem] -> [Topic]
    topicNames :: [TopicItem] -> [Topic]
    topicNames = (TopicItem -> Topic) -> [TopicItem] -> [Topic]
    forall a b. (a -> b) -> [a] -> [b]
    map TopicItem -> Topic
    tiName

    -- | From a list of 'TopicItem's, build a map that can be supplied as
    -- the 'topicDirs'.
    tiDirs :: [TopicItem] -> Map Topic Dir
    tiDirs :: [TopicItem] -> Map Topic Topic
    tiDirs = [(Topic, Topic)] -> Map Topic Topic
    forall k a. Ord k => [(k, a)] -> Map k a
    M.fromList ([(Topic, Topic)] -> Map Topic Topic)
    -> ([TopicItem] -> [(Topic, Topic)])
    -> [TopicItem]
    -> Map Topic Topic
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (TopicItem -> (Topic, Topic)) -> [TopicItem] -> [(Topic, Topic)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\TI{ Topic
    tiName :: TopicItem -> Topic
    tiName :: Topic
    tiName, Topic
    tiDir :: TopicItem -> Topic
    tiDir :: Topic
    tiDir } -> (Topic
    tiName, Topic
    tiDir))

    -- | From a list of 'TopicItem's, build a map that can be supplied as
    -- the 'topicActions'.
    tiActions :: [TopicItem] -> Map Topic (X ())
    tiActions :: [TopicItem] -> Map Topic (X ())
    tiActions = [(Topic, X ())] -> Map Topic (X ())
    forall k a. Ord k => [(k, a)] -> Map k a
    M.fromList ([(Topic, X ())] -> Map Topic (X ()))
    -> ([TopicItem] -> [(Topic, X ())])
    -> [TopicItem]
    -> Map Topic (X ())
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (TopicItem -> (Topic, X ())) -> [TopicItem] -> [(Topic, X ())]
    forall a b. (a -> b) -> [a] -> [b]
    map (\TI{ Topic
    tiName :: TopicItem -> Topic
    tiName :: Topic
    tiName, X ()
    tiAction :: TopicItem -> X ()
    tiAction :: X ()
    tiAction } -> (Topic
    tiName, X ()
    tiAction))

    -- | Associate a directory with the topic, but don't spawn anything.
    noAction :: Topic -> Dir -> TopicItem
    noAction :: Topic -> Topic -> TopicItem
    noAction Topic
    n Topic
    d = Topic -> Topic -> X () -> TopicItem
    TI Topic
    n Topic
    d (() -> X ()
    forall a. a -> X a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure ())

    -- | Topic with @tiDir = ~/@.
    inHome :: Topic -> X () -> TopicItem
    inHome :: Topic -> X () -> TopicItem
    inHome Topic
    n = Topic -> Topic -> X () -> TopicItem
    TI Topic
    n Topic
    "."