{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE Rank2Types         #-}

module Yi.Tab
 (
  Tab,
  TabRef,
  tabWindowsA,
  tabLayoutManagerA,
  tabDividerPositionA,
  tkey,
  tabMiniWindows,
  tabFocus,
  forceTab,
  mapWindows,
  tabLayout,
  tabFoldl,
  makeTab,
  makeTab1,
 )  where

import           Prelude               hiding (foldl, foldr)

import           Lens.Micro.Platform            (Lens', lens, over, (^.))
import qualified Data.Binary           as Binary (Binary, get, put)
import           Data.Default          (def)
import           Data.Foldable         (foldl, foldr, toList)
import qualified Data.List.PointedList as PL (PointedList, singleton, _focus)
import           Data.Typeable         (Typeable)
import           Yi.Buffer.Basic       (WindowRef)
import           Yi.Layout
import           Yi.Window             (Window, isMini, wkey)

type TabRef = Int

-- | A tab, containing a collection of windows.
data Tab = Tab {
  Tab -> TabRef
tkey             :: !TabRef,                  -- ^ For UI sync; fixes #304
  Tab -> PointedList Window
tabWindows       :: !(PL.PointedList Window), -- ^ Visible windows
  Tab -> Layout WindowRef
tabLayout        :: !(Layout WindowRef),      -- ^ Current layout. Invariant: must be the layout generated by 'tabLayoutManager', up to changing the 'divPos's.
  Tab -> AnyLayoutManager
tabLayoutManager :: !AnyLayoutManager -- ^ layout manager (for regenerating the layout when we add/remove windows)
  }
 deriving Typeable

tabFocus :: Tab -> Window
tabFocus :: Tab -> Window
tabFocus = PointedList Window -> Window
forall a. PointedList a -> a
PL._focus (PointedList Window -> Window)
-> (Tab -> PointedList Window) -> Tab -> Window
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Tab -> PointedList Window
tabWindows

-- | Returns a list of all mini windows associated with the given tab
tabMiniWindows :: Tab -> [Window]
tabMiniWindows :: Tab -> [Window]
tabMiniWindows = (Window -> Bool) -> [Window] -> [Window]
forall a. (a -> Bool) -> [a] -> [a]
Prelude.filter Window -> Bool
isMini ([Window] -> [Window]) -> (Tab -> [Window]) -> Tab -> [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PointedList Window -> [Window]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (PointedList Window -> [Window])
-> (Tab -> PointedList Window) -> Tab -> [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Tab -> PointedList Window
tabWindows

-- | Accessor for the windows. If the windows (but not the focus) have changed when setting, then a relayout will be triggered to preserve the internal invariant.
tabWindowsA :: Functor f =>
    (PL.PointedList Window -> f (PL.PointedList Window)) -> Tab -> f Tab
tabWindowsA :: (PointedList Window -> f (PointedList Window)) -> Tab -> f Tab
tabWindowsA PointedList Window -> f (PointedList Window)
f Tab
s = (PointedList Window -> Tab -> Tab
`setter` Tab
s) (PointedList Window -> Tab) -> f (PointedList Window) -> f Tab
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PointedList Window -> f (PointedList Window)
f (Tab -> PointedList Window
getter Tab
s)
  where
    setter :: PointedList Window -> Tab -> Tab
setter PointedList Window
ws Tab
t = Bool -> Tab -> Tab
relayoutIf (PointedList Window -> [Window]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList PointedList Window
ws [Window] -> [Window] -> Bool
forall a. Eq a => a -> a -> Bool
/= PointedList Window -> [Window]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Tab -> PointedList Window
tabWindows Tab
t)) (Tab
t { tabWindows :: PointedList Window
tabWindows = PointedList Window
ws})
    getter :: Tab -> PointedList Window
getter = Tab -> PointedList Window
tabWindows

-- | Accessor for the layout manager. When setting, will trigger a relayout if the layout manager has changed.
tabLayoutManagerA :: Functor f =>
    (AnyLayoutManager -> f AnyLayoutManager) -> Tab -> f Tab
tabLayoutManagerA :: (AnyLayoutManager -> f AnyLayoutManager) -> Tab -> f Tab
tabLayoutManagerA AnyLayoutManager -> f AnyLayoutManager
f Tab
s = (AnyLayoutManager -> Tab -> Tab
`setter` Tab
s) (AnyLayoutManager -> Tab) -> f AnyLayoutManager -> f Tab
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> AnyLayoutManager -> f AnyLayoutManager
f (Tab -> AnyLayoutManager
getter Tab
s)
  where
    setter :: AnyLayoutManager -> Tab -> Tab
setter AnyLayoutManager
lm Tab
t = Bool -> Tab -> Tab
relayoutIf (AnyLayoutManager
lm AnyLayoutManager -> AnyLayoutManager -> Bool
forall a. Eq a => a -> a -> Bool
/= Tab -> AnyLayoutManager
tabLayoutManager Tab
t) (Tab
t { tabLayoutManager :: AnyLayoutManager
tabLayoutManager = AnyLayoutManager
lm })
    getter :: Tab -> AnyLayoutManager
getter = Tab -> AnyLayoutManager
tabLayoutManager

-- | Gets / sets the position of the divider with the given reference. The caller must ensure that the DividerRef is valid, otherwise an error will (might!) occur.
tabDividerPositionA :: DividerRef -> Lens' Tab DividerPosition
tabDividerPositionA :: TabRef -> Lens' Tab DividerPosition
tabDividerPositionA TabRef
ref = (Tab -> Layout WindowRef)
-> (Tab -> Layout WindowRef -> Tab)
-> Lens Tab Tab (Layout WindowRef) (Layout WindowRef)
forall s a b t. (s -> a) -> (s -> b -> t) -> Lens s t a b
lens Tab -> Layout WindowRef
tabLayout (\ Tab
t Layout WindowRef
l -> Tab
t{tabLayout :: Layout WindowRef
tabLayout = Layout WindowRef
l}) ((Layout WindowRef -> f (Layout WindowRef)) -> Tab -> f Tab)
-> ((DividerPosition -> f DividerPosition)
    -> Layout WindowRef -> f (Layout WindowRef))
-> (DividerPosition -> f DividerPosition)
-> Tab
-> f Tab
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TabRef -> Lens' (Layout WindowRef) DividerPosition
forall a. TabRef -> Lens' (Layout a) DividerPosition
dividerPositionA TabRef
ref

relayoutIf :: Bool -> Tab -> Tab
relayoutIf :: Bool -> Tab -> Tab
relayoutIf Bool
False Tab
t = Tab
t
relayoutIf Bool
True Tab
t = Tab -> Tab
relayout Tab
t

relayout :: Tab -> Tab
relayout :: Tab -> Tab
relayout Tab
t = Tab
t { tabLayout :: Layout WindowRef
tabLayout = PointedList Window
-> AnyLayoutManager -> Layout WindowRef -> Layout WindowRef
buildLayout (Tab -> PointedList Window
tabWindows Tab
t) (Tab -> AnyLayoutManager
tabLayoutManager Tab
t) (Tab -> Layout WindowRef
tabLayout Tab
t) }

instance Binary.Binary Tab where
  put :: Tab -> Put
put (Tab TabRef
tk PointedList Window
ws Layout WindowRef
_ AnyLayoutManager
_) = TabRef -> Put
forall t. Binary t => t -> Put
Binary.put TabRef
tk Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> PointedList Window -> Put
forall t. Binary t => t -> Put
Binary.put PointedList Window
ws
  get :: Get Tab
get = TabRef -> PointedList Window -> Tab
makeTab (TabRef -> PointedList Window -> Tab)
-> Get TabRef -> Get (PointedList Window -> Tab)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get TabRef
forall t. Binary t => Get t
Binary.get Get (PointedList Window -> Tab)
-> Get (PointedList Window) -> Get Tab
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get (PointedList Window)
forall t. Binary t => Get t
Binary.get


-- | Equality on tab identity (the 'tkey')
instance Eq Tab where
  == :: Tab -> Tab -> Bool
(==) Tab
t1 Tab
t2 = Tab -> TabRef
tkey Tab
t1 TabRef -> TabRef -> Bool
forall a. Eq a => a -> a -> Bool
== Tab -> TabRef
tkey Tab
t2

instance Show Tab where
  show :: Tab -> String
show Tab
t = String
"Tab " String -> ShowS
forall a. [a] -> [a] -> [a]
++ TabRef -> String
forall a. Show a => a -> String
show (Tab -> TabRef
tkey Tab
t)

-- | A specialised version of "fmap".
mapWindows :: (Window -> Window) -> Tab -> Tab
mapWindows :: (Window -> Window) -> Tab -> Tab
mapWindows Window -> Window
f = ASetter Tab Tab (PointedList Window) (PointedList Window)
-> (PointedList Window -> PointedList Window) -> Tab -> Tab
forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
over ASetter Tab Tab (PointedList Window) (PointedList Window)
forall (f :: * -> *).
Functor f =>
(PointedList Window -> f (PointedList Window)) -> Tab -> f Tab
tabWindowsA ((Window -> Window) -> PointedList Window -> PointedList Window
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Window -> Window
f)

-- | Forces all windows in the tab
forceTab :: Tab -> Tab
forceTab :: Tab -> Tab
forceTab Tab
t = (Window -> Tab -> Tab) -> Tab -> PointedList Window -> Tab
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Window -> Tab -> Tab
seq Tab
t (Tab
t Tab
-> Getting (PointedList Window) Tab (PointedList Window)
-> PointedList Window
forall s a. s -> Getting a s a -> a
^. Getting (PointedList Window) Tab (PointedList Window)
forall (f :: * -> *).
Functor f =>
(PointedList Window -> f (PointedList Window)) -> Tab -> f Tab
tabWindowsA)

-- | Folds over the windows in the tab
tabFoldl :: (a -> Window -> a) -> a -> Tab -> a
tabFoldl :: (a -> Window -> a) -> a -> Tab -> a
tabFoldl a -> Window -> a
f a
z Tab
t = (a -> Window -> a) -> a -> PointedList Window -> a
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl a -> Window -> a
f a
z (Tab
t Tab
-> Getting (PointedList Window) Tab (PointedList Window)
-> PointedList Window
forall s a. s -> Getting a s a -> a
^. Getting (PointedList Window) Tab (PointedList Window)
forall (f :: * -> *).
Functor f =>
(PointedList Window -> f (PointedList Window)) -> Tab -> f Tab
tabWindowsA)

-- | Run the layout on the given tab, for the given aspect ratio
buildLayout :: PL.PointedList Window -> AnyLayoutManager -> Layout WindowRef -> Layout WindowRef
buildLayout :: PointedList Window
-> AnyLayoutManager -> Layout WindowRef -> Layout WindowRef
buildLayout PointedList Window
ws AnyLayoutManager
m Layout WindowRef
l = AnyLayoutManager
-> Layout WindowRef -> [WindowRef] -> Layout WindowRef
forall m a. LayoutManager m => m -> Layout a -> [a] -> Layout a
pureLayout AnyLayoutManager
m Layout WindowRef
l ([WindowRef] -> Layout WindowRef)
-> (PointedList Window -> [WindowRef])
-> PointedList Window
-> Layout WindowRef
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Window -> WindowRef) -> [Window] -> [WindowRef]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Window -> WindowRef
wkey ([Window] -> [WindowRef])
-> (PointedList Window -> [Window])
-> PointedList Window
-> [WindowRef]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Window -> Bool) -> [Window] -> [Window]
forall a. (a -> Bool) -> [a] -> [a]
Prelude.filter (Bool -> Bool
not (Bool -> Bool) -> (Window -> Bool) -> Window -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> Bool
isMini) ([Window] -> [Window])
-> (PointedList Window -> [Window])
-> PointedList Window
-> [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PointedList Window -> [Window]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (PointedList Window -> Layout WindowRef)
-> PointedList Window -> Layout WindowRef
forall a b. (a -> b) -> a -> b
$ PointedList Window
ws

-- | Make a tab from multiple windows
makeTab :: TabRef -> PL.PointedList Window -> Tab
makeTab :: TabRef -> PointedList Window -> Tab
makeTab TabRef
key PointedList Window
ws = TabRef
-> PointedList Window
-> Layout WindowRef
-> AnyLayoutManager
-> Tab
Tab TabRef
key PointedList Window
ws (PointedList Window
-> AnyLayoutManager -> Layout WindowRef -> Layout WindowRef
buildLayout PointedList Window
ws AnyLayoutManager
forall a. Default a => a
def Layout WindowRef
forall a. Default a => a
def) AnyLayoutManager
forall a. Default a => a
def

-- | Make a tab from one window
makeTab1 :: TabRef -> Window -> Tab
makeTab1 :: TabRef -> Window -> Tab
makeTab1 TabRef
key Window
win = TabRef -> PointedList Window -> Tab
makeTab TabRef
key (Window -> PointedList Window
forall a. a -> PointedList a
PL.singleton Window
win)