{-# LANGUAGE DerivingVia #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Hooks.WorkspaceHistory
-- Description :  Keep track of workspace viewing order.
-- Copyright   :  (c) 2013 Dmitri Iouchtchenko
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Dmitri Iouchtchenko <johnnyspoon@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Keeps track of workspace viewing order.
--
-----------------------------------------------------------------------------

module XMonad.Hooks.WorkspaceHistory (
      -- * Usage
      -- $usage
      -- * Hooking
    workspaceHistoryHook
  , workspaceHistoryHookExclude
      -- * Querying
  , workspaceHistory
  , workspaceHistoryByScreen
  , workspaceHistoryWithScreen
    -- * Handling edits
  , workspaceHistoryTransaction
  , workspaceHistoryModify
  ) where

import Control.Applicative
import Control.DeepSeq
import Prelude
import XMonad
import XMonad.StackSet hiding (delete, filter, new)
import XMonad.Prelude (delete, find, foldl', groupBy, nub, sortBy, listToMaybe)
import qualified XMonad.Util.ExtensibleState as XS

-- $usage
-- To record the order in which you view workspaces, you can use this
-- module with the following in your @xmonad.hs@:
--
-- > import XMonad.Hooks.WorkspaceHistory (workspaceHistoryHook)
--
-- Then add the hook to your 'logHook':
--
-- >  main = xmonad $ def
-- >      { ...
-- >      , logHook = ... >> workspaceHistoryHook >> ...
-- >      , ...
-- >      }
--
-- If you want to completely exclude certain workspaces from entering
-- the history, you can use 'workspaceHistoryHookExclude' instead.  For
-- example, to ignore the named scratchpad workspace:
--
-- > import XMonad.Util.NamedScratchpad (scratchpadWorkspaceTag)
-- > ...
-- > , logHook = ... >> workspaceHistoryHookExclude [scratchpadWorkspaceTag] >> ...
--
-- To make use of the collected data, a query function is provided.

newtype WorkspaceHistory = WorkspaceHistory
  { WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history :: [(ScreenId, WorkspaceId)] -- ^ Workspace Screens in
                                         -- reverse-chronological order.
  }
  deriving (ReadPrec [WorkspaceHistory]
ReadPrec WorkspaceHistory
Int -> ReadS WorkspaceHistory
ReadS [WorkspaceHistory]
(Int -> ReadS WorkspaceHistory)
-> ReadS [WorkspaceHistory]
-> ReadPrec WorkspaceHistory
-> ReadPrec [WorkspaceHistory]
-> Read WorkspaceHistory
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS WorkspaceHistory
readsPrec :: Int -> ReadS WorkspaceHistory
$creadList :: ReadS [WorkspaceHistory]
readList :: ReadS [WorkspaceHistory]
$creadPrec :: ReadPrec WorkspaceHistory
readPrec :: ReadPrec WorkspaceHistory
$creadListPrec :: ReadPrec [WorkspaceHistory]
readListPrec :: ReadPrec [WorkspaceHistory]
Read, Int -> WorkspaceHistory -> ShowS
[WorkspaceHistory] -> ShowS
WorkspaceHistory -> WorkspaceId
(Int -> WorkspaceHistory -> ShowS)
-> (WorkspaceHistory -> WorkspaceId)
-> ([WorkspaceHistory] -> ShowS)
-> Show WorkspaceHistory
forall a.
(Int -> a -> ShowS)
-> (a -> WorkspaceId) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> WorkspaceHistory -> ShowS
showsPrec :: Int -> WorkspaceHistory -> ShowS
$cshow :: WorkspaceHistory -> WorkspaceId
show :: WorkspaceHistory -> WorkspaceId
$cshowList :: [WorkspaceHistory] -> ShowS
showList :: [WorkspaceHistory] -> ShowS
Show)
  deriving WorkspaceHistory -> ()
(WorkspaceHistory -> ()) -> NFData WorkspaceHistory
forall a. (a -> ()) -> NFData a
$crnf :: WorkspaceHistory -> ()
rnf :: WorkspaceHistory -> ()
NFData via [(Int, WorkspaceId)]

instance ExtensionClass WorkspaceHistory where
    initialValue :: WorkspaceHistory
initialValue = [(ScreenId, WorkspaceId)] -> WorkspaceHistory
WorkspaceHistory []
    extensionType :: WorkspaceHistory -> StateExtension
extensionType = WorkspaceHistory -> StateExtension
forall a. (Read a, Show a, ExtensionClass a) => a -> StateExtension
PersistentExtension

-- | A 'logHook' that keeps track of the order in which workspaces have
-- been viewed.
workspaceHistoryHook :: X ()
workspaceHistoryHook :: X ()
workspaceHistoryHook = [WorkspaceId] -> X ()
workspaceHistoryHookExclude []

-- | Like 'workspaceHistoryHook', but with the ability to exclude
-- certain workspaces.
workspaceHistoryHookExclude :: [WorkspaceId] -> X ()
workspaceHistoryHookExclude :: [WorkspaceId] -> X ()
workspaceHistoryHookExclude [WorkspaceId]
ws = (WorkspaceHistory -> WorkspaceHistory) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify' ((WorkspaceHistory -> WorkspaceHistory) -> X ())
-> (WindowSet -> WorkspaceHistory -> WorkspaceHistory)
-> WindowSet
-> X ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> WorkspaceHistory -> WorkspaceHistory
update (WindowSet -> X ()) -> X WindowSet -> X ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (XState -> WindowSet) -> X WindowSet
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets XState -> WindowSet
windowset
  where
    update :: WindowSet -> WorkspaceHistory -> WorkspaceHistory
    update :: WindowSet -> WorkspaceHistory -> WorkspaceHistory
update WindowSet
s = WorkspaceHistory -> WorkspaceHistory
forall a. NFData a => a -> a
force (WorkspaceHistory -> WorkspaceHistory)
-> (WorkspaceHistory -> WorkspaceHistory)
-> WorkspaceHistory
-> WorkspaceHistory
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [WorkspaceId] -> WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreenExclude [WorkspaceId]
ws WindowSet
s

workspaceHistoryWithScreen :: X [(ScreenId, WorkspaceId)]
workspaceHistoryWithScreen :: X [(ScreenId, WorkspaceId)]
workspaceHistoryWithScreen = (WorkspaceHistory -> [(ScreenId, WorkspaceId)])
-> X [(ScreenId, WorkspaceId)]
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history

workspaceHistoryByScreen :: X [(ScreenId, [WorkspaceId])]
workspaceHistoryByScreen :: X [(ScreenId, [WorkspaceId])]
workspaceHistoryByScreen =
  ([(ScreenId, WorkspaceId)] -> (ScreenId, [WorkspaceId]))
-> [[(ScreenId, WorkspaceId)]] -> [(ScreenId, [WorkspaceId])]
forall a b. (a -> b) -> [a] -> [b]
map (\[(ScreenId, WorkspaceId)]
wss -> (ScreenId
-> ((ScreenId, WorkspaceId) -> ScreenId)
-> Maybe (ScreenId, WorkspaceId)
-> ScreenId
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ScreenId
0 (ScreenId, WorkspaceId) -> ScreenId
forall a b. (a, b) -> a
fst ([(ScreenId, WorkspaceId)] -> Maybe (ScreenId, WorkspaceId)
forall a. [a] -> Maybe a
listToMaybe [(ScreenId, WorkspaceId)]
wss), ((ScreenId, WorkspaceId) -> WorkspaceId)
-> [(ScreenId, WorkspaceId)] -> [WorkspaceId]
forall a b. (a -> b) -> [a] -> [b]
map (ScreenId, WorkspaceId) -> WorkspaceId
forall a b. (a, b) -> b
snd [(ScreenId, WorkspaceId)]
wss)) ([[(ScreenId, WorkspaceId)]] -> [(ScreenId, [WorkspaceId])])
-> ([(ScreenId, WorkspaceId)] -> [[(ScreenId, WorkspaceId)]])
-> [(ScreenId, WorkspaceId)]
-> [(ScreenId, [WorkspaceId])]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  ((ScreenId, WorkspaceId) -> (ScreenId, WorkspaceId) -> Bool)
-> [(ScreenId, WorkspaceId)] -> [[(ScreenId, WorkspaceId)]]
forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupBy (\(ScreenId, WorkspaceId)
a (ScreenId, WorkspaceId)
b -> (ScreenId, WorkspaceId) -> ScreenId
forall a b. (a, b) -> a
fst (ScreenId, WorkspaceId)
a ScreenId -> ScreenId -> Bool
forall a. Eq a => a -> a -> Bool
== (ScreenId, WorkspaceId) -> ScreenId
forall a b. (a, b) -> a
fst (ScreenId, WorkspaceId)
b) ([(ScreenId, WorkspaceId)] -> [[(ScreenId, WorkspaceId)]])
-> ([(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)])
-> [(ScreenId, WorkspaceId)]
-> [[(ScreenId, WorkspaceId)]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  ((ScreenId, WorkspaceId) -> (ScreenId, WorkspaceId) -> Ordering)
-> [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (\(ScreenId, WorkspaceId)
a (ScreenId, WorkspaceId)
b -> ScreenId -> ScreenId -> Ordering
forall a. Ord a => a -> a -> Ordering
compare ((ScreenId, WorkspaceId) -> ScreenId
forall a b. (a, b) -> a
fst (ScreenId, WorkspaceId)
a) (ScreenId -> Ordering) -> ScreenId -> Ordering
forall a b. (a -> b) -> a -> b
$ (ScreenId, WorkspaceId) -> ScreenId
forall a b. (a, b) -> a
fst (ScreenId, WorkspaceId)
b)([(ScreenId, WorkspaceId)] -> [(ScreenId, [WorkspaceId])])
-> X [(ScreenId, WorkspaceId)] -> X [(ScreenId, [WorkspaceId])]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
  X [(ScreenId, WorkspaceId)]
workspaceHistoryWithScreen

-- | A list of workspace tags in the order they have been viewed, with the
-- most recent first. No duplicates are present, but not all workspaces are
-- guaranteed to appear, and there may be workspaces that no longer exist.
workspaceHistory :: X [WorkspaceId]
workspaceHistory :: X [WorkspaceId]
workspaceHistory = [WorkspaceId] -> [WorkspaceId]
forall a. Eq a => [a] -> [a]
nub ([WorkspaceId] -> [WorkspaceId])
-> ([(ScreenId, WorkspaceId)] -> [WorkspaceId])
-> [(ScreenId, WorkspaceId)]
-> [WorkspaceId]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((ScreenId, WorkspaceId) -> WorkspaceId)
-> [(ScreenId, WorkspaceId)] -> [WorkspaceId]
forall a b. (a -> b) -> [a] -> [b]
map (ScreenId, WorkspaceId) -> WorkspaceId
forall a b. (a, b) -> b
snd ([(ScreenId, WorkspaceId)] -> [WorkspaceId])
-> X [(ScreenId, WorkspaceId)] -> X [WorkspaceId]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (WorkspaceHistory -> [(ScreenId, WorkspaceId)])
-> X [(ScreenId, WorkspaceId)]
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history

workspaceHistoryTransaction :: X () -> X ()
workspaceHistoryTransaction :: X () -> X ()
workspaceHistoryTransaction X ()
action = do
  [(ScreenId, WorkspaceId)]
startingHistory <- (WorkspaceHistory -> [(ScreenId, WorkspaceId)])
-> X [(ScreenId, WorkspaceId)]
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history
  X ()
action
  WorkspaceHistory
new <- (WindowSet -> WorkspaceHistory -> WorkspaceHistory)
-> WorkspaceHistory -> WindowSet -> WorkspaceHistory
forall a b c. (a -> b -> c) -> b -> a -> c
flip WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreen ([(ScreenId, WorkspaceId)] -> WorkspaceHistory
WorkspaceHistory [(ScreenId, WorkspaceId)]
startingHistory) (WindowSet -> WorkspaceHistory)
-> X WindowSet -> X WorkspaceHistory
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (XState -> WindowSet) -> X WindowSet
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets XState -> WindowSet
windowset
  WorkspaceHistory -> X ()
forall a (m :: * -> *). (ExtensionClass a, XLike m) => a -> m ()
XS.put (WorkspaceHistory -> X ()) -> WorkspaceHistory -> X ()
forall a b. (a -> b) -> a -> b
$! WorkspaceHistory -> WorkspaceHistory
forall a. NFData a => a -> a
force WorkspaceHistory
new

-- | Update the last visible workspace on each monitor if needed
-- already there, or move it to the front if it is.
updateLastActiveOnEachScreen :: WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreen :: WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreen = [WorkspaceId] -> WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreenExclude []

-- | Like 'updateLastActiveOnEachScreen', but with the ability to
-- exclude certain workspaces.
updateLastActiveOnEachScreenExclude :: [WorkspaceId] -> WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreenExclude :: [WorkspaceId] -> WindowSet -> WorkspaceHistory -> WorkspaceHistory
updateLastActiveOnEachScreenExclude [WorkspaceId]
ws StackSet {current :: forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
current = Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
cur, visible :: forall i l a sid sd. StackSet i l a sid sd -> [Screen i l a sid sd]
visible = [Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
vis} WorkspaceHistory
wh =
  WorkspaceHistory { history :: [(ScreenId, WorkspaceId)]
history = Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]
forall {sid} {l} {a} {sd}.
Eq sid =>
Screen WorkspaceId l a sid sd
-> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
doUpdate Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
cur ([(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)])
-> [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]
forall a b. (a -> b) -> a -> b
$ ([(ScreenId, WorkspaceId)]
 -> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
 -> [(ScreenId, WorkspaceId)])
-> [(ScreenId, WorkspaceId)]
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
-> [(ScreenId, WorkspaceId)]
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' [(ScreenId, WorkspaceId)]
-> Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
-> [(ScreenId, WorkspaceId)]
forall {sid} {l} {a} {sd}.
Eq sid =>
[(sid, WorkspaceId)]
-> Screen WorkspaceId l a sid sd -> [(sid, WorkspaceId)]
updateLastForScreen (WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history WorkspaceHistory
wh) ([Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
 -> [(ScreenId, WorkspaceId)])
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
-> [(ScreenId, WorkspaceId)]
forall a b. (a -> b) -> a -> b
$ [Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
vis [Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
-> [Screen
      WorkspaceId (Layout Window) Window ScreenId ScreenDetail]
forall a. [a] -> [a] -> [a]
++ [Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
cur] }
  where
    firstOnScreen :: b -> t (b, b) -> Maybe (b, b)
firstOnScreen b
sid = ((b, b) -> Bool) -> t (b, b) -> Maybe (b, b)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((b -> b -> Bool
forall a. Eq a => a -> a -> Bool
== b
sid) (b -> Bool) -> ((b, b) -> b) -> (b, b) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (b, b) -> b
forall a b. (a, b) -> a
fst)
    doUpdate :: Screen WorkspaceId l a sid sd
-> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
doUpdate Screen {workspace :: forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
workspace = Workspace { tag :: forall i l a. Workspace i l a -> i
tag = WorkspaceId
wid }, screen :: forall i l a sid sd. Screen i l a sid sd -> sid
screen = sid
sid} [(sid, WorkspaceId)]
curr =
      let newEntry :: (sid, WorkspaceId)
newEntry = (sid
sid, WorkspaceId
wid)
       in if WorkspaceId
wid WorkspaceId -> [WorkspaceId] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [WorkspaceId]
ws then [(sid, WorkspaceId)]
curr else (sid, WorkspaceId)
newEntry (sid, WorkspaceId) -> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
forall a. a -> [a] -> [a]
: (sid, WorkspaceId) -> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
forall a. Eq a => a -> [a] -> [a]
delete (sid, WorkspaceId)
newEntry [(sid, WorkspaceId)]
curr
    updateLastForScreen :: [(sid, WorkspaceId)]
-> Screen WorkspaceId l a sid sd -> [(sid, WorkspaceId)]
updateLastForScreen [(sid, WorkspaceId)]
curr Screen {workspace :: forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
workspace = Workspace { tag :: forall i l a. Workspace i l a -> i
tag = WorkspaceId
wid }, screen :: forall i l a sid sd. Screen i l a sid sd -> sid
screen = sid
sid} =
      let newEntry :: (sid, WorkspaceId)
newEntry = (sid
sid, WorkspaceId
wid)
          alreadyCurrent :: Bool
alreadyCurrent = (sid, WorkspaceId) -> Maybe (sid, WorkspaceId)
forall a. a -> Maybe a
Just (sid, WorkspaceId)
newEntry Maybe (sid, WorkspaceId) -> Maybe (sid, WorkspaceId) -> Bool
forall a. Eq a => a -> a -> Bool
== sid -> [(sid, WorkspaceId)] -> Maybe (sid, WorkspaceId)
forall {t :: * -> *} {b} {b}.
(Foldable t, Eq b) =>
b -> t (b, b) -> Maybe (b, b)
firstOnScreen sid
sid [(sid, WorkspaceId)]
curr
      in if Bool
alreadyCurrent Bool -> Bool -> Bool
|| WorkspaceId
wid WorkspaceId -> [WorkspaceId] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [WorkspaceId]
ws then [(sid, WorkspaceId)]
curr else (sid, WorkspaceId)
newEntry (sid, WorkspaceId) -> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
forall a. a -> [a] -> [a]
: (sid, WorkspaceId) -> [(sid, WorkspaceId)] -> [(sid, WorkspaceId)]
forall a. Eq a => a -> [a] -> [a]
delete (sid, WorkspaceId)
newEntry [(sid, WorkspaceId)]
curr

-- | Modify a the workspace history with a given pure function.
workspaceHistoryModify :: ([(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]) -> X ()
workspaceHistoryModify :: ([(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]) -> X ()
workspaceHistoryModify [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]
action = (WorkspaceHistory -> WorkspaceHistory) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify' ((WorkspaceHistory -> WorkspaceHistory) -> X ())
-> (WorkspaceHistory -> WorkspaceHistory) -> X ()
forall a b. (a -> b) -> a -> b
$ WorkspaceHistory -> WorkspaceHistory
forall a. NFData a => a -> a
force (WorkspaceHistory -> WorkspaceHistory)
-> (WorkspaceHistory -> WorkspaceHistory)
-> WorkspaceHistory
-> WorkspaceHistory
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(ScreenId, WorkspaceId)] -> WorkspaceHistory
WorkspaceHistory ([(ScreenId, WorkspaceId)] -> WorkspaceHistory)
-> (WorkspaceHistory -> [(ScreenId, WorkspaceId)])
-> WorkspaceHistory
-> WorkspaceHistory
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)]
action ([(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)])
-> (WorkspaceHistory -> [(ScreenId, WorkspaceId)])
-> WorkspaceHistory
-> [(ScreenId, WorkspaceId)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WorkspaceHistory -> [(ScreenId, WorkspaceId)]
history