module System.Console.SimpleLineEditor
( initialise
, restore
, getLineEdited
, delChars
) where
import System.IO (stdin, stdout, BufferMode(..), hSetBuffering)
import Control.Monad (when)
import Data.Char (isSpace)
import Data.Maybe (isJust, fromJust)
#if USE_READLINE
import System.Console.Readline
#else
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import System.Cmd (system)
import System.IO (hGetChar)
import System.IO.Unsafe (unsafePerformIO)
#endif
initialise :: IO ()
initialise = do
hSetBuffering stdout NoBuffering
hSetBuffering stdin NoBuffering
#if USE_READLINE
initialize
#else
system("stty -icanon min 1 -echo")
return ()
#endif
restore :: IO ()
restore = do
hSetBuffering stdout LineBuffering
hSetBuffering stdin LineBuffering
#if ! USE_READLINE
system("stty icanon echo")
return ()
#endif
delChars :: String -> IO ()
delChars [] = return ()
delChars (_:xs) = do putStr "\BS \BS"
delChars xs
#if USE_READLINE
getLineEdited :: String -> IO (Maybe String)
getLineEdited prompt = do
ms <- readline prompt
case ms of
Nothing -> return ms
Just s -> when (not (all isSpace s)) (addHistory s) >> return ms
#else
history :: IORef [String]
history = unsafePerformIO (newIORef [])
getLineEdited :: String -> IO (Maybe String)
getLineEdited prompt = do
putStr prompt
previous <- readIORef history
ms <- gl "" 0 ([],previous)
case ms of
Nothing -> return ms
Just s -> do when (not (all isSpace s))
(writeIORef history (reverse s: previous))
return ms
where
gl s 0 hist = do
cmd <- lineCmd
case cmd of
Char c -> putChar c >> gl (c:s) 0 hist
Accept -> return (Just (reverse s))
Cancel -> return Nothing
Delete L -> if null s then gl s 0 hist
else delChars "_" >> gl (tail s) 0 hist
Delete Begin -> delChars s >> gl "" 0 hist
Move L -> if not (null s) then putStr ("\BS") >> gl s 1 hist
else gl s 0 hist
History -> case hist of
(_fut, []) -> gl s 0 hist
(fut, p:past) -> do delChars s
putStr (reverse p)
gl p 0 (s:fut, past)
Future -> case hist of
([], _past) -> gl s 0 hist
(f:fut, past) -> do delChars s
putStr (reverse f)
gl f 0 (fut, s:past)
_ -> gl s 0 hist
gl s n hist = do
cmd <- lineCmd
case cmd of
Char c -> do putStr (c: reverse (take n s))
putStr (replicate n '\BS')
gl (take n s ++ c: drop n s) n hist
Accept -> return (Just (reverse s))
Cancel -> return Nothing
Move R -> do let n1 = n1
putStr (reverse (take n s)++" ")
putStr (replicate n '\BS')
gl s n1 hist
Delete R -> do let n1 = n1
putStr (reverse (take n1 s) ++ " ")
putStr (replicate (n1+1) '\BS')
gl (take n1 s ++ drop n s) n1 hist
Move L -> do let n1 = n+1
if n1 <= length s then do
putStr ('\BS':reverse (take n1 s))
putStr (replicate n1 '\BS')
gl s n1 hist
else do
putStr (reverse s++" ")
putStr (replicate n1 '\BS')
gl s n hist
Delete L -> do let n1 = n+1
if n1 <= length s then do
putStr ('\BS':reverse (take n s)++" ")
putStr (replicate n1 '\BS')
gl (take n s ++ drop n1 s) n hist
else do
putStr (reverse s++" ")
putStr (replicate n1 '\BS')
gl s n hist
History -> case hist of
(_fut, []) -> gl s n hist
(fut, p:past) -> do putStr (replicate n ' ')
delChars s
putStr (reverse p)
gl p 0 (s:fut, past)
Future -> case hist of
([], _past) -> gl s n hist
(f:fut, past) -> do putStr (replicate n ' ')
delChars s
putStr (reverse f)
gl f 0 (fut, s:past)
_ -> gl s n hist
data LineCmd = Char Char | Move Cursor | Delete Cursor
| Accept | Cancel | History | Future | NoOp
data Cursor = L | R | Begin | End
lineCmd :: IO LineCmd
lineCmd = do
c1 <- hGetChar stdin
case c1 of
'\n' -> putChar '\n' >> return Accept
'\^K' -> putChar '\n' >> return Cancel
'\DEL' -> return (Delete L)
'\BS' -> return (Delete L)
'\^L' -> return (Move R)
'\^[' -> do
c2 <- hGetChar stdin
case c2 of
'k' -> return History
'j' -> return Future
'[' -> do
c3 <- hGetChar stdin
case c3 of
'D' -> return (Move L)
'C' -> return (Move R)
'A' -> return History
'B' -> return Future
'3' -> do c <- hGetChar stdin
case c of
'~' -> return (Delete R)
_ -> return NoOp
'4' -> do c <- hGetChar stdin
case c of
'~' -> return (Move End)
_ -> return NoOp
'1' -> do c <- hGetChar stdin
case c of
'~' -> return (Move Begin)
_ -> return NoOp
_ -> return NoOp
'O' -> do
c3 <- hGetChar stdin
case c3 of
'D' -> return (Move L)
'C' -> return (Move R)
'A' -> return History
'B' -> return Future
_ -> return NoOp
_ -> return NoOp
_ -> return (Char c1)
#endif /* USE_READLINE */