module System.Console.Haskeline.Key(Key(..), Modifier(..), BaseKey(..), noModifier, simpleKey, simpleChar, metaChar, ctrlChar, metaKey, ctrlKey, parseKey ) where import Data.Bits import Data.Char import Data.Maybe import Data.List (intercalate) import Control.Monad data Key = Key Modifier BaseKey deriving (Eq,Ord) instance Show Key where show (Key modifier base) | modifier == noModifier = show base | otherwise = show modifier ++ "-" ++ show base data Modifier = Modifier {hasControl, hasMeta, hasShift :: Bool} deriving (Eq,Ord) instance Show Modifier where show m = intercalate "-" $ catMaybes [maybeUse hasControl "ctrl" , maybeUse hasMeta "meta" , maybeUse hasShift "shift" ] where maybeUse f str = if f m then Just str else Nothing noModifier :: Modifier noModifier = Modifier False False False -- Note: a few of these aren't really keys (e.g., KillLine), -- but they provide useful enough binding points to include. data BaseKey = KeyChar Char | FunKey Int | LeftKey | RightKey | DownKey | UpKey | KillLine | Home | End | PageDown | PageUp | Backspace | Delete | SearchReverse | SearchForward deriving (Eq, Ord) instance Show BaseKey where show (KeyChar '\n') = "Return" show (KeyChar '\t') = "Tab" show (KeyChar '\ESC') = "Esc" show (KeyChar c) | isPrint c = [c] | isPrint unCtrld = "ctrl-" ++ [unCtrld] | otherwise = show c where unCtrld = toEnum (fromEnum c .|. ctrlBits) show (FunKey n) = 'f' : show n show LeftKey = "Left" show RightKey = "Right" show DownKey = "Down" show UpKey = "Up" show KillLine = "KillLine" show Home = "Home" show End = "End" show PageDown = "PageDown" show PageUp = "PageUp" show Backspace = "Backspace" show Delete = "Delete" show SearchReverse = "SearchReverse" show SearchForward = "SearchForward" simpleKey :: BaseKey -> Key simpleKey = Key noModifier metaKey :: Key -> Key metaKey (Key m bc) = Key m {hasMeta = True} bc ctrlKey :: Key -> Key ctrlKey (Key m bc) = Key m {hasControl = True} bc simpleChar, metaChar, ctrlChar :: Char -> Key simpleChar = simpleKey . KeyChar metaChar = metaKey . simpleChar ctrlChar = simpleChar . setControlBits setControlBits :: Char -> Char setControlBits '?' = toEnum 127 setControlBits c = toEnum $ fromEnum c .&. complement ctrlBits ctrlBits :: Int ctrlBits = bit 5 .|. bit 6 specialKeys :: [(String,BaseKey)] specialKeys = [("left",LeftKey) ,("right",RightKey) ,("down",DownKey) ,("up",UpKey) ,("killline",KillLine) ,("home",Home) ,("end",End) ,("pagedown",PageDown) ,("pageup",PageUp) ,("backspace",Backspace) ,("delete",Delete) ,("return",KeyChar '\n') ,("enter",KeyChar '\n') ,("tab",KeyChar '\t') ,("esc",KeyChar '\ESC') ,("escape",KeyChar '\ESC') ,("reversesearchhistory",SearchReverse) ,("forwardsearchhistory",SearchForward) ] parseModifiers :: [String] -> BaseKey -> Key parseModifiers strs = Key mods where mods = foldl1 (.) (map parseModifier strs) noModifier parseModifier :: String -> (Modifier -> Modifier) parseModifier str m = case map toLower str of "ctrl" -> m {hasControl = True} "control" -> m {hasControl = True} "meta" -> m {hasMeta = True} "shift" -> m {hasShift = True} _ -> m breakAtDashes :: String -> [String] breakAtDashes "" = [] breakAtDashes str = case break (=='-') str of (xs,'-':rest) -> xs : breakAtDashes rest (xs,_) -> [xs] parseKey :: String -> Maybe Key parseKey str = fmap canonicalizeKey $ case reverse (breakAtDashes str) of [ks] -> liftM simpleKey (parseBaseKey ks) ks:ms -> liftM (parseModifiers ms) (parseBaseKey ks) [] -> Nothing parseBaseKey :: String -> Maybe BaseKey parseBaseKey ks = lookup (map toLower ks) specialKeys `mplus` parseFunctionKey ks `mplus` parseKeyChar ks where parseKeyChar [c] | isPrint c = Just (KeyChar c) parseKeyChar _ = Nothing parseFunctionKey (f:ns) | f `elem` "fF" = case reads ns of [(n,"")] -> Just (FunKey n) _ -> Nothing parseFunctionKey _ = Nothing canonicalizeKey :: Key -> Key canonicalizeKey (Key m (KeyChar c)) | hasControl m = Key m {hasControl = False} (KeyChar (setControlBits c)) | hasShift m = Key m {hasShift = False} (KeyChar (toUpper c)) canonicalizeKey k = k