module System.Console.Haskeline.Vi where
import System.Console.Haskeline.Command
import System.Console.Haskeline.Command.Completion
import System.Console.Haskeline.Command.History
import System.Console.Haskeline.Command.Undo
import System.Console.Haskeline.LineState
import System.Console.Haskeline.InputT
import Data.Char(isAlphaNum,isSpace)
type InputCmd s t = forall m . Monad m => Command (InputCmdT m) s t
viActions :: Monad m => KeyMap (InputCmdT m) InsertMode
viActions = runCommand insertionCommands
insertionCommands :: InputCmd InsertMode InsertMode
insertionCommands = choiceCmd [startCommand, simpleInsertions]
simpleInsertions :: InputCmd InsertMode InsertMode
simpleInsertions = choiceCmd
[ KeyChar '\n' +> finish
, KeyChar '\r' +> finish
, KeyLeft +> change goLeft
, KeyRight +> change goRight
, Backspace +> change deletePrev
, KeyChar '\b' +> change deletePrev
, DeleteForward +> change deleteNext
, changeFromChar insertChar
, controlKey 'l' +> clearScreenCmd
, controlKey 'd' +> eofIfEmpty
, KeyUp +> historyBack
, KeyDown +> historyForward
, searchHistory
, saveForUndo $ choiceCmd
[ KillLine +> change (deleteFromMove moveToStart)
, KeyChar '\t' +> completionCmd
]
]
eofIfEmpty :: Save s => Key -> InputCmd s s
eofIfEmpty k = k +> acceptKeyOrFail (\s -> if save s == emptyIM
then Nothing
else Just $ Change s >=> continue)
startCommand :: InputCmd InsertMode InsertMode
startCommand = KeyChar '\ESC' +> change enterCommandMode
>|> viCommandActions
viCommandActions :: InputCmd CommandMode InsertMode
viCommandActions = simpleCmdActions `loopUntil` exitingCommands
exitingCommands :: InputCmd CommandMode InsertMode
exitingCommands = choiceCmd [ KeyChar 'i' +> change insertFromCommandMode
, KeyChar 'I' +> change (moveToStart . insertFromCommandMode)
, KeyChar 'a' +> change appendFromCommandMode
, KeyChar 'A' +> change (moveToEnd . appendFromCommandMode)
, KeyChar 's' +> change (insertFromCommandMode . deleteChar)
, repeated
, saveForUndo $ choiceCmd
[ KeyChar 'S' +> change (const emptyIM)
, deleteIOnce
]
]
simpleCmdActions :: InputCmd CommandMode CommandMode
simpleCmdActions = choiceCmd [ KeyChar '\n' +> finish
, KeyChar '\ESC' +> change id
, controlKey 'd' +> eofIfEmpty
, KeyChar 'r' +> replaceOnce
, KeyChar 'R' +> loopReplace
, KeyChar 'x' +> change deleteChar
, controlKey 'l' +> clearScreenCmd
, KeyChar 'u' +> commandUndo
, controlKey 'r' +> commandRedo
, KeyChar '.' +> commandRedo
, useMovements withCommandMode
, KeyDown +> historyForward
, KeyUp +> historyBack
, saveForUndo $ choiceCmd
[ KillLine +> change (withCommandMode
$ deleteFromMove moveToStart)
, deleteOnce
]
]
replaceOnce :: Key -> InputCmd CommandMode CommandMode
replaceOnce k = k >+> try (changeFromChar replaceChar)
loopReplace :: Key -> InputCmd CommandMode CommandMode
loopReplace k = k >+> loop
where
loop = choiceCmd [changeFromChar (\c -> goRight . replaceChar c) >|> loop
, continue]
repeated :: InputCmd CommandMode InsertMode
repeated = let
start = foreachDigit startArg ['1'..'9']
addDigit = foreachDigit addNum ['0'..'9']
deleteR = KeyChar 'd'
>+> choiceCmd [useMovements (deleteFromRepeatedMove),
KeyChar 'd' +> change (const CEmpty)]
deleteIR = KeyChar 'c'
>+> choiceCmd [useMovements deleteAndInsertR,
KeyChar 'c' +> change (const emptyIM)]
applyArg' f am = enterCommandModeRight $ applyArg f $ fmap insertFromCommandMode am
loop = choiceCmd [addDigit >|> loop
, useMovements applyArg' >|> viCommandActions
, saveForUndo (deleteR >|> viCommandActions)
, saveForUndo deleteIR
, saveForUndo (KeyChar 'x' +> change (applyArg deleteChar)
>|> viCommandActions)
, changeWithoutKey argState >|> viCommandActions
]
in start >|> loop
movements :: [(Key,InsertMode -> InsertMode)]
movements = [ (KeyChar 'h', goLeft)
, (KeyChar 'l', goRight)
, (KeyChar 'w', skipRight isSpace . (\s -> skipRight (cmdChar s) s))
, (KeyChar 'b', (\s -> skipLeft (cmdChar s) s) . goLeft . skipLeft isSpace)
, (KeyChar 'W', skipRight isSpace . skipRight (not . isSpace))
, (KeyChar 'B', skipLeft (not . isSpace) . skipLeft isSpace)
, (KeyChar ' ', goRight)
, (KeyLeft, goLeft)
, (KeyRight, goRight)
, (KeyChar '0', moveToStart)
, (KeyChar '$', moveToEnd)
]
cmdChar :: InsertMode -> (Char -> Bool)
cmdChar (IMode _ (c:_))
| isWordChar c = isWordChar
cmdChar _ = \d -> not (isWordChar d) && not (isSpace d)
isWordChar :: Char -> Bool
isWordChar d = isAlphaNum d || d == '_'
useMovements :: LineState t => ((InsertMode -> InsertMode) -> s -> t)
-> InputCmd s t
useMovements f = choiceCmd $ map (\(k,g) -> k +> change (f g))
movements
deleteOnce :: InputCmd CommandMode CommandMode
deleteOnce = KeyChar 'd'
>+> choiceCmd [useMovements deleteFromCmdMove,
KeyChar 'd' +> change (const CEmpty)]
deleteIOnce :: InputCmd CommandMode InsertMode
deleteIOnce = KeyChar 'c'
>+> choiceCmd [useMovements deleteAndInsert,
KeyChar 'c' +> change (const emptyIM)]
deleteAndInsert :: (InsertMode -> InsertMode) -> CommandMode -> InsertMode
deleteAndInsert f = insertFromCommandMode . deleteFromCmdMove f
deleteAndInsertR :: (InsertMode -> InsertMode)
-> ArgMode CommandMode -> InsertMode
deleteAndInsertR f = insertFromCommandMode . deleteFromRepeatedMove f
foreachDigit :: (Monad m, LineState t) => (Int -> s -> t) -> [Char]
-> Command m s t
foreachDigit f ds = choiceCmd $ map digitCmd ds
where digitCmd d = KeyChar d +> change (f (toDigit d))
toDigit d = fromEnum d fromEnum '0'
deleteFromCmdMove :: (InsertMode -> InsertMode) -> CommandMode -> CommandMode
deleteFromCmdMove f = withCommandMode $ \x -> deleteFromDiff x (f x)
deleteFromRepeatedMove :: (InsertMode -> InsertMode)
-> ArgMode CommandMode -> CommandMode
deleteFromRepeatedMove f am = let
am' = fmap insertFromCommandMode am
in enterCommandModeRight $
deleteFromDiff (argState am') (applyArg f am')