vimus-0.1.0: src/Tab.hs
module Tab (
Tab (..)
, TabName (..)
, CloseMode (..)
, isAutoClose
, Tabs (..) -- constructors exported for testing
, fromList
, toList
, preCurSuf
, previous
, next
, select
, current
, modify
, insert
, close
) where
import Prelude hiding (foldl, foldr)
import Data.List (foldl')
import Control.Applicative
import Data.Traversable (Traversable(..))
import Data.Foldable (Foldable(foldr))
-- | Tab zipper
data Tabs a = Tabs ![Tab a] !(Tab a) ![Tab a]
data CloseMode =
Persistent -- ^ tab can not be closed
| Closeable -- ^ tab can be closed
| AutoClose -- ^ tab is automatically closed on unfocus
deriving (Eq, Ord, Show)
-- | True, if tab is automatically closed on unfocus.
isAutoClose :: Tab a -> Bool
isAutoClose = (== AutoClose) . tabCloseMode
-- | True, if tab can be closed.
isCloseable :: Tab a -> Bool
isCloseable = (/= Persistent) . tabCloseMode
data Tab a = Tab {
tabName :: !TabName
, tabContent :: !a
, tabCloseMode :: !CloseMode
}
instance Functor Tab where
fmap f (Tab n c a) = Tab n (f c) a
instance Functor Tabs where
fmap g (Tabs xs c ys) = Tabs (map f xs) (f c) (map f ys)
where f = fmap g
instance Foldable Tabs where
foldr g z (Tabs xs y ys) = foldl' (flip f) (foldr f z (y:ys)) xs
where f (Tab _ c _) = g c
instance Traversable Tabs where
traverse g (Tabs xs y ys) = Tabs <$> (reverse <$> traverse f (reverse xs)) <*> f y <*> traverse f ys
where f (Tab n c a) = flip (Tab n) a <$> g c
data TabName = Playlist | Library | Browser | SearchResult | Other String
deriving Eq
instance Show TabName where
show name = case name of
Playlist -> "Playlist"
Library -> "Library"
Browser -> "Browser"
SearchResult -> "SearchResult"
Other s -> s
fromList :: [Tab a] -> Tabs a
fromList (c:ys) = Tabs [] c ys
fromList [] = error "Tab.fromList: empty list"
toList :: Tabs a -> [Tab a]
toList (Tabs xs c ys) = foldl' (flip (:)) (c:ys) xs
-- | Return prefix, current, and suffix.
preCurSuf :: Tabs a -> ([Tab a], Tab a, [Tab a])
preCurSuf (Tabs pre c suf) = (reverse pre, c, suf)
-- | Move focus to the left.
previous :: Tabs a -> Tabs a
previous (Tabs pre c suf) = case pre of
x:xs -> Tabs xs x ys
[] ->
case reverse ys of
x:xs -> Tabs xs x []
[] -> error "Tab.previous: no tabs"
where ys = if isAutoClose c then suf else c:suf
-- | Move focus to the right.
next :: Tabs a -> Tabs a
next (Tabs pre c suf) = case suf of
y:ys -> Tabs xs y ys
[] ->
case reverse xs of
y:ys -> Tabs [] y ys
[] -> error "Tab.next: no tabs"
where xs = if isAutoClose c then pre else c:pre
-- | Set focus to next tab that matches given predicate.
select :: (Tab a -> Bool) -> Tabs a -> Tabs a
select p tabs@(Tabs pre c suf) =
case break p suf of
(ys, z:zs) -> Tabs (xs `combine` ys) z zs
_ ->
case break p (reverse xs) of
(ys, z:zs) -> Tabs (reverse ys) z (zs ++ suf)
_ -> tabs
where
xs = if isAutoClose c then pre else c:pre
-- | reverse and prepend the second list to the first
combine = foldl' (flip (:))
-- | Return the focused tab.
current :: Tabs a -> Tab a
current (Tabs _ c _) = c
-- | Close the focused tab, if possible.
close :: Tabs a -> Maybe (Tabs a)
close (Tabs pre c suf)
| isCloseable c =
case pre of
x:xs -> Just (Tabs xs x suf)
[] -> case reverse suf of
x:xs -> Just (Tabs xs x pre)
[] -> Nothing
| otherwise = Nothing
-- | Modify the focused tab.
modify :: (Tab a -> Tab a) -> Tabs a -> Tabs a
modify f (Tabs xs c ys) = Tabs xs (f c) ys
-- | Insert a new tab after the focused tab; set focus to the new tab.
insert :: Tab a -> Tabs a -> Tabs a
insert x (Tabs pre c ys) = Tabs xs x ys
where xs = if isAutoClose c then pre else c:pre