kmonad-0.4.2: src/KMonad/Gesture.hs
-- |
module KMonad.Gesture
where
import KMonad.Prelude hiding (try)
import KMonad.Parsing
import Control.Monad.Except
import Control.Monad.State
import Data.Char
import RIO.List.Partial (head)
import RIO.Seq (Seq(..))
import qualified RIO.List as L
import qualified RIO.Seq as Q
import qualified RIO.Set as S
--------------------------------------------------------------------------------
data Toggle a = On a | Off a deriving (Eq, Show, Functor)
-- | A sequence of toggle-changes guaranteed to be valid
newtype Gesture a = Gesture { _gesture :: Q.Seq (Toggle a) }
deriving (Eq, Show, Functor)
instance Semigroup (Gesture a) where
(Gesture a) <> (Gesture b) = Gesture $ a <> b
instance Monoid (Gesture a) where
mempty = Gesture Q.empty
-- | All the ways a '[Toggle a]' can be an invalid 'Gesture'
data GestureError a
= OffWithoutOn a -- ^ An Off not preceded by an On
| OnWithoutOff a -- ^ An On not succeeded by an Off
deriving (Eq, Show)
--------------------------------------------------------------------------------
-- | A lens into the i
tag :: Lens' (Toggle a) a
tag = lens get set
where get (On x) = x
get (Off x) = x
set (On _) x = On x
set (Off _) x = Off x
-- | A fold of all the unique elements in a gesture
tags :: Ord a => Fold (Gesture a) a
tags = folding $ \(Gesture as) -> toList . S.fromList $ as^..folded.tag
-- | Create a tapping gesture
tap :: a -> Gesture a
tap a = Gesture . Q.fromList $ [On a, Off a]
-- | Wrap a gesture in a toggle iff the id does not already occur
around :: Ord a => a -> Gesture a -> Either (GestureError a) (Gesture a)
around x g@(Gesture seq)
| anyOf tags (== x) g = Left $ OnWithoutOff x
| otherwise = Right . Gesture $ (On x <| seq) |> Off x
-- | Create a gesture from a list of toggles
fromList :: Ord a => [Toggle a] -> Either (GestureError a) (Gesture a)
fromList as = case (`runState` S.empty) . runExceptT . foldM f Q.empty $ as of
(Left e, _) -> Left e
(Right g, s) | S.null s -> Right $ Gesture g
| otherwise -> Left $ OnWithoutOff (head . S.elems $ s)
where
f s x = do
pressed <- get
case x of
On c | c `S.member` pressed -> throwError $ OnWithoutOff c
On c -> put (S.insert c pressed) >> pure (s |> On c)
Off c | not (c `S.member` pressed) -> throwError $ OffWithoutOn c
Off c -> put (S.delete c pressed) >> pure (s |> Off c)
--------------------------------------------------------------------------------
type Gest = Q.Seq (Toggle Text)
data GestureReadError
= GestureParseError ParseError
| GestureValidateError (GestureError Text)
deriving Eq
instance Show GestureReadError where
show (GestureParseError e) = show e
show (GestureValidateError e) = show e
instance Exception GestureReadError
-- | Parse a Gesture straight from Text
prsGesture :: Text -> Either GestureReadError (Gesture Text)
prsGesture t = case runParser gest "" t of
Left e -> Left . GestureParseError . ParseError $ e
Right gs -> case fromList (toList gs) of
Left e -> Left . GestureValidateError $ e
Right g -> pure g
-- | Characters that may not occur in tag-names
reserved :: [Char]
reserved = "()-~[]"
-- | Parse a series of valid characters as a tag
tag_ :: Parser Text
tag_ = takeWhile1P (Just "tag-character") f
where f c = not $ isSpace c || c `elem` reserved
-- | Parse a "S-" sequence as 1 tag around another
around_ :: Parser Gest
around_ = do
a <- tag_
_ <- char '-'
b <- try around_ <|> subg <|> tap_
pure $ (On a <| b) |> Off a
-- | Parse a ")-X" as an OFF-toggle
closeTag :: Parser Gest
closeTag = do
_ <- string ")-"
a <- tag_
pure . Q.singleton $ Off a
-- | Parse a "X-(" as an ON-toggle
openTag :: Parser Gest
openTag = do
a <- tag_
_ <- string "-("
pure . Q.singleton $ On a
-- | Parse only a tag as a tap of that element
tap_ :: Parser Gest
tap_ = do
a <- tag_
pure . Q.fromList $ [On a, Off a]
-- | Parse a [] delimited series as a nested gesture
subg :: Parser Gest
subg = do
_ <- char '['
g <- gest
_ <- char ']'
pure g
-- | Parse a full gesture
gest :: Parser Gest
gest = do
let one = lex . choice $ [subg, try openTag, try around_, try closeTag, tap_]
es <- some one
pure $ mconcat es