diff --git a/Changelog b/Changelog
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,14 @@
+Changed in version 0.8.4.0:
+
+   * On Windows, when terminal-style and printing output, no longer skips
+     `"\ESC...\STX"` sequences. (If used with legacy ConHost directly (for
+     example, `conhost.exe cmd.exe`), it will require ConHost's ANSI-capability
+     to be enabled separately.) (#126)
+
+   * Added `System.Console.Haskeline.ReaderT` module for `ReaderT` interface.
+
+   * `System.Console.Haskeline.Completion.listFiles` now returns a sorted list.
+
 Changed in version 0.8.3.0:
 
    * User preferences are now read from $XDG_CONFIG_HOME/haskeline/haskeline.
diff --git a/System/Console/Haskeline.hs b/System/Console/Haskeline.hs
--- a/System/Console/Haskeline.hs
+++ b/System/Console/Haskeline.hs
@@ -91,6 +91,7 @@
 import System.Console.Haskeline.Key
 import System.Console.Haskeline.RunCommand
 
+import Control.Monad ((>=>))
 import Control.Monad.Catch (MonadMask, handle)
 import Data.Char (isSpace, isPrint)
 import Data.Maybe (isJust)
@@ -144,6 +145,10 @@
 
 If @'autoAddHistory' == 'True'@ and the line input is nonblank (i.e., is not all
 spaces), it will be automatically added to the history.
+
+To include ANSI escape sequences in the input prompt, terminate them by @\STX@.
+See <https://github.com/haskell/haskeline/wiki/ControlSequencesInPrompt> for
+more information.
 -}
 getInputLine :: (MonadIO m, MonadMask m)
             => String -- ^ The input prompt
@@ -228,7 +233,7 @@
 acceptOneChar :: Monad m => KeyCommand m InsertMode (Maybe Char)
 acceptOneChar = choiceCmd [useChar $ \c s -> change (insertChar c) s
                                                 >> return (Just c)
-                          , ctrlChar 'l' +> clearScreenCmd >|>
+                          , ctrlChar 'l' +> clearScreenCmd >=>
                                         keyCommand acceptOneChar
                           , ctrlChar 'd' +> failCmd]
 
@@ -282,12 +287,12 @@
  where
     loop = choiceCmd [ simpleChar '\n' +> finish
                      , simpleKey Backspace +> change deletePasswordChar
-                                                >|> loop'
-                     , useChar $ \c -> change (addPasswordChar c) >|> loop'
+                                                >=> loop'
+                     , useChar $ \c -> change (addPasswordChar c) >=> loop'
                      , ctrlChar 'd' +> \p -> if null (passwordState p)
                                                 then failCmd p
                                                 else finish p
-                     , ctrlChar 'l' +> clearScreenCmd >|> loop'
+                     , ctrlChar 'l' +> clearScreenCmd >=> loop'
                      ]
     loop' = keyCommand loop
 
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
@@ -381,9 +381,7 @@
 crlf = "\r\n"
 
 instance (MonadMask m, MonadIO m, MonadReader Layout m) => Term (Draw m) where
-    drawLineDiff (xs1,ys1) (xs2,ys2) = let
-        fixEsc = filter ((/= '\ESC') . baseChar)
-        in drawLineDiffWin (fixEsc xs1, fixEsc ys1) (fixEsc xs2, fixEsc ys2)
+    drawLineDiff = drawLineDiffWin
     -- TODO now that we capture resize events.
     -- first, looks like the cursor stays on the same line but jumps
     -- to the beginning if cut off.
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
@@ -8,7 +8,6 @@
                         KeyConsumed(..),
                         withoutConsuming,
                         keyCommand,
-                        (>|>),
                         (>+>),
                         try,
                         effect,
@@ -29,8 +28,7 @@
                         ) where
 
 import Data.Char(isPrint)
-import Control.Applicative(Applicative(..))
-import Control.Monad(ap, mplus, liftM)
+import Control.Monad(ap, mplus, liftM, (>=>))
 import Control.Monad.Trans.Class
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.Key
@@ -45,7 +43,7 @@
 
 data KeyMap a = KeyMap {lookupKM :: Key -> Maybe (KeyConsumed a)}
 
-data KeyConsumed a = NotConsumed a | Consumed a
+data KeyConsumed a = NotConsumed a | Consumed a deriving Show
 
 instance Functor KeyMap where
     fmap f km = KeyMap $ fmap (fmap f) . lookupKM km
@@ -55,10 +53,10 @@
     fmap f (Consumed x) = Consumed (f x)
 
 
-data CmdM m a   = GetKey (KeyMap (CmdM m a))
-                | DoEffect Effect (CmdM m a)
-                | CmdM (m (CmdM m a))
-                | Result a
+data CmdM m a = GetKey (KeyMap (CmdM m a))
+              | DoEffect Effect (CmdM m a)
+              | CmdM (m (CmdM m a))
+              | Result a
 
 type Command m s t = s -> CmdM m t
 
@@ -112,20 +110,16 @@
 keyChoiceCmdM :: [KeyMap (CmdM m a)] -> CmdM m a
 keyChoiceCmdM = GetKey . choiceCmd
 
-infixr 6 >|>
-(>|>) :: Monad m => Command m s t -> Command m t u -> Command m s u
-f >|> g = \x -> f x >>= g
-
 infixr 6 >+>
 (>+>) :: Monad m => KeyCommand m s t -> Command m t u -> KeyCommand m s u
-km >+> g = fmap (>|> g) km
+km >+> g = fmap (>=> g) km
 
 -- 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]
 
-infixr 6 +>
+infixr 0 +>
 (+>) :: Key -> a -> KeyMap a
 (+>) = useKey
 
@@ -163,4 +157,4 @@
 changeFromChar f = useChar $ change . f
 
 doBefore :: Monad m => Command m s t -> KeyCommand m t u -> KeyCommand m s u
-doBefore cmd = fmap (cmd >|>)
+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
@@ -15,6 +15,7 @@
 import System.Console.Haskeline.Completion
 import System.Console.Haskeline.Monads
 
+import Control.Monad ((>=>))
 import Data.List(transpose, unfoldr)
 
 useCompletion :: InsertMode -> Completion -> InsertMode
@@ -35,7 +36,7 @@
 -- | Create a 'Command' for word completion.
 completionCmd :: (MonadState Undo m, CommandMonad m)
                 => Key -> KeyCommand m InsertMode InsertMode
-completionCmd k = k +> saveForUndo >|> \oldIM -> do
+completionCmd k = k +> saveForUndo >=> \oldIM -> do
     (rest,cs) <- askIMCompletions oldIM
     case cs of
         [] -> effect RingBell >> return oldIM
@@ -59,7 +60,7 @@
 menuCompletion k = loop
     where
         loop [] = setState
-        loop (c:cs) = change (const c) >|> try (k +> loop cs)
+        loop (c:cs) = change (const c) >=> try (k +> loop cs)
 
 makePartialCompletion :: InsertMode -> [Completion] -> InsertMode
 makePartialCompletion im completions = insertString partial im
diff --git a/System/Console/Haskeline/Command/KillRing.hs b/System/Console/Haskeline/Command/KillRing.hs
--- a/System/Console/Haskeline/Command/KillRing.hs
+++ b/System/Console/Haskeline/Command/KillRing.hs
@@ -55,14 +55,14 @@
 killFromHelper :: (MonadState KillRing m, MonadState Undo m,
                         Save s, Save t)
                 => KillHelper -> Command m s t
-killFromHelper helper = saveForUndo >|> \oldS -> do
+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
+killFromArgHelper helper = saveForUndo >=> \oldS -> do
     let (gs,newIM) = applyArgHelper helper (fmap save oldS)
     modify (push gs)
     setState (restore newIM)
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
@@ -18,7 +18,7 @@
 
 
 import System.FilePath
-import Data.List(isPrefixOf)
+import Data.List(isPrefixOf, sort)
 import Control.Monad(forM)
 
 import System.Console.Haskeline.Directory
@@ -186,7 +186,7 @@
     -- get all of the files in that directory, as basenames
     allFiles <- if not dirExists
                     then return []
-                    else fmap (map completion . filterPrefix)
+                    else fmap (sort . map completion . filterPrefix)
                             $ getDirectoryContents fixedDir
     -- The replacement text should include the directory part, and also
     -- have a trailing slash if it's itself a directory.
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
@@ -13,6 +13,7 @@
 import System.Console.Haskeline.LineState
 import System.Console.Haskeline.InputT
 
+import Control.Monad ((>=>))
 import Control.Monad.Catch (MonadMask)
 import Data.Char
 
@@ -32,7 +33,7 @@
         deleteCharOrEOF s
             | s == emptyIM  = return Nothing
             | otherwise = change deleteNext s >>= justDelete
-        justDelete = keyChoiceCmd [eotKey +> change deleteNext >|> justDelete
+        justDelete = keyChoiceCmd [eotKey +> change deleteNext >=> justDelete
                             , emacsCommands]
 
 
diff --git a/System/Console/Haskeline/Internal.hs b/System/Console/Haskeline/Internal.hs
--- a/System/Console/Haskeline/Internal.hs
+++ b/System/Console/Haskeline/Internal.hs
@@ -9,6 +9,8 @@
 import System.Console.Haskeline.RunCommand
 import System.Console.Haskeline.Term
 
+import Control.Monad ((>=>))
+
 -- | This function may be used to debug Haskeline's input.
 --
 -- It loops indefinitely; every time a key is pressed, it will
@@ -33,5 +35,5 @@
                 effect (LineChange $ const ([],[]))
                 effect (PrintLines [show k])
                 setState emptyIM)
-            >|> keyCommand loop
+            >=> keyCommand loop
     prompt = stringToGraphemes "> "
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
@@ -84,7 +84,7 @@
     put s = ask >>= liftIO . flip writeIORef s
 
 evalStateT' :: Monad m => s -> StateT s m a -> m a
-evalStateT' s f = liftM fst $ runStateT f s
+evalStateT' = flip evalStateT
 
 orElse :: Monad m => MaybeT m a -> m a -> m a
 orElse (MaybeT f) g = f >>= maybe g return
diff --git a/System/Console/Haskeline/ReaderT.hs b/System/Console/Haskeline/ReaderT.hs
new file mode 100644
--- /dev/null
+++ b/System/Console/Haskeline/ReaderT.hs
@@ -0,0 +1,133 @@
+-- | Provides an interface for 'ReaderT' rather than 'InputT'.
+--
+-- @since 0.8.4.0
+module System.Console.Haskeline.ReaderT
+    ( -- * ReaderT
+      -- $reader
+      toReaderT,
+      fromReaderT,
+      InputTEnv,
+      mapInputTEnv,
+    ) where
+
+import Control.Monad.Trans.Reader (ReaderT (ReaderT, runReaderT))
+import Data.IORef (IORef)
+import System.Console.Haskeline.Command.KillRing (KillRing)
+import System.Console.Haskeline.History (History)
+import System.Console.Haskeline.InputT(
+                         InputT(InputT),
+                         Settings(
+                            Settings,
+                            autoAddHistory,
+                            complete,
+                            historyFile
+                            )
+                         )
+import System.Console.Haskeline.Prefs (Prefs)
+import System.Console.Haskeline.Term (RunTerm)
+
+
+-- $reader
+--
+-- These functions expose the 'InputT' type in terms of ordinary 'ReaderT'.
+-- This allows for easier integration with applications that do not use a
+-- concrete monad transformer stack, or do not want to commit to having
+-- 'InputT' in the stack.
+
+-- | The abstract environment used by 'InputT', for 'ReaderT'
+-- usage.
+--
+-- @since 0.8.4.0
+data InputTEnv m = MkInputTEnv
+    { runTerm :: RunTerm,
+      history :: IORef History,
+      killRing :: IORef KillRing,
+      prefs :: Prefs,
+      settings :: Settings m
+    }
+
+-- | Maps the environment.
+--
+-- @since 0.8.4.0
+mapInputTEnv :: (forall x. m x -> n x) -> InputTEnv m -> InputTEnv n
+mapInputTEnv f (MkInputTEnv t h k p s) =
+    MkInputTEnv
+        { runTerm = t,
+          history = h,
+          killRing = k,
+          prefs = p,
+          settings = settings'
+        }
+    where
+        settings' = Settings
+            { complete = f . complete s,
+              historyFile = historyFile s,
+              autoAddHistory = autoAddHistory s
+            }
+
+-- | Maps an 'InputT' to a 'ReaderT'. Useful for lifting 'InputT' functions
+-- into some other reader-like monad.
+--
+-- __Examples:__
+--
+-- @
+-- import System.Console.Haskeline qualified as H
+--
+-- runApp :: ReaderT (InputTEnv IO) IO ()
+-- runApp = do
+--   input <- toReaderT (H.getInputLine "Enter your name: ")
+--   ...
+-- @
+--
+-- @
+-- -- MTL-style polymorphism
+-- class MonadHaskeline m where
+--   getInputLine :: String -> m (Maybe String)
+--
+-- -- AppT is the core type over ReaderT.
+-- instance MonadHaskeline (AppT m) where
+--   getInputLine = lift . toReaderT . H.getInputLine
+--
+-- runApp :: (MonadHaskeline m) => m ()
+-- runApp = do
+--   input <- getInputLine "Enter your name: "
+--   ...
+-- @
+--
+-- @since 0.8.4.0
+toReaderT :: InputT m a -> ReaderT (InputTEnv m) m a
+toReaderT (InputT rdr) = ReaderT $ \st ->
+    usingReaderT (settings st)
+        . usingReaderT (prefs st)
+        . usingReaderT (killRing st)
+        . usingReaderT (history st)
+        . usingReaderT (runTerm st)
+        $ rdr
+
+-- | Maps a 'ReaderT' to an 'InputT'. Allows defining an application in terms
+-- of 'ReaderT'.
+--
+-- __Examples:__
+--
+-- @
+-- import System.Console.Haskeline qualified as H
+--
+-- -- Could be generalized to MonadReader, effects libraries.
+-- runApp :: ReaderT (InputTEnv IO) IO ()
+--
+-- main :: IO ()
+-- main = H.runInputT H.defaultSettings $ fromReaderT runApp
+-- @
+--
+-- @since 0.8.4.0
+fromReaderT :: ReaderT (InputTEnv m) m a -> InputT m a
+fromReaderT (ReaderT onEnv) = InputT $ ReaderT
+    $ \runTerm' -> ReaderT
+    $ \history' -> ReaderT
+    $ \killRing' -> ReaderT
+    $ \prefs' -> ReaderT
+    $ \settings' ->
+        onEnv (MkInputTEnv runTerm' history' killRing' prefs' settings')
+
+usingReaderT :: env -> ReaderT env m a -> m a
+usingReaderT = flip runReaderT
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,7 +1,7 @@
 #if __GLASGOW_HASKELL__ < 802
 {-# OPTIONS_GHC -Wno-redundant-constraints #-}
 #endif
-module System.Console.Haskeline.Vi where
+module System.Console.Haskeline.Vi (emptyViState, viKeyCommands) where
 
 import System.Console.Haskeline.Command
 import System.Console.Haskeline.Monads
@@ -14,14 +14,14 @@
 import System.Console.Haskeline.InputT
 
 import Data.Char
-import Control.Monad(liftM)
+import Control.Monad(liftM, (>=>))
 import Control.Monad.Catch (MonadMask)
 
 type EitherMode = Either CommandMode InsertMode
 
 type SavedCommand m = Command (ViT m) (ArgMode CommandMode) EitherMode
 
-data ViState m = ViState { 
+data ViState m = ViState {
             lastCommand :: SavedCommand m,
             lastSearch :: [Grapheme]
          }
@@ -43,7 +43,7 @@
                 , ctrlChar 'd' +> eofIfEmpty
                 , simpleInsertions >+> viCommands
                 , simpleChar '\ESC' +> change enterCommandMode
-                    >|> viCommandActions
+                    >=> viCommandActions
                 ]
 
 viCommands :: InputCmd InsertMode (Maybe String)
@@ -51,10 +51,10 @@
 
 simpleInsertions :: InputKeyCmd InsertMode InsertMode
 simpleInsertions = choiceCmd
-                [  simpleKey LeftKey +> change goLeft 
+                [  simpleKey LeftKey +> change goLeft
                    , simpleKey RightKey +> change goRight
-                   , simpleKey Backspace +> change deletePrev 
-                   , simpleKey Delete +> change deleteNext 
+                   , simpleKey Backspace +> change deletePrev
+                   , simpleKey Delete +> change deleteNext
                    , simpleKey Home +> change moveToStart
                    , simpleKey End +> change moveToEnd
                    , insertChars
@@ -72,13 +72,13 @@
 insertChars :: InputKeyCmd InsertMode InsertMode
 insertChars = useChar $ loop []
     where
-        loop ds d = change (insertChar d) >|> keyChoiceCmd [
+        loop ds d = change (insertChar d) >=> keyChoiceCmd [
                         useChar $ loop (d:ds)
                         , withoutConsuming (storeCharInsertion (reverse ds))
                         ]
-        storeCharInsertion s = storeLastCmd $ change (applyArg 
+        storeCharInsertion s = storeLastCmd $ change (applyArg
                                                         $ withCommandMode $ insertString s)
-                                                >|> return . Left
+                                                >=> return . Left
 
 -- If we receive a ^D and the line is empty, return Nothing
 -- otherwise, act like '\n' (mimicking how Readline behaves)
@@ -101,7 +101,7 @@
         chooseEitherMode (Right im) = viCommands im
 
 exitingCommands :: InputKeyCmd CommandMode InsertMode
-exitingCommands =  choiceCmd [ 
+exitingCommands =  choiceCmd [
                       simpleChar 'i' +> change insertFromCommandMode
                     , simpleChar 'I' +> change (moveToStart . insertFromCommandMode)
                     , simpleKey Home +> change (moveToStart . insertFromCommandMode)
@@ -109,29 +109,29 @@
                     , simpleChar 'A' +> change (moveToEnd . appendFromCommandMode)
                     , simpleKey End +> change (moveToStart  . insertFromCommandMode)
                     , simpleChar 's' +> change (insertFromCommandMode . deleteChar)
-                    , simpleChar 'S' +> noArg >|> killAndStoreI killAll
-                    , simpleChar 'C' +> noArg >|> killAndStoreI (SimpleMove moveToEnd)
+                    , simpleChar 'S' +> noArg >=> killAndStoreI killAll
+                    , simpleChar 'C' +> noArg >=> killAndStoreI (SimpleMove moveToEnd)
                     ]
 
 simpleCmdActions :: InputKeyCmd CommandMode CommandMode
-simpleCmdActions = choiceCmd [ 
+simpleCmdActions = choiceCmd [
                     simpleChar '\ESC' +> change id -- helps break out of loops
-                    , simpleChar 'r'   +> replaceOnce 
+                    , simpleChar 'r'   +> replaceOnce
                     , simpleChar 'R'   +> replaceLoop
-                    , simpleChar 'D' +> noArg >|> killAndStoreCmd (SimpleMove moveToEnd)
+                    , simpleChar 'D' +> noArg >=> killAndStoreCmd (SimpleMove moveToEnd)
                     , ctrlChar 'l' +> clearScreenCmd
                     , simpleChar 'u' +> commandUndo
                     , ctrlChar 'r' +> commandRedo
                     -- 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 '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)
+                    , simpleKey KillLine +> noArg >=> killAndStoreCmd (SimpleMove moveToStart)
                     ]
 
 replaceOnce :: InputCmd CommandMode CommandMode
@@ -186,7 +186,7 @@
 repeatableCommands = choiceCmd
                         [ repeatableCmdToIMode
                         , repeatableCmdMode >+> return . Left
-                        , simpleChar '.' +> saveForUndo >|> runLastCommand
+                        , simpleChar '.' +> saveForUndo >=> runLastCommand
                         ]
     where
         runLastCommand s = liftM lastCommand get >>= ($ s)
@@ -204,7 +204,7 @@
                     , pureMovements
                     ]
     where
-        repeatableChange f = storedCmdAction (saveForUndo >|> change (applyArg f))
+        repeatableChange f = storedCmdAction (saveForUndo >=> change (applyArg f))
 
 flipCase :: CommandMode -> CommandMode
 flipCase CEmpty = CEmpty
@@ -288,14 +288,14 @@
             , (simpleChar 'E', goRightUntil (atEnd isBigWordChar))
             ]
 
-{- 
+{-
 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 .||. (=='_')
@@ -304,7 +304,7 @@
 (.||.) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
 (f .||. g) x = f x || g x
 
-foreachDigit :: (Monad m, LineState t) => (Int -> s -> t) -> [Char] 
+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))
@@ -348,7 +348,7 @@
                     | baseChar x == d = n-1
                     | otherwise = n
 
-matchingRightBrace, matchingLeftBrace :: Char -> Maybe Char 
+matchingRightBrace, matchingLeftBrace :: Char -> Maybe Char
 matchingRightBrace = flip lookup braceList
 matchingLeftBrace = flip lookup (map (\(c,d) -> (d,c)) braceList)
 
@@ -358,8 +358,8 @@
 ---------------
 -- Replace mode
 replaceLoop :: InputCmd CommandMode CommandMode
-replaceLoop = saveForUndo >|> change insertFromCommandMode >|> loop
-                >|> change enterCommandModeRight
+replaceLoop = saveForUndo >=> change insertFromCommandMode >=> loop
+                >=> change enterCommandModeRight
     where
         loop = try (oneReplaceCmd >+> loop)
         oneReplaceCmd = choiceCmd [
@@ -378,15 +378,15 @@
         return s
 
 storedAction :: Monad m => SavedCommand m -> SavedCommand m
-storedAction act = storeLastCmd act >|> act
+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
+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
+storedIAction act = storeLastCmd (liftM Right . act) >=> act
 
 killAndStoreCmd :: MonadIO m => KillHelper -> Command (ViT m) (ArgMode CommandMode) CommandMode
 killAndStoreCmd = storedCmdAction . killFromArgHelper
@@ -395,7 +395,7 @@
 killAndStoreI = storedIAction . killFromArgHelper
 
 killAndStoreIE :: MonadIO m => KillHelper -> Command (ViT m) (ArgMode CommandMode) EitherMode
-killAndStoreIE helper = storedAction (killFromArgHelper helper >|> return . Right)
+killAndStoreIE helper = storedAction (killFromArgHelper helper >=> return . Right)
 
 noArg :: Monad m => Command m s (ArgMode s)
 noArg = return . startArg 1
@@ -423,7 +423,7 @@
         modifySE f se = se {entryState = f (entryState se)}
         loopEntry = keyChoiceCmd [
                         editEntry >+> loopEntry
-                        , simpleChar '\n' +> \se -> 
+                        , simpleChar '\n' +> \se ->
                             viSearchHist dir (searchText se) s
                         , withoutConsuming (change (const s))
                         ]
@@ -433,7 +433,7 @@
                         , 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
diff --git a/haskeline.cabal b/haskeline.cabal
--- a/haskeline.cabal
+++ b/haskeline.cabal
@@ -1,6 +1,6 @@
 Name:           haskeline
 Cabal-Version:  >=1.10
-Version:        0.8.3.0
+Version:        0.8.4.0
 Category:       User Interfaces
 License:        BSD3
 License-File:   LICENSE
@@ -21,6 +21,7 @@
 Build-Type:     Simple
 
 tested-with:
+  GHC == 9.12.1
   GHC == 9.10.1
   GHC == 9.8.2
   GHC == 9.6.5
@@ -34,7 +35,7 @@
   GHC == 8.2.2
   GHC == 8.0.2
 
-extra-source-files: examples/Test.hs Changelog includes/*.h
+extra-source-files: examples/Test.hs Changelog
 
 source-repository head
     type: git
@@ -62,8 +63,8 @@
 
 Library
     Build-depends:
-        base         >= 4.9 && < 4.21
-      , containers   >= 0.4 && < 0.8
+        base         >= 4.9 && < 4.23
+      , containers   >= 0.4 && < 0.9
       , directory    >= 1.1 && < 1.4
       , bytestring   >= 0.9 && < 0.13
       , filepath     >= 1.2 && < 1.6
@@ -88,6 +89,7 @@
                 System.Console.Haskeline.History
                 System.Console.Haskeline.IO
                 System.Console.Haskeline.Internal
+                System.Console.Haskeline.ReaderT
     Other-Modules:
                 System.Console.Haskeline.Backend
                 System.Console.Haskeline.Backend.WCWidth
@@ -149,9 +151,10 @@
     Main-Is:    Unit.hs
     Build-depends:
       -- shared dependencies with library
-        base         >= 4.9 && < 4.21
-      , containers   >= 0.4 && < 0.8
+        base         >= 4.9 && < 4.23
+      , containers   >= 0.4 && < 0.9
       , bytestring   >= 0.9 && < 0.13
+      , directory
       , process      >= 1.0 && < 1.7
       -- dependencies for test-suite
       , HUnit
diff --git a/includes/windows_cconv.h b/includes/windows_cconv.h
deleted file mode 100644
--- a/includes/windows_cconv.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#ifndef WINDOWS_CCONV_H
-#define WINDOWS_CCONV_H
-
-#if defined(i386_HOST_ARCH)
-# define WINDOWS_CCONV stdcall
-#elif defined(x86_64_HOST_ARCH) || defined(aarch64_HOST_ARCH)
-# define WINDOWS_CCONV ccall
-#else
-# error Unknown mingw32 arch
-#endif
-
-#endif
diff --git a/tests/RunTTY.hs b/tests/RunTTY.hs
--- a/tests/RunTTY.hs
+++ b/tests/RunTTY.hs
@@ -41,9 +41,9 @@
 
 
 setUTF8 :: Invocation -> Invocation
-setUTF8 = setLang "en_US.UTF-8"
+setUTF8 = setLang "C.UTF-8"
 setLatin1 :: Invocation -> Invocation
-setLatin1 = setLang "en_US.ISO8859-1"
+setLatin1 = setLang "C.ISO8859-1"
 
 
 runInvocation :: Invocation
diff --git a/tests/Unit.hs b/tests/Unit.hs
--- a/tests/Unit.hs
+++ b/tests/Unit.hs
@@ -16,6 +16,7 @@
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as E
 import Data.Monoid ((<>))
+import System.Directory (createDirectoryIfMissing)
 import System.Exit (exitFailure)
 import System.Process (readProcess)
 import Test.HUnit
@@ -34,6 +35,13 @@
 whenLegacy :: BC.ByteString -> BC.ByteString
 whenLegacy s = if legacyEncoding then s else B.empty
 
+-- These files are used to test tab completion.
+makeTestFiles :: IO ()
+makeTestFiles = do
+  createDirectoryIfMissing True "tests/dummy-μασ"
+  writeFile "tests/dummy-μασ/bar" ""
+  writeFile "tests/dummy-μασ/ςερτ" ""
+
 main :: IO ()
 main = do
     -- forkProcess needs an absolute path to the binary.
@@ -45,6 +53,7 @@
                 runInTTY = True,
                 environment = []
             }
+    makeTestFiles
     result <- runTestTT $ test [interactionTests i, fileStyleTests i]
     when (errors result > 0 || failures result > 0) exitFailure
 
@@ -113,7 +122,7 @@
 tabCompletion i = "tab completion" ~:
     [ utf8Test i [ utf8 "tests/dummy-μ\t\t" ]
         [ prompt 0, utf8 "tests/dummy-μασ/"
-            <> nl <> utf8 "ςερτ  bar" <> nl
+            <> nl <> utf8 "bar   ςερτ" <> nl
             <> prompt' 0 <> utf8 "tests/dummy-μασ/"
         ]
     ]
@@ -259,13 +268,14 @@
         ]
     , "line char input" ~: utf8Test (setCharInput i)
         [utf8 "xαβ"]
-        [ prompt' 0, utf8 "x" <> nl <> output 0 (utf8 "x")
-          <> prompt' 1 <> utf8 "α" <> nl <> output 1 (utf8 "α")
-          <> prompt' 2 <> utf8 "β" <> nl <> output 2 (utf8 "β")
+        [ prompt' 0, utf8 "x" <> dumbnl <> output 0 (utf8 "x")
+          <> prompt' 1 <> utf8 "α" <> dumbnl <> output 1 (utf8 "α")
+          <> prompt' 2 <> utf8 "β" <> dumbnl <> output 2 (utf8 "β")
           <> prompt' 3
         ]
     ]
   where
+    dumbnl = raw [13,10]
     wideChar = T.concat $ replicate 10 $ "안기영"
 
 -------------
@@ -287,7 +297,7 @@
 cr = raw [13]
 
 nl :: B.ByteString
-nl = raw [13,10] -- NB: see fixNL: this is really [13,13,10]
+nl = raw [27, 69]
 
 output :: Int -> B.ByteString -> B.ByteString
 output k s = utf8 (T.pack $ "line " ++ show k ++ ":")
