yi-0.3: Yi/Keymap/Vim.hs
{-# LANGUAGE FlexibleContexts, DeriveDataTypeable #-}
--
-- Copyright (c) 2004-5 Don Stewart - http://www.cse.unsw.edu.au/~dons
--
--
-- | Vim keymap for Yi. Emulates vim :set nocompatible
module Yi.Keymap.Vim ( keymap, VimMode, viWrite ) where
import Yi.Yi
import Yi.Style as Style
import Prelude hiding ( any, error )
import Data.Char
import Data.Maybe ( fromMaybe )
import Data.Dynamic
import qualified Data.Map as M
import Control.Exception ( ioErrors, try, evaluate )
import Control.Monad.State
import Yi.Editor
import Yi.Accessor
import Yi.History
import Yi.Buffer
import Yi.Debug
import Yi.Indent
import Yi.Keymap.Emacs.Utils (completeFileName,completeBufferName)
import Yi.MiniBuffer
import Yi.TextCompletion
import Yi.Syntax.Table (highlighters)
--
-- What's missing?
-- fancier :s//
-- '.'
-- movement parameterised \> \<
--
{-
For now we just make the selected style the same as the
modeline_focused style... Just because i'm not good with
styles yet - Jim
-}
defaultVimUiStyle :: Style.UIStyle
defaultVimUiStyle = Style.uiStyle { selected = Style.modeline_focused Style.uiStyle}
-- ---------------------------------------------------------------------
type VimMode = VimProc ()
type VimProc a = (Interact Char) a
-- | Top level
keymap :: Keymap
keymap = do write $ do setWindowFillE '~'
withBuffer0 unsetMarkB
setWindowStyleE defaultVimUiStyle
runVim cmd_mode
runVim :: VimMode -> Keymap
runVim = comap eventToChar
------------------------------------------------------------------------
-- The Vim VimProc is divided into several parts, roughly corresponding
-- to the different modes of vi. Each mode is in turn broken up into
-- separate VimProcs for each phase of key input in that mode.
-- | Command mode consists of simple commands that take a count arg,
-- the replace cmd, which consumes one char of input, and commands
-- that switch modes.
cmd_mode :: VimMode
cmd_mode = choice [cmd_eval,eval cmd_move,cmd2other,cmd_op]
-- | Take a VimMode that returns and action; "run" it and write the returned action.
eval :: YiAction a () => VimProc (b, a) -> VimMode
eval p = do (_, a) <- p; write a
-- | Leave a mode. This always has priority over catch-all actions inside the mode.
leave :: VimMode
leave = event '\ESC' >> adjustPriority (-1) (write msgClr)
-- | Insert mode is either insertion actions, or the meta (\ESC) action
ins_mode :: VimMode
ins_mode = write (msgEditor "-- INSERT --") >> many (ins_char <|> kwd_mode) >> leave
-- | Replace mode is like insert, except it performs writes, not inserts
rep_mode :: VimMode
rep_mode = write (msgEditor "-- REPLACE --") >> many rep_char >> leave
-- | Visual mode, similar to command mode
vis_mode :: RegionStyle -> VimMode
vis_mode regionStyle = do
write (withBuffer (pointB >>= setSelectionMarkPointB))
core_vis_mode regionStyle
write (msgClr >> withBuffer unsetMarkB >> withBuffer (setDynamicB $ SelectionStyle Character))
core_vis_mode :: RegionStyle -> VimMode
core_vis_mode regionStyle = do
write $ withEditor $ do setA regionStyleA regionStyle
withBuffer0 $ setDynamicB $ SelectionStyle $
case regionStyle of { LineWise -> fullLine; CharWise -> Character }
printMsg $ msg regionStyle
many (eval cmd_move)
(vis_single regionStyle <|| vis_multi)
where msg CharWise = "-- VISUAL --"
msg LineWise = "-- VISUAL LINE --"
-- | Change visual mode
change_vis_mode :: RegionStyle -> RegionStyle -> VimMode
change_vis_mode src dst | src == dst = return ()
change_vis_mode _ dst = core_vis_mode dst
-- | A VimProc to accumulate digits.
-- typically what is needed for integer repetition arguments to commands
count :: VimProc (Maybe Int)
count = option Nothing $ do
c <- satisfy isDigitNon0
cs <- many (satisfy isDigit)
return $ Just $ read (c:cs)
where isDigitNon0 '0' = False
isDigitNon0 x = isDigit x
data RegionStyle = LineWise
| CharWise
deriving (Eq, Typeable, Show)
instance Initializable RegionStyle where
initial = CharWise
regionStyleToUnit :: RegionStyle -> TextUnit
regionStyleToUnit LineWise = fullLine
regionStyleToUnit CharWise = Character
fullLine :: TextUnit
fullLine = GenUnit {genEnclosingUnit=Document, genUnitBoundary=bound}
where bound d = withOffset d $ atBoundaryB Line d
withOffset Backward f = f
withOffset Forward f = savingPointB (leftB >> f)
regionStyleA :: Accessor Editor RegionStyle
regionStyleA = dynamicValueA .> dynamicA
-- ---------------------------------------------------------------------
-- | VimProc for movement commands
--
-- The may be invoked directly, or sometimes as arguments to other
-- /operator/ commands (like d).
--
cmd_move :: VimProc (RegionStyle, BufferM ())
cmd_move = do
cnt <- count
let x = maybe 1 id cnt
choice ([event c >> return (CharWise, a x) | (c,a) <- moveCmdFM, (c /= '0' || Nothing == cnt) ] ++
[event c >> return (LineWise, a x) | (c,a) <- moveUpDownCmdFM] ++
[do event c; c' <- anyEvent; return (CharWise, a x c') | (c,a) <- move2CmdFM] ++
[do event 'G'; return (LineWise, case cnt of
Nothing -> botB >> moveToSol
Just n -> gotoLn n >> return ())
,do events "gg"; return (LineWise, gotoLn 0 >> return ())
,do events "ge"; return (CharWise, replicateM_ x $ genMoveB ViWord (Forward, InsideBound) Backward)])
-- | movement commands
moveCmdFM :: [(Char, Int -> BufferM ())]
moveCmdFM =
-- left/right
[('h', left)
,('\^H', left)
,(keyBackspace, left)
,('\BS', left)
,('\127', left)
,(keyLeft, left)
,(keyRight, right)
,('l', right)
,(' ', right)
,(keyHome, sol)
,('0', sol)
,('^', const firstNonSpaceB)
,('$', eol)
,(keyEnd, eol)
,('|', \i -> moveToSol >> moveXorEol (i-1))
-- words
,('w', \i -> replicateM_ i $ genMoveB ViWord (Backward,InsideBound) Forward)
,('b', \i -> replicateM_ i $ genMoveB ViWord (Backward,InsideBound) Backward)
,('e', \i -> replicateM_ i $ genMoveB ViWord (Forward, InsideBound) Forward)
-- text
,('{', prevNParagraphs)
,('}', nextNParagraphs)
-- misc
,('H', \i -> downFromTosB (i - 1))
,('M', const middleB)
,('L', \i -> upFromBosB (i - 1))
]
where
left = moveXorSol
right = moveXorEol
sol _ = moveToSol
eol _ = moveToEol
-- | up/down movement commands. these one are separated from moveCmdFM
-- because they behave differently when yanking/cuting (line mode).
moveUpDownCmdFM :: [(Char, Int -> BufferM ())]
moveUpDownCmdFM =
[('k', up)
,(keyUp, up)
,('\^P', up)
,('j', down)
,(keyDown, down)
,('\^J', down)
,('\^N', down)
,('\r', down)
]
where
up i = lineMoveRel (-i) >> return ()
down i = lineMoveRel i >> return ()
-- | more movement commands. these ones are paramaterised by a character
-- to find in the buffer.
move2CmdFM :: [(Char, Int -> Char -> BufferM ())]
move2CmdFM =
[('f', \i c -> replicateM_ i $ nextCInc c)
,('F', \i c -> replicateM_ i $ prevCInc c)
,('t', \i c -> replicateM_ i $ nextCExc c)
,('T', \i c -> replicateM_ i $ prevCExc c)
]
-- | Other command mode functions
cmd_eval :: VimMode
cmd_eval = do
cnt <- count
let i = maybe 1 id cnt
choice
([event c >> write (a i) | (c,a) <- singleCmdFM ] ++
[events evs >> write (action i) | (evs, action) <- multiCmdFM ]) <|>
(do event 'r'; c <- anyButEscOrDel; write (writeB c)) <|>
(events ">>" >> write (shiftIndentOfLine i)) <|>
(events "<<" >> write (shiftIndentOfLine (-i))) <|>
(events "ZZ" >> write (viWrite >> quitEditor))
-- TODO: escape the current word
-- at word bounds: search for \<word\>
searchCurrentWord :: YiM ()
searchCurrentWord = do
w <- withBuffer $ readRegionB =<< regionOfB ViWord
doSearch (Just w) [] Forward
anyButEscOrDel :: VimProc Char
anyButEscOrDel = satisfy (not . (`elem` ('\ESC':delete')))
continueSearching :: Direction -> YiM ()
continueSearching direction = do
withEditor $ getRegexE >>= printMsg . ("/" ++) . fst . fromMaybe ([],undefined)
doSearch Nothing [] direction
-- | cmd mode commands
singleCmdFM :: [(Char, Int -> YiM ())]
singleCmdFM =
[('\^B', withBuffer . upScreensB) -- vim does (firstNonSpaceB;moveXorSol)
,('\^F', withBuffer . downScreensB)
,('\^G', const viFileInfo) -- hmm. not working. duh. we clear
,('\^L', const refreshEditor)
,('\^R', withBuffer . flip replicateM_ redoB)
,('\^Z', const suspendEditor)
,('D', const (withEditor $ withBuffer0 readRestOfLnB >>= setRegE >> withBuffer0 deleteToEol))
,('J', const (withBuffer (moveToEol >> deleteN 1))) -- the "\n"
,('U', withBuffer . flip replicateM_ undoB) -- NB not correct
,('n', const $ continueSearching Forward)
,('u', withBuffer . flip replicateM_ undoB)
,('X', \i -> withBuffer $ do p <- pointB
moveXorSol i
q <- pointB
when (p-q > 0) $ deleteN (p-q) )
,('x', \i -> withBuffer $ do p <- pointB -- not handling eol properly
moveXorEol i
q <- pointB
moveTo p
when (q-p > 0) $ deleteN (q-p))
,('p', withEditor . flip replicateM_ pasteAfter)
,('P', withEditor . flip replicateM_ pasteBefore)
,(keyPPage, withBuffer . upScreensB)
,(keyNPage, withBuffer . downScreensB)
,('*', const $ searchCurrentWord)
,('~', \i -> withBuffer $ do
p <- pointB
moveXorEol i
q <- pointB
moveTo p
mapRegionB (mkRegion p q) $ \c ->
if isUpper c then toLower c else toUpper c
moveTo q)
]
multiCmdFM :: [(String, Int -> YiM ())]
multiCmdFM =
[("\^Wc", const $ withEditor tryCloseE)
,("\^Wo", const $ withEditor closeOtherE)
,("\^Ws", const $ withEditor splitE)
,("\^Ww", const $ withEditor nextWinE)
,("\^WW", const $ withEditor prevWinE)
,("\^Wp", const $ withEditor prevWinE)
-- since we don't have vertical splitting,
-- these moving can be done using next/prev.
,(['\^W',keyDown], const $ withEditor nextWinE)
,(['\^W',keyUp], const $ withEditor prevWinE)
,(['\^W',keyRight], const $ withEditor nextWinE)
,(['\^W',keyLeft], const $ withEditor prevWinE)
]
-- | So-called 'operators', which take movement actions as arguments.
--
-- How do we achive this? We parse a known operator char then parse
-- one of the known movement commands. We then apply the returned
-- action and then the operator. For example, we 'd' command stores
-- the current point, does a movement, then deletes from the old to
-- the new point.
cmd_op :: VimMode
cmd_op = do
cnt <- count
let i = maybe 1 id cnt
choice $ [events "dd" >> write (cut pointB (lineMoveRel (i-1) >> return ()) LineWise),
events "yy" >> write (yank pointB (lineMoveRel (i-1) >> return ()) LineWise)] ++
[do event c
(regionStyle, m) <- cmd_move
write $ a (replicateM_ i m) regionStyle
| (c,a) <- opCmdFM]
where
-- | operator (i.e. movement-parameterised) actions
opCmdFM :: [(Char, BufferM () -> RegionStyle -> EditorM ())]
opCmdFM =
[('d', cut pointB),
('y', yank pointB)]
regionFromTo :: BufferM Point -> BufferM () -> RegionStyle -> BufferM Region
regionFromTo mstart move regionStyle = do
start <- mstart
move
stop <- pointB
let region = mkRegion start stop
unitWiseRegion (regionStyleToUnit regionStyle) region
yank :: BufferM Point -> BufferM () -> RegionStyle -> EditorM ()
yank mstart move regionStyle = do
txt <- withBuffer0 $ do
p <- pointB
region <- regionFromTo mstart move regionStyle
moveTo p
readRegionB region
setRegE $ if (regionStyle /= CharWise) then '\n':txt else txt
let rowsYanked = length (filter (== '\n') txt)
when (rowsYanked > 2) $ printMsg $ (show rowsYanked) ++ " lines yanked"
cut :: BufferM Point -> BufferM () -> RegionStyle -> EditorM ()
cut mstart move regionStyle = do
txt <- withBuffer0 $ do
region <- regionFromTo mstart move regionStyle
txt <- readRegionB region
deleteRegionB region
return txt
setRegE $ if (regionStyle /= CharWise) then '\n':txt else txt
let rowsCut = length (filter (== '\n') txt)
when (rowsCut > 2) $ printMsg ( (show rowsCut) ++ " fewer lines")
cutSelection :: EditorM ()
cutSelection = cut getSelectionMarkPointB (return ()) =<< getA regionStyleA
yankSelection :: EditorM ()
yankSelection = yank getSelectionMarkPointB (return ()) =<< getA regionStyleA
pasteOverSelection :: EditorM ()
pasteOverSelection = do
txt <- getRegE
regionStyle <- getA regionStyleA
withBuffer0 $ do
region <- regionFromTo getSelectionMarkPointB (return ()) regionStyle
moveTo $ regionStart region
deleteRegionB region
insertN txt
pasteAfter :: EditorM ()
pasteAfter = do
txt' <- getRegE
withBuffer0 $
case txt' of
'\n':txt -> moveToEol >> rightB >> insertN txt >> leftN (length txt)
_ -> moveXorEol 1 >> insertN txt' >> leftB
pasteBefore :: EditorM ()
pasteBefore = do
txt' <- getRegE
withBuffer0 $ do
case txt' of
'\n':txt -> moveToSol >> insertN txt >> leftN (length txt)
_ -> insertN txt' >> leftB
-- | Switching to another mode from visual mode.
--
-- All visual commands are meta actions, as they transfer control to another
-- VimProc. In this way vis_single is analogous to cmd2other
--
vis_single :: RegionStyle -> VimMode
vis_single regionStyle =
let beginIns a = do write (a >> withBuffer0 unsetMarkB) >> ins_mode
in choice [
event '\ESC' >> return (),
event 'V' >> change_vis_mode regionStyle LineWise,
event 'v' >> change_vis_mode regionStyle CharWise,
event ':' >> ex_mode ":'<,'>",
event 'y' >> write yankSelection,
event 'x' >> write cutSelection,
event 'd' >> write cutSelection,
event 'p' >> write pasteOverSelection,
event 'c' >> beginIns cutSelection]
-- | These also switch mode, as all visual commands do, but these are
-- analogous to the commands in cmd_eval. They are different in that
-- they are multiple characters
vis_multi :: VimMode
vis_multi = do
cnt <- count
let i = maybe 1 id cnt
choice ([events "ZZ" >> write (viWrite >> quitEditor),
events ">>" >> write (shiftIndentOfSelection i),
events "<<" >> write (shiftIndentOfSelection (-i)),
do event 'r'; x <- anyEvent; write $ do
mrk <- getSelectionMarkPointB
pt <- pointB
text <- readRegionB (mkVimRegion mrk pt)
moveTo mrk
deleteRegionB (mkVimRegion mrk pt)
let convert '\n' = '\n'
convert _ = x
insertN $ map convert $ text] ++
[event c >> write (a i) | (c,a) <- singleCmdFM ])
-- | Switch to another vim mode from command mode.
--
-- These commands are meta actions, as they transfer control to another
-- VimProc. Some of these commands also perform an action before switching.
--
cmd2other :: VimMode
cmd2other = let beginIns a = write a >> ins_mode
beginIns :: EditorM () -> VimMode
in choice [
do event ':' ; ex_mode ":",
do event 'v' ; vis_mode CharWise,
do event 'V' ; vis_mode LineWise,
do event 'R' ; rep_mode,
do event 'i' ; ins_mode,
do event 'I' ; beginIns (withBuffer0 moveToSol),
do event 'a' ; beginIns $ withBuffer0 $ moveXorEol 1,
do event 'A' ; beginIns (withBuffer0 moveToEol),
do event 'o' ; beginIns $ withBuffer0 $ moveToEol >> insertB '\n',
do event 'O' ; beginIns $ withBuffer0 $ moveToSol >> insertB '\n' >> lineUp,
do event 'c' ; (regionStyle, m) <- cmd_move ; beginIns $ cut pointB m regionStyle,
do events "cc" ; beginIns $ cut (moveToSol >> pointB) moveToEol CharWise,
do event 'C' ; beginIns $ cut pointB moveToEol CharWise, -- alias of "c$"
do event 'S' ; beginIns $ cut (moveToSol >> pointB) moveToEol CharWise, -- alias of "cc"
do event 's' ; beginIns $ cut pointB (moveXorEol 1) CharWise, -- alias of "cl"
do event '/' ; ex_mode "/",
do event '?' ; write $ not_implemented '?',
leave,
do event keyIC ; ins_mode]
ins_mov_char :: VimMode
ins_mov_char = choice [event keyPPage >> write upScreenB,
event keyNPage >> write downScreenB,
event keyUp >> write lineUp,
event keyDown >> write lineDown,
event keyLeft >> write (moveXorSol 1),
event keyRight >> write (moveXorEol 1),
event keyEnd >> write moveToEol,
event keyHome >> write moveToSol]
-- ---------------------------------------------------------------------
-- | vim insert mode
--
-- Some ideas for a better insert mode are contained in:
--
-- Poller and Garter , "A comparative study of moded and modeless
-- text editing by experienced editor users", 1983
--
-- which suggest that movement commands be added to insert mode, along
-- with delete.
--
ins_char :: VimMode
ins_char = choice [satisfy isDel >> write (deleteB Character Backward),
event keyDC >> write (deleteB Character Forward),
event '\t' >> write insertTabB]
<|> ins_mov_char
<|| do c <- anyEvent; write (insertB c)
-- --------------------
-- | Keyword
kwd_mode :: VimMode
kwd_mode = some (event '\^N' >> adjustPriority (-1) (write wordCompleteB)) >> (write resetCompleteB)
-- 'adjustPriority' is there to lift the ambiguity between "continuing" completion
-- and resetting it (restarting at the 1st completion).
-- ---------------------------------------------------------------------
-- | vim replace mode
--
-- To quote vim:
-- In Replace mode, one character in the line is deleted for every character
-- you type. If there is no character to delete (at the end of the line), the
-- typed character is appended (as in Insert mode). Thus the number of
-- characters in a line stays the same until you get to the end of the line.
-- If a <NL> is typed, a line break is inserted and no character is deleted.
rep_char :: VimMode
rep_char = choice [satisfy isDel >> write leftB, -- should undo unless pointer has been moved
event '\t' >> write (insertN " "),
event '\r' >> write (insertB '\n')]
<|> ins_mov_char
<|| do c <- anyEvent; write (do e <- atEol; if e then insertB c else writeB c)
-- ---------------------------------------------------------------------
-- Ex mode. We also process regex searching mode here.
spawn_ex_buffer :: String -> YiM ()
spawn_ex_buffer prompt = do
-- The above ensures that the action is performed on the buffer that originated the minibuffer.
let ex_buffer_finish = do
withEditor $ historyFinish
lineString <- withBuffer elemsB
withEditor closeBufferAndWindowE
ex_eval (head prompt : lineString)
ex_process :: VimMode
ex_process =
choice [enter >> write ex_buffer_finish,
event '\t' >> write completeMinibuffer,
event '\ESC' >> write closeBufferAndWindowE,
delete >> write bdeleteB,
event keyUp >> write historyUp,
event keyDown >> write historyDown,
event keyLeft >> write (moveXorSol 1),
event keyRight >> write (moveXorEol 1)]
<|| (do c <- anyEvent; write $ insertB c)
completeMinibuffer = withBuffer elemsB >>= ex_complete >>= withBuffer . insertN
b_complete f = completeBufferName f >>= return . drop (length f)
ex_complete ('e':' ':f) = completeFileName Nothing f >>= return . drop (length f)
ex_complete ('b':' ':f) = b_complete f
ex_complete ('b':'u':'f':'f':'e':'r':' ':f) = b_complete f
ex_complete ('b':'d':' ':f) = b_complete f
ex_complete ('b':'d':'!':' ':f) = b_complete f
ex_complete ('b':'d':'e':'l':'e':'t':'e':' ':f) = b_complete f
ex_complete ('b':'d':'e':'l':'e':'t':'e':'!':' ':f) = b_complete f
ex_complete _ = return ""
withEditor $ historyStart
spawnMinibufferE prompt (const $ runVim $ ex_process) (return ())
ex_mode :: String -> VimMode
ex_mode = write . spawn_ex_buffer
-- | eval an ex command to an YiM (), also appends to the ex history
ex_eval :: String -> YiM ()
ex_eval cmd = do
case cmd of
-- regex searching
('/':pat) -> doSearch (Just pat) [] Forward
-- TODO: We give up on re-mapping till there exists a generic Yi mechanism to do so.
-- add mapping to command mode
(_:'m':'a':'p':' ':_cs) -> error "Not yet implemented."
-- add mapping to insert mode
(_:'m':'a':'p':'!':' ':_cs) -> error "Not yet implemented."
-- unmap a binding from command mode
(_:'u':'n':'m':'a':'p':' ':_cs) -> error "Not yet implemented."
-- unmap a binding from insert mode
(_:'u':'n':'m':'a':'p':'!':' ':_cs) -> error "Not yet implemented."
-- just a normal ex command
(_:src) -> fn src
-- can't happen, but deal with it
[] -> return ()
where
whenUnchanged mu f = do u <- mu
if u then f
else errorEditor "No write since last change (add ! to override)"
quitB = whenUnchanged (withBuffer isUnchangedB) closeWindow
quitNoW = do bufs <- withEditor $ do closeBufferE ""
bufs <- gets bufferStack
mapM (\x -> withGivenBuffer0 x isUnchangedB) bufs
whenUnchanged (return $ all id bufs) quitEditor
quitall = withAllBuffers quitB
wquitall = withAllBuffers viWrite >> quitEditor
bdelete = whenUnchanged (withBuffer isUnchangedB) . withEditor . closeBufferE
bdeleteNoW = withEditor . closeBufferE
fn "" = msgClr
fn s@(c:_) | isDigit c = do
e <- lift $ try $ evaluate $ read s
case e of Left _ -> errorEditor $ "The " ++show s++ " command is unknown."
Right lineNum -> withBuffer (gotoLn lineNum) >> return ()
fn "w" = viWrite
fn ('w':' ':f) = viWriteTo f
fn "qa" = quitall
fn "qal" = quitall
fn "qall" = quitall
fn "quita" = quitall
fn "quital" = quitall
fn "quitall" = quitall
fn "q" = quitB
fn "qu" = quitB
fn "qui" = quitB
fn "quit" = quitB
fn "q!" = quitNoW
fn "qu!" = quitNoW
fn "qui!" = quitNoW
fn "quit!" = quitNoW
fn "qa!" = quitEditor
fn "quita!" = quitEditor
fn "quital!" = quitEditor
fn "quitall!" = quitEditor
fn "wq" = viWrite >> closeWindow
fn "wqa" = wquitall
fn "wqal" = wquitall
fn "wqall" = wquitall
fn "x" = do unchanged <- withBuffer isUnchangedB
unless unchanged viWrite
closeWindow
fn "n" = withEditor nextBufW
fn "next" = withEditor nextBufW
fn "$" = withBuffer botB
fn "p" = withEditor prevBufW
fn "prev" = withEditor prevBufW
fn ('s':'p':_) = withEditor splitE
fn "e" = revertE
fn ('e':' ':f) = fnewE f
fn ('s':'e':'t':' ':'f':'t':'=':ft) = withBuffer $ setSyntaxB $ highlighters M.! ft
fn ('n':'e':'w':' ':f) = withEditor splitE >> fnewE f
fn ('s':'/':cs) = viSub cs
fn ('b':' ':"m") = withEditor $ switchToBufferWithNameE "*messages*"
fn ('b':' ':f) = withEditor $ switchToBufferWithNameE f
fn "bd" = bdelete ""
fn "bdelete" = bdelete ""
fn ('b':'d':' ':f) = bdelete f
fn ('b':'d':'e':'l':'e':'t':'e':' ':f) = bdelete f
fn "bd!" = bdeleteNoW ""
fn "bdelete!" = bdeleteNoW ""
fn ('b':'d':'!':' ':f) = bdeleteNoW f
fn ('b':'d':'e':'l':'e':'t':'e':'!':' ':f) = bdeleteNoW f
-- TODO: bd[!] [N]
-- send just this line through external command /fn/
fn ('.':'!':f) = do
ln <- withBuffer readLnB
ln' <- runProcessWithInput f ln
withBuffer $ do moveToSol
deleteToEol
insertN ln'
moveToSol
-- Needs to occur in another buffer
-- fn ('!':f) = runProcessWithInput f []
fn "reload" = reloadEditor >> return () -- not in vim
fn "eval" = withBuffer (regionOfB Document >>= readRegionB) >>= evalE -- not in vim
fn "redr" = refreshEditor
fn "redraw" = refreshEditor
fn "u" = withBuffer undoB
fn "undo" = withBuffer undoB
fn "r" = withBuffer redoB
fn "redo" = withBuffer redoB
fn "sus" = suspendEditor
fn "suspend" = suspendEditor
fn "st" = suspendEditor
fn "stop" = suspendEditor
fn s = errorEditor $ "The "++show s++ " command is unknown."
------------------------------------------------------------------------
not_implemented :: Char -> YiM ()
not_implemented c = errorEditor $ "Not implemented: " ++ show c
-- ---------------------------------------------------------------------
-- Misc functions
withAllBuffers :: YiM () -> YiM ()
withAllBuffers m = mapM_ (\b -> withEditor (setBuffer b) >> m) =<< readEditor bufferStack
viFileInfo :: YiM ()
viFileInfo =
do bufInfo <- withBuffer bufInfoB
msgEditor $ showBufInfo bufInfo
where
showBufInfo :: BufferFileInfo -> String
showBufInfo bufInfo = concat [ show $ bufInfoFileName bufInfo
, " Line "
, show $ bufInfoLineNo bufInfo
, " ["
, bufInfoPercent bufInfo
, "]"
]
-- | Try to write a file in the manner of vi\/vim
-- Need to catch any exception to avoid losing bindings
viWrite :: YiM ()
viWrite = do
mf <- withBuffer getfileB
case mf of
Nothing -> errorEditor "no file name associate with buffer"
Just f -> do
bufInfo <- withBuffer bufInfoB
let s = bufInfoFileName bufInfo
let msg = msgEditor $ show f ++" "++show s ++ "C written"
catchJustE ioErrors (fwriteToE f >> msg) (msgEditor . show)
-- | Try to write to a named file in the manner of vi\/vim
viWriteTo :: String -> YiM ()
viWriteTo f = do
let f' = (takeWhile (/= ' ') . dropWhile (== ' ')) f
bufInfo <- withBuffer bufInfoB
let s = bufInfoFileName bufInfo
let msg = msgEditor $ show f'++" "++show s ++ "C written"
catchJustE ioErrors (fwriteToE f' >> msg) (msgEditor . show)
-- | Try to do a substitution
viSub :: [Char] -> YiM ()
viSub cs = do
let (pat,rep') = break (== '/') cs
(rep,opts) = case rep' of
[] -> ([],[])
(_:ds) -> case break (== '/') ds of
(rep'', []) -> (rep'', [])
(rep'', (_:fs)) -> (rep'',fs)
case opts of
[] -> do_single pat rep
['g'] -> do_single pat rep
_ -> do_single pat rep-- TODO
where do_single p r = do
s <- searchAndRepLocal p r
if not s then errorEditor ("Pattern not found: "++p) else msgClr
-- | Is a delete sequence
isDel :: Char -> Bool
isDel '\BS' = True
isDel '\127' = True
isDel c | c == keyBackspace = True
isDel _ = False
-- ---------------------------------------------------------------------
-- | Character ranges
--
delete, enter :: VimProc Char
enter = oneOf ['\n', '\r']
delete = oneOf delete'
delete' :: [Char]
delete' = ['\BS', '\127', keyBackspace ]