diff --git a/CHANGES b/CHANGES
new file mode 100644
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,23 @@
+Changed in version 0.6.2:
+
+  User interface changes:
+   * A multitude of new emacs and vi commands
+
+   * New preference 'historyDuplicates' to prevent storage of duplicate lines
+
+   * Support PageUp and PageDown keys
+
+   * Let ctrl-L (clear-screen) work during getInputChar
+
+  Internal/API changes:
+   * Compatibility with ghc-6.12
+
+   * Calculate the correct width for Unicode combining characters
+
+   * Removed RankNTypes requirement; added Rank2Types and UndecidableInstances
+
+   * Use simpleUserHooks instead of autoconfUserHooks in the Setup script
+
+   * Internal refactoring to make command declaration more flexible
+
+   * Read the .haskeline file completely before starting the UI (laziness issue)
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -14,24 +14,25 @@
 import System.Exit
 import System.Directory
 import Control.Exception.Extensible
+import Control.Monad(when)
 
--- TODO: it's a hack that we use the autoconfUserHooks when we're not actually
--- using autoconf...
 main :: IO ()
 main = defaultMainWithHooks myHooks
 
 myHooks :: UserHooks
 myHooks
     | buildOS == Windows    = simpleUserHooks
-    | otherwise = autoconfUserHooks {
-            postConf = \args flags pkgDescr lbi -> do
-                            let Just lib = library pkgDescr
-                            let bi = libBuildInfo lib
-                            bi' <- maybeSetLibiconv flags bi lbi
-                            let pkgDescr' = pkgDescr {
+    | otherwise = simpleUserHooks {
+            confHook = \genericDescript flags -> do
+                        warnIfNotTerminfo flags
+                        lbi <- confHook simpleUserHooks genericDescript flags
+                        let pkgDescr = localPkgDescr lbi
+                        let Just lib = library pkgDescr
+                        let bi = libBuildInfo lib
+                        bi' <- maybeSetLibiconv flags bi lbi
+                        return lbi {localPkgDescr = pkgDescr {
                                                 library = Just lib {
-                                                    libBuildInfo = bi'}}
-                            postConf simpleUserHooks args flags pkgDescr' lbi
+                                                    libBuildInfo = bi'}}}
             }
 
 -- Test whether compiling a c program that links against libiconv needs -liconv.
@@ -42,7 +43,6 @@
     if hasFlagSet flags (FlagName "libiconv")
         then do
             putStrLn "Using -liconv."
-            writeBuildInfo "iconv"
             return biWithIconv
         else do
     putStr "checking whether to use -liconv... "
@@ -51,20 +51,14 @@
     if worksWithout
         then do
             putStrLn "not needed."
-            writeBuildInfo ""
             return bi
         else do
     worksWith <- tryCompile iconv_prog biWithIconv lbi verb
     if worksWith
         then do
             putStrLn "using -liconv."
-            writeBuildInfo "iconv"
             return biWithIconv
         else error "Unable to link against the iconv library."
-  where
-    -- Cabal (at least 1.6.0.1) won't parse an empty buildinfo file.
-    writeBuildInfo libs = writeFile "haskeline.buildinfo"
-                            $ unlines ["extra-libraries: " ++ libs]
 
 hasFlagSet :: ConfigFlags -> FlagName -> Bool
 hasFlagSet cflags flag = Just True == lookup flag (configConfigurationsFlags cflags)
@@ -112,3 +106,6 @@
     , "}"
     ]
     
+warnIfNotTerminfo flags = when (not (hasFlagSet flags (FlagName "terminfo"))) $
+  putStrLn $
+    "*** Warning: running on POSIX but not building the terminfo backend. ***"
diff --git a/System/Console/Haskeline.hs b/System/Console/Haskeline.hs
--- a/System/Console/Haskeline.hs
+++ b/System/Console/Haskeline.hs
@@ -69,11 +69,11 @@
 import System.Console.Haskeline.Completion
 import System.Console.Haskeline.Term
 import System.Console.Haskeline.Key
+import System.Console.Haskeline.RunCommand
 
 import System.IO
-import Data.Char (isSpace)
+import Data.Char (isSpace, isPrint)
 import Control.Monad
-import Data.Char(isPrint)
 import qualified Data.ByteString.Char8 as B
 import System.IO.Error (isEOFError)
 
@@ -105,7 +105,7 @@
 
 -- | Write a string to the standard output, followed by a newline.
 outputStrLn :: MonadIO m => String -> InputT m ()
-outputStrLn xs = outputStr (xs++"\n")
+outputStrLn = outputStr . (++ "\n")
 
 
 {- $inputfncs
@@ -143,53 +143,27 @@
 
 getInputCmdLine :: MonadException m => TermOps -> String -> InputT m (Maybe String)
 getInputCmdLine tops prefix = do
-    -- Load the necessary settings/prefs
-    -- TODO: Cache the actions
-    emode <- asks (\prefs -> case editMode prefs of
-                    Vi -> viActions
-                    Emacs -> emacsCommands)
-    -- Run the main event processing loop
-    result <- runInputCmdT tops $ runTerm tops
-                    $ \getEvent -> do
-                            let ls = emptyIM
-                            drawLine prefix ls 
-                            repeatTillFinish tops getEvent prefix ls emode
+    emode <- asks editMode
+    result <- runInputCmdT tops $ case emode of
+                Emacs -> runCommandLoop tops prefix emacsCommands
+                Vi -> evalStateT' emptyViState $
+                        runCommandLoop tops prefix viKeyCommands
     maybeAddHistory result
     return result
 
 maybeAddHistory :: forall m . Monad m => Maybe String -> InputT m ()
 maybeAddHistory result = do
     settings :: Settings m <- ask
+    histDupes <- asks historyDuplicates
     case result of
         Just line | autoAddHistory settings && not (all isSpace line) 
-            -> modify (addHistory line)
+            -> let adder = case histDupes of
+                        AlwaysAdd -> addHistory
+                        IgnoreConsecutive -> addHistoryUnlessConsecutiveDupe
+                        IgnoreAll -> addHistoryRemovingAllDupes
+               in modify (adder line)
         _ -> return ()
 
-repeatTillFinish :: forall m s d 
-    . (MonadTrans d, Term (d m), LineState s, MonadReader Prefs m)
-            => TermOps -> d m Event -> String -> s -> KeyMap m s 
-            -> d m (Maybe String)
-repeatTillFinish tops getEvent prefix = loop []
-    where 
-        loop :: forall t . LineState t
-                    => [Key] -> t -> KeyMap m t -> d m (Maybe String)
-        loop [] s processor = do
-                event <- handle (\(e::SomeException) -> movePast prefix s >> throwIO e) getEvent
-                case event of
-                    ErrorEvent e -> movePast prefix s >> throwIO e
-                    WindowResize -> withReposition tops prefix s $ loop [] s processor
-                    KeyInput k -> do
-                        ks <- lift $ asks $ lookupKeyBinding k
-                        loop ks s processor
-        loop (k:ks) s processor = case lookupKM processor k of
-                        Nothing -> actBell >> loop [] s processor
-                        Just g -> case g s of
-                            Left r -> movePast prefix s >> return r
-                            Right f -> do
-                                        KeyAction effect next <- lift f
-                                        drawEffect prefix s effect
-                                        loop ks (effectState effect) next
-
 simpleFileLoop :: MonadIO m => String -> RunTerm -> m (Maybe String)
 simpleFileLoop prefix rterm = liftIO $ do
     putStrOut rterm prefix
@@ -205,52 +179,7 @@
                         _ -> B.getLine
             fmap Just $ decodeForTerm rterm line
 
-drawEffect :: (LineState s, LineState t, Term (d m), 
-                MonadTrans d, MonadReader Prefs m) 
-    => String -> s -> Effect t -> d m ()
-drawEffect prefix s (Redraw shouldClear t) = if shouldClear
-    then clearLayout >> drawLine prefix t
-    else clearLine prefix s >> drawLine prefix t
-drawEffect prefix s (Change t) = drawLineStateDiff prefix s t
-drawEffect prefix s (PrintLines ls t) = do
-    if isTemporary s
-        then clearLine prefix s
-        else movePast prefix s
-    printLines ls
-    drawLine prefix t
-drawEffect prefix s (RingBell t) = drawLineStateDiff prefix s t >> actBell
 
-drawLine :: (LineState s, Term m) => String -> s -> m ()
-drawLine prefix s = drawLineStateDiff prefix Cleared s
-
-drawLineStateDiff :: (LineState s, LineState t, Term m) 
-                        => String -> s -> t -> m ()
-drawLineStateDiff prefix s t = drawLineDiff (lineChars prefix s) 
-                                        (lineChars prefix t)
-
-clearLine :: (LineState s, Term m) => String -> s -> m ()
-clearLine prefix s = drawLineStateDiff prefix s Cleared
-        
-actBell :: (Term (d m), MonadTrans d, MonadReader Prefs m) => d m ()
-actBell = do
-    style <- lift (asks bellStyle)
-    case style of
-        NoBell -> return ()
-        VisualBell -> ringBell False
-        AudibleBell -> ringBell True
-
-movePast :: (LineState s, Term m) => String -> s -> m ()
-movePast prefix s = moveToNextLine (lineChars prefix s)
-
-withReposition :: (LineState s, Term m) => TermOps -> String -> s -> m a -> m a
-withReposition tops prefix s f = do
-    oldLayout <- ask
-    newLayout <- liftIO $ getLayout tops
-    if oldLayout == newLayout
-        then f
-        else local newLayout $ do
-                reposition oldLayout (lineChars prefix s)
-                f
 ----------
 
 {- | Reads one character of input.  Ignores non-printable characters.
@@ -305,34 +234,16 @@
                                 then return x
                                 else throwIO e
 
--- TODO: it might be possible to unify this function with getInputCmdLine,
--- maybe by calling repeatTillFinish here...
--- It shouldn't be too hard to make Commands parametrized over a return
--- value (which would be Maybe Char in this case).
--- My primary obstacle is that there's currently no way to have a
--- single character input cause a character to be printed and then
--- immediately exit without waiting for Return to be pressed.
 getInputCmdChar :: MonadException m => TermOps -> String -> InputT m (Maybe Char)
-getInputCmdChar tops prefix = runInputCmdT tops $ runTerm tops $ \getEvent -> do
-                                                drawLine prefix emptyIM
-                                                loop getEvent
-    where
-        s = emptyIM
-        loop :: Term m => m Event -> m (Maybe Char)
-        loop getEvent = do
-            event <- handle (\(e::SomeException) -> movePast prefix emptyIM >> throwIO e) getEvent
-            case event of
-                KeyInput (Key m (KeyChar c))
-                    | m /= noModifier -> loop getEvent
-                    | c == '\EOT'     -> movePast prefix s >> return Nothing
-                    | isPrint c -> do
-                            let s' = insertChar c s
-                            drawLineStateDiff prefix s s'
-                            movePast prefix s'
-                            return (Just c)
-                WindowResize -> withReposition tops prefix emptyIM $ loop getEvent
-                _ -> loop getEvent
+getInputCmdChar tops prefix = runInputCmdT tops 
+        $ runCommandLoop tops prefix acceptOneChar
 
+acceptOneChar :: Monad m => KeyCommand m InsertMode (Maybe Char)
+acceptOneChar = choiceCmd [useChar $ \c s -> change (insertChar c) s
+                                                >> return (Just c)
+                          , ctrlChar 'l' +> clearScreenCmd >|>
+                                        keyCommand acceptOneChar
+                          , ctrlChar 'd' +> failCmd]
 
 ------------
 -- Interrupt
diff --git a/System/Console/Haskeline/Backend/DumbTerm.hs b/System/Console/Haskeline/Backend/DumbTerm.hs
--- a/System/Console/Haskeline/Backend/DumbTerm.hs
+++ b/System/Console/Haskeline/Backend/DumbTerm.hs
@@ -24,9 +24,7 @@
                           MonadState Window,
                           MonadReader Handle, MonadReader Encoders)
 
-instance MonadReader Layout m => MonadReader Layout (DumbTerm m) where
-    ask = lift ask
-    local r = DumbTerm . local r . unDumbTerm
+type DumbTermM a = forall m . (MonadIO m, MonadReader Layout m) => DumbTerm m a
 
 instance MonadTrans DumbTerm where
     lift = DumbTerm . lift . lift . lift
@@ -36,19 +34,20 @@
     ch <- newChan
     posixRunTerm $ \enc h ->
                 TermOps {
-                        getLayout = tryGetLayouts (posixLayouts h),
-                        runTerm = \f -> 
-                                runPosixT enc h $ evalStateT' initWindow
-                                $ unDumbTerm
-                                $ withPosixGetEvent ch enc [] f
+                        getLayout = tryGetLayouts (posixLayouts h)
+                        , withGetEvent = withPosixGetEvent ch h enc []
+                        , runTerm = \(RunTermType f) -> 
+                                    runPosixT enc h
+                                    $ evalStateT' initWindow
+                                    $ unDumbTerm f
                         }
                                 
-instance (MonadException m, MonadLayout m) => Term (DumbTerm m) where
+instance (MonadException m, MonadReader Layout m) => Term (DumbTerm m) where
     reposition _ s = refitLine s
     drawLineDiff = drawLineDiff'
     
-    printLines = mapM_ (\s -> printText (s ++ crlf))
-    moveToNextLine = \_ -> printText crlf
+    printLines = mapM_ (printText . (++ crlf))
+    moveToNextLine _ = printText crlf
     clearLayout = clearLayoutD
     ringBell True = printText "\a"
     ringBell False = return ()
@@ -69,16 +68,16 @@
 spaces n = replicate n ' '
 
 
-clearLayoutD :: MonadLayout m => DumbTerm m ()
+clearLayoutD :: DumbTermM ()
 clearLayoutD = do
     w <- maxWidth
     printText (cr ++ spaces w ++ cr)
 
 -- Don't want to print in the last column, as that may wrap to the next line.
-maxWidth :: MonadLayout m => DumbTerm m Int
+maxWidth :: DumbTermM Int
 maxWidth = asks (\lay -> width lay - 1)
 
-drawLineDiff' :: MonadLayout m => LineChars -> LineChars -> DumbTerm m ()
+drawLineDiff' :: LineChars -> LineChars -> DumbTermM ()
 drawLineDiff' (xs1,ys1) (xs2,ys2) = do
     Window {pos=p} <- get
     w <- maxWidth
@@ -94,15 +93,15 @@
                 (_,[]) | xs1' ++ ys1 == ys2 -> -- moved left
                     printText $ backs (length xs1')
                 ([],_) | ys1 == xs2' ++ ys2 -> -- moved right
-                    printText xs2'
+                    printText (graphemesToString xs2')
                 _ -> let
                         extraLength = length xs1' + length ys1
                                     - length xs2' - length ys2
                      in printText $ backs (length xs1')
-                        ++ xs2' ++ ys2' ++ clearDeadText extraLength
+                        ++ graphemesToString (xs2' ++ ys2') ++ clearDeadText extraLength
                         ++ backs (length ys2')
 
-refitLine :: MonadLayout m => (String,String) -> DumbTerm m ()
+refitLine :: ([Grapheme],[Grapheme]) -> DumbTermM ()
 refitLine (xs,ys) = do
     w <- maxWidth
     let xs' = dropFrames w xs
@@ -110,12 +109,12 @@
     put Window {pos=p}
     let ys' = take (w - p) ys
     let k = length ys'
-    printText $ cr ++ xs' ++ ys'
+    printText $ cr ++ graphemesToString (xs' ++ ys')
         ++ spaces (w-k-p)
         ++ backs (w-p)
   where
     dropFrames w zs = case splitAt w zs of
-                        (_,"") -> zs
+                        (_,[]) -> zs
                         (_,zs') -> dropFrames w zs'
     
 clearDeadText :: Int -> String
diff --git a/System/Console/Haskeline/Backend/IConv.hsc b/System/Console/Haskeline/Backend/IConv.hsc
--- a/System/Console/Haskeline/Backend/IConv.hsc
+++ b/System/Console/Haskeline/Backend/IConv.hsc
@@ -145,8 +145,8 @@
     createAndTrim' outSize $ \outPtr ->
     with outPtr $ \outBuff ->
     with (toEnum outSize) $ \outBytesLeft -> do
-        c_iconv cd_p inBuff inBytesLeft
-                            (castPtr outBuff) outBytesLeft
+        -- ignore the return value; checking the errno is more reliable.
+        _ <- c_iconv cd_p inBuff inBytesLeft (castPtr outBuff) outBytesLeft
         outLeft <- fmap fromEnum $ peek outBytesLeft
         inLeft <- peek inBytesLeft
         errno <- if inLeft > 0
diff --git a/System/Console/Haskeline/Backend/Posix.hsc b/System/Console/Haskeline/Backend/Posix.hsc
--- a/System/Console/Haskeline/Backend/Posix.hsc
+++ b/System/Console/Haskeline/Backend/Posix.hsc
@@ -16,7 +16,6 @@
 import System.Posix.Terminal hiding (Interrupt)
 import Control.Monad
 import Control.Concurrent hiding (throwTo)
-import Control.Concurrent.Chan
 import Data.Maybe (catMaybes)
 import System.Posix.Signals.Exts
 import System.Posix.IO(stdInput)
@@ -33,8 +32,18 @@
 
 import System.Console.Haskeline.Backend.IConv
 
-import GHC.IOBase (haFD,FD)
+#if __GLASGOW_HASKELL__ >= 611
+import GHC.IO.FD (fdFD)
+import Data.Dynamic (cast)
+import System.IO.Error
+import GHC.IO.Exception
+import GHC.IO.Handle.Types hiding (getState)
+import GHC.IO.Handle.Internals
+import System.Posix.Internals (FD)
+#else
+import GHC.IOBase(haFD,FD)
 import GHC.Handle (withHandle_)
+#endif
 
 #ifdef USE_TERMIOS_H
 #include <termios.h>
@@ -60,7 +69,17 @@
                     else return Nothing
 
 unsafeHandleToFD :: Handle -> IO FD
+#if __GLASGOW_HASKELL__ >= 611
+unsafeHandleToFD h =
+  withHandle_ "unsafeHandleToFd" h $ \Handle__{haDevice=dev} -> do
+  case cast dev of
+    Nothing -> ioError (ioeSetErrorString (mkIOError IllegalOperation
+                                           "unsafeHandleToFd" (Just h) Nothing)
+                        "handle is not a file descriptor")
+    Just fd -> return (fdFD fd)
+#else
 unsafeHandleToFD h = withHandle_ "unsafeHandleToFd" h (return . haFD)
+#endif
 
 envLayout :: IO (Maybe Layout)
 envLayout = handle (\(_::IOException) -> return Nothing) $ do
@@ -163,10 +182,11 @@
 
 -----------------------------
 
-withPosixGetEvent :: (MonadTrans t, MonadIO m, MonadException (t m), MonadReader Prefs m) 
-                        => Chan Event -> Encoders -> [(String,Key)] -> (t m Event -> t m a) -> t m a
-withPosixGetEvent eventChan enc termKeys f = do
-    baseMap <- lift $ getKeySequences termKeys
+withPosixGetEvent :: (MonadException m, MonadReader Prefs m) 
+        => Chan Event -> Handle -> Encoders -> [(String,Key)]
+                -> (m Event -> m a) -> m a
+withPosixGetEvent eventChan h enc termKeys f = wrapTerminalOps h $ do
+    baseMap <- getKeySequences termKeys
     withWindowHandler eventChan
         $ f $ liftIO $ getEvent enc baseMap eventChan
 
@@ -194,12 +214,23 @@
         -- Read at least one character of input, and more if available.
         -- In particular, the characters making up a control sequence will all
         -- be available at once, so we can process them together with lexKeys.
-        threadWaitRead stdInput -- hWaitForInput doesn't work with -threaded on
-                                -- ghc < 6.10 (#2363 in ghc's trac)
+        blockUntilInput
         bs <- B.hGetNonBlocking stdin bufferSize
         cs <- convert (localeToUnicode enc) bs
         return $ map KeyInput $ lexKeys baseMap cs
 
+-- Different versions of ghc work better using different functions.
+blockUntilInput :: IO ()
+#if __GLASGOW_HASKELL__ >= 611
+-- threadWaitRead doesn't work with the new ghc IO library,
+-- because it keeps a buffer even when NoBuffering is set.
+blockUntilInput = hWaitForInput stdin (-1) >> return ()
+#else
+-- hWaitForInput doesn't work with -threaded on ghc < 6.10
+-- (#2363 in ghc's trac)
+blockUntilInput = threadWaitRead stdInput
+#endif
+
 -- try to convert to the locale encoding using iconv.
 -- if the buffer has an incomplete shift sequence,
 -- read another byte of input and try again.
@@ -231,7 +262,9 @@
     inIsTerm <- hIsTerminalDevice stdin
     if inIsTerm
         then handle (\(_::IOException) -> return Nothing) $ do
-                h <- openFile "/dev/tty" WriteMode
+            -- NB: we open the tty as a binary file since otherwise the terminfo
+            -- backend, which writes output as Chars, would double-encode on ghc-6.12.
+                h <- openBinaryFile "/dev/tty" WriteMode
                 return (Just h)
         else return Nothing
 
@@ -246,7 +279,7 @@
         Just h -> return fileRT {
                     closeTerm = closeTerm fileRT >> hClose h,
                     -- NOTE: could also alloc Encoders once for each call to wrapRunTerm
-                    termOps = Just (wrapRunTerm (wrapTerminalOps h) (tOps encoders h))
+                    termOps = Just $ tOps encoders h
                 }
 
 type PosixT m = ReaderT Encoders (ReaderT Handle m)
@@ -289,10 +322,6 @@
     bracketSet (hGetBuffering stdin) (hSetBuffering stdin) NoBuffering
     . bracketSet (hGetBuffering outH) (hSetBuffering outH) LineBuffering
     . bracketSet (hGetEcho stdin) (hSetEcho stdin) False
-
-wrapRunTerm :: (forall m a . MonadException m => m a -> m a) -> TermOps -> TermOps
-wrapRunTerm wrap tops = tops {runTerm = \getE -> wrap (runTerm tops getE)
-                                }
 
 bracketSet :: (Eq a, MonadException m) => IO a -> (a -> IO ()) -> a -> m b -> m b
 bracketSet getState set newState f = bracket (liftIO getState)
diff --git a/System/Console/Haskeline/Backend/Terminfo.hs b/System/Console/Haskeline/Backend/Terminfo.hs
--- a/System/Console/Haskeline/Backend/Terminfo.hs
+++ b/System/Console/Haskeline/Backend/Terminfo.hs
@@ -10,7 +10,7 @@
 import System.IO
 import qualified Control.Exception.Extensible as Exception
 import qualified Data.ByteString.Char8 as B
-import Data.Maybe (fromMaybe, catMaybes)
+import Data.Maybe (fromMaybe, mapMaybe)
 import Control.Concurrent.Chan
 
 import System.Console.Haskeline.Monads as Monads
@@ -66,12 +66,12 @@
 type TermAction = Actions -> TermOutput
     
 left,right,up :: Int -> TermAction
-left n = flip leftA n
-right n = flip rightA n
-up n = flip upA n
+left = flip leftA
+right = flip rightA
+up = flip upA
 
 clearAll :: LinesAffected -> TermAction
-clearAll la = flip clearAllA la
+clearAll = flip clearAllA
 
 --------
 
@@ -99,9 +99,7 @@
               MonadReader Actions, MonadReader Terminal, MonadState TermPos,
               MonadReader Handle, MonadReader Encoders)
 
-instance MonadReader Layout m => MonadReader Layout (Draw m) where
-    ask = lift ask
-    local r = Draw . local r . unDraw
+type DrawM a = forall m . (MonadReader Layout m, MonadIO m) => Draw m a
 
 instance MonadTrans Draw where
     lift = Draw . lift . lift . lift . lift . lift
@@ -119,15 +117,16 @@
             Just actions -> fmap Just $ posixRunTerm $ \enc h ->
                 TermOps {
                     getLayout = tryGetLayouts (posixLayouts h
-                                                ++ [tinfoLayout term]),
-                    runTerm = \f ->
+                                                ++ [tinfoLayout term])
+                    , withGetEvent = wrapKeypad h term
+                                        . withPosixGetEvent ch h enc
+                                            (terminfoKeys term)
+                    , runTerm = \(RunTermType f) -> 
                              runPosixT enc h
                               $ evalStateT' initTermPos
                               $ runReaderT' term
                               $ runReaderT' actions
-                              $ unDraw
-                              $ wrapKeypad h term
-                              $ withPosixGetEvent ch enc (terminfoKeys term) f
+                              $ unDraw f
                     }
 
 -- If the keypad on/off capabilities are defined, wrap the computation with them.
@@ -135,8 +134,8 @@
 wrapKeypad h term f = (maybeOutput keypadOn >> f)
                             `finally` maybeOutput keypadOff
   where
-    maybeOutput cap = liftIO $ hRunTermOutput h term $
-                            fromMaybe mempty (getCapability term cap)
+    maybeOutput = liftIO . hRunTermOutput h term .
+                            fromMaybe mempty . getCapability term
 
 tinfoLayout :: Terminal -> IO (Maybe Layout)
 tinfoLayout term = return $ getCapability term $ do
@@ -145,7 +144,7 @@
                         return Layout {height=r,width=c}
 
 terminfoKeys :: Terminal -> [(String,Key)]
-terminfoKeys term = catMaybes $ map getSequence keyCapabilities
+terminfoKeys term = mapMaybe getSequence keyCapabilities
     where
         getSequence (cap,x) = do
                             keys <- getCapability term cap
@@ -159,6 +158,8 @@
                 ,(keyDeleteChar, simpleKey Delete)
                 ,(keyHome,       simpleKey Home)
                 ,(keyEnd,        simpleKey End)
+                ,(keyPageDown,   simpleKey PageDown)
+                ,(keyPageUp,     simpleKey PageUp)
                 ]
 
     
@@ -171,7 +172,7 @@
 
 
 
-changeRight, changeLeft :: MonadLayout m => Int -> Draw m ()
+changeRight, changeLeft :: Int -> DrawM ()
 changeRight n = do
     w <- asks width
     TermPos {termRow=r,termCol=c} <- get
@@ -201,30 +202,30 @@
                 output $ cr <#> up linesUp <#> right newCol
                 
 -- TODO: I think if we wrap this all up in one call to output, it'll be faster...
-printText :: MonadLayout m => String -> Draw m ()
-printText "" = return ()
+printText :: [Grapheme] -> DrawM ()
+printText [] = return ()
 printText xs = fillLine xs >>= printText
 
 -- Draws as much of the string as possible in the line, and returns the rest.
 -- If we fill up the line completely, wrap to the next row.
-fillLine :: MonadLayout m => String -> Draw m String
+fillLine :: [Grapheme] -> DrawM [Grapheme]
 fillLine str = do
     w <- asks width
     TermPos {termRow=r,termCol=c} <- get
     let roomLeft = w - c
     if length str < roomLeft
         then do
-                posixEncode str >>= output . text
+                posixEncode (graphemesToString str) >>= output . text
                 put TermPos{termRow=r, termCol=c+length str}
-                return ""
+                return []
         else do
                 let (thisLine,rest) = splitAt roomLeft str
-                bstr <- posixEncode thisLine
+                bstr <- posixEncode (graphemesToString thisLine)
                 output (text bstr <#> wrapLine)
                 put TermPos {termRow=r+1,termCol=0}
                 return rest
 
-drawLineDiffT :: MonadLayout m => LineChars -> LineChars -> Draw m ()
+drawLineDiffT :: LineChars -> LineChars -> DrawM ()
 drawLineDiffT (xs1,ys1) (xs2,ys2) = case matchInit xs1 xs2 of
     ([],[])     | ys1 == ys2            -> return ()
     (xs1',[])   | xs1' ++ ys1 == ys2    -> changeLeft (length xs1')
@@ -242,9 +243,9 @@
     | otherwise = 1 + div (c+n) w
 
 lsLinesLeft :: Layout -> TermPos -> LineChars -> Int
-lsLinesLeft layout pos s = linesLeft layout pos (lengthToEnd s)
+lsLinesLeft layout pos = linesLeft layout pos . lengthToEnd
 
-clearDeadText :: MonadLayout m => Int -> Draw m ()
+clearDeadText :: Int -> DrawM ()
 clearDeadText n
     | n <= 0    = return ()
     | otherwise = do
@@ -258,30 +259,29 @@
                     , up (numLinesToClear - 1)
                     , right (termCol pos)]
 
-clearLayoutT :: MonadLayout m => Draw m ()
+clearLayoutT :: DrawM ()
 clearLayoutT = do
     h <- asks height
     output (clearAll h)
     put initTermPos
 
-moveToNextLineT :: MonadLayout m => LineChars -> Draw m ()
+moveToNextLineT :: LineChars -> DrawM ()
 moveToNextLineT s = do
     pos <- get
     layout <- ask
     output $ mreplicate (lsLinesLeft layout pos s) nl
     put initTermPos
 
-repositionT :: (MonadLayout m, MonadException m) =>
-                Layout -> LineChars -> Draw m ()
+repositionT :: Layout -> LineChars -> DrawM ()
 repositionT oldLayout s = do
     oldPos <- get
     let l = lsLinesLeft oldLayout oldPos s - 1
     output $ cr <#> mreplicate l nl
             <#> mreplicate (l + termRow oldPos) (clearToLineEnd <#> up 1)
     put initTermPos
-    drawLineDiffT ("","") s
+    drawLineDiffT ([],[]) s
 
-instance (MonadException m, MonadLayout m) => Term (Draw m) where
+instance (MonadException m, MonadReader Layout m) => Term (Draw m) where
     drawLineDiff = drawLineDiffT
     reposition = repositionT
     
diff --git a/System/Console/Haskeline/Backend/Win32.hsc b/System/Console/Haskeline/Backend/Win32.hsc
--- a/System/Console/Haskeline/Backend/Win32.hsc
+++ b/System/Console/Haskeline/Backend/Win32.hsc
@@ -10,8 +10,6 @@
 import Graphics.Win32.Misc(getStdHandle, sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE)
 import Data.List(intercalate)
 import Control.Concurrent hiding (throwTo)
-import Control.Concurrent.Chan
-import Data.Bits
 import Data.Char(isPrint)
 import Data.Maybe(mapMaybe)
 import Control.Monad
@@ -93,6 +91,8 @@
 keyFromCode (#const VK_DELETE) = Just Delete
 keyFromCode (#const VK_HOME) = Just Home
 keyFromCode (#const VK_END) = Just End
+keyFromCode (#const VK_PRIOR) = Just PageUp
+keyFromCode (#const VK_NEXT) = Just PageDown
 -- The Windows console will return '\r' when return is pressed.
 keyFromCode (#const VK_RETURN) = Just (KeyChar '\n')
 -- TODO: KillLine?
@@ -232,13 +232,11 @@
 newtype Draw m a = Draw {runDraw :: ReaderT HANDLE m a}
     deriving (Monad,MonadIO,MonadException, MonadReader HANDLE)
 
+type DrawM a = (MonadIO m, MonadReader Layout m) => Draw m ()
+
 instance MonadTrans Draw where
     lift = Draw . lift
 
-instance MonadReader Layout m => MonadReader Layout (Draw m) where
-    ask = lift ask
-    local r = Draw . local r . runDraw
-
 getPos :: MonadIO m => Draw m Coord
 getPos = ask >>= liftIO . getPosition
     
@@ -252,12 +250,12 @@
     h <- ask
     liftIO (writeConsole h txt)
     
-printAfter :: MonadLayout m => String -> Draw m ()
+printAfter :: String -> DrawM ()
 printAfter str = do
     printText str
     movePos $ negate $ length str
     
-drawLineDiffWin :: MonadLayout m => LineChars -> LineChars -> Draw m ()
+drawLineDiffWin :: LineChars -> LineChars -> DrawM ()
 drawLineDiffWin (xs1,ys1) (xs2,ys2) = case matchInit xs1 xs2 of
     ([],[])     | ys1 == ys2            -> return ()
     (xs1',[])   | xs1' ++ ys1 == ys2    -> movePos $ negate $ length xs1'
@@ -266,10 +264,10 @@
         movePos (negate $ length xs1')
         let m = length xs1' + length ys1 - (length xs2' + length ys2)
         let deadText = replicate m ' '
-        printText xs2'
-        printAfter (ys2 ++ deadText)
+        printText (graphemesToString xs2')
+        printAfter (graphemesToString ys2 ++ deadText)
 
-movePos :: MonadLayout m => Int -> Draw m ()
+movePos :: Int -> DrawM ()
 movePos n = do
     Coord {coordX = x, coordY = y} <- getPos
     w <- asks width
@@ -279,7 +277,7 @@
 crlf :: String
 crlf = "\r\n"
 
-instance (MonadException m, MonadLayout m) => Term (Draw m) where
+instance (MonadException m, MonadReader Layout m) => Term (Draw m) where
     drawLineDiff = drawLineDiffWin
     -- TODO now that we capture resize events.
     -- first, looks like the cursor stays on the same line but jumps
@@ -317,14 +315,17 @@
                         return fileRT {
                             wrapInterrupt = withWindowMode . withCtrlCHandler,
                             termOps = Just TermOps {
-                                            getLayout = getBufferSize h,
-                                            runTerm = consoleRunTerm h ch},
+                                getLayout = getBufferSize h
+                                , withGetEvent = win32WithEvent ch
+                                , runTerm = \(RunTermType f) ->
+                                        runReaderT' h $ runDraw f
+                                },
                             closeTerm = closeHandle h}
 
-consoleRunTerm :: HANDLE -> Chan Event -> RunTermType
-consoleRunTerm conOut eventChan f = do
+win32WithEvent :: MonadException m => Chan Event -> (m Event -> m a) -> m a
+win32WithEvent eventChan f = do
     inH <- liftIO $ getStdHandle sTD_INPUT_HANDLE
-    runReaderT' conOut $ runDraw $ f $ liftIO $ getEvent inH eventChan
+    f $ liftIO $ getEvent inH eventChan
 
 -- stdin is not a terminal, but we still need to check the right way to output unicode to stdout.
 fileRunTerm :: IO RunTerm
@@ -366,7 +367,9 @@
 withCtrlCHandler f = bracket (liftIO $ do
                                     tid <- myThreadId
                                     fp <- wrapHandler (handler tid)
-                                    c_SetConsoleCtrlHandler fp True
+                                -- don't fail if we can't set the ctrl-c handler
+                                -- for example, we might not be attached to a console?
+                                    _ <- c_SetConsoleCtrlHandler fp True
                                     return fp)
                                 (\fp -> liftIO $ c_SetConsoleCtrlHandler fp False)
                                 (const f)
diff --git a/System/Console/Haskeline/Command.hs b/System/Console/Haskeline/Command.hs
--- a/System/Console/Haskeline/Command.hs
+++ b/System/Console/Haskeline/Command.hs
@@ -1,152 +1,158 @@
 module System.Console.Haskeline.Command(
                         -- * Commands
                         Effect(..),
-                        KeyMap(), 
-                        lookupKM,
-                        KeyAction(..),
-                        CmdAction(..),
-                        (>=>),
-                        Command(),
-                        runCommand,
-                        continue,
+                        KeyMap(..), 
+                        CmdM(..),
+                        Command,
+                        KeyCommand,
+                        KeyConsumed(..),
+                        withoutConsuming,
+                        keyCommand,
                         (>|>),
                         (>+>),
-                        acceptKey,
-                        acceptKeyM,
-                        acceptKeyOrFail,
-                        loopUntil,
                         try,
+                        effect,
+                        clearScreenCmd,
                         finish,
                         failCmd,
                         simpleCommand,
                         charCommand,
+                        setState,
                         change,
                         changeFromChar,
-                        changeWithoutKey,
-                        clearScreenCmd,
                         (+>),
+                        useChar,
                         choiceCmd,
-                        withState
+                        keyChoiceCmd,
+                        keyChoiceCmdM,
+                        doBefore
                         ) where
 
 import Data.Char(isPrint)
-import Control.Monad(mplus)
+import Control.Monad(mplus, liftM)
+import Control.Monad.Trans
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.Key
 
+data Effect = LineChange (String -> LineChars)
+              | PrintLines [String]
+              | ClearScreen
+              | RingBell
 
-data Effect s = Change {effectState :: s} 
-              | PrintLines {linesToPrint :: [String], effectState :: s}
-              | Redraw {shouldClearScreen :: Bool, effectState :: s}
-              | RingBell {effectState :: s}
+lineChange :: LineState s => s -> Effect
+lineChange = LineChange . flip lineChars
 
-newtype KeyMap m s = KeyMap {lookupKM :: Key -> Maybe 
-            (s -> Either (Maybe String) (m (KeyAction m)))}
+data KeyMap a = KeyMap {lookupKM :: Key -> Maybe (KeyConsumed a)}
 
-useKey :: Key -> (s -> Either (Maybe String) (m (KeyAction m))) -> KeyMap m s
-useKey k f = KeyMap $ \k' -> if k==k' then Just f else Nothing
+data KeyConsumed a = NotConsumed a | Consumed a
 
-data KeyAction m = forall t . LineState t => KeyAction (Effect t) (KeyMap m t)
+instance Functor KeyMap where
+    fmap f km = KeyMap $ fmap (fmap f) . lookupKM km
 
-nullKM :: KeyMap m s
-nullKM = KeyMap $ const Nothing
+instance Functor KeyConsumed where
+    fmap f (NotConsumed x) = NotConsumed (f x)
+    fmap f (Consumed x) = Consumed (f x)
 
-orKM :: KeyMap m s -> KeyMap m s -> KeyMap m s
-orKM (KeyMap m) (KeyMap n) = KeyMap $ \k -> m k `mplus` n k
 
-choiceKM :: [KeyMap m s] -> KeyMap m s
-choiceKM = foldl orKM nullKM
+data CmdM m a   = GetKey (KeyMap (CmdM m a))
+                | DoEffect Effect (CmdM m a)
+                | CmdM (m (CmdM m a))
+                | Result a
 
-newtype Command m s t = Command (KeyMap m t -> KeyMap m s)
+type Command m s t = s -> CmdM m t
 
-runCommand :: Command m s s -> KeyMap m s
-runCommand (Command f) = let m = f m in m
+instance Monad m => Monad (CmdM m) where
+    return = Result
 
-continue :: Command m s s
-continue = Command id
+    GetKey km >>= g = GetKey $ fmap (>>= g) km
+    DoEffect e f >>= g = DoEffect e (f >>= g)
+    CmdM f >>= g = CmdM $ liftM (>>= g) f
+    Result x >>= g = g x
 
-infixl 6 >|>
-(>|>) :: Command m s t -> Command m t u -> Command m s u
-Command f >|> Command g = Command (f . g)
+type KeyCommand m s t = KeyMap (Command m s t)
 
-infixl 6 >+>
-(>+>) :: (Monad m, LineState s) => Key -> Command m s t -> Command m s t
-k >+> f = k +> change id >|> f
+instance MonadTrans CmdM where
+    lift m = CmdM $ do
+        x <- m
+        return $ Result x
 
-data CmdAction m s = forall t . LineState t => CmdAction (Effect t) (Command m t s)
+keyCommand :: KeyCommand m s t -> Command m s t
+keyCommand km = \s -> GetKey $ fmap ($ s) km
 
-(>=>) :: LineState t => Effect t -> Command m t s -> CmdAction m s
-(>=>) = CmdAction
+useKey :: Key -> a -> KeyMap a
+useKey k x = KeyMap $ \k' -> if k==k' then Just (Consumed x) else Nothing
 
-acceptKey :: (Monad m) => (s -> CmdAction m t) -> Key -> Command m s t
-acceptKey f = acceptKeyFull (Just . return . f)
+-- TODO: could just be a monadic action that returns a Char.
+useChar :: (Char -> Command m s t) -> KeyCommand m s t
+useChar act = KeyMap $ \k -> case k of
+                    Key m (KeyChar c) | isPrint c && m==noModifier
+                        -> Just $ Consumed (act c)
+                    _ -> Nothing
 
-acceptKeyM :: Monad m => (s -> m (CmdAction m t)) -> Key -> Command m s t
-acceptKeyM f = acceptKeyFull (Just . f)
+withoutConsuming :: Command m s t -> KeyCommand m s t
+withoutConsuming = KeyMap . const . Just . NotConsumed
 
-acceptKeyFull :: Monad m => (s -> Maybe (m (CmdAction m t)))
-                            -> Key -> Command m s t
-acceptKeyFull f k = Command $ \next -> useKey k $ \s -> case f s of
-                Nothing -> Left Nothing
-                Just act -> Right $ do
-                    CmdAction effect (Command g) <- act
-                    return (KeyAction effect (g next))
+choiceCmd :: [KeyMap a] -> KeyMap a
+choiceCmd = foldl orKM nullKM
+    where
+        nullKM = KeyMap $ const Nothing
+        orKM (KeyMap f) (KeyMap g) = KeyMap $ \k -> f k `mplus` g k
 
-acceptKeyOrFail :: Monad m => (s -> Maybe (CmdAction m t)) -> Key
-            -> Command m s t
-acceptKeyOrFail f = acceptKeyFull (fmap return . f)
-                         
-loopUntil :: Command m s s -> Command m s t -> Command m s t
-loopUntil f g = choiceCmd [g, f >|> loopUntil f g]
+keyChoiceCmd :: [KeyCommand m s t] -> Command m s t
+keyChoiceCmd = keyCommand . choiceCmd
 
-try :: Command m s s -> Command m s s
-try (Command f) = Command $ \next -> f next `orKM` next
+keyChoiceCmdM :: [KeyMap (CmdM m a)] -> CmdM m a
+keyChoiceCmdM = GetKey . choiceCmd
 
-finish :: forall s m t . (Result s, Monad m) => Key -> Command m s t
-finish k = Command $ \_-> useKey k (Left . Just . toResult)
+infixr 6 >|>
+(>|>) :: Monad m => Command m s t -> Command m t u -> Command m s u
+f >|> g = \x -> f x >>= g
 
-failCmd :: forall s m t . (LineState s, Monad m) => Key -> Command m s t
-failCmd k = Command $ \_-> useKey k (const $ Left Nothing)
+infixr 6 >+>
+(>+>) :: Monad m => KeyCommand m s t -> Command m t u -> KeyCommand m s u
+km >+> g = fmap (>|> g) km
 
-simpleCommand :: (LineState t, Monad m) => (s -> m (Effect t)) 
-                    -> Key -> Command m s t
-simpleCommand f = acceptKeyM $ \s -> do
-            act <- f s
-            return (act >=> continue)
+-- attempt to run the command (predicated on getting a valid key); but if it fails, just keep
+-- going.
+try :: Monad m => KeyCommand m s s -> Command m s s
+try f = keyChoiceCmd [f,withoutConsuming return]
 
-charCommand :: (LineState t, Monad m) => (Char -> s -> m (Effect t))
-                    -> Command m s t
-charCommand f = Command $ \next -> KeyMap $ \k -> case k of
-                    Key m (KeyChar c) | isPrint c && m==noModifier-> Just $ \s -> Right $ do
-                                    effect <- f c s
-                                    return (KeyAction effect next)
-                    _ -> Nothing
-                    
+infixr 6 +>
+(+>) :: Key -> a -> KeyMap a
+(+>) = useKey
 
-change :: (LineState t, Monad m) => (s -> t) -> Key -> Command m s t
-change f = simpleCommand (return . Change . f)
+finish :: (Monad m, Result s) => Command m s (Maybe String)
+finish = return . Just . toResult
 
-changeFromChar :: (Monad m, LineState t) => (Char -> s -> t) -> Command m s t
-changeFromChar f = charCommand (\c s -> return $ Change (f c s))
+failCmd :: Monad m => Command m s (Maybe a)
+failCmd _ = return Nothing
 
-changeWithoutKey :: (s -> t) -> Command m s t
-changeWithoutKey f = Command $ \(KeyMap next) -> KeyMap $ fmap (. f) . next
+effect :: Effect -> CmdM m ()
+effect e = DoEffect e $ Result ()
 
-clearScreenCmd :: (LineState s, Monad m) => Key -> Command m s s
-clearScreenCmd k = k +> simpleCommand (\s -> return (Redraw True s))
+clearScreenCmd :: Command m s s
+clearScreenCmd = DoEffect ClearScreen . Result
 
-infixl 7 +>
-(+>) :: Key -> (Key -> a) -> a 
-k +> f = f k
+simpleCommand :: (LineState s, Monad m) => (s -> m (Either Effect s))
+        -> Command m s s
+simpleCommand f = \s -> do
+    et <- lift (f s)
+    case et of
+        Left e -> effect e >> return s
+        Right t -> setState t
 
-choiceCmd :: [Command m s t] -> Command m s t
-choiceCmd cmds = Command $ \next -> 
-    choiceKM $ map (\(Command f) -> f next) cmds
+charCommand :: (LineState s, Monad m) => (Char -> s -> m (Either Effect s))
+                    -> KeyCommand m s s
+charCommand f = useChar $ simpleCommand . f
 
-withState :: Monad m => (s -> m a) -> Command m s t -> Command m s t
-withState act (Command thisCmd) = Command $ \next -> KeyMap $ \k -> 
-    case lookupKM (thisCmd next) k of
-        Nothing -> Nothing
-        Just f -> Just $ \s -> case f s of
-            Left r -> Left r
-            Right effect -> Right $ act s >> effect
+setState :: (Monad m, LineState s) => Command m s s
+setState s = effect (lineChange s) >> return s
+
+change :: (LineState t, Monad m) => (s -> t) -> Command m s t
+change = (setState .)
+
+changeFromChar :: (LineState t, Monad m) => (Char -> s -> t) -> KeyCommand m s t
+changeFromChar f = useChar $ change . f
+
+doBefore :: Monad m => Command m s t -> KeyCommand m t u -> KeyCommand m s u
+doBefore cmd = fmap (cmd >|>)
diff --git a/System/Console/Haskeline/Command/Completion.hs b/System/Console/Haskeline/Command/Completion.hs
--- a/System/Console/Haskeline/Command/Completion.hs
+++ b/System/Console/Haskeline/Command/Completion.hs
@@ -6,107 +6,115 @@
                             ) where
 
 import System.Console.Haskeline.Command
+import System.Console.Haskeline.Command.Undo
 import System.Console.Haskeline.Key
-import System.Console.Haskeline.Term(Layout(..))
+import System.Console.Haskeline.Term (Layout(..), CommandMonad(..))
 import System.Console.Haskeline.LineState
-import System.Console.Haskeline.InputT
 import System.Console.Haskeline.Prefs
 import System.Console.Haskeline.Completion
 import System.Console.Haskeline.Monads
 
 import Data.List(transpose, unfoldr)
 
-fullReplacement :: Completion -> String
-fullReplacement c   | isFinished c  = replacement c ++ " "
-                    | otherwise     = replacement c
+useCompletion :: InsertMode -> Completion -> InsertMode
+useCompletion im c = insertString r im
+    where r | isFinished c = replacement c ++ " "
+            | otherwise = replacement c
 
-makeCompletion :: Monad m => InsertMode -> InputCmdT m (InsertMode, [Completion])
-makeCompletion (IMode xs ys) = do
-    f <- asks complete
-    (rest,completions) <- liftCmdT (f (xs, ys))
-    return (IMode rest ys,completions)
+askIMCompletions :: CommandMonad m => 
+            Command m InsertMode (InsertMode, [Completion])
+askIMCompletions (IMode xs ys) = do
+    (rest, completions) <- runCompletion (withRev graphemesToString xs,
+                                            graphemesToString ys)
+    return (IMode (withRev stringToGraphemes rest) ys, completions)
+  where
+    withRev f = reverse . f . reverse
 
 -- | Create a 'Command' for word completion.
-completionCmd :: Monad m => Key -> Command (InputCmdT m) InsertMode InsertMode
-completionCmd k = k +> acceptKeyM (\s -> do
+completionCmd :: (MonadState Undo m, CommandMonad m)
+                => Key -> KeyCommand m InsertMode InsertMode
+completionCmd k = k +> saveForUndo >|> \oldIM -> do
+    (rest,cs) <- askIMCompletions oldIM
+    case cs of
+        [] -> effect RingBell >> return oldIM
+        [c] -> setState $ useCompletion rest c
+        _ -> presentCompletions k oldIM rest cs
+
+presentCompletions :: (MonadReader Prefs m, MonadReader Layout m)
+        => Key -> InsertMode -> InsertMode
+            -> [Completion] -> CmdM m InsertMode
+presentCompletions k oldIM rest cs = do
     prefs <- ask
-    (rest,completions) <- makeCompletion s
     case completionType prefs of
-        MenuCompletion -> return $ menuCompletion k s
-                        $ map (\c -> insertString (fullReplacement c) rest) completions
-        ListCompletion -> 
-                pagingCompletion prefs s rest completions k)
+        MenuCompletion -> menuCompletion k (map (useCompletion rest) cs) oldIM
+        ListCompletion -> do
+            withPartial <- setState $ makePartialCompletion rest cs
+            if withPartial /= oldIM
+                then return withPartial
+                else pagingCompletion k prefs cs withPartial
 
-pagingCompletion :: Monad m => Prefs
-                -> InsertMode -> InsertMode -> [Completion] 
-                -> Key -> InputCmdT m (CmdAction (InputCmdT m) InsertMode)
-pagingCompletion _ oldIM _ [] _ = return $ RingBell oldIM >=> continue
-pagingCompletion _ _ im [newWord] _ 
-        = return $ (Change $ insertString (fullReplacement newWord) im) >=> continue
-pagingCompletion prefs oldIM im completions k
-    | oldIM /= withPartial = return $ Change withPartial >=> continue
-    | otherwise = do
-        layout <- ask
-        let wordLines = makeLines (map display completions) layout
-        printingCmd <- if completionPaging prefs
-                        then printPage wordLines moreMessage
-                        else return $ printAll wordLines withPartial
-        let pageAction = askFirst (completionPromptLimit prefs) (length completions) 
-                            withPartial printingCmd
-        if listCompletionsImmediately prefs
-            then return pageAction
-            else return $ RingBell withPartial >=> 
-                        try (k +> acceptKey (const pageAction))
+menuCompletion :: Monad m => Key -> [InsertMode] -> Command m InsertMode InsertMode
+menuCompletion k = loop
+    where
+        loop [] = setState
+        loop (c:cs) = change (const c) >|> try (k +> loop cs)
+
+makePartialCompletion :: InsertMode -> [Completion] -> InsertMode
+makePartialCompletion im completions = insertString partial im
   where
-    withPartial = insertString partial im
     partial = foldl1 commonPrefix (map replacement completions)
     commonPrefix (c:cs) (d:ds) | c == d = c : commonPrefix cs ds
     commonPrefix _ _ = ""
-    moreMessage = Message withPartial "----More----"
 
-askFirst :: Monad m => Maybe Int -> Int -> InsertMode
-            -> CmdAction (InputCmdT m) InsertMode
-            -> CmdAction (InputCmdT m) InsertMode
-askFirst mlimit numCompletions im printingCmd = case mlimit of
-    Just limit | limit < numCompletions -> 
-        Change (Message im ("Display all " ++ show numCompletions
-                            ++ " possibilities? (y or n)"))
-                    >=> choiceCmd [
-                            simpleChar 'y' +> acceptKey (const printingCmd)
-                            , simpleChar 'n' +> change messageState
-                            ]
-    _ -> printingCmd
-
-printOneLine :: Monad m => [String] -> Message InsertMode -> CmdAction (InputCmdT m) InsertMode
-printOneLine (w:ws) im | not (null ws) =
-            PrintLines [w] im >=> pagingCommands ws
-printOneLine _ im = Change (messageState im) >=> continue
-
-printPage :: Monad m => [String] -> Message InsertMode
-                    -> InputCmdT m (CmdAction (InputCmdT m) InsertMode)
-printPage ws im = do
-    layout <- ask
-    return $ case splitAt (height layout - 1) ws of
-        (_,[]) -> PrintLines ws (messageState im) >=> continue
-        (zs,rest) -> PrintLines zs im
-                    >=> pagingCommands rest
-
-
--- TODO: move testing of nullity into here
-pagingCommands :: Monad m => [String] -> Command (InputCmdT m) (Message InsertMode) InsertMode
-pagingCommands ws = choiceCmd [
-                            simpleChar ' ' +> acceptKeyM (printPage ws)
-                            ,simpleChar 'q' +> change messageState
-                            ,simpleChar '\n' +> acceptKey (printOneLine ws)
-                            ,simpleKey DownKey +> acceptKey (printOneLine ws)
-                            ]
+pagingCompletion :: MonadReader Layout m => Key -> Prefs
+                -> [Completion] -> Command m InsertMode InsertMode
+pagingCompletion k prefs completions = \im -> do
+        ls <- asks $ makeLines (map display completions)
+        let pageAction = do
+            askFirst prefs (length completions) $ 
+                            if completionPaging prefs
+                                then printPage ls
+                                else effect (PrintLines ls)
+            setState im
+        if listCompletionsImmediately prefs
+            then pageAction
+            else effect RingBell >> try (k +> const pageAction) im
 
+askFirst :: Monad m => Prefs -> Int -> CmdM m ()
+            -> CmdM m ()
+askFirst prefs n cmd
+    | maybe False (< n) (completionPromptLimit prefs) = do
+        _ <- setState (Message () $ "Display all " ++ show n
+                                 ++ " possibilities? (y or n)")
+        keyChoiceCmdM [
+            simpleChar 'y' +> cmd
+            , simpleChar 'n' +> return ()
+            ]
+    | otherwise = cmd
 
-printAll :: Monad m => [String] -> InsertMode 
-            -> CmdAction (InputCmdT m) InsertMode
-printAll ws im = PrintLines ws im >=> continue
+pageCompletions :: MonadReader Layout m => [String] -> CmdM m ()
+pageCompletions [] = return ()
+pageCompletions wws@(w:ws) = do
+    _ <- setState $ Message () "----More----"
+    keyChoiceCmdM [
+        simpleChar '\n' +> oneLine
+        , simpleKey DownKey +> oneLine
+        , simpleChar 'q' +> return ()
+        , simpleChar ' ' +> (clearMessage >> printPage wws)
+        ]
+  where
+    oneLine = clearMessage >> effect (PrintLines [w]) >> pageCompletions ws
+    clearMessage = effect $ LineChange $ const ([],[])
 
+printPage :: MonadReader Layout m => [String] -> CmdM m ()
+printPage ls = do
+    layout <- ask
+    let (ps,rest) = splitAt (height layout - 1) ls
+    effect $ PrintLines ps
+    pageCompletions rest
 
+-----------------------------------------------
+-- Splitting the list of completions into lines for paging.
 makeLines :: [String] -> Layout -> [String]
 makeLines ws layout = let
     minColPad = 2
@@ -114,7 +122,7 @@
     maxLength = min printWidth (maximum (map length ws) + minColPad)
     numCols = printWidth `div` maxLength
     ls = if maxLength >= printWidth
-                    then map (\x -> [x]) ws
+                    then map (: []) ws
                     else splitIntoGroups numCols ws
     in map (padWords maxLength) ls
 
@@ -124,8 +132,14 @@
 padWords :: Int -> [String] -> String
 padWords _ [x] = x
 padWords _ [] = ""
-padWords len (x:xs) = x ++ replicate (len - length x) ' '
+padWords len (x:xs) = x ++ replicate (len - glength x) ' '
 			++ padWords len xs
+    where
+        -- kludge: compute the length in graphemes, not chars.
+        -- but don't use graphemes for the max length, since I'm not convinced
+        -- that would work correctly. (This way, the worst that can happen is
+        -- that columns are longer than necessary.)
+        glength = length . stringToGraphemes
 
 -- Split xs into rows of length n,
 -- such that the list increases incrementally along the columns.
@@ -145,12 +159,4 @@
 ceilDiv m n | m `rem` n == 0    =  m `div` n
             | otherwise         =  m `div` n + 1
 
-menuCompletion :: forall m . Monad m => Key -> InsertMode -> [InsertMode] 
-                    -> CmdAction m InsertMode
-menuCompletion _ oldState [] = RingBell oldState >=> continue
-menuCompletion _ _ [c] = Change c >=> continue
-menuCompletion k oldState (c:cs) = Change c >=> loop cs
-    where
-        loop [] = choiceCmd [change (const oldState) k,continue]
-        loop (d:ds) = choiceCmd [change (const d) k >|> loop ds,continue]
 
diff --git a/System/Console/Haskeline/Command/History.hs b/System/Console/Haskeline/Command/History.hs
--- a/System/Console/Haskeline/Command/History.hs
+++ b/System/Console/Haskeline/Command/History.hs
@@ -9,21 +9,22 @@
 import Data.Maybe(fromMaybe)
 import System.Console.Haskeline.History
 
-data HistLog = HistLog {pastHistory, futureHistory :: [String]}
+data HistLog = HistLog {pastHistory, futureHistory :: [[Grapheme]]}
                     deriving Show
 
-prevHistoryM :: String -> HistLog -> Maybe (String,HistLog)
+prevHistoryM :: [Grapheme] -> HistLog -> Maybe ([Grapheme],HistLog)
 prevHistoryM _ HistLog {pastHistory = []} = Nothing
 prevHistoryM s HistLog {pastHistory=ls:past, futureHistory=future}
         = Just (ls, 
             HistLog {pastHistory=past, futureHistory= s:future})
 
-prevHistories :: String -> HistLog -> [(String,HistLog)]
+prevHistories :: [Grapheme] -> HistLog -> [([Grapheme],HistLog)]
 prevHistories s h = flip unfoldr (s,h) $ \(s',h') -> fmap (\r -> (r,r))
                     $ prevHistoryM s' h'
 
 histLog :: History -> HistLog
-histLog hist = HistLog {pastHistory = historyLines hist, futureHistory = []}
+histLog hist = HistLog {pastHistory = map stringToGraphemes $ historyLines hist,
+                        futureHistory = []}
 
 runHistoryFromFile :: MonadIO m => Maybe FilePath -> Maybe Int -> StateT History m a -> m a
 runHistoryFromFile Nothing _ f = evalStateT' emptyHistory f
@@ -39,18 +40,27 @@
     lift (evalStateT' (histLog history) f)
 
 
-prevHistory :: FromString s => s -> HistLog -> (s, HistLog)
-prevHistory s h = let (s',h') = fromMaybe (toResult s,h) $ prevHistoryM (toResult s) h
-                  in (fromString s',h')
+prevHistory, firstHistory :: Save s => s -> HistLog -> (s, HistLog)
+prevHistory s h = let (s',h') = fromMaybe (listSave s,h) 
+                                    $ prevHistoryM (listSave s) h
+                  in (listRestore s',h')
 
-historyBack, historyForward :: (FromString s, MonadState HistLog m) => 
-                        Key -> Command m s s
+firstHistory s h = let prevs = (listSave s,h):prevHistories (listSave s) h
+                       -- above makes sure we don't take the last of an empty list.
+                       (s',h') = last prevs
+                   in (listRestore s',h')
+
+historyBack, historyForward :: (Save s, MonadState HistLog m) => Command m s s
 historyBack = simpleCommand $ histUpdate prevHistory
 historyForward = simpleCommand $ reverseHist . histUpdate prevHistory
 
+historyStart, historyEnd :: (Save s, MonadState HistLog m) => Command m s s
+historyStart = simpleCommand $ histUpdate firstHistory
+historyEnd = simpleCommand $ reverseHist . histUpdate firstHistory
+
 histUpdate :: MonadState HistLog m => (s -> HistLog -> (t,HistLog))
-                        -> s -> m (Effect t)
-histUpdate f = liftM Change . update . f
+                        -> s -> m (Either Effect t)
+histUpdate f = liftM Right . update . f
 
 reverseHist :: MonadState HistLog m => m b -> m b
 reverseHist f = do
@@ -62,7 +72,7 @@
     reverser h = HistLog {futureHistory=pastHistory h, 
                             pastHistory=futureHistory h}
 
-data SearchMode = SearchMode {searchTerm :: String,
+data SearchMode = SearchMode {searchTerm :: [Grapheme],
                               foundHistory :: InsertMode,
                               direction :: Direction}
                         deriving Show
@@ -78,46 +88,51 @@
     beforeCursor _ sm = beforeCursor prefix (foundHistory sm)
         where 
             prefix = "(" ++ directionName (direction sm) ++ ")`" 
-                    ++ searchTerm sm ++ "': "
+                            ++ graphemesToString (searchTerm sm) ++ "': "
     afterCursor = afterCursor . foundHistory
 
 instance Result SearchMode where
     toResult = toResult . foundHistory
 
+saveSM :: SearchMode -> [Grapheme]
+saveSM = listSave . foundHistory
+
 startSearchMode :: Direction -> InsertMode -> SearchMode
-startSearchMode dir im = SearchMode {searchTerm = "",foundHistory=im, direction=dir}
+startSearchMode dir im = SearchMode {searchTerm = [],foundHistory=im, direction=dir}
 
 addChar :: Char -> SearchMode -> SearchMode
-addChar c s = s {searchTerm = searchTerm s ++ [c]}
+addChar c s = s {searchTerm = listSave $ insertChar c 
+                                $ listRestore $ searchTerm s}
 
-searchHistories :: Direction -> String -> [(String,HistLog)] -> Maybe (SearchMode,HistLog)
+searchHistories :: Direction -> [Grapheme] -> [([Grapheme],HistLog)]
+            -> Maybe (SearchMode,HistLog)
 searchHistories dir text = foldr mplus Nothing . map findIt
     where
         findIt (l,h) = do 
             im <- findInLine text l
             return (SearchMode text im dir,h)
 
-findInLine :: String -> String -> Maybe InsertMode
+findInLine :: [Grapheme] -> [Grapheme] -> Maybe InsertMode
 findInLine text l = find' [] l
     where
-        find' _ "" = Nothing
+        find' _ [] = Nothing
         find' prev ccs@(c:cs)
             | text `isPrefixOf` ccs = Just (IMode prev ccs)
             | otherwise = find' (c:prev) cs
 
-prepSearch :: SearchMode -> HistLog -> (String,[(String,HistLog)])
+prepSearch :: SearchMode -> HistLog -> ([Grapheme],[([Grapheme],HistLog)])
 prepSearch sm h = let
     text = searchTerm sm
-    l = toResult sm
+    l = saveSM sm
     in (text,prevHistories l h)
 
 searchBackwards :: Bool -> SearchMode -> HistLog -> Maybe (SearchMode, HistLog)
 searchBackwards useCurrent s h = let
     (text,hists) = prepSearch s h
-    hists' = if useCurrent then (toResult s,h):hists else hists
+    hists' = if useCurrent then (saveSM s,h):hists else hists
     in searchHistories (direction s) text hists'
 
-doSearch :: MonadState HistLog m => Bool -> SearchMode -> m (Effect SearchMode)
+doSearch :: MonadState HistLog m => Bool -> SearchMode -> m (Either Effect SearchMode)
 doSearch useCurrent sm = case direction sm of
     Reverse -> searchHist
     Forward -> reverseHist searchHist
@@ -125,27 +140,63 @@
     searchHist = do
         hist <- get
         case searchBackwards useCurrent sm hist of
-            Just (sm',hist') -> put hist' >> return (Change sm')
-            Nothing -> return (RingBell sm)
+            Just (sm',hist') -> put hist' >> return (Right sm')
+            Nothing -> return $ Left RingBell
 
-searchHistory :: MonadState HistLog m => Command m InsertMode InsertMode
+searchHistory :: MonadState HistLog m => KeyCommand m InsertMode InsertMode
 searchHistory = choiceCmd [
+            metaChar 'j' +> searchForPrefix Forward
+            , metaChar 'k' +> searchForPrefix Reverse
+            , choiceCmd [
                  backKey +> change (startSearchMode Reverse)
                  , forwardKey +> change (startSearchMode Forward)
-                 ] >|> keepSearching
+                 ] >+> keepSearching
+            ]
     where
         backKey = ctrlChar 'r'
         forwardKey = ctrlChar 's'
-        keepSearching = choiceCmd [
+        keepSearching = keyChoiceCmd [
                             choiceCmd [
                                 charCommand oneMoreChar
                                 , backKey +> simpleCommand (searchMore Reverse)
                                 , forwardKey +> simpleCommand (searchMore Forward)
                                 , simpleKey Backspace +> change delLastChar
-                                ] >|> keepSearching
-                            , changeWithoutKey foundHistory -- abort
+                                ] >+> keepSearching
+                            , withoutConsuming (change foundHistory) -- abort
                             ]
         delLastChar s = s {searchTerm = minit (searchTerm s)}
-        minit xs = if null xs then "" else init xs
+        minit xs = if null xs then [] else init xs
         oneMoreChar c = doSearch True . addChar c
         searchMore d s = doSearch False s {direction=d}
+
+
+searchForPrefix :: MonadState HistLog m => Direction
+                    -> Command m InsertMode InsertMode
+searchForPrefix dir s@(IMode xs _) = do
+    next <- findFirst prefixed dir s
+    maybe (return s) setState next
+  where
+    prefixed gs = if rxs `isPrefixOf` gs
+                    then Just $ IMode xs (drop (length xs) gs)
+                    else Nothing
+    rxs = reverse xs
+
+-- Search for the first entry in the history which satisfies the constraint.
+-- If it succeeds, the HistLog is updated and the result is returned.
+-- If it fails, the HistLog is unchanged.
+-- TODO: make the other history searching functions use this instead.
+findFirst :: forall s m . (Save s, MonadState HistLog m)
+    => ([Grapheme] -> Maybe s) -> Direction -> s -> m (Maybe s)
+findFirst cond Forward s = reverseHist $ findFirst cond Reverse s
+findFirst cond Reverse s = do
+    hist <- get
+    case search (prevHistories (listSave s) hist) of
+        Nothing -> return Nothing
+        Just (s',hist') -> put hist' >> return (Just s')
+  where
+    search :: [([Grapheme],HistLog)] -> Maybe (s,HistLog)
+    search [] = Nothing
+    search ((g,h):gs) = case cond g of
+        Nothing -> search gs
+        Just s' -> Just (s',h)
+
diff --git a/System/Console/Haskeline/Command/KillRing.hs b/System/Console/Haskeline/Command/KillRing.hs
new file mode 100644
--- /dev/null
+++ b/System/Console/Haskeline/Command/KillRing.hs
@@ -0,0 +1,89 @@
+module System.Console.Haskeline.Command.KillRing where
+
+import System.Console.Haskeline.LineState
+import System.Console.Haskeline.Command
+import System.Console.Haskeline.Monads
+import System.Console.Haskeline.Command.Undo
+import Control.Monad
+
+-- standard trick for a purely functional queue:
+data Stack a = Stack [a] [a]
+                deriving Show
+
+emptyStack :: Stack a
+emptyStack = Stack [] []
+
+peek :: Stack a -> Maybe a
+peek (Stack [] []) = Nothing
+peek (Stack (x:_) _) = Just x
+peek (Stack [] ys) = peek (Stack (reverse ys) [])
+
+rotate :: Stack a -> Stack a
+rotate s@(Stack [] []) = s
+rotate (Stack (x:xs) ys) = Stack xs (x:ys)
+rotate (Stack [] ys) = rotate (Stack (reverse ys) [])
+
+push :: a -> Stack a -> Stack a
+push x (Stack xs ys) = Stack (x:xs) ys
+
+type KillRing = Stack [Grapheme]
+
+runKillRing :: Monad m => StateT KillRing m a -> m a
+runKillRing = evalStateT' emptyStack
+
+
+pasteCommand :: (Save s, MonadState KillRing m, MonadState Undo m)
+            => ([Grapheme] -> s -> s) -> Command m (ArgMode s) s
+pasteCommand use = \s -> do
+    ms <- liftM peek get
+    case ms of
+        Nothing -> return $ argState s
+        Just p -> do
+            modify $ saveToUndo $ argState s
+            setState $ applyArg (use p) s
+
+deleteFromDiff' :: InsertMode -> InsertMode -> ([Grapheme],InsertMode)
+deleteFromDiff' (IMode xs1 ys1) (IMode xs2 ys2)
+    | posChange >= 0 = (take posChange ys1, IMode xs1 ys2)
+    | otherwise = (take (negate posChange) ys2 ,IMode xs2 ys1)
+  where
+    posChange = length xs2 - length xs1
+
+killFromHelper :: (MonadState KillRing m, MonadState Undo m,
+                        Save s, Save t)
+                => KillHelper -> Command m s t
+killFromHelper helper = saveForUndo >|> \oldS -> do
+    let (gs,newIM) = applyHelper helper (save oldS)
+    modify (push gs)
+    setState (restore newIM)
+
+killFromArgHelper :: (MonadState KillRing m, MonadState Undo m, Save s, Save t)
+                => KillHelper -> Command m (ArgMode s) t
+killFromArgHelper helper = saveForUndo >|> \oldS -> do
+    let (gs,newIM) = applyArgHelper helper (fmap save oldS)
+    modify (push gs)
+    setState (restore newIM)
+
+copyFromArgHelper :: (MonadState KillRing m, Save s)
+                => KillHelper -> Command m (ArgMode s) s
+copyFromArgHelper helper = \oldS -> do
+    let (gs,_) = applyArgHelper helper (fmap save oldS)
+    modify (push gs)
+    setState (argState oldS)
+
+
+data KillHelper = SimpleMove (InsertMode -> InsertMode)
+                 | GenericKill (InsertMode -> ([Grapheme],InsertMode))
+        -- a generic kill gives more flexibility, but isn't repeatable.
+        -- for example: dd,cc, %
+
+killAll :: KillHelper
+killAll = GenericKill $ \(IMode xs ys) -> (reverse xs ++ ys, emptyIM)
+
+applyHelper :: KillHelper -> InsertMode -> ([Grapheme],InsertMode)
+applyHelper (SimpleMove move) im = deleteFromDiff' im (move im)
+applyHelper (GenericKill act) im = act im
+
+applyArgHelper :: KillHelper -> ArgMode InsertMode -> ([Grapheme],InsertMode)
+applyArgHelper (SimpleMove move) im = deleteFromDiff' (argState im) (applyArg move im)
+applyArgHelper (GenericKill act) im = act (argState im)
diff --git a/System/Console/Haskeline/Command/Undo.hs b/System/Console/Haskeline/Command/Undo.hs
--- a/System/Console/Haskeline/Command/Undo.hs
+++ b/System/Console/Haskeline/Command/Undo.hs
@@ -1,30 +1,11 @@
 module System.Console.Haskeline.Command.Undo where
 
 import System.Console.Haskeline.Command
-import System.Console.Haskeline.Key
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.Monads
 
 import Control.Monad
 
-
-class LineState s => Save s where
-    save :: s -> InsertMode
-    restore :: InsertMode -> s
-
-instance Save InsertMode where
-    save = id
-    restore = id
-
-instance Save CommandMode where
-    save = insertFromCommandMode
-    restore = enterCommandModeRight
-
-instance Save s => Save (ArgMode s) where
-    save = save . argState
-    restore = ArgMode 0 . restore
-
-
 data Undo = Undo {pastUndo, futureRedo :: [InsertMode]}
 
 type UndoT = StateT Undo
@@ -58,11 +39,12 @@
 
 
 saveForUndo :: (Save s, MonadState Undo m)
-                => Command m s t -> Command m s t
-saveForUndo  = withState $ modify . saveToUndo
+                => Command m s s
+saveForUndo s = do
+    modify (saveToUndo s)
+    return s
 
-commandUndo, commandRedo :: (MonadState Undo m, Save s)
-                => Key -> Command m s s
-commandUndo = simpleCommand $ liftM Change . update . undoPast
-commandRedo = simpleCommand $ liftM Change . update . redoFuture
+commandUndo, commandRedo :: (MonadState Undo m, Save s) => Command m s s
+commandUndo = simpleCommand $ liftM Right . update . undoPast
+commandRedo = simpleCommand $ liftM Right . update . redoFuture
 
diff --git a/System/Console/Haskeline/Completion.hs b/System/Console/Haskeline/Completion.hs
--- a/System/Console/Haskeline/Completion.hs
+++ b/System/Console/Haskeline/Completion.hs
@@ -62,7 +62,7 @@
   where
     escapedBreak e (c:d:cs) | d == e && c `elem` (e:ws)
             = let (xs,ys) = escapedBreak e cs in (c:xs,ys)
-    escapedBreak e (c:cs) | not (elem c ws)
+    escapedBreak e (c:cs) | notElem c ws
             = let (xs,ys) = escapedBreak e cs in (c:xs,ys)
     escapedBreak _ cs = ("",cs)
     
@@ -156,7 +156,7 @@
             return $ setReplacement fullName $ alterIfDir isDir c
   where
     (dir, file) = splitFileName path
-    filterPrefix = filter (\f -> not (f `elem` [".",".."])
+    filterPrefix = filter (\f -> notElem f [".",".."]
                                         && file `isPrefixOf` f)
     alterIfDir False c = c
     alterIfDir True c = c {replacement = addTrailingPathSeparator (replacement c),
@@ -167,7 +167,7 @@
     -- an absolute path (strange, but it can happen).
     -- The FilePath docs state that (++) is an exact inverse of splitFileName, so
     -- that's the right function to user here.
-    fullName f = dir ++ f
+    fullName = (dir ++)
 
 -- turn a user-visible path into an internal version useable by System.FilePath.
 fixPath :: String -> IO String
diff --git a/System/Console/Haskeline/Directory.hsc b/System/Console/Haskeline/Directory.hsc
--- a/System/Console/Haskeline/Directory.hsc
+++ b/System/Console/Haskeline/Directory.hsc
@@ -14,7 +14,9 @@
 import Foreign
 import Foreign.C
 import System.Win32.Types
-import Data.Bits
+#if __GLASGOW_HASKELL__ >= 611
+import qualified System.Directory
+#endif
 
 #include <windows.h>
 #include <Shlobj.h>
@@ -52,6 +54,10 @@
     return $ attrs /= (#const INVALID_FILE_ATTRIBUTES)
             && (attrs .&. (#const FILE_ATTRIBUTE_DIRECTORY)) /= 0
 
+#if __GLASGOW_HASKELL__ >= 611
+getHomeDirectory :: IO FilePath
+getHomeDirectory = System.Directory.getHomeDirectory
+#else
 type HRESULT = #type HRESULT
 
 foreign import stdcall "SHGetFolderPathW" c_SHGetFolderPath
@@ -60,9 +66,11 @@
 getHomeDirectory :: IO FilePath
 getHomeDirectory = allocaBytes ((#const MAX_PATH) * (#size TCHAR)) $ \pathPtr -> do
     result <- c_SHGetFolderPath nullPtr (#const CSIDL_PROFILE) nullPtr 0 pathPtr
+
     if result /= (#const S_OK)
         then return ""
         else peekCWString pathPtr
+#endif
 
 #else
 
diff --git a/System/Console/Haskeline/Emacs.hs b/System/Console/Haskeline/Emacs.hs
--- a/System/Console/Haskeline/Emacs.hs
+++ b/System/Console/Haskeline/Emacs.hs
@@ -1,29 +1,45 @@
 module System.Console.Haskeline.Emacs where
 
 import System.Console.Haskeline.Command
+import System.Console.Haskeline.Monads
 import System.Console.Haskeline.Key
 import System.Console.Haskeline.Command.Completion
 import System.Console.Haskeline.Command.History
 import System.Console.Haskeline.Command.Undo
+import System.Console.Haskeline.Command.KillRing
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.InputT
 
 import Data.Char
 
 type InputCmd s t = forall m . Monad m => Command (InputCmdT m) s t
+type InputKeyCmd s t = forall m . Monad m => KeyCommand (InputCmdT m) s t
 
-emacsCommands :: Monad m => KeyMap (InputCmdT m) InsertMode
-emacsCommands = runCommand $ choiceCmd [simpleActions, controlActions]
+emacsCommands :: InputKeyCmd InsertMode (Maybe String)
+emacsCommands = choiceCmd [
+                    choiceCmd [simpleActions, controlActions] >+> 
+                        keyCommand emacsCommands
+                    , enders]
 
-simpleActions, controlActions :: InputCmd InsertMode InsertMode
+enders :: InputKeyCmd InsertMode (Maybe String)
+enders = choiceCmd [simpleChar '\n' +> finish, eotKey +> deleteCharOrEOF]
+    where
+        eotKey = ctrlChar 'd'
+        deleteCharOrEOF s
+            | s == emptyIM  = return Nothing
+            | otherwise = change deleteNext s >>= justDelete
+        justDelete = keyChoiceCmd [eotKey +> change deleteNext >|> justDelete
+                            , emacsCommands]
+
+
+simpleActions, controlActions :: InputKeyCmd InsertMode InsertMode
 simpleActions = choiceCmd 
-            [ simpleChar '\n' +> finish
-            , simpleKey LeftKey +> change goLeft
+            [ simpleKey LeftKey +> change goLeft
             , simpleKey RightKey +> change goRight
             , simpleKey Backspace +> change deletePrev
             , simpleKey Delete +> change deleteNext 
             , changeFromChar insertChar
-            , saveForUndo $ simpleChar '\t' +> completionCmd
+            , completionCmd (simpleChar '\t')
             , simpleKey UpKey +> historyBack
             , simpleKey DownKey +> historyForward
             , searchHistory
@@ -34,33 +50,51 @@
             , ctrlChar 'e' +> change moveToEnd
             , ctrlChar 'b' +> change goLeft
             , ctrlChar 'f' +> change goRight
-            , ctrlChar 'd' +> deleteCharOrEOF
             , ctrlChar 'l' +> clearScreenCmd
             , metaChar 'f' +> change wordRight
             , metaChar 'b' +> change wordLeft
+            , metaChar 'c' +> change (modifyWord capitalize)
+            , metaChar 'l' +> change (modifyWord (mapBaseChars toLower))
+            , metaChar 'u' +> change (modifyWord (mapBaseChars toUpper))
             , ctrlChar '_' +> commandUndo
-            , ctrlChar 'x' +> change id 
+            , ctrlChar 'x' +> try (ctrlChar 'u' +> commandUndo)
+            , ctrlChar 't' +> change transposeChars
+            , ctrlChar 'p' +> historyBack
+            , ctrlChar 'n' +> historyForward
+            , metaChar '<' +> historyStart
+            , metaChar '>' +> historyEnd
             , simpleKey Home +> change moveToStart
             , simpleKey End +> change moveToEnd
-                >|> choiceCmd [ctrlChar 'u' +> commandUndo
-                              , continue]
-            , saveForUndo $ choiceCmd
-                [ ctrlChar 'w' +> change (deleteFromMove bigWordLeft)
-                , metaKey (simpleKey Backspace) +> change (deleteFromMove wordLeft)
-                , metaChar 'd' +> change (deleteFromMove wordRight)
-                , ctrlChar 'k' +> change (deleteFromMove moveToEnd)
-                , simpleKey KillLine +> change (deleteFromMove moveToStart)
+            , choiceCmd
+                [ ctrlChar 'w' +> killFromHelper (SimpleMove bigWordLeft)
+                , metaKey (simpleKey Backspace) +> killFromHelper (SimpleMove wordLeft)
+                , metaChar 'd' +> killFromHelper (SimpleMove wordRight)
+                , ctrlChar 'k' +> killFromHelper (SimpleMove moveToEnd)
+                , simpleKey KillLine +> killFromHelper (SimpleMove moveToStart)
                 ]
+            , ctrlChar 'y' +> rotatePaste
             ]
 
-deleteCharOrEOF :: Key -> InputCmd InsertMode InsertMode
-deleteCharOrEOF k = k +> acceptKeyOrFail (\s -> if s == emptyIM
-            then Nothing
-            else Just $ Change (deleteNext s) >=> justDelete)
-    where
-        justDelete = try (change deleteNext k >|> justDelete)
+rotatePaste :: InputCmd InsertMode InsertMode
+rotatePaste im = get >>= loop
+  where
+    loop kr = case peek kr of
+                    Nothing -> return im
+                    Just s -> setState (insertGraphemes s im)
+                            >>= try (metaChar 'y' +> \_ -> loop (rotate kr))
 
+
 wordRight, wordLeft, bigWordLeft :: InsertMode -> InsertMode
-wordRight = skipRight isAlphaNum . skipRight (not . isAlphaNum)
-wordLeft = skipLeft isAlphaNum . skipLeft (not . isAlphaNum)
-bigWordLeft = skipLeft (not . isSpace) . skipLeft isSpace
+wordRight = goRightUntil (atStart (not . isAlphaNum))
+wordLeft = goLeftUntil (atStart isAlphaNum)
+bigWordLeft = goLeftUntil (atStart isSpace)
+
+modifyWord :: ([Grapheme] -> [Grapheme]) -> InsertMode -> InsertMode
+modifyWord f im = IMode (reverse (f ys1) ++ xs) ys2
+    where
+        IMode xs ys = skipRight (not . isAlphaNum) im
+        (ys1,ys2) = span (isAlphaNum . baseChar) ys
+
+capitalize :: [Grapheme] -> [Grapheme]
+capitalize [] = []
+capitalize (c:cs) = modifyBaseChar toUpper c : cs
diff --git a/System/Console/Haskeline/History.hs b/System/Console/Haskeline/History.hs
--- a/System/Console/Haskeline/History.hs
+++ b/System/Console/Haskeline/History.hs
@@ -15,6 +15,8 @@
                         History(),
                         emptyHistory,
                         addHistory,
+                        addHistoryUnlessConsecutiveDupe,
+                        addHistoryRemovingAllDupes,
                         historyLines,
                         readHistory,
                         writeHistory,
@@ -23,7 +25,8 @@
                         ) where
 
 import qualified Data.Sequence as Seq
-import Data.Foldable
+import Data.Sequence ( Seq, (<|), ViewL(..), ViewR(..), viewl, viewr )
+import Data.Foldable (toList)
 
 import qualified Data.ByteString as B
 import qualified Data.ByteString.UTF8 as UTF8
@@ -31,7 +34,7 @@
 
 import System.Directory(doesFileExist)
 
-data History = History {histLines :: Seq.Seq String,
+data History = History {histLines :: Seq String,
                         stifleAmt :: Maybe Int}
                     -- stored in reverse
 
@@ -59,8 +62,8 @@
         -- which cause confusion when switching between systems.
         then fmap UTF8.toString (B.readFile file)
         else return ""
-    evaluate (length contents) -- force file closed
-    return $ History {histLines = Seq.fromList $ lines contents,
+    _ <- evaluate (length contents) -- force file closed
+    return History {histLines = Seq.fromList $ lines contents,
                     stifleAmt = Nothing}
 
 -- | Writes the line history to the given file.  If there is an
@@ -81,10 +84,27 @@
                         else Seq.fromList . take n . toList
 
 addHistory :: String -> History -> History
-addHistory s h = h {histLines = s Seq.<| stifledLines}
+addHistory s h = h {histLines = maybeDropLast (stifleAmt h) (s <| (histLines h))}
+
+-- If the sequence is too big, drop the last entry.
+maybeDropLast :: Ord a => Maybe Int -> Seq a -> Seq a
+maybeDropLast maxAmt hs
+    | rightSize = hs
+    | otherwise = case Seq.viewr hs of
+                    EmptyR -> hs
+                    hs' :> _ -> hs'
   where
-    stifledLines = if maybe True (> Seq.length (histLines h)) (stifleAmt h)
-                    then histLines h
-                    else case Seq.viewr (histLines h) of
-                            Seq.EmptyR -> histLines h -- shouldn't ever happen
-                            ls Seq.:> _ -> ls
+    rightSize = maybe True (>= Seq.length hs) maxAmt
+
+-- | Add a line to the history unless it matches the previously recorded line.
+addHistoryUnlessConsecutiveDupe :: String -> History -> History
+addHistoryUnlessConsecutiveDupe h hs = case viewl (histLines hs) of
+    h1 :< _ | h==h1   -> hs
+    _                   -> addHistory h hs
+
+-- | Add a line to the history, and remove all previous entries which are the 
+-- same as it.
+addHistoryRemovingAllDupes :: String -> History -> History
+addHistoryRemovingAllDupes h hs = addHistory h hs {histLines = filteredHS}
+  where
+    filteredHS = Seq.fromList $ filter (/= h) $ toList $ histLines hs
diff --git a/System/Console/Haskeline/IO.hs b/System/Console/Haskeline/IO.hs
--- a/System/Console/Haskeline/IO.hs
+++ b/System/Console/Haskeline/IO.hs
@@ -41,8 +41,6 @@
 
 import System.Console.Haskeline hiding (completeFilename)
 import Control.Concurrent
-import Control.Concurrent.MVar
-import System.IO
 
 import Control.Monad.Trans
 
diff --git a/System/Console/Haskeline/InputT.hs b/System/Console/Haskeline/InputT.hs
--- a/System/Console/Haskeline/InputT.hs
+++ b/System/Console/Haskeline/InputT.hs
@@ -4,6 +4,7 @@
 import System.Console.Haskeline.History
 import System.Console.Haskeline.Command.History
 import System.Console.Haskeline.Command.Undo
+import System.Console.Haskeline.Command.KillRing
 import System.Console.Haskeline.Monads as Monads
 import System.Console.Haskeline.Prefs
 import System.Console.Haskeline.Completion
@@ -37,8 +38,9 @@
 -- | A monad transformer which carries all of the state and settings
 -- relevant to a line-reading application.
 newtype InputT m a = InputT {unInputT :: ReaderT RunTerm
-                                (StateT History (ReaderT Prefs
-                                (ReaderT (Settings m) m))) a}
+                                (StateT History
+                                (StateT KillRing (ReaderT Prefs
+                                (ReaderT (Settings m) m)))) a}
                             deriving (Monad, MonadIO, MonadException,
                                 MonadState History, MonadReader Prefs,
                                 MonadReader (Settings m), MonadReader RunTerm)
@@ -51,31 +53,31 @@
     (<*>) = State.ap
 
 instance MonadTrans InputT where
-    lift = InputT . lift . lift . lift . lift
+    lift = InputT . lift . lift . lift . lift . lift
 
 instance Monad m => State.MonadState History (InputT m) where
     get = get
     put = put
 
 -- for internal use only
-type InputCmdT m = ReaderT Layout (UndoT (StateT HistLog 
-                (ReaderT Prefs (ReaderT (Settings m) m))))
-
-instance MonadIO m => MonadLayout (InputCmdT m) where
+type InputCmdT m = StateT Layout (UndoT (StateT HistLog (StateT KillRing
+                (ReaderT Prefs (ReaderT (Settings m) m)))))
 
 runInputCmdT :: MonadIO m => TermOps -> InputCmdT m a -> InputT m a
 runInputCmdT tops f = InputT $ do
     layout <- liftIO $ getLayout tops
-    lift $ runHistLog $ runUndoT $ runReaderT' layout f
-
+    lift $ runHistLog $ runUndoT $ evalStateT' layout f
 
-liftCmdT :: Monad m => m a -> InputCmdT m a
-liftCmdT = lift  . lift . lift . lift . lift
+instance Monad m => CommandMonad (InputCmdT m) where
+    runCompletion lcs = do
+        settings <- ask
+        lift $ lift $ lift $ lift $ lift $ lift $ complete settings lcs
 
 runInputTWithPrefs :: MonadException m => Prefs -> Settings m -> InputT m a -> m a
 runInputTWithPrefs prefs settings (InputT f) = bracket (liftIO myRunTerm)
     (liftIO . closeTerm)
     $ \run -> runReaderT' settings $ runReaderT' prefs 
+        $ runKillRing
         $ runHistoryFromFile (historyFile settings) (maxHistorySize prefs) 
         $ runReaderT f run
         
diff --git a/System/Console/Haskeline/Key.hs b/System/Console/Haskeline/Key.hs
--- a/System/Console/Haskeline/Key.hs
+++ b/System/Console/Haskeline/Key.hs
@@ -36,7 +36,7 @@
              | FunKey Int
              | LeftKey | RightKey | DownKey | UpKey
              -- TODO: is KillLine really a key?
-             | KillLine | Home | End
+             | KillLine | Home | End | PageDown | PageUp
              | Backspace | Delete
             deriving (Show,Eq,Ord)
 
@@ -64,6 +64,8 @@
               ,("killline",KillLine)
               ,("home",Home)
               ,("end",End)
+              ,("pagedown",PageDown)
+              ,("pageup",PageUp)
               ,("backspace",Backspace)
               ,("delete",Delete)
               ,("return",KeyChar '\n')
diff --git a/System/Console/Haskeline/LineState.hs b/System/Console/Haskeline/LineState.hs
--- a/System/Console/Haskeline/LineState.hs
+++ b/System/Console/Haskeline/LineState.hs
@@ -1,40 +1,170 @@
-module System.Console.Haskeline.LineState where
+{- |
+This module contains the various datatypes which model the state of the line; that is, the characters displayed and the position of the cursor.
+-}
+module System.Console.Haskeline.LineState(
+                    -- * Graphemes
+                    Grapheme(),
+                    baseChar,
+                    stringToGraphemes,
+                    graphemesToString,
+                    modifyBaseChar,
+                    mapBaseChars,
+                    -- * Line State class
+                    LineState(..),
+                    -- ** Convenience functions for the drawing backends
+                    LineChars,
+                    lineChars,
+                    lengthToEnd,
+                    -- ** Supplementary classes
+                    Result(..),
+                    Save(..),
+                    listSave,
+                    listRestore,
+                    Move(..),
+                    -- * Instances
+                    -- ** InsertMode
+                    InsertMode(..),
+                    emptyIM,
+                    insertChar,
+                    insertString,
+                    replaceCharIM,
+                    insertGraphemes,
+                    deleteNext,
+                    deletePrev,
+                    skipLeft,
+                    skipRight,
+                    transposeChars,
+                    -- *** Moving to word boundaries
+                    goRightUntil,
+                    goLeftUntil,
+                    atStart,
+                    atEnd,
+                    beforeChar,
+                    afterChar,
+                    overChar,
+                    -- ** CommandMode
+                    CommandMode(..),
+                    deleteChar,
+                    replaceChar,
+                    pasteGraphemesBefore,
+                    pasteGraphemesAfter,
+                    -- *** Transitioning between modes
+                    enterCommandMode,
+                    enterCommandModeRight,
+                    insertFromCommandMode,
+                    appendFromCommandMode,
+                    withCommandMode,
+                    -- ** ArgMode
+                    ArgMode(..),
+                    startArg,
+                    addNum,
+                    applyArg,
+                    applyCmdArg,
+                    -- ** Other line state types
+                    Message(..),
+                    ) where
 
+import Data.Char
 
+-- | A 'Grapheme' is a fundamental unit of display for the UI.  Several characters in sequence
+-- can represent one grapheme; for example, an @a@ followed by the diacritic @\'\\768\'@ should
+-- be treated as one unit.
+data Grapheme = Grapheme {gBaseChar :: Char,
+                            combiningChars :: [Char]}
+                    deriving Eq
+
+instance Show Grapheme where
+    show g = show (gBaseChar g : combiningChars g)
+
+baseChar :: Grapheme -> Char
+baseChar = gBaseChar
+
+modifyBaseChar :: (Char -> Char) -> Grapheme -> Grapheme
+modifyBaseChar f g = g {gBaseChar = f (gBaseChar g)}
+
+mapBaseChars :: (Char -> Char) -> [Grapheme] -> [Grapheme]
+mapBaseChars f = map (modifyBaseChar f)
+
+-- | Create a 'Grapheme' from a single base character.
+--
+-- NOTE: Careful, don't use outside this module; and inside, make sure this is only
+-- ever called on non-combining characters.
+baseGrapheme :: Char -> Grapheme
+baseGrapheme c = Grapheme {gBaseChar = c, combiningChars = []}
+
+-- | Add a combining character to the given 'Grapheme'.
+addCombiner :: Grapheme -> Char -> Grapheme
+addCombiner g c = g {combiningChars = combiningChars g ++ [c]}
+
+isCombiningChar :: Char -> Bool
+isCombiningChar c = generalCategory c == NonSpacingMark
+
+-- | Converts a string into a sequence of graphemes.
+--
+-- NOTE: Drops any initial, unattached combining characters.
+stringToGraphemes :: String -> [Grapheme]
+stringToGraphemes = mkString . dropWhile isCombiningChar
+    where
+        mkString [] = []
+        mkString (c:cs) = Grapheme c (takeWhile isCombiningChar cs)
+                                : mkString (dropWhile isCombiningChar cs)
+
+graphemesToString :: [Grapheme] -> String
+graphemesToString = concatMap (\g -> (baseChar g : combiningChars g))
+
+-- | This class abstracts away the internal representations of the line state,
+-- for use by the drawing actions.  Line state is generally stored in a zipper format.
 class LineState s where
-    beforeCursor :: String -> s -> String -- text to left of cursor
-    afterCursor :: s -> String -- text under and to right of cursor
-    isTemporary :: s -> Bool
-    isTemporary _ = False
+    beforeCursor :: String -- ^ The input prefix.
+                    -> s -- ^ The current line state.
+                    -> [Grapheme] -- ^ The text to the left of the cursor, reversed.  (This 
+                                  -- includes the prefix.)
+    afterCursor :: s -> [Grapheme] -- ^ The text under and to the right of the cursor.
 
-type LineChars = (String,String)
+-- | The characters in the line (with the cursor in the middle).  NOT in a zippered format;
+-- both lists are in the order left->right that appears on the screen.
+type LineChars = ([Grapheme],[Grapheme])
 
+-- | Accessor function for the various backends.
 lineChars :: LineState s => String -> s -> LineChars
 lineChars prefix s = (beforeCursor prefix s, afterCursor s)
 
+-- | Compute the number of characters under and to the right of the cursor.
 lengthToEnd :: LineChars -> Int
 lengthToEnd = length . snd
 
 class LineState s => Result s where
     toResult :: s -> String
 
-class (Result s) => FromString s where
-    fromString :: String -> s
+class LineState s => Save s where
+    save :: s -> InsertMode
+    restore :: InsertMode -> s
 
+listSave :: Save s => s -> [Grapheme]
+listSave s = case save s of IMode xs ys -> reverse xs ++ ys
+
+listRestore :: Save s => [Grapheme] -> s
+listRestore xs = restore $ IMode (reverse xs) []
+
 class Move s where
     goLeft, goRight, moveToStart, moveToEnd :: s -> s
     
-
-data InsertMode = IMode String String 
+-- | The standard line state representation; considers the cursor to be located
+-- between two characters.  The first list is reversed.
+data InsertMode = IMode [Grapheme] [Grapheme]
                     deriving (Show, Eq)
 
 instance LineState InsertMode where
-    beforeCursor prefix (IMode xs _) = prefix ++ reverse xs
+    beforeCursor prefix (IMode xs _) = stringToGraphemes prefix ++ reverse xs
     afterCursor (IMode _ ys) = ys
 
 instance Result InsertMode where
-    toResult (IMode xs ys) = reverse xs ++ ys
+    toResult (IMode xs ys) = graphemesToString $ reverse xs ++ ys
 
+instance Save InsertMode where
+    save = id
+    restore = id
+
 instance Move InsertMode where
     goLeft im@(IMode [] _) = im 
     goLeft (IMode (x:xs) ys) = IMode xs (x:ys)
@@ -45,17 +175,22 @@
     moveToStart (IMode xs ys) = IMode [] (reverse xs ++ ys)
     moveToEnd (IMode xs ys) = IMode (reverse ys ++ xs) []
 
-instance FromString InsertMode where
-    fromString s = IMode (reverse s) []
-
 emptyIM :: InsertMode
-emptyIM = IMode "" ""
+emptyIM = IMode [] []
 
+-- | Insert one character, which may be combining, to the left of the cursor.
+--  
 insertChar :: Char -> InsertMode -> InsertMode
-insertChar c (IMode xs ys) = IMode (c:xs) ys
+insertChar c im@(IMode xs ys)
+    | isCombiningChar c = case xs of
+                            []   -> im -- drop a combining character if it
+                                       -- appears at the start of the line.
+                            z:zs -> IMode (addCombiner z c : zs) ys
+    | otherwise         = IMode (baseGrapheme c : xs) ys
 
+-- | Insert a sequence of characters to the left of the cursor. 
 insertString :: String -> InsertMode -> InsertMode
-insertString s (IMode xs ys) = IMode (reverse s ++ xs) ys
+insertString s (IMode xs ys) = IMode (reverse (stringToGraphemes s) ++ xs) ys
 
 deleteNext, deletePrev :: InsertMode -> InsertMode
 deleteNext im@(IMode _ []) = im
@@ -65,25 +200,51 @@
 deletePrev (IMode (_:xs) ys) = IMode xs ys 
 
 skipLeft, skipRight :: (Char -> Bool) -> InsertMode -> InsertMode
-skipLeft f (IMode xs ys) = let (ws,zs) = span f xs 
+skipLeft f (IMode xs ys) = let (ws,zs) = span (f . baseChar) xs 
                            in IMode zs (reverse ws ++ ys)
-skipRight f (IMode xs ys) = let (ws,zs) = span f ys 
+skipRight f (IMode xs ys) = let (ws,zs) = span (f . baseChar) ys 
                             in IMode (reverse ws ++ xs) zs
 
+transposeChars :: InsertMode -> InsertMode
+transposeChars (IMode (x:xs) (y:ys)) = IMode (x:y:xs) ys
+transposeChars (IMode (y:x:xs) []) = IMode (x:y:xs) []
+transposeChars im = im
 
-data CommandMode = CMode String Char String | CEmpty
+insertGraphemes :: [Grapheme] -> InsertMode -> InsertMode
+insertGraphemes s (IMode xs ys) = IMode (reverse s ++ xs) ys
+
+-- For the 'R' command.
+replaceCharIM :: Char -> InsertMode -> InsertMode
+replaceCharIM c im
+    | isCombiningChar c = case im of
+                    IMode [] [] -> im
+                    IMode [] (y:ys) -> IMode [] (addCombiner y c:ys)
+                    IMode (x:xs) ys -> IMode (addCombiner x c:xs) ys
+    | otherwise = let g = baseGrapheme c
+                  in case im of
+                    IMode xs [] -> IMode (g:xs) []
+                    IMode xs (_:ys) -> IMode (g:xs) ys
+
+
+-- | Used by vi mode.  Considers the cursor to be located over some specific character.
+-- The first list is reversed.
+data CommandMode = CMode [Grapheme] Grapheme [Grapheme] | CEmpty
                     deriving Show
 
 instance LineState CommandMode where
-    beforeCursor prefix CEmpty = prefix
-    beforeCursor prefix (CMode xs _ _) = prefix ++ reverse xs
-    afterCursor CEmpty = ""
+    beforeCursor prefix CEmpty = stringToGraphemes prefix
+    beforeCursor prefix (CMode xs _ _) = stringToGraphemes prefix ++ reverse xs
+    afterCursor CEmpty = []
     afterCursor (CMode _ c ys) = c:ys
 
 instance Result CommandMode where
     toResult CEmpty = ""
-    toResult (CMode xs c ys) = reverse xs ++ (c:ys)
+    toResult (CMode xs c ys) = graphemesToString $ reverse xs ++ (c:ys)
 
+instance Save CommandMode where
+    save = insertFromCommandMode
+    restore = enterCommandModeRight
+
 instance Move CommandMode where
     goLeft (CMode (x:xs) c ys) = CMode xs x (c:ys)
     goLeft cm = cm
@@ -97,20 +258,24 @@
     moveToEnd (CMode xs c ys) = let zs = reverse ys ++ (c:xs) in CMode (tail zs) (head zs) []
     moveToEnd CEmpty = CEmpty
 
-instance FromString CommandMode where
-    fromString s = case reverse s of
-                    [] -> CEmpty
-                    (c:cs) -> CMode cs c []
-
 deleteChar :: CommandMode -> CommandMode
 deleteChar (CMode xs _ (y:ys)) = CMode xs y ys
 deleteChar (CMode (x:xs) _ []) = CMode xs x []
 deleteChar _ = CEmpty
 
 replaceChar :: Char -> CommandMode -> CommandMode
-replaceChar c (CMode xs _ ys) = CMode xs c ys
+replaceChar c (CMode xs d ys)
+    | not (isCombiningChar c)   = CMode xs (baseGrapheme c) ys
+    | otherwise                 = CMode xs (addCombiner d c) ys
 replaceChar _ CEmpty = CEmpty
 
+pasteGraphemesBefore, pasteGraphemesAfter :: [Grapheme] -> CommandMode -> CommandMode
+pasteGraphemesBefore [] = id
+pasteGraphemesBefore s = enterCommandMode . insertGraphemes s . insertFromCommandMode
+
+pasteGraphemesAfter [] = id
+pasteGraphemesAfter s = enterCommandMode . insertGraphemes s . appendFromCommandMode
+
 ------------------------
 -- Transitioning between modes
 
@@ -138,19 +303,24 @@
 ----------------------
 -- Supplementary modes
 
+-- | Used for commands which take an integer argument.
 data ArgMode s = ArgMode {arg :: Int, argState :: s}
 
 instance Functor ArgMode where
-    fmap f (ArgMode n s) = ArgMode n (f s)
+    fmap f am = am {argState = f (argState am)}
 
 instance LineState s => LineState (ArgMode s) where
-    beforeCursor _ am = beforeCursor ("(arg: " ++ show (arg am) ++ ") ")
-                            (argState am)
+    beforeCursor _ am = let pre = "(arg: " ++ show (arg am) ++ ") "
+                             in beforeCursor pre (argState am) 
     afterCursor = afterCursor . argState
 
 instance Result s => Result (ArgMode s) where
     toResult = toResult . argState
 
+instance Save s => Save (ArgMode s) where
+    save = save . argState
+    restore = startArg 0 . restore
+
 startArg :: Int -> s -> ArgMode s
 startArg = ArgMode
 
@@ -161,30 +331,48 @@
 
 -- todo: negatives
 applyArg :: (s -> s) -> ArgMode s -> s
-applyArg f am = repeatN (arg am) (argState am)
-    where
-        repeatN n | n <= 1 = f
-                    | otherwise = f . repeatN (n-1)
+applyArg f am = repeatN (arg am) f (argState am)
 
----------------
-data Cleared = Cleared
+repeatN :: Int -> (a -> a) -> a -> a
+repeatN n f | n <= 1 = f
+          | otherwise = f . repeatN (n-1) f
 
-instance LineState Cleared where
-    beforeCursor _ Cleared = ""
-    afterCursor Cleared = ""
+applyCmdArg :: (InsertMode -> InsertMode) -> ArgMode CommandMode -> CommandMode
+applyCmdArg f am = withCommandMode (repeatN (arg am) f) (argState am)
 
+---------------
+-- TODO: messageState param not needed anymore.
 data Message s = Message {messageState :: s, messageText :: String}
 
-instance LineState s => LineState (Message s) where
-    beforeCursor _ = messageText
-    afterCursor _ = ""
-    isTemporary _ = True
+instance LineState (Message s) where
+    beforeCursor _ = stringToGraphemes . messageText
+    afterCursor _ = []
 
 -----------------
-deleteFromDiff :: InsertMode -> InsertMode -> InsertMode
-deleteFromDiff (IMode xs1 ys1) (IMode xs2 ys2)
-    | length xs1 < length xs2 = IMode xs1 ys2
-    | otherwise = IMode xs2 ys1
+atStart, atEnd :: (Char -> Bool) -> InsertMode -> Bool
+atStart f (IMode (x:_) (y:_)) = not (f (baseChar x)) && f (baseChar y)
+atStart _ _ = False
 
-deleteFromMove :: (InsertMode -> InsertMode) -> InsertMode -> InsertMode
-deleteFromMove f = \x -> deleteFromDiff x (f x)
+atEnd f (IMode _ (y1:y2:_)) = f (baseChar y1) && not (f (baseChar y2))
+atEnd _ _ = False
+
+overChar, beforeChar, afterChar :: (Char -> Bool) -> InsertMode -> Bool
+overChar f (IMode _ (y:_)) = f (baseChar y)
+overChar _ _ = False
+
+beforeChar f (IMode _ (_:y:_)) = f (baseChar y)
+beforeChar _ _ = False
+
+afterChar f (IMode (x:_) _) = f (baseChar x)
+afterChar _ _ = False
+
+goRightUntil, goLeftUntil :: (InsertMode -> Bool) -> InsertMode -> InsertMode
+goRightUntil f = loop . goRight
+    where
+        loop im@(IMode _ ys) | null ys || f im  = im
+                             | otherwise = loop (goRight im)
+goLeftUntil f = loop . goLeft
+    where
+        loop im@(IMode xs _)   | null xs || f im = im
+                            | otherwise = loop (goLeft im)
+
diff --git a/System/Console/Haskeline/MonadException.hs b/System/Console/Haskeline/MonadException.hs
--- a/System/Console/Haskeline/MonadException.hs
+++ b/System/Console/Haskeline/MonadException.hs
@@ -35,8 +35,8 @@
 finally f ender = block (do
     r <- catch
             (unblock f)
-            (\(e::SomeException) -> do {ender; throwIO e})
-    ender
+            (\(e::SomeException) -> do {_ <- ender; throwIO e})
+    _ <- ender
     return r)
 
 throwIO :: (MonadIO m, Exception e) => e -> m a
@@ -51,8 +51,8 @@
     a <- before 
     r <- catch 
 	   (unblock (thing a))
-	   (\(e::SomeException) -> do { after a; throwIO e })
-    after a
+	   (\(e::SomeException) -> do { _ <- after a; throwIO e })
+    _ <- after a
     return r
  )
 
diff --git a/System/Console/Haskeline/Monads.hs b/System/Console/Haskeline/Monads.hs
--- a/System/Console/Haskeline/Monads.hs
+++ b/System/Console/Haskeline/Monads.hs
@@ -22,19 +22,15 @@
 
 class Monad m => MonadReader r m where
     ask :: m r
-    local :: r -> m a -> m a
 
 instance Monad m => MonadReader r (ReaderT r m) where
     ask = Reader.ask
-    local r = Reader.local (const r)
 
-instance MonadReader r m => MonadReader r (ReaderT t m) where
-    ask = lift ask
-    local r = mapReaderT (local r)
-    
-instance MonadReader r m => MonadReader r (StateT s m) where
+instance Monad m => MonadReader s (StateT s m) where
+    ask = get
+
+instance (MonadReader r m, MonadTrans t, Monad (t m)) => MonadReader r (t m) where
     ask = lift ask
-    local r = mapStateT (local r)
 
 asks :: MonadReader r m => (r -> a) -> m a
 asks f = liftM f ask
@@ -47,13 +43,10 @@
     get = State.get
     put = State.put
 
-instance MonadState s m => MonadState s (StateT t m) where
+instance (MonadState s m, MonadTrans t, Monad (t m)) => MonadState s (t m) where
     get = lift get
     put = lift . put
 
-instance MonadState s m => MonadState s (ReaderT r m) where
-    get = lift get
-    put = lift . put
 
 modify :: MonadState s m => (s -> s) -> m ()
 modify f = get >>= put . f
diff --git a/System/Console/Haskeline/Prefs.hs b/System/Console/Haskeline/Prefs.hs
--- a/System/Console/Haskeline/Prefs.hs
+++ b/System/Console/Haskeline/Prefs.hs
@@ -5,6 +5,7 @@
                         CompletionType(..),
                         BellStyle(..),
                         EditMode(..),
+                        HistoryDuplicates(..),
                         lookupKeyBinding
                         ) where
 
@@ -31,6 +32,7 @@
 data Prefs = Prefs { bellStyle :: !BellStyle,
                      editMode :: !EditMode,
                      maxHistorySize :: !(Maybe Int),
+                     historyDuplicates :: HistoryDuplicates,
                      completionType :: !CompletionType,
                      completionPaging :: !Bool, 
                         -- ^ When listing completion alternatives, only display
@@ -59,6 +61,9 @@
 data EditMode = Vi | Emacs
                     deriving (Show,Read)
 
+data HistoryDuplicates = AlwaysAdd | IgnoreConsecutive | IgnoreAll
+                    deriving (Show,Read)
+
 -- | The default preferences which may be overwritten in the
 -- @.haskeline@ file.
 defaultPrefs :: Prefs
@@ -69,6 +74,7 @@
                       completionPaging = True,
                       completionPromptLimit = Just 100,
                       listCompletionsImmediately = True,
+                      historyDuplicates = AlwaysAdd,
                       customBindings = Map.empty,
                       customKeySequences = []
                     }
@@ -90,17 +96,18 @@
           ,("completionpaging", mkSettor $ \x p -> p {completionPaging = x})
           ,("completionpromptlimit", mkSettor $ \x p -> p {completionPromptLimit = x})
           ,("listcompletionsimmediately", mkSettor $ \x p -> p {listCompletionsImmediately = x})
+          ,("historyduplicates", mkSettor $ \x p -> p {historyDuplicates = x})
           ,("bind", addCustomBinding)
           ,("keyseq", addCustomKeySequence)
           ]
 
 addCustomBinding :: String -> Prefs -> Prefs
-addCustomBinding str p = case sequence $ map parseKey (words str) of
+addCustomBinding str p = case mapM parseKey (words str) of
     Just (k:ks) -> p {customBindings = Map.insert k ks (customBindings p)}
     _ -> p
 
 addCustomKeySequence :: String -> Prefs -> Prefs
-addCustomKeySequence str = maybe id addKS $ maybeParse
+addCustomKeySequence str = maybe id addKS maybeParse
     where
         maybeParse :: Maybe (Maybe String, String,Key)
         maybeParse = case words str of
@@ -121,7 +128,7 @@
 readPrefs :: FilePath -> IO Prefs
 readPrefs file = handle (\(_::IOException) -> return defaultPrefs) $ do
     ls <- fmap lines $ readFile file
-    return $ foldl' applyField defaultPrefs ls
+    return $! foldl' applyField defaultPrefs ls
   where
     applyField p l = case break (==':') l of
                 (name,val)  -> case lookup (map toLower $ trimSpaces name) settors of
diff --git a/System/Console/Haskeline/RunCommand.hs b/System/Console/Haskeline/RunCommand.hs
new file mode 100644
--- /dev/null
+++ b/System/Console/Haskeline/RunCommand.hs
@@ -0,0 +1,84 @@
+module System.Console.Haskeline.RunCommand (runCommandLoop) where
+
+import System.Console.Haskeline.Command
+import System.Console.Haskeline.Term
+import System.Console.Haskeline.LineState
+import System.Console.Haskeline.Monads
+import System.Console.Haskeline.Prefs
+import System.Console.Haskeline.Key
+
+import Control.Monad
+
+runCommandLoop :: (MonadException m, CommandMonad m, MonadState Layout m)
+    => TermOps -> String -> KeyCommand m InsertMode a -> m a
+runCommandLoop tops prefix cmds = runTerm tops $ 
+    RunTermType (withGetEvent tops $ runCommandLoop' tops prefix cmds)
+
+runCommandLoop' :: forall t m a . (MonadTrans t, Term (t m), CommandMonad (t m),
+        MonadState Layout m, MonadReader Prefs m)
+        => TermOps -> String -> KeyCommand m InsertMode a -> t m Event -> t m a
+runCommandLoop' tops prefix cmds getEvent = do
+    let s = lineChars prefix emptyIM
+    drawLine s
+    loopKeys [] s (fmap ($ emptyIM) cmds)
+  where 
+    loopKeys :: [Key] -> LineChars -> KeyMap (CmdM m a) -> t m a
+    loopKeys [] s processor = do -- no keys left, so read some more
+        event <- handle (\(e::SomeException) -> moveToNextLine s
+                                    >> throwIO e) getEvent
+        case event of
+                    ErrorEvent e -> moveToNextLine s >> throwIO e
+                    WindowResize -> drawReposition tops s
+                                    >> loopKeys [] s processor
+                    KeyInput k -> do
+                        ks <- lift $ asks $ lookupKeyBinding k
+                        loopKeys ks s processor
+    loopKeys (k:ks) s processor = case lookupKM processor k of
+                        Nothing -> actBell >> loopKeys [] s processor
+                        Just (Consumed cmd) -> loopCmd ks s cmd
+                        Just (NotConsumed cmd) -> loopCmd (k:ks) s cmd
+
+    loopCmd :: [Key] -> LineChars -> CmdM m a -> t m a
+    loopCmd ks s (GetKey next) = loopKeys ks s next
+    loopCmd ks s (DoEffect e next) = do
+                                        t <- drawEffect prefix s e
+                                        loopCmd ks t next
+    loopCmd ks s (CmdM next) = lift next >>= loopCmd ks s
+    loopCmd _ s (Result x) = moveToNextLine s >> return x
+
+drawEffect :: (MonadTrans t, Term (t m), MonadReader Prefs m)
+    => String -> LineChars -> Effect -> t m LineChars
+drawEffect prefix s (LineChange ch) = do
+    let t = ch prefix
+    drawLineDiff s t
+    return t
+drawEffect _ s ClearScreen = do
+    clearLayout
+    drawLine s
+    return s
+drawEffect _ s (PrintLines ls) = do
+    when (s /= ([],[])) $ moveToNextLine s
+    printLines ls
+    drawLine s
+    return s
+drawEffect _ s RingBell = actBell >> return s
+
+actBell :: (MonadTrans t, Term (t m), MonadReader Prefs m) => t m ()
+actBell = do
+    style <- lift $ asks bellStyle
+    case style of
+        NoBell -> return ()
+        VisualBell -> ringBell False
+        AudibleBell -> ringBell True
+
+drawReposition :: (MonadTrans t, Term (t m), MonadState Layout m)
+                    => TermOps -> LineChars -> t m ()
+drawReposition tops s = do
+    -- explicit lifts prevent the need for IncoherentInstances.
+    oldLayout <- lift get
+    newLayout <- liftIO $ getLayout tops
+    when (oldLayout /= newLayout) $ do
+        lift $ put newLayout
+        reposition oldLayout s
+
+
diff --git a/System/Console/Haskeline/Term.hs b/System/Console/Haskeline/Term.hs
--- a/System/Console/Haskeline/Term.hs
+++ b/System/Console/Haskeline/Term.hs
@@ -4,9 +4,9 @@
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.Key
 import System.Console.Haskeline.Prefs(Prefs)
+import System.Console.Haskeline.Completion(Completion)
 
 import Control.Concurrent
-import Control.Concurrent.Chan
 import Data.Typeable
 import Data.ByteString (ByteString)
 import Control.Exception.Extensible (fromException, AsyncException(..),bracket_)
@@ -18,6 +18,11 @@
     drawLineDiff :: LineChars -> LineChars -> m ()
     clearLayout :: m ()
     ringBell :: Bool -> m ()
+
+drawLine, clearLine :: Term m => LineChars -> m ()
+drawLine = drawLineDiff ([],[])
+
+clearLine = flip drawLineDiff ([],[])
     
 -- putStrOut is the right way to send unicode chars to stdout.
 -- termOps being Nothing means we should read the input as a UTF-8 file.
@@ -31,14 +36,28 @@
             closeTerm :: IO ()
     }
 
-data TermOps = TermOps {runTerm :: RunTermType,
-                        getLayout :: IO Layout}
+data TermOps = TermOps {
+            getLayout :: IO Layout
+            , withGetEvent :: (MonadException m, CommandMonad m)
+                                => (m Event -> m a) -> m a
+            , runTerm :: (MonadException m, CommandMonad m) => RunTermType m a -> m a
+        }
 
-type RunTermType = forall m a . (MonadLayout m, MonadException m, MonadReader Prefs m) 
-                    => (forall t . (MonadTrans t, Term (t m), MonadException (t m)) 
-                            => (t m Event -> t m a)) -> m a
+-- Generic terminal actions which are independent of the Term being used.
+-- Wrapped in a newtype so that we don't need RankNTypes.
+newtype RunTermType m a = RunTermType (forall t . 
+            (MonadTrans t, Term (t m), MonadException (t m), CommandMonad (t m))
+                            => t m a)
 
+class (MonadReader Prefs m , MonadReader Layout m)
+        => CommandMonad m where
+    runCompletion :: (String,String) -> m (String,[Completion])
 
+instance (MonadTrans t, CommandMonad m, MonadReader Prefs (t m),
+        MonadReader Layout (t m))
+            => CommandMonad (t m) where
+    runCompletion = lift . runCompletion
+
 -- Utility function for drawLineDiff instances.
 matchInit :: Eq a => [a] -> [a] -> ([a],[a])
 matchInit (x:xs) (y:ys)  | x == y = matchInit xs ys
@@ -75,7 +94,6 @@
                                 Just ThreadKilled -> return ()
                                 _ -> writeChan eventChan (ErrorEvent e)
 
-class (MonadReader Layout m, MonadIO m) => MonadLayout m where
 
 data Interrupt = Interrupt
                 deriving (Show,Typeable,Eq)
@@ -84,4 +102,3 @@
 
 data Layout = Layout {width, height :: Int}
                     deriving (Show,Eq)
-
diff --git a/System/Console/Haskeline/Vi.hs b/System/Console/Haskeline/Vi.hs
--- a/System/Console/Haskeline/Vi.hs
+++ b/System/Console/Haskeline/Vi.hs
@@ -1,59 +1,100 @@
 module System.Console.Haskeline.Vi where
 
 import System.Console.Haskeline.Command
+import System.Console.Haskeline.Monads
 import System.Console.Haskeline.Key
 import System.Console.Haskeline.Command.Completion
 import System.Console.Haskeline.Command.History
+import System.Console.Haskeline.Command.KillRing
 import System.Console.Haskeline.Command.Undo
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.InputT
 
-import Data.Char(isAlphaNum,isSpace)
+import Data.Char
+import Control.Monad(liftM)
 
-type InputCmd s t = forall m . Monad m => Command (InputCmdT m) s t
+type EitherMode = Either CommandMode InsertMode
 
-viActions :: Monad m => KeyMap (InputCmdT m) InsertMode
-viActions = runCommand insertionCommands
+type SavedCommand m = Command (ViT m) (ArgMode CommandMode) EitherMode
 
-insertionCommands :: InputCmd InsertMode InsertMode
-insertionCommands = choiceCmd [startCommand, simpleInsertions]
-                            
-simpleInsertions :: InputCmd InsertMode InsertMode
+data ViState m = ViState { 
+            lastCommand :: SavedCommand m,
+            lastSearch :: [Grapheme]
+         }
+
+emptyViState :: Monad m => ViState m
+emptyViState = ViState {
+            lastCommand = return . Left . argState,
+            lastSearch = []
+        }
+
+type ViT m = StateT (ViState m) (InputCmdT m)
+
+type InputCmd s t = forall m . Monad m => Command (ViT m) s t
+type InputKeyCmd s t = forall m . Monad m => KeyCommand (ViT m) s t
+
+viKeyCommands :: InputKeyCmd InsertMode (Maybe String)
+viKeyCommands = choiceCmd [
+                simpleChar '\n' +> finish
+                , ctrlChar 'd' +> eofIfEmpty
+                , simpleInsertions >+> viCommands
+                , simpleChar '\ESC' +> change enterCommandMode
+                    >|> viCommandActions
+                ]
+
+viCommands :: InputCmd InsertMode (Maybe String)
+viCommands = keyCommand viKeyCommands
+
+simpleInsertions :: InputKeyCmd InsertMode InsertMode
 simpleInsertions = choiceCmd
-                [ simpleChar '\n' +> finish
-                   , simpleKey LeftKey +> change goLeft 
+                [  simpleKey LeftKey +> change goLeft 
                    , simpleKey RightKey +> change goRight
                    , simpleKey Backspace +> change deletePrev 
                    , simpleKey Delete +> change deleteNext 
                    , simpleKey Home +> change moveToStart
                    , simpleKey End +> change moveToEnd
-                   , changeFromChar insertChar
+                   , insertChars
                    , ctrlChar 'l' +> clearScreenCmd
-                   , ctrlChar 'd' +> eofIfEmpty
                    , simpleKey UpKey +> historyBack
                    , simpleKey DownKey +> historyForward
                    , searchHistory
-                   , saveForUndo $ choiceCmd
-                        [ simpleKey KillLine +> change (deleteFromMove moveToStart)
-                        , simpleChar '\t' +> completionCmd
-                        ]
+                   , simpleKey KillLine +> killFromHelper (SimpleMove moveToStart)
+                   , ctrlChar 'w' +> killFromHelper wordErase
+                   , completionCmd (simpleChar '\t')
                    ]
 
--- If we receive a ^D and the line is empty, return Nothing
--- otherwise, ignore it.
-eofIfEmpty :: Save s => Key -> InputCmd s s
-eofIfEmpty k = k +> acceptKeyOrFail (\s -> if save s == emptyIM
-                    then Nothing
-                    else Just $ Change s >=> continue)
+insertChars :: InputKeyCmd InsertMode InsertMode
+insertChars = useChar $ loop []
+    where
+        loop ds d = change (insertChar d) >|> keyChoiceCmd [
+                        useChar $ loop (d:ds)
+                        , withoutConsuming (storeCharInsertion (reverse ds))
+                        ]
+        storeCharInsertion s = storeLastCmd $ change (applyArg 
+                                                        $ withCommandMode $ insertString s)
+                                                >|> return . Left
 
-startCommand :: InputCmd InsertMode InsertMode
-startCommand = simpleChar '\ESC' +> change enterCommandMode
-                    >|> viCommandActions
+-- If we receive a ^D and the line is empty, return Nothing
+-- otherwise, act like '\n' (mimicing how Readline behaves)
+eofIfEmpty :: (Monad m, Save s, Result s) => Command m s (Maybe String)
+eofIfEmpty s
+    | save s == emptyIM = return Nothing
+    | otherwise = finish s
 
-viCommandActions :: InputCmd CommandMode InsertMode
-viCommandActions = simpleCmdActions `loopUntil` exitingCommands
+viCommandActions :: InputCmd CommandMode (Maybe String)
+viCommandActions = keyChoiceCmd [
+                    simpleChar '\n' +> finish
+                    , ctrlChar 'd' +> eofIfEmpty
+                    , simpleCmdActions >+> viCommandActions
+                    , exitingCommands >+> viCommands
+                    , repeatedCommands >+> chooseEitherMode
+                    ]
+    where
+        chooseEitherMode :: InputCmd EitherMode (Maybe String)
+        chooseEitherMode (Left cm) = viCommandActions cm
+        chooseEitherMode (Right im) = viCommands im
 
-exitingCommands :: InputCmd CommandMode InsertMode
+exitingCommands :: InputKeyCmd CommandMode InsertMode
 exitingCommands =  choiceCmd [ 
                       simpleChar 'i' +> change insertFromCommandMode
                     , simpleChar 'I' +> change (moveToStart . insertFromCommandMode)
@@ -62,122 +103,332 @@
                     , simpleChar 'A' +> change (moveToEnd . appendFromCommandMode)
                     , simpleKey End +> change (moveToStart  . insertFromCommandMode)
                     , simpleChar 's' +> change (insertFromCommandMode . deleteChar)
-                    , repeated
-                    , saveForUndo $ choiceCmd
-                        [ simpleChar 'S' +> change (const emptyIM)
-                        , deleteIOnce
-                        ]
+                    , simpleChar 'S' +> noArg >|> killAndStoreI killAll
+                    , simpleChar 'C' +> noArg >|> killAndStoreI (SimpleMove moveToEnd)
                     ]
 
-simpleCmdActions :: InputCmd CommandMode CommandMode
-simpleCmdActions = choiceCmd [ simpleChar '\n'  +> finish
-                    , simpleChar '\ESC' +> change id -- helps break out of loops
-                    , ctrlChar 'd' +> eofIfEmpty
+simpleCmdActions :: InputKeyCmd CommandMode CommandMode
+simpleCmdActions = choiceCmd [ 
+                    simpleChar '\ESC' +> change id -- helps break out of loops
                     , simpleChar 'r'   +> replaceOnce 
-                    , simpleChar 'R'   +> loopReplace
-                    , simpleChar 'x' +> change deleteChar
+                    , simpleChar 'R'   +> replaceLoop
+                    , simpleChar 'D' +> noArg >|> killAndStoreCmd (SimpleMove moveToEnd)
                     , ctrlChar 'l' +> clearScreenCmd
                     , simpleChar 'u' +> commandUndo
                     , ctrlChar 'r' +> commandRedo
-                    , simpleChar '.' +> commandRedo
-                    , useMovements withCommandMode
-                    , simpleKey DownKey +> historyForward
-                    , simpleKey UpKey +> historyBack
-                    , saveForUndo $ choiceCmd
-                        [ simpleKey KillLine +> change (withCommandMode
-                                        $ deleteFromMove moveToStart)
-                        , deleteOnce
+                    -- vi-mode quirk: history is put at the start of the line.
+                    , simpleChar 'j' +> historyForward >|> change moveToStart
+                    , simpleChar 'k' +> historyBack >|> change moveToStart
+                    , simpleKey DownKey +> historyForward  >|> change moveToStart
+                    , simpleKey UpKey +> historyBack >|> change moveToStart
+                    , simpleChar '/' +> viEnterSearch '/' Reverse
+                    , simpleChar '?' +> viEnterSearch '?' Forward
+                    , simpleChar 'n' +> viSearchHist Reverse []
+                    , simpleChar 'N' +> viSearchHist Forward []
+                    , simpleKey KillLine +> noArg >|> killAndStoreCmd (SimpleMove moveToStart)
+                    ]
+
+replaceOnce :: InputCmd CommandMode CommandMode
+replaceOnce = try $ changeFromChar replaceChar
+
+repeatedCommands :: InputKeyCmd CommandMode EitherMode
+repeatedCommands = choiceCmd [argumented, doBefore noArg repeatableCommands]
+    where
+        start = foreachDigit startArg ['1'..'9']
+        addDigit = foreachDigit addNum ['0'..'9']
+        argumented = start >+> loop
+        loop = keyChoiceCmd [addDigit >+> loop
+                            , repeatableCommands
+                            -- if no match, bail out.
+                            , withoutConsuming (change argState) >+> return . Left
+                            ]
+
+pureMovements :: InputKeyCmd (ArgMode CommandMode) CommandMode
+pureMovements = choiceCmd $
+            map mkCharCommand charMovements
+            ++ map mkSimpleCommand movements
+    where
+        mkSimpleCommand (k,move) = k +> change (applyCmdArg move)
+        mkCharCommand (k,move) = k +> keyChoiceCmd [
+                                        useChar (change . applyCmdArg . move)
+                                        , withoutConsuming (change argState)
+                                        ]
+
+useMovementsForKill :: Command m s t -> (KillHelper -> Command m s t) -> KeyCommand m s t
+useMovementsForKill alternate useHelper = choiceCmd $
+            map mkCharCommand charMovements
+            ++ specialCases
+            ++ map (\(k,move) -> k +> useHelper (SimpleMove move)) movements
+    where
+        specialCases = [ simpleChar 'e' +> useHelper (SimpleMove goToWordDelEnd)
+                       , simpleChar 'E' +> useHelper (SimpleMove goToBigWordDelEnd)
+                       , simpleChar '%' +> useHelper (GenericKill deleteMatchingBrace)
+                       ]
+        mkCharCommand (k,move) = k +> keyChoiceCmd [
+                                    useChar (useHelper . SimpleMove . move)
+                                    , withoutConsuming alternate]
+
+
+repeatableCommands :: InputKeyCmd (ArgMode CommandMode) EitherMode
+repeatableCommands = choiceCmd
+                        [ repeatableCmdToIMode
+                        , repeatableCmdMode >+> return . Left
+                        , simpleChar '.' +> saveForUndo >|> runLastCommand
                         ]
+    where
+        runLastCommand s = liftM lastCommand get >>= ($ s)
+
+repeatableCmdMode :: InputKeyCmd (ArgMode CommandMode) CommandMode
+repeatableCmdMode = choiceCmd
+                    [ simpleChar 'x' +> repeatableChange deleteChar
+                    , simpleChar 'X' +> repeatableChange (withCommandMode deletePrev)
+                    , simpleChar '~' +> repeatableChange (goRight . flipCase)
+                    , simpleChar 'p' +> storedCmdAction (pasteCommand pasteGraphemesAfter)
+                    , simpleChar 'P' +> storedCmdAction (pasteCommand pasteGraphemesBefore)
+                    , simpleChar 'd' +> deletionCmd
+                    , simpleChar 'y' +> yankCommand
+                    , ctrlChar 'w' +> killAndStoreCmd wordErase
+                    , pureMovements
                     ]
+    where
+        repeatableChange f = storedCmdAction (saveForUndo >|> change (applyArg f))
 
-replaceOnce :: Key -> InputCmd CommandMode CommandMode
-replaceOnce k = k >+> try (changeFromChar replaceChar)
+flipCase :: CommandMode -> CommandMode
+flipCase CEmpty = CEmpty
+flipCase (CMode xs y zs) = CMode xs (modifyBaseChar flipCaseG y) zs
+    where
+        flipCaseG c | isLower c = toUpper c
+                    | otherwise = toLower c
 
-loopReplace :: Key -> InputCmd CommandMode CommandMode
-loopReplace k = k >+> loop
+repeatableCmdToIMode :: InputKeyCmd (ArgMode CommandMode) EitherMode
+repeatableCmdToIMode = simpleChar 'c' +> deletionToInsertCmd
+
+deletionCmd :: InputCmd (ArgMode CommandMode) CommandMode
+deletionCmd = keyChoiceCmd
+                    [simpleChar 'd' +> killAndStoreCmd killAll
+                    , useMovementsForKill (change argState) killAndStoreCmd
+                    , withoutConsuming (change argState)
+                    ]
+
+deletionToInsertCmd :: InputCmd (ArgMode CommandMode) EitherMode
+deletionToInsertCmd = keyChoiceCmd
+        [simpleChar 'c' +> killAndStoreIE killAll
+        -- vim, for whatever reason, treats cw same as ce and cW same as cE.
+        -- readline does this too, so we should also.
+        , simpleChar 'w' +> killAndStoreIE (SimpleMove goToWordDelEnd)
+        , simpleChar 'W' +> killAndStoreIE (SimpleMove goToBigWordDelEnd)
+        , withoutConsuming (return . Left . argState)
+        ]
+
+
+yankCommand :: InputCmd (ArgMode CommandMode) CommandMode
+yankCommand = keyChoiceCmd
+                [simpleChar 'y' +> copyAndStore killAll
+                , useMovementsForKill (change argState) copyAndStore
+                , withoutConsuming (change argState)
+                ]
     where
-        loop = choiceCmd [changeFromChar (\c -> goRight . replaceChar c) >|> loop
-                         , continue]
+        copyAndStore = storedCmdAction . copyFromArgHelper
 
-repeated :: InputCmd CommandMode InsertMode
-repeated = let
-    start = foreachDigit startArg ['1'..'9']
-    addDigit = foreachDigit addNum ['0'..'9']
-    deleteR = simpleChar 'd' 
-                >+> choiceCmd [useMovements deleteFromRepeatedMove,
-                             simpleChar 'd' +> change (const CEmpty)]
-    deleteIR = simpleChar 'c'
-                >+> choiceCmd [useMovements deleteAndInsertR,
-                             simpleChar 'c' +> change (const emptyIM)]
-    applyArg' f = enterCommandModeRight . applyArg f . fmap insertFromCommandMode
-    loop = choiceCmd [addDigit >|> loop
-                     , useMovements applyArg' >|> viCommandActions
-                     , saveForUndo (deleteR >|> viCommandActions)
-                     , saveForUndo deleteIR
-                     , saveForUndo (simpleChar 'x' +> change (applyArg deleteChar)
-                        >|> viCommandActions)
-                     , changeWithoutKey argState >|> viCommandActions
-                     ]
-    in start >|> loop
+goToWordDelEnd, goToBigWordDelEnd :: InsertMode -> InsertMode
+goToWordDelEnd = goRightUntil $ atStart (not . isWordChar)
+                                    .||. atStart (not . isOtherChar)
+goToBigWordDelEnd = goRightUntil $ atStart (not . isBigWordChar)
 
+
 movements :: [(Key,InsertMode -> InsertMode)]
 movements = [ (simpleChar 'h', goLeft)
             , (simpleChar 'l', goRight)
-            , (simpleChar 'w', skipRight isSpace . (\s -> skipRight (cmdChar s) s))
-            , (simpleChar 'b', (\s -> skipLeft (cmdChar s) s) . goLeft . skipLeft isSpace)
-            , (simpleChar 'W', skipRight isSpace . skipRight (not . isSpace))
-            , (simpleChar 'B', skipLeft (not . isSpace) . skipLeft isSpace)
             , (simpleChar ' ', goRight)
             , (simpleKey LeftKey, goLeft)
             , (simpleKey RightKey, goRight)
             , (simpleChar '0', moveToStart)
             , (simpleChar '$', moveToEnd)
+            , (simpleChar '^', skipRight isSpace . moveToStart)
+            , (simpleChar '%', findMatchingBrace)
+            ------------------
+            -- Word movements
+            -- move to the start of the next word
+            , (simpleChar 'w', goRightUntil $
+                                atStart isWordChar .||. atStart isOtherChar)
+            , (simpleChar 'W', goRightUntil (atStart isBigWordChar))
+            -- move to the beginning of the previous word
+            , (simpleChar 'b', goLeftUntil $
+                                atStart isWordChar .||. atStart isOtherChar)
+            , (simpleChar 'B', goLeftUntil (atStart isBigWordChar))
+            -- move to the end of the current word
+            , (simpleChar 'e', goRightUntil $
+                                atEnd isWordChar .||. atEnd isOtherChar)
+            , (simpleChar 'E', goRightUntil (atEnd isBigWordChar))
             ]
 
-cmdChar :: InsertMode -> (Char -> Bool)
-cmdChar (IMode _ (c:_))
-    | isWordChar c = isWordChar
-cmdChar _ = \d -> not (isWordChar d) && not (isSpace d)
+charMovements :: [(Key, Char -> InsertMode -> InsertMode)]
+charMovements = [ (simpleChar 'f', \c -> goRightUntil $ overChar (==c))
+                       , (simpleChar 'F', \c -> goLeftUntil $ overChar (==c))
+                       , (simpleChar 't', \c -> goRightUntil $ beforeChar (==c))
+                       , (simpleChar 'T', \c -> goLeftUntil $ afterChar (==c))
+                       ]
 
-isWordChar :: Char -> Bool
-isWordChar d = isAlphaNum d || d == '_'
+{- 
+From IEEE 1003.1:
+A "bigword" consists of: a maximal sequence of non-blanks preceded and followed by blanks
+A "word" consists of either:
+ - a maximal sequence of wordChars, delimited at both ends by non-wordchars
+ - a maximal sequence of non-blank non-wordchars, delimited at both ends by either blanks
+   or a wordchar.
+-}            
+isBigWordChar, isWordChar, isOtherChar :: Char -> Bool
+isBigWordChar = not . isSpace
+isWordChar = isAlphaNum .||. (=='_')
+isOtherChar = not . (isSpace .||. isWordChar)
 
-useMovements :: LineState t => ((InsertMode -> InsertMode) -> s -> t) 
-                -> InputCmd s t
-useMovements f = choiceCmd $ map (\(k,g) -> k +> change (f g))
-                                movements
+(.||.) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
+(f .||. g) x = f x || g x
 
-deleteOnce :: InputCmd CommandMode CommandMode
-deleteOnce = simpleChar 'd'
-            >+> choiceCmd [useMovements deleteFromCmdMove,
-                         simpleChar 'd' +> change (const CEmpty)]
+foreachDigit :: (Monad m, LineState t) => (Int -> s -> t) -> [Char] 
+                -> KeyCommand m s t
+foreachDigit f ds = choiceCmd $ map digitCmd ds
+    where digitCmd d = simpleChar d +> change (f (toDigit d))
+          toDigit d = fromEnum d - fromEnum '0'
 
-deleteIOnce :: InputCmd CommandMode InsertMode
-deleteIOnce = simpleChar 'c'
-              >+> choiceCmd [useMovements deleteAndInsert,
-                            simpleChar 'c' +> change (const emptyIM)]
 
-deleteAndInsert :: (InsertMode -> InsertMode) -> CommandMode -> InsertMode
-deleteAndInsert f = insertFromCommandMode . deleteFromCmdMove f
+-- This mimics the ctrl-w command in readline's vi mode, which corresponds to
+-- the tty's werase character.
+wordErase :: KillHelper
+wordErase = SimpleMove $ goLeftUntil $ atStart isBigWordChar
 
-deleteAndInsertR :: (InsertMode -> InsertMode) 
-                -> ArgMode CommandMode -> InsertMode
-deleteAndInsertR f = insertFromCommandMode . deleteFromRepeatedMove f
+------------------
+-- Matching braces
 
+findMatchingBrace :: InsertMode -> InsertMode
+findMatchingBrace (IMode xs (y:ys))
+    | Just b <- matchingRightBrace yc,
+      Just ((b':bs),ys') <- scanBraces yc b ys = IMode (bs++[y]++xs) (b':ys')
+    | Just b <- matchingLeftBrace yc,
+      Just (bs,xs') <- scanBraces yc b xs = IMode xs' (bs ++ [y]++ys)
+  where yc = baseChar y
+findMatchingBrace im = im
 
-foreachDigit :: (Monad m, LineState t) => (Int -> s -> t) -> [Char] 
-                -> Command m s t
-foreachDigit f ds = choiceCmd $ map digitCmd ds
-    where digitCmd d = simpleChar d +> change (f (toDigit d))
-          toDigit d = fromEnum d - fromEnum '0'
+deleteMatchingBrace :: InsertMode -> ([Grapheme],InsertMode)
+deleteMatchingBrace (IMode xs (y:ys))
+    | Just b <- matchingRightBrace yc,
+      Just (bs,ys') <- scanBraces yc b ys = (y : reverse bs, IMode xs ys')
+    | Just b <- matchingLeftBrace yc,
+      Just (bs,xs') <- scanBraces yc b xs = (bs ++ [y], IMode xs' ys)
+  where yc = baseChar y
+deleteMatchingBrace im = ([],im)
 
 
-deleteFromCmdMove :: (InsertMode -> InsertMode) -> CommandMode -> CommandMode
-deleteFromCmdMove f = withCommandMode $ \x -> deleteFromDiff x (f x)
+scanBraces :: Char -> Char -> [Grapheme] -> Maybe ([Grapheme],[Grapheme])
+scanBraces c d = scanBraces' (1::Int) []
+    where
+        scanBraces' 0 bs xs = Just (bs,xs)
+        scanBraces' _ _ [] = Nothing
+        scanBraces' n bs (x:xs) = scanBraces' m (x:bs) xs
+            where m | baseChar x == c = n+1
+                    | baseChar x == d = n-1
+                    | otherwise = n
 
-deleteFromRepeatedMove :: (InsertMode -> InsertMode)
-            -> ArgMode CommandMode -> CommandMode
-deleteFromRepeatedMove f am = let
-    am' = fmap insertFromCommandMode am
-    in enterCommandModeRight $ 
-                deleteFromDiff (argState am') (applyArg f am')
+matchingRightBrace, matchingLeftBrace :: Char -> Maybe Char 
+matchingRightBrace = flip lookup braceList
+matchingLeftBrace = flip lookup (map (\(c,d) -> (d,c)) braceList)
+
+braceList :: [(Char,Char)]
+braceList = [('(',')'), ('[',']'), ('{','}')]
+
+---------------
+-- Replace mode
+replaceLoop :: InputCmd CommandMode CommandMode
+replaceLoop = saveForUndo >|> change insertFromCommandMode >|> loop
+                >|> change enterCommandModeRight
+    where
+        loop = try (oneReplaceCmd >+> loop)
+        oneReplaceCmd = choiceCmd [
+                simpleKey LeftKey +> change goLeft
+                , simpleKey RightKey +> change goRight
+                , changeFromChar replaceCharIM
+                ]
+
+
+---------------------------
+-- Saving previous commands
+
+storeLastCmd :: Monad m => SavedCommand m -> Command (ViT m) s s
+storeLastCmd act = \s -> do
+        modify $ \vs -> vs {lastCommand = act}
+        return s
+
+storedAction :: Monad m => SavedCommand m -> SavedCommand m
+storedAction act = storeLastCmd act >|> act
+
+storedCmdAction :: Monad m => Command (ViT m) (ArgMode CommandMode) CommandMode
+                            -> Command (ViT m) (ArgMode CommandMode) CommandMode
+storedCmdAction act = storeLastCmd (liftM Left . act) >|> act
+
+storedIAction :: Monad m => Command (ViT m) (ArgMode CommandMode) InsertMode
+                        -> Command (ViT m) (ArgMode CommandMode) InsertMode
+storedIAction act = storeLastCmd (liftM Right . act) >|> act
+
+killAndStoreCmd :: Monad m => KillHelper -> Command (ViT m) (ArgMode CommandMode) CommandMode
+killAndStoreCmd = storedCmdAction . killFromArgHelper
+
+killAndStoreI :: Monad m => KillHelper -> Command (ViT m) (ArgMode CommandMode) InsertMode
+killAndStoreI = storedIAction . killFromArgHelper
+
+killAndStoreIE :: Monad m => KillHelper -> Command (ViT m) (ArgMode CommandMode) EitherMode
+killAndStoreIE helper = storedAction (killFromArgHelper helper >|> return . Right)
+
+noArg :: Monad m => Command m s (ArgMode s)
+noArg = return . startArg 1
+
+-------------------
+-- Vi-style searching
+
+data SearchEntry = SearchEntry {
+                    entryState :: InsertMode,
+                    searchChar :: Char
+                    }
+
+searchText :: SearchEntry -> [Grapheme]
+searchText SearchEntry {entryState = IMode xs ys} = reverse xs ++ ys
+
+instance LineState SearchEntry where
+    beforeCursor prefix se = beforeCursor (prefix ++ [searchChar se])
+                                (entryState se)
+    afterCursor = afterCursor . entryState
+
+viEnterSearch :: Monad m => Char -> Direction
+                    -> Command (ViT m) CommandMode CommandMode
+viEnterSearch c dir s = setState (SearchEntry emptyIM c) >>= loopEntry
+    where
+        modifySE f se = se {entryState = f (entryState se)}
+        loopEntry = keyChoiceCmd [
+                        editEntry >+> loopEntry
+                        , simpleChar '\n' +> \se -> 
+                            viSearchHist dir (searchText se) s
+                        , withoutConsuming (change (const s))
+                        ]
+        editEntry = choiceCmd [
+                        useChar (change . modifySE . insertChar)
+                        , simpleKey LeftKey +> change (modifySE goLeft)
+                        , simpleKey RightKey +> change (modifySE goRight)
+                        , simpleKey Backspace +> change (modifySE deletePrev)
+                        , simpleKey Delete +> change (modifySE deleteNext)
+                        ] 
+
+viSearchHist :: forall m . Monad m
+    => Direction -> [Grapheme] -> Command (ViT m) CommandMode CommandMode
+viSearchHist dir toSearch cm = do
+    vstate :: ViState m <- get
+    let toSearch' = if null toSearch
+                        then lastSearch vstate
+                        else toSearch
+    result <- doSearch False SearchMode {
+                                    searchTerm = toSearch',
+                                    foundHistory = save cm, -- TODO: not needed
+                                    direction = dir}
+    case result of
+        Left e -> effect e >> setState cm
+        Right sm -> do
+            put vstate {lastSearch = toSearch'}
+            setState (restore (foundHistory sm))
diff --git a/configure b/configure
deleted file mode 100644
--- a/configure
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-# Dummy file to be run by autoconfUserHooks.
diff --git a/haskeline.cabal b/haskeline.cabal
--- a/haskeline.cabal
+++ b/haskeline.cabal
@@ -1,6 +1,6 @@
 Name:           haskeline
 Cabal-Version:  >=1.6
-Version:        0.6.1.6
+Version:        0.6.2
 Category:       User Interfaces
 License:        BSD3
 License-File:   LICENSE
@@ -19,10 +19,9 @@
 Homepage:       http://trac.haskell.org/haskeline
 Stability:      Experimental
 Build-Type:     Custom
-extra-source-files: configure examples/Test.hs
-extra-tmp-files: haskeline.buildinfo
+extra-source-files: examples/Test.hs CHANGES
 
-flag old-base
+flag base2
     Description: Use the base packages from before version 6.8
 
 flag terminfo
@@ -34,20 +33,29 @@
     Default: False
 
 Library
-    if flag(old-base)
+    if flag(base2) {
         Build-depends: base == 2.*
         cpp-options:    -DOLD_BASE
-    else
-        Build-depends: base>=3 && <5 , containers>=0.1, directory>=1.0,
-                        bytestring==0.9.*
+    }
+    else {
+        if impl(ghc>=6.11) {
+            Build-depends: base >=4.1 && < 4.3, containers>=0.1 && < 0.3, directory==1.0.*,
+                           bytestring==0.9.*
+        }
+        else {
+            Build-depends: base>=3 && <4.1 , containers>=0.1 && < 0.3, directory==1.0.*,
+                           bytestring==0.9.*
+        }
+    }
     Build-depends:  filepath==1.1.*, mtl==1.1.*,
                     utf8-string==0.3.* && >=0.3.1.1,
                     extensible-exceptions==0.1.* && >=0.1.1.0
-    Extensions:     ForeignFunctionInterface, RankNTypes, FlexibleInstances,
+    Extensions:     ForeignFunctionInterface, Rank2Types, FlexibleInstances,
                 TypeSynonymInstances
                 FlexibleContexts, ExistentialQuantification
                 ScopedTypeVariables, GeneralizedNewtypeDeriving
                 MultiParamTypeClasses, OverlappingInstances
+                UndecidableInstances
                 PatternSignatures, CPP, DeriveDataTypeable,
                 PatternGuards
     Exposed-Modules:
@@ -62,6 +70,7 @@
                 System.Console.Haskeline.Command
                 System.Console.Haskeline.Command.Completion
                 System.Console.Haskeline.Command.History
+                System.Console.Haskeline.Command.KillRing
                 System.Console.Haskeline.Directory
                 System.Console.Haskeline.Emacs
                 System.Console.Haskeline.InputT
@@ -69,6 +78,7 @@
                 System.Console.Haskeline.LineState
                 System.Console.Haskeline.Monads
                 System.Console.Haskeline.Prefs
+                System.Console.Haskeline.RunCommand
                 System.Console.Haskeline.Term
                 System.Console.Haskeline.Command.Undo
                 System.Console.Haskeline.Vi
@@ -91,7 +101,7 @@
                 System.Console.Haskeline.Backend.IConv
                 System.Console.Haskeline.Backend.DumbTerm
         if flag(terminfo) {
-            Build-depends: terminfo==0.3.*
+            Build-depends: terminfo>=0.3.1.1 && <0.4
             Other-modules: System.Console.Haskeline.Backend.Terminfo
             cpp-options: -DTERMINFO
         }
