termonad-0.1.0.0: src/Termonad/FocusList.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Termonad.FocusList where
import Termonad.Prelude
import Control.Lens
import qualified Data.Foldable as Foldable
import Test.QuickCheck
import Text.Show (Show(showsPrec), ShowS, showParen, showString)
-- $setup
-- >>> :set -XFlexibleContexts
-- >>> :set -XScopedTypeVariables
data Focus = Focus {-# UNPACK #-} !Int | NoFocus deriving (Eq, Generic, Read, Show)
-- | 'NoFocus' is always less than 'Focus'.
--
-- prop> NoFocus < Focus a
instance Ord Focus where
compare :: Focus -> Focus -> Ordering
compare NoFocus NoFocus = EQ
compare NoFocus (Focus _) = LT
compare (Focus _) NoFocus = GT
compare (Focus a) (Focus b) = compare a b
instance CoArbitrary Focus
foldFocus :: b -> (Int -> b) -> Focus -> b
foldFocus b _ NoFocus = b
foldFocus _ f (Focus i) = f i
_Focus :: Prism' Focus Int
_Focus = prism' Focus (foldFocus Nothing Just)
_NoFocus :: Prism' Focus ()
_NoFocus = prism' (const NoFocus) (foldFocus (Just ()) (const Nothing))
hasFocus :: Focus -> Bool
hasFocus NoFocus = False
hasFocus (Focus _) = True
unsafeGetFocus :: Focus -> Int
unsafeGetFocus NoFocus = error "unsafeGetFocus: NoFocus"
unsafeGetFocus (Focus i) = i
-- TODO: Probably be better
-- implemented as an Order statistic tree
-- (https://en.wikipedia.org/wiki/Order_statistic_tree).
data FocusList a = FocusList
{ focusListFocus :: !Focus
, focusListLen :: {-# UNPACK #-} !Int
, focusList :: !(IntMap a)
} deriving (Eq, Generic)
$(makeLensesFor
[ ("focusListFocus", "lensFocusListFocus")
, ("focusListLen", "lensFocusListLen")
, ("focusList", "lensFocusList")
]
''FocusList
)
instance Functor FocusList where
fmap :: (a -> b) -> FocusList a -> FocusList b
fmap f (FocusList focus len intmap) = FocusList focus len (fmap f intmap)
instance Foldable FocusList where
foldr f b (FocusList _ _ intmap) = Foldable.foldr f b intmap
instance Traversable FocusList where
traverse :: Applicative f => (a -> f b) -> FocusList a -> f (FocusList b)
traverse f (FocusList focus len intmap) = FocusList focus len <$> traverse f intmap
type instance Element (FocusList a) = a
instance MonoFunctor (FocusList a)
instance MonoFoldable (FocusList a)
instance MonoTraversable (FocusList a)
instance Arbitrary1 FocusList where
liftArbitrary :: Gen a -> Gen (FocusList a)
liftArbitrary genA = do
arbList <- liftArbitrary genA
case arbList of
[] -> pure emptyFL
(_:_) -> do
let listLen = length arbList
len <- choose (0, listLen - 1)
pure $ unsafeFLFromList (Focus len) arbList
instance Arbitrary a => Arbitrary (FocusList a) where
arbitrary = arbitrary1
instance CoArbitrary a => CoArbitrary (FocusList a)
debugFL :: Show a => FocusList a -> String
debugFL FocusList{..} =
showString "FocusList {" .
showString "focusListFocus = " .
showsPrec 0 focusListFocus .
showString ", " .
showString "focusListLen = " .
showsPrec 0 focusListLen .
showString ", " .
showString "focusList = " .
showsPrec 0 focusList $
showString "}" ""
instance Show a => Show (FocusList a) where
showsPrec :: Int -> FocusList a -> ShowS
showsPrec d FocusList{..} =
let list = fmap snd $ sortOn fst $ mapToList focusList
in
showParen (d > 10) $
showString "FocusList " .
showsPrec 11 focusListFocus .
showString " " .
showsPrec 11 list
lensFocusListAt :: Int -> Lens' (FocusList a) (Maybe a)
lensFocusListAt i = lensFocusList . at i
-- | This is an invariant that the 'FocusList' must always protect.
invariantFL :: FocusList a -> Bool
invariantFL fl =
invariantFocusNotNeg &&
invariantFocusInMap &&
invariantFocusIfLenGT0 &&
invariantLenIsCorrect &&
invariantNoSkippedNumsInMap
where
-- This makes sure that the 'Focus' in a 'FocusList' can never be negative.
invariantFocusNotNeg :: Bool
invariantFocusNotNeg =
case fl ^. lensFocusListFocus of
NoFocus -> True
Focus i -> i >= 0
-- | This makes sure that if there is a 'Focus', then it actually exists in
-- the 'FocusList'.
invariantFocusInMap :: Bool
invariantFocusInMap =
case fl ^. lensFocusListFocus of
NoFocus -> length (fl ^. lensFocusList) == 0
Focus i ->
case lookup i (fl ^. lensFocusList) of
Nothing -> False
Just _ -> True
-- | This makes sure that there needs to be a 'Focus' if the length of the
-- 'FocusList' is greater than 0.
invariantFocusIfLenGT0 :: Bool
invariantFocusIfLenGT0 =
let len = fl ^. lensFocusListLen
focus = fl ^. lensFocusListFocus
in
case focus of
Focus _ -> len /= 0
NoFocus -> len == 0
-- | Make sure that the length of the 'FocusList' is actually the number of
-- elements in the inner 'IntMap'.
invariantLenIsCorrect :: Bool
invariantLenIsCorrect =
let len = fl ^. lensFocusListLen
intmap = fl ^. lensFocusList
in len == length intmap
-- | Make sure that there are no numbers that have been skipped in the
-- inner 'IntMap'.
invariantNoSkippedNumsInMap :: Bool
invariantNoSkippedNumsInMap =
let len = fl ^. lensFocusListLen
intmap = fl ^. lensFocusList
indexes = sort $ fmap fst $ mapToList intmap
in indexes == [0..(len - 1)]
-- | Unsafely create a 'FocusList'. This does not check that the focus
-- actually exists in the list.
--
-- >>> let fl = unsafeFLFromList (Focus 1) [0..2]
-- >>> debugFL fl
-- "FocusList {focusListFocus = Focus 1, focusListLen = 3, focusList = fromList [(0,0),(1,1),(2,2)]}"
--
-- >>> let fl = unsafeFLFromList NoFocus []
-- >>> debugFL fl
-- "FocusList {focusListFocus = NoFocus, focusListLen = 0, focusList = fromList []}"
unsafeFLFromList :: Focus -> [a] -> FocusList a
unsafeFLFromList focus list =
let len = length list
in
FocusList
{ focusListFocus = focus
, focusListLen = len
, focusList = mapFromList $ zip [0..] list
}
focusItemGetter :: Getter (FocusList a) (Maybe a)
focusItemGetter = to getFLFocusItem
-- | Safely create a 'FocusList' from a list.
--
-- >>> flFromList (Focus 1) ["cat","dog","goat"]
-- Just (FocusList (Focus 1) ["cat","dog","goat"])
--
-- >>> flFromList NoFocus []
-- Just (FocusList NoFocus [])
--
-- If the 'Focus' is out of range for the list, then 'Nothing' will be returned.
--
-- >>> flFromList (Focus (-1)) ["cat","dog","goat"]
-- Nothing
--
-- >>> flFromList (Focus 3) ["cat","dog","goat"]
-- Nothing
--
-- >>> flFromList NoFocus ["cat","dog","goat"]
-- Nothing
flFromList :: Focus -> [a] -> Maybe (FocusList a)
flFromList NoFocus [] = Just emptyFL
flFromList _ [] = Nothing
flFromList NoFocus (_:_) = Nothing
flFromList (Focus i) list =
let len = length list
in
if i < 0 || i >= len
then Nothing
else
Just $
FocusList
{ focusListFocus = Focus i
, focusListLen = len
, focusList = mapFromList $ zip [0..] list
}
-- | Create a 'FocusList' with a single element.
--
-- >>> singletonFL "hello"
-- FocusList (Focus 0) ["hello"]
singletonFL :: a -> FocusList a
singletonFL a =
FocusList
{ focusListFocus = Focus 0
, focusListLen = 1
, focusList = singletonMap 0 a
}
-- | Create an empty 'FocusList' without a 'Focus'.
--
-- >>> emptyFL
-- FocusList NoFocus []
emptyFL :: FocusList a
emptyFL =
FocusList
{ focusListFocus = NoFocus
, focusListLen = 0
, focusList = mempty
}
-- | Return 'True' if the 'FocusList' is empty.
--
-- >>> isEmptyFL emptyFL
-- True
--
-- >>> isEmptyFL $ singletonFL "hello"
-- False
--
-- Any 'FocusList' with a 'Focus' should never be empty.
isEmptyFL :: FocusList a -> Bool
isEmptyFL fl = fl ^. lensFocusListLen == 0
-- | Append a value to the end of a 'FocusList'.
--
-- This can be thought of as a \"snoc\" operation.
--
-- >>> appendFL emptyFL "hello"
-- FocusList (Focus 0) ["hello"]
--
-- >>> appendFL (singletonFL "hello") "bye"
-- FocusList (Focus 0) ["hello","bye"]
--
-- Appending a value to an empty 'FocusList' is the same as using 'singletonFL'.
--
-- prop> appendFL emptyFL a == singletonFL a
appendFL :: FocusList a -> a -> FocusList a
appendFL fl a =
if isEmptyFL fl
then singletonFL a
else unsafeInsertNewFL (fl ^. lensFocusListLen) a fl
-- | A combination of 'appendFL' and 'setFocusFL'.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "tree"]
-- >>> appendSetFocusFL fl "pie"
-- FocusList (Focus 3) ["hello","bye","tree","pie"]
--
-- prop> (appendSetFocusFL fl a) ^. lensFocusListFocus /= fl ^. lensFocusListFocus
appendSetFocusFL :: FocusList a -> a -> FocusList a
appendSetFocusFL fl a =
let oldLen = fl ^. lensFocusListLen
in
case setFocusFL oldLen (appendFL fl a) of
Nothing -> error "Internal error with setting the focus. This should never happen."
Just newFL -> newFL
-- | Prepend a value to a 'FocusList'.
--
-- This can be thought of as a \"cons\" operation.
--
-- >>> prependFL "hello" emptyFL
-- FocusList (Focus 0) ["hello"]
--
-- The focus will be updated when prepending:
--
-- >>> prependFL "bye" (singletonFL "hello")
-- FocusList (Focus 1) ["bye","hello"]
--
-- Prepending to a 'FocusList' will always update the 'Focus':
--
-- prop> (fl ^. lensFocusListFocus) < (prependFL a fl ^. lensFocusListFocus)
prependFL :: a -> FocusList a -> FocusList a
prependFL a fl =
if isEmptyFL fl
then singletonFL a
else unsafeInsertNewFL 0 a $ unsafeShiftUpFrom 0 fl
-- | Unsafely get the 'Focus' from a 'FocusList'. If the 'Focus' is
-- 'NoFocus', this function returns 'error'.
unsafeGetFLFocus :: FocusList a -> Int
unsafeGetFLFocus fl =
let focus = fl ^. lensFocusListFocus
in
case focus of
NoFocus -> error "unsafeGetFLFocus: the focus list doesn't have a focus"
Focus i -> i
-- | Unsafely get the value of the 'Focus' from a 'FocusList'. If the 'Focus' is
-- 'NoFocus', this function returns 'error'.
unsafeGetFLFocusItem :: FocusList a -> a
unsafeGetFLFocusItem fl =
let focus = fl ^. lensFocusListFocus
in
case focus of
NoFocus -> error "unsafeGetFLFocusItem: the focus list doesn't have a focus"
Focus i ->
let intmap = fl ^. lensFocusList
in
case lookup i intmap of
Nothing ->
error $
"unsafeGetFLFocusItem: internal error, i (" <>
show i <>
") doesnt exist in intmap"
Just a -> a
getFLFocusItem :: FocusList a -> Maybe a
getFLFocusItem fl =
let focus = fl ^. lensFocusListFocus
in
case focus of
NoFocus -> Nothing
Focus i ->
let intmap = fl ^. lensFocusList
in
case lookup i intmap of
Nothing ->
error $
"getFLFocusItem: internal error, i (" <>
show i <>
") doesnt exist in intmap"
Just a -> Just a
-- | Unsafely insert a new @a@ in a 'FocusList'. This sets the 'Int' value to
-- @a@. The length of the 'FocusList' will be increased by 1. The
-- 'FocusList's 'Focus' is not changed.
--
-- If there is some value in the 'FocusList' already at the 'Int', then it will
-- be overwritten. Also, the 'Int' is not checked to make sure it is above 0.
--
-- This function is meant to be used after 'unsafeShiftUpFrom'.
--
-- >>> let fl = unsafeShiftUpFrom 2 $ unsafeFLFromList (Focus 1) [0,1,200]
-- >>> debugFL $ unsafeInsertNewFL 2 100 fl
-- "FocusList {focusListFocus = Focus 1, focusListLen = 4, focusList = fromList [(0,0),(1,1),(2,100),(3,200)]}"
--
-- >>> let fl = unsafeFLFromList NoFocus []
-- >>> debugFL $ unsafeInsertNewFL 0 100 fl
-- "FocusList {focusListFocus = NoFocus, focusListLen = 1, focusList = fromList [(0,100)]}"
unsafeInsertNewFL :: Int -> a -> FocusList a -> FocusList a
unsafeInsertNewFL i a fl =
fl &
lensFocusListLen +~ 1 &
lensFocusListAt i ?~ a
-- | This unsafely shifts all values up in a 'FocusList' starting at a given
-- index. It also updates the 'Focus' of the 'FocusList' if it has been
-- shifted. This does not change the length of the 'FocusList'.
--
-- It does not check that the 'Int' is greater than 0. It also does not check
-- that there is a 'Focus'.
--
-- ==== __EXAMPLES__
--
-- >>> let fl = unsafeShiftUpFrom 2 $ unsafeFLFromList (Focus 1) [0,1,200]
-- >>> debugFL fl
-- "FocusList {focusListFocus = Focus 1, focusListLen = 3, focusList = fromList [(0,0),(1,1),(3,200)]}"
--
-- >>> let fl = unsafeShiftUpFrom 1 $ unsafeFLFromList (Focus 1) [0,1,200]
-- >>> debugFL fl
-- "FocusList {focusListFocus = Focus 2, focusListLen = 3, focusList = fromList [(0,0),(2,1),(3,200)]}"
--
-- >>> let fl = unsafeShiftUpFrom 0 $ unsafeFLFromList (Focus 1) [0,1,200]
-- >>> debugFL fl
-- "FocusList {focusListFocus = Focus 2, focusListLen = 3, focusList = fromList [(1,0),(2,1),(3,200)]}"
--
-- >>> let fl = unsafeShiftUpFrom 0 $ unsafeFLFromList (Focus 1) [0,1,200]
-- >>> debugFL fl
-- "FocusList {focusListFocus = Focus 2, focusListLen = 3, focusList = fromList [(1,0),(2,1),(3,200)]}"
unsafeShiftUpFrom :: forall a. Int -> FocusList a -> FocusList a
unsafeShiftUpFrom i fl =
let intMap = fl ^. lensFocusList
lastElemIdx = (fl ^. lensFocusListLen) - 1
newIntMap = go i lastElemIdx intMap
oldFocus = unsafeGetFLFocus fl
newFocus = if i > oldFocus then oldFocus else oldFocus + 1
in
fl &
lensFocusList .~ newIntMap &
lensFocusListFocus .~ Focus newFocus
where
go :: Int -> Int -> IntMap a -> IntMap a
go idxToInsert idxToShiftUp intMap
| idxToInsert <= idxToShiftUp =
let val = unsafeLookup idxToShiftUp intMap
newMap =
insertMap (idxToShiftUp + 1) val (deleteMap idxToShiftUp intMap)
in go idxToInsert (idxToShiftUp - 1) newMap
| otherwise = intMap
-- | This is an unsafe lookup function. This assumes that the 'Int' exists in
-- the 'IntMap'.
unsafeLookup :: Int -> IntMap a -> a
unsafeLookup i intmap =
case lookup i intmap of
Nothing -> error $ "unsafeLookup: key " <> show i <> " not found in intmap"
Just a -> a
lookupFL :: Int -> FocusList a -> Maybe a
lookupFL i fl = lookup i (fl ^. lensFocusList)
-- | Insert a new value into the 'FocusList'. The 'Focus' of the list is
-- changed appropriately.
--
-- >>> insertFL 0 "hello" emptyFL
-- Just (FocusList (Focus 0) ["hello"])
--
-- >>> insertFL 0 "hello" (singletonFL "bye")
-- Just (FocusList (Focus 1) ["hello","bye"])
--
-- >>> insertFL 1 "hello" (singletonFL "bye")
-- Just (FocusList (Focus 0) ["bye","hello"])
--
-- This returns 'Nothing' if the index at which to insert the new value is
-- either less than 0 or greater than the length of the list.
--
-- >>> insertFL 100 "hello" emptyFL
-- Nothing
--
-- >>> insertFL 100 "bye" (singletonFL "hello")
-- Nothing
--
-- >>> insertFL (-1) "bye" (singletonFL "hello")
-- Nothing
insertFL
:: Int -- ^ The index at which to insert the value.
-> a
-> FocusList a
-> Maybe (FocusList a)
insertFL i a fl
| i < 0 || i > (fl ^. lensFocusListLen) =
-- Return Nothing if the insertion position is out of bounds.
Nothing
| i == 0 && isEmptyFL fl =
-- Return a 'FocusList' with one element if the insertion position is 0
-- and the 'FocusList' is empty.
Just $ singletonFL a
| otherwise =
-- Shift all existing values up one and insert the new
-- value in the opened place.
let shiftedUpFL = unsafeShiftUpFrom i fl
in Just $ unsafeInsertNewFL i a shiftedUpFL
-- | Unsafely remove a value from a 'FocusList'. It effectively leaves a hole
-- inside the 'FocusList'. It updates the length of the 'FocusList'.
--
-- This function does not check that a value actually exists in the
-- 'FocusList'. It also does not update the 'Focus'.
--
-- This function does update the length of the 'FocusList'.
--
-- >>> debugFL $ unsafeRemove 1 $ unsafeFLFromList (Focus 0) [0..2]
-- "FocusList {focusListFocus = Focus 0, focusListLen = 2, focusList = fromList [(0,0),(2,2)]}"
--
-- >>> debugFL $ unsafeRemove 0 $ unsafeFLFromList (Focus 0) [0..2]
-- "FocusList {focusListFocus = Focus 0, focusListLen = 2, focusList = fromList [(1,1),(2,2)]}"
--
-- Trying to remove the last element is completely safe (unless, of course, it
-- is the 'Focus'):
--
-- >>> debugFL $ unsafeRemove 2 $ unsafeFLFromList (Focus 2) [0..2]
-- "FocusList {focusListFocus = Focus 2, focusListLen = 2, focusList = fromList [(0,0),(1,1)]}"
--
-- If this function is passed an empty 'FocusList', it will make the length -1.
--
-- >>> debugFL $ unsafeRemove 0 emptyFL
-- "FocusList {focusListFocus = NoFocus, focusListLen = -1, focusList = fromList []}"
unsafeRemove
:: Int
-> FocusList a
-> FocusList a
unsafeRemove i fl =
fl &
lensFocusListLen -~ 1 &
lensFocusListAt i .~ Nothing
-- | This shifts all the values down in a 'FocusList' starting at a given
-- index. It does not change the 'Focus' of the 'FocusList'. It does not change the
-- length of the 'FocusList'.
--
-- It does not check that shifting elements down will not overwrite other elements.
-- This function is meant to be called after 'unsafeRemove'.
--
-- >>> let fl = unsafeRemove 1 $ unsafeFLFromList (Focus 0) [0..2]
-- >>> debugFL $ unsafeShiftDownFrom 1 fl
-- "FocusList {focusListFocus = Focus 0, focusListLen = 2, focusList = fromList [(0,0),(1,2)]}"
--
-- >>> let fl = unsafeRemove 0 $ unsafeFLFromList (Focus 0) [0..2]
-- >>> debugFL $ unsafeShiftDownFrom 0 fl
-- "FocusList {focusListFocus = Focus 0, focusListLen = 2, focusList = fromList [(0,1),(1,2)]}"
--
-- Trying to shift down from the last element after it has been removed is a no-op:
--
-- >>> let fl = unsafeRemove 2 $ unsafeFLFromList (Focus 0) [0..2]
-- >>> debugFL $ unsafeShiftDownFrom 2 fl
-- "FocusList {focusListFocus = Focus 0, focusListLen = 2, focusList = fromList [(0,0),(1,1)]}"
unsafeShiftDownFrom :: forall a. Int -> FocusList a -> FocusList a
unsafeShiftDownFrom i fl =
let intMap = fl ^. lensFocusList
len = fl ^. lensFocusListLen
newIntMap = go (i + 1) len intMap
in fl & lensFocusList .~ newIntMap
where
go :: Int -> Int -> IntMap a -> IntMap a
go idxToShiftDown len intMap
| idxToShiftDown < len + 1 =
let val = unsafeLookup idxToShiftDown intMap
newMap =
insertMap (idxToShiftDown - 1) val (deleteMap idxToShiftDown intMap)
in go (idxToShiftDown + 1) len newMap
| otherwise = intMap
-- | Remove an element from a 'FocusList'.
--
-- If the element to remove is not the 'Focus', then update the 'Focus'
-- accordingly.
--
-- For example, if the 'Focus' is on index 1, and we have removed index 2, then
-- the focus is not affected, so it is not changed.
--
-- >>> let focusList = unsafeFLFromList (Focus 1) ["cat","goat","dog","hello"]
-- >>> removeFL 2 focusList
-- Just (FocusList (Focus 1) ["cat","goat","hello"])
--
-- If the 'Focus' is on index 2 and we have removed index 1, then the 'Focus'
-- will be moved back one element to set to index 1.
--
-- >>> let focusList = unsafeFLFromList (Focus 2) ["cat","goat","dog","hello"]
-- >>> removeFL 1 focusList
-- Just (FocusList (Focus 1) ["cat","dog","hello"])
--
-- If we remove the 'Focus', then the next item is set to have the 'Focus'.
--
-- >>> let focusList = unsafeFLFromList (Focus 0) ["cat","goat","dog","hello"]
-- >>> removeFL 0 focusList
-- Just (FocusList (Focus 0) ["goat","dog","hello"])
--
-- If the element to remove is the only element in the list, then the 'Focus'
-- will be set to 'NoFocus'.
--
-- >>> let focusList = unsafeFLFromList (Focus 0) ["hello"]
-- >>> removeFL 0 focusList
-- Just (FocusList NoFocus [])
--
-- If the 'Int' for the index to remove is either less than 0 or greater then
-- the length of the list, then 'Nothing' is returned.
--
-- >>> let focusList = unsafeFLFromList (Focus 0) ["hello"]
-- >>> removeFL (-1) focusList
-- Nothing
--
-- >>> let focusList = unsafeFLFromList (Focus 1) ["hello","bye","cat"]
-- >>> removeFL 3 focusList
-- Nothing
--
-- If the 'FocusList' passed in is 'Empty', then 'Nothing' is returned.
--
-- >>> removeFL 0 emptyFL
-- Nothing
removeFL
:: Int -- ^ Index of the element to remove from the 'FocusList'.
-> FocusList a -- ^ The 'FocusList' to remove an element from.
-> Maybe (FocusList a)
removeFL i fl
| i < 0 || i >= (fl ^. lensFocusListLen) || isEmptyFL fl =
-- Return Nothing if the removal position is out of bounds.
Nothing
| fl ^. lensFocusListLen == 1 =
-- Return an empty focus list if there is currently only one element
Just emptyFL
| otherwise =
let newFLWithHole = unsafeRemove i fl
newFL = unsafeShiftDownFrom i newFLWithHole
focus = unsafeGetFLFocus fl
in
if focus >= i && focus /= 0
then Just $ newFL & lensFocusListFocus . _Focus -~ 1
else Just newFL
-- | Find the index of the first element in the 'FocusList'.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "tree"]
-- >>> indexOfFL "hello" fl
-- Just 0
--
-- If more than one element exists, then return the index of the first one.
--
-- >>> let Just fl = flFromList (Focus 1) ["dog", "cat", "cat"]
-- >>> indexOfFL "cat" fl
-- Just 1
--
-- If the element doesn't exist, then return 'Nothing'
--
-- >>> let Just fl = flFromList (Focus 1) ["foo", "bar", "baz"]
-- >>> indexOfFL "hogehoge" fl
-- Nothing
indexOfFL :: Eq a => a -> FocusList a -> Maybe Int
indexOfFL a fl =
let intmap = focusList fl
keyVals = sortOn fst $ mapToList intmap
maybeKeyVal = find (\(_, val) -> val == a) keyVals
in fmap fst maybeKeyVal
-- | Delete an element from a 'FocusList'.
--
-- >>> let Just fl = flFromList (Focus 0) ["hello", "bye", "tree"]
-- >>> deleteFL "bye" fl
-- FocusList (Focus 0) ["hello","tree"]
--
-- The focus will be updated if an item before it is deleted.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "tree"]
-- >>> deleteFL "hello" fl
-- FocusList (Focus 0) ["bye","tree"]
--
-- If there are multiple matching elements in the 'FocusList', remove them all.
--
-- >>> let Just fl = flFromList (Focus 0) ["hello", "bye", "bye"]
-- >>> deleteFL "bye" fl
-- FocusList (Focus 0) ["hello"]
--
-- If there are no matching elements, return the original 'FocusList'.
--
-- >>> let Just fl = flFromList (Focus 2) ["hello", "good", "bye"]
-- >>> deleteFL "frog" fl
-- FocusList (Focus 2) ["hello","good","bye"]
deleteFL
:: forall a.
(Eq a)
=> a
-> FocusList a
-> FocusList a
deleteFL item = go
where
go :: FocusList a -> FocusList a
go fl =
let maybeIndex = indexOfFL item fl
in
case maybeIndex of
Nothing -> fl
Just i ->
let maybeNewFL = removeFL i fl
in
case maybeNewFL of
Nothing -> fl
Just newFL -> go newFL
-- | Set the 'Focus' for a 'FocusList'.
--
-- This is just like 'updateFocusFL', but doesn't return the new focused item.
--
-- prop> setFocusFL i fl == fmap snd (updateFocusFL i fl)
setFocusFL :: Int -> FocusList a -> Maybe (FocusList a)
setFocusFL i fl
-- Can't set a 'Focus' for an empty 'FocusList'.
| isEmptyFL fl = Nothing
| otherwise =
let len = fl ^. lensFocusListLen
in
if i < 0 || i >= len
then Nothing
else Just $ fl & lensFocusListFocus . _Focus .~ i
-- | Update the 'Focus' for a 'FocusList' and get the new focused element.
--
-- >>> updateFocusFL 1 =<< flFromList (Focus 2) ["hello","bye","dog","cat"]
-- Just ("bye",FocusList (Focus 1) ["hello","bye","dog","cat"])
--
-- If the 'FocusList' is empty, then return 'Nothing'.
--
-- >>> updateFocusFL 1 emptyFL
-- Nothing
--
-- If the new focus is less than 0, or greater than or equal to the length of
-- the 'FocusList', then return 'Nothing'.
--
-- >>> updateFocusFL (-1) =<< flFromList (Focus 2) ["hello","bye","dog","cat"]
-- Nothing
--
-- >>> updateFocusFL 4 =<< flFromList (Focus 2) ["hello","bye","dog","cat"]
-- Nothing
updateFocusFL :: Int -> FocusList a -> Maybe (a, FocusList a)
updateFocusFL i fl
| isEmptyFL fl = Nothing
| otherwise =
let len = fl ^. lensFocusListLen
in
if i < 0 || i >= len
then Nothing
else
let newFL = fl & lensFocusListFocus . _Focus .~ i
in Just (unsafeGetFLFocusItem newFL, newFL)
-- | Find a value in a 'FocusList'. Similar to @Data.List.'Data.List.find'@.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "tree"]
-- >>> findFL (\_ a -> a == "hello") fl
-- Just (0,"hello")
--
-- This will only find the first value.
--
-- >>> let Just fl = flFromList (Focus 0) ["hello", "bye", "bye"]
-- >>> findFL (\_ a -> a == "bye") fl
-- Just (1,"bye")
--
-- If no values match the comparison, this will return 'Nothing'.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "parrot"]
-- >>> findFL (\_ a -> a == "ball") fl
-- Nothing
findFL :: (Int -> a -> Bool) -> FocusList a -> Maybe (Int, a)
findFL f fl =
let intmap = fl ^. lensFocusList
vals = sortOn fst $ mapToList intmap
in find (\(i, a) -> f i a) vals
-- | Move an existing item in a 'FocusList' to a new index.
--
-- The 'Focus' gets updated appropriately when moving items.
--
-- >>> let Just fl = flFromList (Focus 1) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 0 1 fl
-- Just (FocusList (Focus 0) ["bye","hello","parrot"])
--
-- The 'Focus' may not get updated if it is not involved.
--
-- >>> let Just fl = flFromList (Focus 0) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 1 2 fl
-- Just (FocusList (Focus 0) ["hello","parrot","bye"])
--
-- If the element with the 'Focus' is moved, then the 'Focus' will be updated appropriately.
--
-- >>> let Just fl = flFromList (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 2 0 fl
-- Just (FocusList (Focus 0) ["parrot","hello","bye"])
--
-- If the index of the item to move is out bounds, then 'Nothing' will be returned.
--
-- >>> let Just fl = flFromList (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 3 0 fl
-- Nothing
--
-- If the new index is out of bounds, then 'Nothing' wil be returned.
--
-- >>> let Just fl = flFromList (Focus 2) ["hello", "bye", "parrot"]
-- >>> moveFromToFL 1 (-1) fl
-- Nothing
moveFromToFL
:: Show a => Int -- ^ Index of the item to move.
-> Int -- ^ New index for the item.
-> FocusList a
-> Maybe (FocusList a)
moveFromToFL oldPos newPos fl
| oldPos < 0 || oldPos >= length fl = Nothing
| newPos < 0 || newPos >= length fl = Nothing
| otherwise =
let oldFocus = fl ^. lensFocusListFocus
in
case lookupFL oldPos fl of
Nothing -> error "moveFromToFL should have been able to lookup the item"
Just item ->
case removeFL oldPos fl of
Nothing -> error "moveFromToFL should have been able to remove old position"
Just flAfterRemove ->
case insertFL newPos item flAfterRemove of
Nothing -> error "moveFromToFL should have been able to reinsert the item"
Just flAfterInsert ->
if Focus oldPos == oldFocus
then
case setFocusFL newPos flAfterInsert of
Nothing -> error "moveFromToFL should have been able to reset the focus"
Just flWithUpdatedFocus -> Just flWithUpdatedFocus
else Just flAfterInsert