packages feed

haskeline 0.6.2.4 → 0.6.3

raw patch · 21 files changed

+643/−316 lines, 21 filesdep ~containerssetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: containers

API changes (from Hackage documentation)

+ System.Console.Haskeline: data Behavior
+ System.Console.Haskeline: defaultBehavior :: Behavior
+ System.Console.Haskeline: getPassword :: (MonadException m) => Maybe Char -> String -> InputT m (Maybe String)
+ System.Console.Haskeline: haveTerminalUI :: (Monad m) => InputT m Bool
+ System.Console.Haskeline: preferTerm :: Behavior
+ System.Console.Haskeline: runInputTBehavior :: (MonadException m) => Behavior -> Settings m -> InputT m a -> m a
+ System.Console.Haskeline: runInputTBehaviorWithPrefs :: (MonadException m) => Behavior -> Prefs -> Settings m -> InputT m a -> m a
+ System.Console.Haskeline: useFile :: FilePath -> Behavior
+ System.Console.Haskeline: useFileHandle :: Handle -> Behavior
+ System.Console.Haskeline.Completion: completeWordWithPrev :: (Monad m) => Maybe Char -> [Char] -> (String -> String -> m [Completion]) -> CompletionFunc m
- System.Console.Haskeline.Completion: completeQuotedWord :: (Monad m) => Maybe Char -> String -> (String -> m [Completion]) -> CompletionFunc m -> CompletionFunc m
+ System.Console.Haskeline.Completion: completeQuotedWord :: (Monad m) => Maybe Char -> [Char] -> (String -> m [Completion]) -> CompletionFunc m -> CompletionFunc m
- System.Console.Haskeline.Completion: completeWord :: (Monad m) => Maybe Char -> String -> (String -> m [Completion]) -> CompletionFunc m
+ System.Console.Haskeline.Completion: completeWord :: (Monad m) => Maybe Char -> [Char] -> (String -> m [Completion]) -> CompletionFunc m

Files

CHANGES view
@@ -1,3 +1,17 @@+Changed in version 0.6.3:+   * #111: Correct width calculations when the prompt contains newlines.++   * #109: Add function completeWordWithPrev.++   * #101, #44: Extend the API with Behaviors, which control the choice between+     terminal-style and file-style interaction.++   * #78: Correct width calculations for escape sequences ("\ESC...\STX")++   * Better warning message when -fterminfo doesn't work.++   * Added getPassword as a new input function.+ Changed in version 0.6.2.4:    * Added back a MonadException instance for mtl's StateT. 
Setup.hs view
@@ -106,6 +106,11 @@     , "}"     ]     -warnIfNotTerminfo flags = when (not (hasFlagSet flags (FlagName "terminfo"))) $-  putStrLn $-    "*** Warning: running on POSIX but not building the terminfo backend. ***"+warnIfNotTerminfo flags = when (not (hasFlagSet flags (FlagName "terminfo")))+    $ mapM_ putStrLn+    [ "*** Warning: running on POSIX but not building the terminfo backend. ***"+    , "You may need to install the terminfo package manually, e.g. with"+    , "\"cabal install terminfo\"; or, use \"-fterminfo\" when configuring or"+    , "installing this package."+    ,""+    ]
System/Console/Haskeline.hs view
@@ -6,7 +6,7 @@ Users may customize the interface with a @~/.haskeline@ file; see <http://trac.haskell.org/haskeline/wiki/UserPrefs> for more information. -An example use of this library for a simple read-eval-print loop is the+An example use of this library for a simple read-eval-print loop (REPL) is the following:  > import System.Console.Haskeline@@ -27,27 +27,39 @@   module System.Console.Haskeline(-                    -- * Main functions+                    -- * Interactive sessions                     -- ** The InputT monad transformer                     InputT,                     runInputT,-                    runInputTWithPrefs,+                    haveTerminalUI,+                    -- ** Behaviors+                    Behavior,+                    runInputTBehavior,+                    defaultBehavior,+                    useFileHandle,+                    useFile,+                    preferTerm,+                    -- * User interaction functions                     -- ** Reading user input                     -- $inputfncs                     getInputLine,                     getInputChar,+                    getPassword,                     -- ** Outputting text                     -- $outputfncs                     outputStr,                     outputStrLn,-                    -- * Settings+                    -- * Customization+                    -- ** Settings                     Settings(..),                     defaultSettings,                     setComplete,-                    -- * User preferences+                    -- ** User preferences                     Prefs(),                     readPrefs,                     defaultPrefs,+                    runInputTWithPrefs,+                    runInputTBehaviorWithPrefs,                     -- * Ctrl-C handling                     -- $ctrlc                     Interrupt(..),@@ -73,9 +85,6 @@  import System.IO import Data.Char (isSpace, isPrint)-import Control.Monad-import qualified Data.ByteString.Char8 as B-import System.IO.Error (isEOFError)   -- | A useful default.  In particular:@@ -93,17 +102,17 @@                         autoAddHistory = True}  {- $outputfncs-The following functions allow cross-platform output of text that may contain+The following functions enable cross-platform output of text that may contain Unicode characters. -} --- | Write a string to the standard output.+-- | Write a Unicode string to the user's standard output. outputStr :: MonadIO m => String -> InputT m () outputStr xs = do     putter <- asks putStrOut     liftIO $ putter xs --- | Write a string to the standard output, followed by a newline.+-- | Write a string to the user's standard output, followed by a newline. outputStrLn :: MonadIO m => String -> InputT m () outputStrLn = outputStr . (++ "\n") @@ -111,43 +120,30 @@ {- $inputfncs The following functions read one line or character of input from the user. -If 'stdin' is connected to a terminal, then these functions perform all user interaction,-including display of the prompt text, on the user's output terminal (which may differ from-'stdout').-They return 'Nothing' if the user pressed @Ctrl-D@ when the-input text was empty.+When using terminal-style interaction, these functions return 'Nothing' if the user+pressed @Ctrl-D@ when the input text was empty. -If 'stdin' is not connected to a terminal or does not have echoing enabled,-then these functions print the prompt to 'stdout',-and they return 'Nothing' if an @EOF@ was encountered before any characters were read.+When using file-style interaction, these functions return 'Nothing' if+an @EOF@ was encountered before any characters were read. -}  -{- | Reads one line of input.  The final newline (if any) is removed.  Provides a rich-line-editing user interface if 'stdin' is a terminal.+{- | Reads one line of input.  The final newline (if any) is removed.  When using terminal-style interaction, this function provides a rich line-editing user interface.  If @'autoAddHistory' == 'True'@ and the line input is nonblank (i.e., is not all spaces), it will be automatically added to the history. -}-getInputLine :: forall m . MonadException m => String -- ^ The input prompt+getInputLine :: MonadException m => String -- ^ The input prompt                             -> InputT m (Maybe String)-getInputLine prefix = do-    -- If other parts of the program have written text, make sure that it -    -- appears before we interact with the user on the terminal.-    liftIO $ hFlush stdout-    rterm <- ask-    echo <- liftIO $ hGetEcho stdin-    case termOps rterm of-        Just tops | echo -> getInputCmdLine tops prefix-        _ -> simpleFileLoop prefix rterm+getInputLine = promptedInput getInputCmdLine $ unMaybeT . getLocaleLine  getInputCmdLine :: MonadException m => TermOps -> String -> InputT m (Maybe String) getInputCmdLine tops prefix = do     emode <- asks editMode     result <- runInputCmdT tops $ case emode of-                Emacs -> runCommandLoop tops prefix emacsCommands+                Emacs -> runCommandLoop tops prefix emacsCommands emptyIM                 Vi -> evalStateT' emptyViState $-                        runCommandLoop tops prefix viKeyCommands+                        runCommandLoop tops prefix viKeyCommands emptyIM     maybeAddHistory result     return result @@ -164,80 +160,33 @@                in modify (adder line)         _ -> return () -simpleFileLoop :: MonadIO m => String -> RunTerm -> m (Maybe String)-simpleFileLoop prefix rterm = liftIO $ do-    putStrOut rterm prefix-    atEOF <- hIsEOF stdin-    if atEOF-        then return Nothing-        else do-            -- It's more efficient to use B.getLine, but that function throws an-            -- error if stdin is set to NoBuffering.-            buff <- hGetBuffering stdin-            line <- case buff of-                        NoBuffering -> hWithBinaryMode stdin-                                $ fmap B.pack System.IO.getLine-                        _ -> B.getLine-            fmap Just $ decodeForTerm rterm line-- ----------  {- | Reads one character of input.  Ignores non-printable characters. -If stdin is a terminal, the character will be read without waiting for a newline.+When using terminal-style interaction, the character will be read without waiting+for a newline. -If stdin is not a terminal, a newline will be read if it is immediately+When using file-style interaction, a newline will be read if it is immediately available after the input character. -} getInputChar :: MonadException m => String -- ^ The input prompt                     -> InputT m (Maybe Char)-getInputChar prefix = do-    liftIO $ hFlush stdout-    rterm <- ask-    echo <- liftIO $ hGetEcho stdin-    case termOps rterm of-        Just tops | echo -> getInputCmdChar tops prefix-        _ -> simpleFileChar prefix rterm--simpleFileChar :: MonadIO m => String -> RunTerm -> m (Maybe Char)-simpleFileChar prefix rterm = liftIO $ do-    putStrOut rterm prefix-    c <- getPrintableChar-    maybeReadNewline-    return c-  where-    getPrintableChar = returnOnEOF Nothing $ do-                c <- getLocaleChar rterm-                if isPrint c-                    then return (Just c)-                    else getPrintableChar---- If another character is immediately available, and it is a newline, consume it.------ Note that in ghc-6.8.3 and earlier, hReady returns False at an EOF,--- whereas in ghc-6.10.1 and later it throws an exception.  (GHC trac #1063).--- This code handles both of those cases.------ Also note that on Windows with ghc<6.10, hReady may not behave correctly (#1198)--- The net result is that this might cause--- But this function will generally only be used when reading buffered input--- (since stdin isn't a terminal), so it should probably be OK.-maybeReadNewline :: IO ()-maybeReadNewline = returnOnEOF () $ do-    ready <- hReady stdin-    when ready $ do-        c <- hLookAhead stdin-        when (c == '\n') $ getChar >> return ()--returnOnEOF :: a -> IO a -> IO a-returnOnEOF x = handle $ \e -> if isEOFError e-                                then return x-                                else throwIO e+getInputChar = promptedInput getInputCmdChar $ \fops -> do+                        c <- getPrintableChar fops+                        maybeReadNewline fops+                        return c +getPrintableChar :: FileOps -> IO (Maybe Char)+getPrintableChar fops = do+    c <- unMaybeT $ getLocaleChar fops+    case fmap isPrint c of+        Just False -> getPrintableChar fops+        _ -> return c+         getInputCmdChar :: MonadException m => TermOps -> String -> InputT m (Maybe Char) getInputCmdChar tops prefix = runInputCmdT tops -        $ runCommandLoop tops prefix acceptOneChar+        $ runCommandLoop tops prefix acceptOneChar emptyIM  acceptOneChar :: Monad m => KeyCommand m InsertMode (Maybe Char) acceptOneChar = choiceCmd [useChar $ \c s -> change (insertChar c) s@@ -245,6 +194,59 @@                           , ctrlChar 'l' +> clearScreenCmd >|>                                         keyCommand acceptOneChar                           , ctrlChar 'd' +> failCmd]++----------+-- Passwords++{- | Reads one line of input, without displaying the input while it is being typed.+When using terminal-style interaction, the masking character (if given) will replace each typed character.++When using file-style interaction, this function turns off echoing while reading+the line of input.+-}+ +getPassword :: MonadException m => Maybe Char -- ^ A masking character; e.g., @Just \'*\'@+                            -> String -> InputT m (Maybe String)+getPassword x = promptedInput+                    (\tops prefix -> runInputCmdT tops+                                        $ runCommandLoop tops prefix loop+                                        $ Password [] x)+                    (\fops -> let h_in = inputHandle fops+                              in bracketSet (hGetEcho h_in) (hSetEcho h_in) False+                                  $ unMaybeT $ getLocaleLine fops)+ where+    loop = choiceCmd [ simpleChar '\n' +> finish+                     , simpleKey Backspace +> change deletePasswordChar+                                                >|> loop'+                     , useChar $ \c -> change (addPasswordChar c) >|> loop'+                     , ctrlChar 'd' +> \p -> if null (passwordState p)+                                                then failCmd p+                                                else finish p+                     , ctrlChar 'l' +> clearScreenCmd >|> loop'+                     ]+    loop' = keyCommand loop+                        +++-------+-- | Wrapper for input functions.+promptedInput :: MonadIO m => (TermOps -> String -> InputT m a)+                        -> (FileOps -> IO a)+                        -> String -> InputT m a+promptedInput doTerm doFile prompt = do+    -- If other parts of the program have written text, make sure that it+    -- appears before we interact with the user on the terminal.+    liftIO $ hFlush stdout+    rterm <- ask+    case termOps rterm of+        Right fops -> liftIO $ do+                        putStrOut rterm prompt+                        doFile fops+        Left tops -> do+            -- If the prompt contains newlines, print all but the last line.+            let (lastLine,rest) = break (`elem` "\r\n") $ reverse prompt+            outputStr $ reverse rest+            doTerm tops $ reverse lastLine  ------------ -- Interrupt
System/Console/Haskeline/Backend.hs view
@@ -1,28 +1,55 @@ module System.Console.Haskeline.Backend where  import System.Console.Haskeline.Term+import System.Console.Haskeline.Monads+import Control.Monad+import System.IO (stdin, hGetEcho, Handle)  #ifdef MINGW import System.Console.Haskeline.Backend.Win32 as Win32 #else+import System.Console.Haskeline.Backend.Posix as Posix #ifdef TERMINFO import System.Console.Haskeline.Backend.Terminfo as Terminfo #endif import System.Console.Haskeline.Backend.DumbTerm as DumbTerm #endif -myRunTerm :: IO RunTerm +defaultRunTerm :: IO RunTerm+defaultRunTerm = (liftIO (hGetEcho stdin) >>= guard >> stdinTTY)+                    `orElse` fileHandleRunTerm stdin++terminalRunTerm :: IO RunTerm+terminalRunTerm = directTTY `orElse` fileHandleRunTerm stdin++stdinTTY :: MaybeT IO RunTerm #ifdef MINGW-myRunTerm = win32Term+stdinTTY = win32TermStdin #else+stdinTTY = stdinTTYHandles >>= runDraw+#endif++directTTY :: MaybeT IO RunTerm+#ifdef MINGW+directTTY = win32Term+#else+directTTY = ttyHandles >>= runDraw+#endif+++#ifndef MINGW+runDraw :: Handles -> MaybeT IO RunTerm #ifndef TERMINFO-myRunTerm = runDumbTerm+runDraw = runDumbTerm #else-myRunTerm = do-    mRun <- runTerminfoDraw-    case mRun of -        Nothing -> runDumbTerm-        Just run -> return run+runDraw h = runTerminfoDraw h `mplus` runDumbTerm h #endif+#endif++fileHandleRunTerm :: Handle -> IO RunTerm+#ifdef MINGW+fileHandleRunTerm = Win32.fileRunTerm+#else+fileHandleRunTerm = Posix.fileRunTerm #endif
System/Console/Haskeline/Backend/DumbTerm.hs view
@@ -9,6 +9,7 @@ import System.IO import qualified Data.ByteString as B import Control.Concurrent.Chan+import Control.Monad(liftM)  -- TODO:  ---- Put "<" and ">" at end of term if scrolls off.@@ -23,17 +24,17 @@ newtype DumbTerm m a = DumbTerm {unDumbTerm :: StateT Window (PosixT m) a}                 deriving (Monad, MonadIO, MonadException,                           MonadState Window,-                          MonadReader Handle, MonadReader Encoders)+                          MonadReader Handles, MonadReader Encoders)  type DumbTermM a = forall m . (MonadIO m, MonadReader Layout m) => DumbTerm m a  instance MonadTrans DumbTerm where     lift = DumbTerm . lift . lift . lift -runDumbTerm :: IO RunTerm-runDumbTerm = do-    ch <- newChan-    posixRunTerm $ \enc h ->+runDumbTerm :: Handles -> MaybeT IO RunTerm+runDumbTerm h = do+    ch <- liftIO newChan+    posixRunTerm h $ \enc ->                 TermOps {                         getLayout = tryGetLayouts (posixLayouts h)                         , withGetEvent = withPosixGetEvent ch h enc []@@ -55,7 +56,7 @@        printText :: MonadIO m => String -> DumbTerm m () printText str = do-    h <- ask+    h <- liftM hOut ask     posixEncode str >>= liftIO . B.hPutStr h     liftIO $ hFlush h 
System/Console/Haskeline/Backend/Posix.hsc view
@@ -4,10 +4,14 @@                         tryGetLayouts,                         PosixT,                         runPosixT,+                        Handles(..),                         Encoders(),                         posixEncode,                         mapLines,-                        posixRunTerm+                        stdinTTYHandles,+                        ttyHandles,+                        posixRunTerm,+                        fileRunTerm                  ) where  import Foreign@@ -18,16 +22,15 @@ import Control.Concurrent hiding (throwTo) import Data.Maybe (catMaybes) import System.Posix.Signals.Exts-import System.Posix.IO(stdInput)+import System.Posix.Types(Fd(..)) import Data.List import System.IO import qualified Data.ByteString as B-import Data.ByteString.Char8 as Char8 (pack) import System.Environment  import System.Console.Haskeline.Monads import System.Console.Haskeline.Key-import System.Console.Haskeline.Term+import System.Console.Haskeline.Term as Term import System.Console.Haskeline.Prefs  import System.Console.Haskeline.Backend.IConv@@ -50,13 +53,18 @@ #endif #include <sys/ioctl.h> +-----------------------------------------------+-- Input/output handles+data Handles = Handles {hIn, hOut :: Handle,+                        closeHandles :: IO ()}+ ------------------- -- Window size  foreign import ccall ioctl :: FD -> CULong -> Ptr a -> IO CInt -posixLayouts :: Handle -> [IO (Maybe Layout)]-posixLayouts h = [ioctlLayout h, envLayout]+posixLayouts :: Handles -> [IO (Maybe Layout)]+posixLayouts h = [ioctlLayout $ hOut h, envLayout]  ioctlLayout :: Handle -> IO (Maybe Layout) ioctlLayout h = allocaBytes (#size struct winsize) $ \ws -> do@@ -101,9 +109,9 @@ -- Key sequences  getKeySequences :: (MonadIO m, MonadReader Prefs m)-        => [(String,Key)] -> m (TreeMap Char Key)-getKeySequences tinfos = do-    sttys <- liftIO sttyKeys+        => Handles -> [(String,Key)] -> m (TreeMap Char Key)+getKeySequences h tinfos = do+    sttys <- liftIO $ sttyKeys h     customKeySeqs <- getCustomKeySeqs     -- note ++ acts as a union; so the below favors sttys over tinfos     return $ listToTree@@ -142,9 +150,10 @@             ]  -sttyKeys :: IO [(String, Key)]-sttyKeys = do-    attrs <- getTerminalAttributes stdInput+sttyKeys :: Handles -> IO [(String, Key)]+sttyKeys h = do+    fd <- unsafeHandleToFD $ hIn h+    attrs <- getTerminalAttributes (Fd fd)     let getStty (k,c) = do {str <- controlChar attrs k; return ([str],c)}     return $ catMaybes $ map getStty [(Erase,simpleKey Backspace),(Kill,simpleKey KillLine)]                         @@ -199,12 +208,12 @@ -----------------------------  withPosixGetEvent :: (MonadException m, MonadReader Prefs m) -        => Chan Event -> Handle -> Encoders -> [(String,Key)]+        => Chan Event -> Handles -> Encoders -> [(String,Key)]                 -> (m Event -> m a) -> m a withPosixGetEvent eventChan h enc termKeys f = wrapTerminalOps h $ do-    baseMap <- getKeySequences termKeys+    baseMap <- getKeySequences h termKeys     withWindowHandler eventChan-        $ f $ liftIO $ getEvent enc baseMap eventChan+        $ f $ liftIO $ getEvent h enc baseMap eventChan  withWindowHandler :: MonadException m => Chan Event -> m a -> m a withWindowHandler eventChan = withHandler windowChange $ @@ -222,82 +231,92 @@     old_handler <- liftIO $ installHandler signal handler Nothing     f `finally` liftIO (installHandler signal old_handler Nothing) -getEvent :: Encoders -> TreeMap Char Key -> Chan Event -> IO Event-getEvent enc baseMap = keyEventLoop readKeyEvents+getEvent :: Handles -> Encoders -> TreeMap Char Key -> Chan Event -> IO Event+getEvent Handles {hIn=h} enc baseMap = keyEventLoop readKeyEvents   where     bufferSize = 32     readKeyEvents = do         -- 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.-        blockUntilInput-        bs <- B.hGetNonBlocking stdin bufferSize-        cs <- convert (localeToUnicode enc) bs+        blockUntilInput h+        bs <- B.hGetNonBlocking h bufferSize+        cs <- convert h (localeToUnicode enc) bs         return [KeyInput $ lexKeys baseMap cs]  -- Different versions of ghc work better using different functions.-blockUntilInput :: IO ()+blockUntilInput :: Handle -> 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 ()+blockUntilInput h = hWaitForInput h (-1) >> return () #else -- hWaitForInput doesn't work with -threaded on ghc < 6.10 -- (#2363 in ghc's trac)-blockUntilInput = threadWaitRead stdInput+blockUntilInput h = unsafeHandleToFd h >>= threadWaitRead #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.-convert :: (B.ByteString -> IO (String,Result)) -> B.ByteString -> IO String-convert decoder bs = do+convert :: Handle -> (B.ByteString -> IO (String,Result))+            -> B.ByteString -> IO String+convert h decoder bs = do     (cs,result) <- decoder bs     case result of         Incomplete rest -> do-                    extra <- B.hGetNonBlocking stdin 1+                    extra <- B.hGetNonBlocking h 1                     if B.null extra                         then return (cs ++ "?")-                        else fmap (cs ++) $ convert decoder (rest `B.append` extra)-        Invalid rest -> fmap ((cs ++) . ('?':)) $ convert decoder (B.drop 1 rest)+                        else fmap (cs ++)+                                $ convert h decoder (rest `B.append` extra)+        Invalid rest -> fmap ((cs ++) . ('?':)) $ convert h decoder (B.drop 1 rest)         _ -> return cs -getMultiByteChar :: (B.ByteString -> IO (String,Result)) -> IO Char-getMultiByteChar decoder = hWithBinaryMode stdin $ do-    b <- getChar-    cs <- convert decoder (Char8.pack [b])+getMultiByteChar :: Handle -> (B.ByteString -> IO (String,Result))+                        -> MaybeT IO Char+getMultiByteChar h decoder = hWithBinaryMode h $ do+    b <- hGetByte h+    cs <- liftIO $ convert h decoder (B.pack [b])     case cs of         [] -> return '?' -- shouldn't happen, but doesn't hurt to be careful.         (c:_) -> return c  --- fails if stdin is not a handle or if we couldn't access /dev/tty.-openTTY :: IO (Maybe Handle)-openTTY = do-    inIsTerm <- hIsTerminalDevice stdin-    if inIsTerm-        then handle (\(_::IOException) -> return Nothing) $ do+stdinTTYHandles, ttyHandles :: MaybeT IO Handles+stdinTTYHandles = do+    isInTerm <- liftIO $ hIsTerminalDevice stdin+    guard isInTerm+    h <- openTerm WriteMode+    -- Don't close stdin, since a different part of the program may use it later.+    return Handles { hIn = stdin, hOut = h, closeHandles = hClose h }++ttyHandles = do+    -- Open the input and output separately, since they need different buffering.+    h_in <- openTerm ReadMode+    h_out <- openTerm WriteMode+    return Handles { hIn = h_in, hOut = h_out,+                     closeHandles = hClose h_in >> hClose h_out }++openTerm :: IOMode -> MaybeT IO Handle+openTerm mode = handle (\(_::IOException) -> mzero)             -- 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+            $ liftIO $ openBinaryFile "/dev/tty" mode -posixRunTerm :: (Encoders -> Handle -> TermOps) -> IO RunTerm-posixRunTerm tOps = do-    fileRT <- fileRunTerm+posixRunTerm :: MonadIO m => Handles -> (Encoders -> TermOps) -> m RunTerm+posixRunTerm hs tOps = liftIO $ do     codeset <- getCodeset-    ttyH <- openTTY-    encoders <- liftM2 Encoders (openEncoder codeset) (openPartialDecoder codeset)-    case ttyH of-        Nothing -> return fileRT-        Just h -> return fileRT {-                    closeTerm = closeTerm fileRT >> hClose h,-                    -- NOTE: could also alloc Encoders once for each call to wrapRunTerm-                    termOps = Just $ tOps encoders h-                }+    encoders <- liftM2 Encoders (openEncoder codeset)+                                         (openPartialDecoder codeset)+    fileRT <- fileRunTerm $ hIn hs+    return fileRT {+                closeTerm = closeTerm fileRT >> closeHandles hs,+                -- NOTE: could also alloc Encoders once for each call to wrapRunTerm+                termOps = Left $ tOps encoders+            } -type PosixT m = ReaderT Encoders (ReaderT Handle m)+type PosixT m = ReaderT Encoders (ReaderT Handles m)  data Encoders = Encoders {unicodeToLocale :: String -> IO B.ByteString,                           localeToUnicode :: B.ByteString -> IO (String, Result)}@@ -307,42 +326,44 @@     encoder <- asks unicodeToLocale     liftIO $ encoder str -runPosixT :: Monad m => Encoders -> Handle -> PosixT m a -> m a+runPosixT :: Monad m => Encoders -> Handles -> PosixT m a -> m a runPosixT enc h = runReaderT' h . runReaderT' enc -putTerm :: B.ByteString -> IO ()-putTerm str = B.putStr str >> hFlush stdout+putTerm :: Handle -> B.ByteString -> IO ()+putTerm h str = B.hPutStr h str >> hFlush h -fileRunTerm :: IO RunTerm-fileRunTerm = do+fileRunTerm :: Handle -> IO RunTerm+fileRunTerm h_in = do+    let h_out = stdout     oldLocale <- setLocale (Just "")     codeset <- getCodeset     let encoder str = join $ fmap ($ str) $ openEncoder codeset     let decoder str = join $ fmap ($ str) $ openDecoder codeset     decoder' <- openPartialDecoder codeset-    return RunTerm {putStrOut = \str -> encoder str >>= putTerm,+    return RunTerm {putStrOut = encoder >=> putTerm h_out,                 closeTerm = setLocale oldLocale >> return (),                 wrapInterrupt = withSigIntHandler,                 encodeForTerm = encoder,                 decodeForTerm = decoder,-                getLocaleChar = getMultiByteChar decoder',-                termOps = Nothing+                termOps = Right FileOps {+                            inputHandle = h_in,+                            getLocaleChar = getMultiByteChar h_in decoder',+                            maybeReadNewline = hMaybeReadNewline h_in,+                            getLocaleLine = Term.hGetLine h_in+                                                >>= liftIO . decoder+                        }+                 }  -- NOTE: If we set stdout to NoBuffering, there can be a flicker effect when many -- characters are printed at once.  We'll keep it buffered here, and let the Draw -- monad manually flush outputs that don't print a newline.-wrapTerminalOps:: MonadException m => Handle -> m a -> m a-wrapTerminalOps outH =-    bracketSet (hGetBuffering stdin) (hSetBuffering stdin) NoBuffering+wrapTerminalOps :: MonadException m => Handles -> m a -> m a+wrapTerminalOps Handles {hIn = h_in, hOut = h_out} = +    bracketSet (hGetBuffering h_in) (hSetBuffering h_in) NoBuffering     -- TODO: block buffering?  Certain \r and \n's are causing flicker...     -- - moving to the right     -- - breaking line after offset widechar?-    . bracketSet (hGetBuffering outH) (hSetBuffering outH) LineBuffering-    . bracketSet (hGetEcho stdin) (hSetEcho stdin) False-    . hWithBinaryMode stdin--bracketSet :: (Eq a, MonadException m) => IO a -> (a -> IO ()) -> a -> m b -> m b-bracketSet getState set newState f = bracket (liftIO getState)-                            (liftIO . set)-                            (\_ -> liftIO (set newState) >> f)+    . bracketSet (hGetBuffering h_out) (hSetBuffering h_out) LineBuffering+    . bracketSet (hGetEcho h_in) (hSetEcho h_in) False+    . hWithBinaryMode h_in
System/Console/Haskeline/Backend/Terminfo.hs view
@@ -131,26 +131,26 @@     deriving (Monad, MonadIO, MonadException,               MonadReader Actions, MonadReader Terminal, MonadState TermPos,               MonadState TermRows,-              MonadReader Handle, MonadReader Encoders)+              MonadReader Handles, MonadReader Encoders)  type DrawM a = forall m . (MonadReader Layout m, MonadIO m) => Draw m a  instance MonadTrans Draw where     lift = Draw . lift . lift . lift . lift . lift . lift     -runTerminfoDraw :: IO (Maybe RunTerm)-runTerminfoDraw = do-    mterm <- Exception.try setupTermFromEnv-    ch <- newChan+runTerminfoDraw :: Handles -> MaybeT IO RunTerm+runTerminfoDraw h = do+    mterm <- liftIO $ Exception.try setupTermFromEnv+    ch <- liftIO newChan     case mterm of-        Left (_::SetupTermError) -> return Nothing-        Right term -> case getCapability term getActions of-            Nothing -> return Nothing-            Just actions -> fmap Just $ posixRunTerm $ \enc h ->+        Left (_::SetupTermError) -> mzero+        Right term -> do+            actions <- MaybeT $ return $ getCapability term getActions+            posixRunTerm h $ \enc ->                 TermOps {                     getLayout = tryGetLayouts (posixLayouts h                                                 ++ [tinfoLayout term])-                    , withGetEvent = wrapKeypad h term+                    , withGetEvent = wrapKeypad (hOut h) term                                         . withPosixGetEvent ch h enc                                             (terminfoKeys term)                     , runTerm = \(RunTermType f) -> @@ -201,7 +201,7 @@ output f = do     toutput <- asks f     term <- ask-    ttyh <- ask+    ttyh <- liftM hOut ask     liftIO $ hRunTermOutput ttyh term toutput  
System/Console/Haskeline/Backend/WCWidth.hs view
@@ -17,8 +17,8 @@  wcwidth :: Char -> Int wcwidth c = case mk_wcwidth $ toEnum $ fromEnum c of-                -1 -> 0 -- shouldn't happen, since control characters-                        -- aren't turned into graphemes; but better to be safe.+                -1 -> 0 -- Control characters have zero width.  (Used by the+                        -- "\SOH...\STX" hack in LineState.stringToGraphemes.)                 w -> w  gWidth :: Grapheme -> Int
System/Console/Haskeline/Backend/Win32.hsc view
@@ -1,5 +1,7 @@ module System.Console.Haskeline.Backend.Win32(
-                win32Term
+                win32Term,
+                win32TermStdin,
+                fileRunTerm
                 )where
 
 
@@ -7,7 +9,7 @@ import Foreign
 import Foreign.C
 import System.Win32 hiding (multiByteToWideChar)
-import Graphics.Win32.Misc(getStdHandle, sTD_INPUT_HANDLE, sTD_OUTPUT_HANDLE)
+import Graphics.Win32.Misc(getStdHandle, sTD_OUTPUT_HANDLE)
 import Data.List(intercalate)
 import Control.Concurrent hiding (throwTo)
 import Data.Char(isPrint)
@@ -17,7 +19,7 @@ import System.Console.Haskeline.Key
 import System.Console.Haskeline.Monads
 import System.Console.Haskeline.LineState
-import System.Console.Haskeline.Term
+import System.Console.Haskeline.Term as Term
 
 import Data.ByteString.Internal (createAndTrim)
 import qualified Data.ByteString as B
@@ -53,13 +55,19 @@         else do
             es <- readEvents h
             return $ mapMaybe processEvent es
-                       
-getConOut :: IO (Maybe HANDLE)
-getConOut = handle (\(_::IOException) -> return Nothing) $ fmap Just
-    $ createFile "CONOUT$" (gENERIC_READ .|. gENERIC_WRITE)
+
+consoleHandles :: MaybeT IO Handles
+consoleHandles = do
+    h_in <- open "CONIN$"
+    h_out <- open "CONOUT$"
+    return Handles { hIn = h_in, hOut = h_out }
+  where
+   open file = handle (\(_::IOException) -> mzero) $ liftIO
+                $ createFile file (gENERIC_READ .|. gENERIC_WRITE)
                         (fILE_SHARE_READ .|. fILE_SHARE_WRITE) Nothing
-                    oPEN_EXISTING 0 Nothing
+                        oPEN_EXISTING 0 Nothing
 
+                       
 processEvent :: InputEvent -> Maybe Event
 processEvent KeyEvent {keyDown = True, unicodeChar = c, virtualKeyCode = vc,
                     controlKeyState = cstate}
@@ -215,9 +223,9 @@ foreign import stdcall "windows.h SetConsoleMode" c_SetConsoleMode
     :: HANDLE -> DWORD -> IO Bool
 
-withWindowMode :: MonadException m => m a -> m a
-withWindowMode f = do
-    h <- liftIO $ getStdHandle sTD_INPUT_HANDLE
+withWindowMode :: MonadException m => Handles -> m a -> m a
+withWindowMode hs f = do
+    let h = hIn hs
     bracket (getConsoleMode h) (setConsoleMode h)
             $ \m -> setConsoleMode h (m .|. (#const ENABLE_WINDOW_INPUT)) >> f
   where
@@ -229,25 +237,30 @@ ----------------------------
 -- Drawing
 
-newtype Draw m a = Draw {runDraw :: ReaderT HANDLE m a}
-    deriving (Monad,MonadIO,MonadException, MonadReader HANDLE)
+data Handles = Handles { hIn, hOut :: HANDLE }
 
+closeHandles :: Handles -> IO ()
+closeHandles hs = closeHandle (hIn hs) >> closeHandle (hOut hs)
+
+newtype Draw m a = Draw {runDraw :: ReaderT Handles m a}
+    deriving (Monad,MonadIO,MonadException, MonadReader Handles)
+
 type DrawM a = (MonadIO m, MonadReader Layout m) => Draw m ()
 
 instance MonadTrans Draw where
     lift = Draw . lift
 
 getPos :: MonadIO m => Draw m Coord
-getPos = ask >>= liftIO . getPosition
+getPos = asks hOut >>= liftIO . getPosition
     
 setPos :: MonadIO m => Coord -> Draw m ()
 setPos c = do
-    h <- ask
+    h <- asks hOut
     liftIO (setPosition h c)
 
 printText :: MonadIO m => String -> Draw m ()
 printText txt = do
-    h <- ask
+    h <- asks hOut
     liftIO (writeConsole h txt)
     
 printAfter :: String -> DrawM ()
@@ -278,7 +291,9 @@ crlf = "\r\n"
 
 instance (MonadException m, MonadReader Layout m) => Term (Draw m) where
-    drawLineDiff = drawLineDiffWin
+    drawLineDiff (xs1,ys1) (xs2,ys2) = let
+        fixEsc = filter ((/= '\ESC') . baseChar)
+        in drawLineDiffWin (fixEsc xs1, fixEsc ys1) (fixEsc xs2, fixEsc ys2)
     -- 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.
@@ -300,46 +315,52 @@     ringBell True = liftIO messageBeep
     ringBell False = return () -- TODO
 
-win32Term :: IO RunTerm
+win32TermStdin :: MaybeT IO RunTerm
+win32TermStdin = do
+    liftIO (hIsTerminalDevice stdin) >>= guard
+    win32Term
+
+win32Term :: MaybeT IO RunTerm
 win32Term = do
-    fileRT <- fileRunTerm
-    inIsTerm <- hIsTerminalDevice stdin
-    if not inIsTerm
-        then return fileRT
-        else do
-            oterm <- getConOut
-            case oterm of
-                Nothing -> return fileRT
-                Just h -> do
-                        ch <- newChan
-                        return fileRT {
-                            wrapInterrupt = withWindowMode . withCtrlCHandler,
-                            termOps = Just TermOps {
-                                getLayout = getBufferSize h
-                                , withGetEvent = win32WithEvent ch
+    hs <- consoleHandles
+    ch <- liftIO newChan
+    fileRT <- liftIO $ fileRunTerm stdin
+    return fileRT {
+                            termOps = Left TermOps {
+                                getLayout = getBufferSize (hOut hs)
+                                , withGetEvent = withWindowMode hs
+                                                    . win32WithEvent hs ch
                                 , runTerm = \(RunTermType f) ->
-                                        runReaderT' h $ runDraw f
+                                        runReaderT' hs $ runDraw f
                                 },
-                            closeTerm = closeHandle h}
+                            closeTerm = closeHandles hs
+                        }
 
-win32WithEvent :: MonadException m => Chan Event -> (m Event -> m a) -> m a
-win32WithEvent eventChan f = do
-    inH <- liftIO $ getStdHandle sTD_INPUT_HANDLE
-    f $ liftIO $ getEvent inH eventChan
+win32WithEvent :: MonadException m => Handles -> Chan Event
+                                        -> (m Event -> m a) -> m a
+win32WithEvent h eventChan f = f $ liftIO $ getEvent (hIn h) eventChan
 
 -- stdin is not a terminal, but we still need to check the right way to output unicode to stdout.
-fileRunTerm :: IO RunTerm
-fileRunTerm = do
+fileRunTerm :: Handle -> IO RunTerm
+fileRunTerm h_in = do
     putter <- putOut
     cp <- getCodePage
-    return RunTerm {termOps = Nothing,
+    return RunTerm {
                     closeTerm = return (),
                     putStrOut = putter,
                     encodeForTerm = unicodeToCodePage cp,
                     decodeForTerm = codePageToUnicode cp,
-                    getLocaleChar = getMultiByteChar cp,
-                    wrapInterrupt = withCtrlCHandler}
+                    wrapInterrupt = withCtrlCHandler,
+                    termOps = Right FileOps {
+                                inputHandle = h_in,
+                                getLocaleChar = getMultiByteChar cp h_in,
+                                maybeReadNewline = hMaybeReadNewline h_in,
+                                getLocaleLine = Term.hGetLine h_in
+                                            >>= liftIO . codePageToUnicode cp
+                            }
 
+                    }
+
 -- On Windows, Unicode written to the console must be written with the WriteConsole API call.
 -- And to make the API cross-platform consistent, Unicode to a file should be UTF-8.
 putOut :: IO (String -> IO ())
@@ -352,9 +373,9 @@         else do
             cp <- getCodePage
             return $ \str -> unicodeToCodePage cp str >>= B.putStr >> hFlush stdout
-                
-    
 
+
+
 type Handler = DWORD -> IO BOOL
 
 foreign import ccall "wrapper" wrapHandler :: Handler -> IO (FunPtr Handler)
@@ -420,16 +441,15 @@ foreign import stdcall "IsDBCSLeadByteEx" c_IsDBCSLeadByteEx
         :: CodePage -> BYTE -> BOOL
 
-getMultiByteChar :: CodePage -> IO Char
-getMultiByteChar cp = hWithBinaryMode stdin $ do
-    b1 <- getByte
-    bs <- if c_IsDBCSLeadByteEx cp b1
-            then getByte >>= \b2 -> return [b1,b2]
-            else return [b1]
-    cs <- codePageToUnicode cp (B.pack bs)
-    case cs of
-        [] -> getMultiByteChar cp
-        (c:_) -> return c
+getMultiByteChar :: CodePage -> Handle -> MaybeT IO Char
+getMultiByteChar cp h = hWithBinaryMode h loop
   where
-    -- NOTE: relies on getChar returning an 8-bit Char.
-    getByte = fmap (toEnum . fromEnum) getChar
+    loop = do
+        b1 <- hGetByte h
+        bs <- if c_IsDBCSLeadByteEx cp b1
+                then hGetByte h >>= \b2 -> return [b1,b2]
+                else return [b1]
+        cs <- liftIO $ codePageToUnicode cp (B.pack bs)
+        case cs of
+            [] -> loop
+            (c:_) -> return c
System/Console/Haskeline/Command.hs view
@@ -34,7 +34,7 @@ import System.Console.Haskeline.LineState import System.Console.Haskeline.Key -data Effect = LineChange (String -> LineChars)+data Effect = LineChange (Prefix -> LineChars)               | PrintLines [String]               | ClearScreen               | RingBell
System/Console/Haskeline/Command/History.hs view
@@ -87,8 +87,8 @@ instance LineState SearchMode where     beforeCursor _ sm = beforeCursor prefix (foundHistory sm)         where -            prefix = "(" ++ directionName (direction sm) ++ ")`" -                            ++ graphemesToString (searchTerm sm) ++ "': "+            prefix = stringToGraphemes ("(" ++ directionName (direction sm) ++ ")`")+                            ++ searchTerm sm ++ stringToGraphemes "': "     afterCursor = afterCursor . foundHistory  instance Result SearchMode where
System/Console/Haskeline/Completion.hs view
@@ -1,11 +1,12 @@ module System.Console.Haskeline.Completion(                             CompletionFunc,                             Completion(..),-                            completeWord,-                            completeQuotedWord,-                            -- * Building 'CompletionFunc's                             noCompletion,                             simpleCompletion,+                            -- * Word completion+                            completeWord,+                            completeWordWithPrev,+                            completeQuotedWord,                             -- * Filename completion                             completeFilename,                             listFiles,@@ -47,17 +48,33 @@ -------------- -- Word break functions --- | The following function creates a custom 'CompletionFunc' for use in the 'Settings.'+-- | A custom 'CompletionFunc' which completes the word immediately to the left of the cursor.+--+-- A word begins either at the start of the line or after an unescaped whitespace character. completeWord :: Monad m => Maybe Char          -- ^ An optional escape character-        -> String -- ^ List of characters which count as whitespace+        -> [Char]-- ^ Characters which count as whitespace         -> (String -> m [Completion]) -- ^ Function to produce a list of possible completions         -> CompletionFunc m-completeWord esc ws f (line, _) = do+completeWord esc ws = completeWordWithPrev esc ws . const++-- | A custom 'CompletionFunc' which completes the word immediately to the left of the cursor,+-- and takes into account the line contents to the left of the word.+--+-- A word begins either at the start of the line or after an unescaped whitespace character.+completeWordWithPrev :: Monad m => Maybe Char+        -- ^ An optional escape character+        -> [Char]-- ^ Characters which count as whitespace+        -> (String ->  String -> m [Completion])+            -- ^ Function to produce a list of possible completions.  The first argument is the+            -- line contents to the left of the word, reversed.  The second argument is the word+            -- to be completed.+        -> CompletionFunc m+completeWordWithPrev esc ws f (line, _) = do     let (word,rest) = case esc of                         Nothing -> break (`elem` ws) line                         Just e -> escapedBreak e line-    completions <- f (reverse word)+    completions <- f rest (reverse word)     return (rest,map (escapeReplacement esc ws) completions)   where     escapedBreak e (c:d:cs) | d == e && c `elem` (e:ws)@@ -65,7 +82,7 @@     escapedBreak e (c:cs) | notElem c ws             = let (xs,ys) = escapedBreak e cs in (c:xs,ys)     escapedBreak _ cs = ("",cs)-    + -- | Create a finished completion out of the given word. simpleCompletion :: String -> Completion simpleCompletion = completion@@ -100,7 +117,7 @@ --------- -- Quoted completion completeQuotedWord :: Monad m => Maybe Char -- ^ An optional escape character-                            -> String -- ^ List of characters which set off quotes+                            -> [Char] -- ^ Characters which set off quotes                             -> (String -> m [Completion]) -- ^ Function to produce a list of possible completions                             -> CompletionFunc m -- ^ Alternate completion to perform if the                                              -- cursor is not at a quoted word
System/Console/Haskeline/InputT.hs view
@@ -15,6 +15,7 @@ import System.FilePath import Control.Applicative import qualified Control.Monad.State as State+import System.IO  -- | Application-specific customizations to the user interface. data Settings m = Settings {complete :: CompletionFunc m, -- ^ Custom tab completion.@@ -73,20 +74,102 @@         settings <- ask         lift $ lift $ lift $ lift $ lift $ lift $ complete settings lcs +-- | Run a line-reading application.  Uses 'defaultBehavior' to determine the+-- interaction behavior. 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-        --- | Run a line-reading application, reading user 'Prefs' from --- @~/.haskeline@+runInputTWithPrefs = runInputTBehaviorWithPrefs defaultBehavior++-- | Run a line-reading application.  This function should suffice for most applications.+--+-- This function is equivalent to @'runInputTBehavior' 'defaultBehavior'@.  It +-- uses terminal-style interaction if 'stdin' is connected to a terminal and has+-- echoing enabled.  Otherwise (e.g., if 'stdin' is a pipe), it uses file-style interaction.+--+-- If it uses terminal-style interaction, 'Prefs' will be read from the user's @~/.haskeline@ file+-- (if present).+-- If it uses file-style interaction, 'Prefs' are not relevant and will not be read. runInputT :: MonadException m => Settings m -> InputT m a -> m a-runInputT settings f = do-    prefs <- liftIO readPrefsFromHome-    runInputTWithPrefs prefs settings f+runInputT = runInputTBehavior defaultBehavior++-- | Returns 'True' if the current session uses terminal-style interaction.  (See 'Behavior'.)+haveTerminalUI :: Monad m => InputT m Bool+haveTerminalUI = asks isTerminalStyle+++{- | Haskeline has two ways of interacting with the user:++ * \"Terminal-style\" interaction provides an rich user interface by connecting+   to the user's terminal (which may be different than 'stdin' or 'stdout').  + + * \"File-style\" interaction treats the input as a simple stream of characters, for example+    when reading from a file or pipe.  Input functions (e.g., @getInputLine@) print the prompt to 'stdout'.+ + A 'Behavior' is a method for deciding at run-time which type of interaction to use.  + + For most applications (e.g., a REPL), 'defaultBehavior' should have the correct effect.+-}+data Behavior = Behavior (IO RunTerm)++-- | Create and use a RunTerm, ensuring that it will be closed even if+-- an async exception occurs during the creation or use.+withBehavior :: MonadException m => Behavior -> (RunTerm -> m a) -> m a+withBehavior (Behavior run) f = bracket (liftIO run) (liftIO . closeTerm) f++-- | Run a line-reading application according to the given behavior.+--+-- If it uses terminal-style interaction, 'Prefs' will be read from the+-- user's @~/.haskeline@ file (if present).+-- If it uses file-style interaction, 'Prefs' are not relevant and will not be read.+runInputTBehavior :: MonadException m => Behavior -> Settings m -> InputT m a -> m a+runInputTBehavior behavior settings f = withBehavior behavior $ \run -> do+    prefs <- if isTerminalStyle run+                then liftIO readPrefsFromHome+                else return defaultPrefs+    execInputT prefs settings run f++-- | Run a line-reading application.+runInputTBehaviorWithPrefs :: MonadException m+    => Behavior -> Prefs -> Settings m -> InputT m a -> m a+runInputTBehaviorWithPrefs behavior prefs settings f+    = withBehavior behavior $ flip (execInputT prefs settings) f++-- | Helper function to feed the parameters into an InputT.+execInputT :: MonadException m => Prefs -> Settings m -> RunTerm+                -> InputT m a -> m a+execInputT prefs settings run (InputT f)+    = runReaderT' settings $ runReaderT' prefs+            $ runKillRing+            $ runHistoryFromFile (historyFile settings) (maxHistorySize prefs)+            $ runReaderT f run+++-- | Read input from 'stdin'.  +-- Use terminal-style interaction if 'stdin' is connected to+-- a terminal and has echoing enabled.  Otherwise (e.g., if 'stdin' is a pipe), use+-- file-style interaction.+--+-- This behavior should suffice for most applications.  +defaultBehavior :: Behavior+defaultBehavior = Behavior defaultRunTerm++-- | Use file-style interaction, reading input from the given 'Handle'.  +useFileHandle :: Handle -> Behavior+useFileHandle = Behavior . fileHandleRunTerm++-- | Use file-style interaction, reading input from the given file.+useFile :: FilePath -> Behavior+useFile file = Behavior $ do+            h <- openBinaryFile file ReadMode+            rt <- fileHandleRunTerm h+            return rt { closeTerm = closeTerm rt >> hClose h}++-- | Use terminal-style interaction whenever possible, even if 'stdin' and/or 'stdout' are not+-- terminals.+--+-- If it cannot open the user's terminal, use file-style interaction, reading input from 'stdin'.+preferTerm :: Behavior+preferTerm = Behavior terminalRunTerm+  -- | Read 'Prefs' from @~/.haskeline.@   If there is an error reading the file, -- the 'defaultPrefs' will be returned.
System/Console/Haskeline/LineState.hs view
@@ -11,6 +11,7 @@                     mapBaseChars,                     -- * Line State class                     LineState(..),+                    Prefix,                     -- ** Convenience functions for the drawing backends                     LineChars,                     lineChars,@@ -62,6 +63,9 @@                     applyCmdArg,                     -- ** Other line state types                     Message(..),+                    Password(..),+                    addPasswordChar,+                    deletePasswordChar,                     ) where  import Data.Char@@ -106,6 +110,15 @@ stringToGraphemes = mkString . dropWhile isCombiningChar     where         mkString [] = []+        -- Minor hack: "\ESC...\STX" or "\SOH\ESC...\STX", where "\ESC..." is some+        -- control sequence (e.g., ANSI colors), is represented as a grapheme+        -- of zero length with '\ESC' as the base character.+        -- Note that this won't round-trip correctly with graphemesToString.+        -- In practice, however, that's fine since control characters can only occur+        -- in the prompt.+        mkString ('\SOH':cs) = stringToGraphemes cs+        mkString ('\ESC':cs) | (ctrl,'\STX':rest) <- break (=='\STX') cs+                    = Grapheme '\ESC' ctrl : stringToGraphemes rest         mkString (c:cs) = Grapheme c (takeWhile isCombiningChar cs)                                 : mkString (dropWhile isCombiningChar cs) @@ -115,18 +128,20 @@ -- | 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 -- ^ The input prefix.+    beforeCursor :: Prefix -- ^ The input prefix.                     -> s -- ^ The current line state.-                    -> [Grapheme] -- ^ The text to the left of the cursor, reversed.  (This -                                  -- includes the prefix.)+                    -> [Grapheme] -- ^ The text to the left of the cursor+                                  -- (including the prefix).     afterCursor :: s -> [Grapheme] -- ^ The text under and to the right of the cursor. +type Prefix = [Grapheme]+ -- | 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 :: LineState s => Prefix -> s -> LineChars lineChars prefix s = (beforeCursor prefix s, afterCursor s)  -- | Compute the number of characters under and to the right of the cursor.@@ -155,7 +170,7 @@                     deriving (Show, Eq)  instance LineState InsertMode where-    beforeCursor prefix (IMode xs _) = stringToGraphemes prefix ++ reverse xs+    beforeCursor prefix (IMode xs _) = prefix ++ reverse xs     afterCursor (IMode _ ys) = ys  instance Result InsertMode where@@ -232,8 +247,8 @@                     deriving Show  instance LineState CommandMode where-    beforeCursor prefix CEmpty = stringToGraphemes prefix-    beforeCursor prefix (CMode xs _ _) = stringToGraphemes prefix ++ reverse xs+    beforeCursor prefix CEmpty = prefix+    beforeCursor prefix (CMode xs _ _) = prefix ++ reverse xs     afterCursor CEmpty = []     afterCursor (CMode _ c ys) = c:ys @@ -310,7 +325,7 @@     fmap f am = am {argState = f (argState am)}  instance LineState s => LineState (ArgMode s) where-    beforeCursor _ am = let pre = "(arg: " ++ show (arg am) ++ ") "+    beforeCursor _ am = let pre = map baseGrapheme $ "(arg: " ++ show (arg am) ++ ") "                              in beforeCursor pre (argState am)      afterCursor = afterCursor . argState @@ -347,6 +362,29 @@ instance LineState (Message s) where     beforeCursor _ = stringToGraphemes . messageText     afterCursor _ = []++----------------++data Password = Password {passwordState :: [Char], -- ^ reversed+                          passwordChar :: Maybe Char}++instance LineState Password where+    beforeCursor prefix p+        = prefix ++ (stringToGraphemes+                      $ case passwordChar p of+                        Nothing -> []+                        Just c -> replicate (length $ passwordState p) c)+    afterCursor _ = []++instance Result Password where+    toResult = reverse . passwordState++addPasswordChar :: Char -> Password -> Password+addPasswordChar c p = p {passwordState = c : passwordState p}++deletePasswordChar :: Password -> Password+deletePasswordChar (Password (_:cs) m) = Password cs m+deletePasswordChar p = p  ----------------- atStart, atEnd :: (Char -> Bool) -> InsertMode -> Bool
System/Console/Haskeline/Monads.hs view
@@ -11,7 +11,9 @@                 modify,                 update,                 MonadReader(..),-                MonadState(..)+                MonadState(..),+                MaybeT(..),+                orElse                 ) where  import Control.Monad.Trans@@ -94,3 +96,27 @@     unblock m = StateT $ \s -> unblock $ getStateTFunc m s     catch f h = StateT $ \s -> catch (getStateTFunc f s)                             $ \e -> getStateTFunc (h e) s++newtype MaybeT m a = MaybeT { unMaybeT :: m (Maybe a) }++instance Monad m => Monad (MaybeT m) where+    return x = MaybeT $ return $ Just x+    MaybeT f >>= g = MaybeT $ f >>= maybe (return Nothing) (unMaybeT . g)++instance MonadIO m => MonadIO (MaybeT m) where+    liftIO = lift . liftIO++instance MonadTrans MaybeT where+    lift = MaybeT . liftM Just++instance MonadException m => MonadException (MaybeT m) where+    block = MaybeT . block . unMaybeT+    unblock = MaybeT . unblock . unMaybeT+    catch f h = MaybeT $ catch (unMaybeT f) $ unMaybeT . h++orElse :: Monad m => MaybeT m a -> m a -> m a+orElse (MaybeT f) g = f >>= maybe g return++instance Monad m => MonadPlus (MaybeT m) where+    mzero = MaybeT $ return Nothing+    MaybeT f `mplus` MaybeT g = MaybeT $ f >>= maybe g (return . Just)
System/Console/Haskeline/Prefs.hs view
@@ -16,7 +16,7 @@ import System.Console.Haskeline.Key  {- |-'Prefs' allow the user to customize the line-editing interface.  They are+'Prefs' allow the user to customize the terminal-style line-editing interface.  They are read by default from @~/.haskeline@; to override that behavior, use 'readPrefs' and @runInputTWithPrefs@. 
System/Console/Haskeline/RunCommand.hs view
@@ -9,18 +9,20 @@  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 :: (MonadException m, CommandMonad m, MonadState Layout m,+                    LineState s)+    => TermOps -> String -> KeyCommand m s a -> s -> m a+runCommandLoop tops prefix cmds initState = runTerm tops $ +    RunTermType (withGetEvent tops+        $ runCommandLoop' tops (stringToGraphemes prefix) initState 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+runCommandLoop' :: forall t m s a . (MonadTrans t, Term (t m), CommandMonad (t m),+        MonadState Layout m, MonadReader Prefs m, LineState s)+        => TermOps -> Prefix -> s -> KeyCommand m s a -> t m Event -> t m a+runCommandLoop' tops prefix initState cmds getEvent = do+    let s = lineChars prefix initState     drawLine s-    readMoreKeys s (fmap ($ emptyIM) cmds)+    readMoreKeys s (fmap ($ initState) cmds)   where     readMoreKeys :: LineChars -> KeyMap (CmdM m a) -> t m a     readMoreKeys s next = do@@ -49,7 +51,7 @@   drawEffect :: (MonadTrans t, Term (t m), MonadReader Prefs m)-    => String -> LineChars -> Effect -> t m LineChars+    => Prefix -> LineChars -> Effect -> t m LineChars drawEffect prefix s (LineChange ch) = do     let t = ch prefix     drawLineDiff s t
System/Console/Haskeline/Term.hs view
@@ -8,13 +8,13 @@  import Control.Concurrent import Data.Typeable-import Data.ByteString (ByteString)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as B+import Data.Word import Control.Exception.Extensible (fromException, AsyncException(..),bracket_)-import System.IO(Handle)--#if __GLASGOW_HASKELL__ >= 611-import System.IO (hGetEncoding, hSetEncoding, hSetBinaryMode)-#endif+import System.IO+import Control.Monad(liftM,when,guard)+import System.IO.Error (isEOFError)  class (MonadReader Layout m, MonadException m) => Term m where     reposition :: Layout -> LineChars -> m ()@@ -29,18 +29,17 @@  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. data RunTerm = RunTerm {+            -- | Write unicode characters to stdout.             putStrOut :: String -> IO (),             encodeForTerm :: String -> IO ByteString,             decodeForTerm :: ByteString -> IO String,-            getLocaleChar :: IO Char,-            termOps :: Maybe TermOps,+            termOps :: Either TermOps FileOps,             wrapInterrupt :: MonadException m => m a -> m a,             closeTerm :: IO ()     } +-- | Operations needed for terminal-style interaction. data TermOps = TermOps {             getLayout :: IO Layout             , withGetEvent :: (MonadException m, CommandMonad m)@@ -48,6 +47,20 @@             , runTerm :: (MonadException m, CommandMonad m) => RunTermType m a -> m a         } +-- | Operations needed for file-style interaction.+data FileOps = FileOps {+            inputHandle :: Handle, -- ^ e.g. for turning off echoing.+            getLocaleLine :: MaybeT IO String,+            getLocaleChar :: MaybeT IO Char,+            maybeReadNewline :: IO ()+        }++-- | Are we using terminal-style interaction?+isTerminalStyle :: RunTerm -> Bool+isTerminalStyle r = case termOps r of+                    Left TermOps{} -> True+                    _ -> False+ -- 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 . @@ -108,8 +121,10 @@ data Layout = Layout {width, height :: Int}                     deriving (Show,Eq) ------------ Utility function since we're not using the new IO library yet.+-----------------------------------+-- Utility functions for the various backends.++-- | Utility function since we're not using the new IO library yet. hWithBinaryMode :: MonadException m => Handle -> m a -> m a #if __GLASGOW_HASKELL__ >= 611 hWithBinaryMode h = bracket (liftIO $ hGetEncoding h)@@ -118,3 +133,55 @@ #else hWithBinaryMode _ = id #endif++-- | Utility function for changing a property of a terminal for the duration of+-- a computation.+bracketSet :: (Eq a, MonadException m) => IO a -> (a -> IO ()) -> a -> m b -> m b+bracketSet getState set newState f = bracket (liftIO getState)+                            (liftIO . set)+                            (\_ -> liftIO (set newState) >> f)+++-- | Returns one 8-bit word.  Needs to be wrapped by hWithBinaryMode.+hGetByte :: Handle -> MaybeT IO Word8+hGetByte h = do+    eof <- liftIO $ hIsEOF h+    guard (not eof)+    liftIO $ liftM (toEnum . fromEnum) $ hGetChar h+++-- | Utility function to correctly get a ByteString line of input.+hGetLine :: Handle -> MaybeT IO ByteString+hGetLine h = do+    atEOF <- liftIO $ hIsEOF h+    guard (not atEOF)+    -- It's more efficient to use B.getLine, but that function throws an+    -- error if the Handle (e.g., stdin) is set to NoBuffering.+    buff <- liftIO $ hGetBuffering h+    liftIO $ if buff == NoBuffering+        then hWithBinaryMode h $ fmap B.pack $ System.IO.hGetLine h+        else B.hGetLine h++-- If another character is immediately available, and it is a newline, consume it.+--+-- Two portability fixes:+-- +-- 1) Note that in ghc-6.8.3 and earlier, hReady returns False at an EOF,+-- whereas in ghc-6.10.1 and later it throws an exception.  (GHC trac #1063).+-- This code handles both of those cases.+--+-- 2) Also note that on Windows with ghc<6.10, hReady may not behave correctly (#1198)+-- The net result is that this might cause+-- But this function will generally only be used when reading buffered input+-- (since stdin isn't a terminal), so it should probably be OK.+hMaybeReadNewline :: Handle -> IO ()+hMaybeReadNewline h = returnOnEOF () $ do+    ready <- hReady h+    when ready $ do+        c <- hLookAhead h+        when (c == '\n') $ getChar >> return ()++returnOnEOF :: MonadException m => a -> m a -> m a+returnOnEOF x = handle $ \e -> if isEOFError e+                                then return x+                                else throwIO e
System/Console/Haskeline/Vi.hs view
@@ -406,7 +406,7 @@ searchText SearchEntry {entryState = IMode xs ys} = reverse xs ++ ys  instance LineState SearchEntry where-    beforeCursor prefix se = beforeCursor (prefix ++ [searchChar se])+    beforeCursor prefix se = beforeCursor (prefix ++ stringToGraphemes [searchChar se])                                 (entryState se)     afterCursor = afterCursor . entryState 
examples/Test.hs view
@@ -9,6 +9,8 @@ Usage: ./Test          (line input) ./Test chars    (character input)+./Test password (no masking characters)+./Test password \* --}  mySettings :: Settings IO@@ -19,6 +21,8 @@         args <- getArgs         let inputFunc = case args of                 ["chars"] -> fmap (fmap (\c -> [c])) . getInputChar+                ["password"] -> getPassword Nothing+                ["password", [c]] -> getPassword (Just c)                 _ -> getInputLine         runInputT mySettings $ withInterrupt $ loop inputFunc 0     where
haskeline.cabal view
@@ -1,6 +1,6 @@ Name:           haskeline Cabal-Version:  >=1.6-Version:        0.6.2.4+Version:        0.6.3 Category:       User Interfaces License:        BSD3 License-File:   LICENSE@@ -50,7 +50,7 @@     }     else {         if impl(ghc>=6.11) {-            Build-depends: base >=4.1 && < 4.4, containers>=0.1 && < 0.4, directory==1.0.*,+            Build-depends: base >=4.1 && < 4.4, containers>=0.1 && < 0.5, directory==1.0.*,                            bytestring==0.9.*         }         else {