module Vimus.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