diff options
| author | tv <tv@shackspace.de> | 2014-07-27 11:02:45 +0200 | 
|---|---|---|
| committer | tv <tv@shackspace.de> | 2014-07-27 11:03:05 +0200 | 
| commit | bc8d3ab9d134baa4517757f6f7ab80857361bd65 (patch) | |
| tree | 046f79d43cc0dfb99cdca71918c505530b834bb1 | |
initial commit
| -rw-r--r-- | Main-gut-ohne-transformers.hs | 340 | ||||
| -rw-r--r-- | Main-kaputt-mit-mtl.hs | 396 | ||||
| -rw-r--r-- | Main.hs | 400 | ||||
| -rw-r--r-- | Makefile | 47 | ||||
| -rw-r--r-- | OldMain.hs | 206 | ||||
| -rw-r--r-- | default.nix | 24 | ||||
| -rw-r--r-- | defaultGetCommand.hs | 33 | ||||
| -rw-r--r-- | hack.cabal | 20 | 
8 files changed, 1466 insertions, 0 deletions
| diff --git a/Main-gut-ohne-transformers.hs b/Main-gut-ohne-transformers.hs new file mode 100644 index 0000000..c4646ec --- /dev/null +++ b/Main-gut-ohne-transformers.hs @@ -0,0 +1,340 @@ +{-# LANGUAGE OverloadedStrings #-} +module Main where + +import Control.Concurrent +import Control.Concurrent.MVar +import Control.Monad +import Data.Char +import Data.IORef +import Data.List +import Data.Time.Clock (getCurrentTime) +import Data.Time.Format (formatTime) +import Numeric (showIntAtBase) +import System.IO +import System.Locale (defaultTimeLocale, rfc822DateFormat) +--import System.Posix.Signals + + +type Buffer = (String, String) + +emptyBuffer = ("", "") + + +main :: IO () +main = do +  hSetEcho stdin False +  hSetBuffering stdin NoBuffering + +  tid <- myThreadId + +  -- WINCH +  -- TODO installHandler 28 (Catch $ ioctl 0 ...) Nothing + +  lock <- newMVar emptyBuffer + +  renderInputLine emptyBuffer +  hFlush stdout + +  forkIO $ (dateThread 1000000) lock +  uiThread (NormalMode nmap) lock + + +dateThread delay lock = forever $ do +    t <- getCurrentTime +    withMVar lock $ \ buf -> do +      putLine $ formatTime defaultTimeLocale rfc822DateFormat t  +      renderInputLine buf +      hFlush stdout +    threadDelay delay +  + +uiThread mode lock = do +    c <- getCommand mode +    mbMode <- modifyMVar lock (execCommand c) +    case mbMode of +        Nothing -> +            uiThread mode lock +        Just mode' ->  +            uiThread mode' lock + + +data Command +  = AlertBadInput String +  | InsertChar Char +  | InsertNextCharVerbatim +  | InsertCharThenChangeMode Char Mode +  | MoveCursorRight +  | MoveCursorLeft +  | KillLastWord +  | KillLastChar +  | KillNextChar +  | ExecuteInputBuffer +  | UnboundSequence String String +  | GotoBOL +  | GotoEOL + + +finishCommand :: Buffer -> IO (Buffer, Maybe Mode) +finishCommand buf = do +    clearLine +    renderInputLine buf +    hFlush stdout +    return (buf, Nothing) + +finishCommandChangeMode :: Buffer -> Mode -> IO (Buffer, Maybe Mode) +finishCommandChangeMode buf mode = do +    clearLine +    putStrLn $ "change mode: " ++ (show mode) +    renderInputLine buf +    hFlush stdout +    return (buf, Just mode) + + + +execCommand :: Command -> Buffer -> IO (Buffer, Maybe Mode) + +execCommand GotoBOL (lhs, rhs) = +    finishCommand ("", lhs ++ rhs) + +execCommand GotoEOL (lhs, rhs) = +    finishCommand (lhs ++ rhs, "") + +execCommand MoveCursorLeft buf@(lhs@(_:_),rhs) = do +    finishCommand (init lhs, last lhs : rhs) + +execCommand MoveCursorRight (lhs,rhs@(_:_)) = do +    finishCommand (lhs ++ [head rhs], tail rhs) + +execCommand (InsertChar c) (lhs,rhs) = do +    finishCommand (lhs ++ [c], rhs) + +execCommand (InsertCharThenChangeMode c m) (lhs, rhs) = do +    finishCommandChangeMode (lhs ++ [c], rhs) m + +execCommand InsertNextCharVerbatim buf = do +    finishCommandChangeMode buf VerbatimMode + +execCommand ExecuteInputBuffer (lhs,rhs) = do +    putLine $ "input: <" ++ (concat $ map (reform 32) $ lhs ++ rhs) ++ ">" +    finishCommand emptyBuffer + +execCommand KillNextChar buf@(lhs,_:rhs') = do +    finishCommand (lhs, rhs') + +execCommand KillLastChar (lhs@(_:_),rhs) = do +    finishCommand (init lhs, rhs) + +execCommand KillLastWord (lhs@(_:_),rhs) = do +    finishCommand (foldr dropWhileEnd lhs [not . isSpace, isSpace], rhs) + +execCommand (AlertBadInput s) buf@(lhs,rhs) = do +    putLine $ "unhandled input: <" ++ (concat $ map (reform 31) s) ++ ">" +    finishCommand buf + +execCommand (UnboundSequence s n) buf@(lhs,rhs) = do +    putLine $ "unbound sequence: <" ++ (concat $ map (reform 31) s) ++ "> " +              ++ (special 31 n) +    finishCommand buf + +execCommand _ buf = do +    ringBell +    hFlush stdout +    return (buf, Nothing) + + + + +putLine s = do +    clearLine -- TODO this renders finishCommand's clearLine redundant +    putStrLn s + + + +reform colorCode c = +    if isPrint c +      then normal colorCode [c] +      else +        special colorCode $ +          case ord c of +            27 -> "^[" +            _ -> charToCode c + +normal colorCode s = "\x1b[" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" +special colorCode s = "\x1b[1;" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" + + + +-- XXX assumes that the cursor is already at the input line +renderInputLine :: Buffer -> IO () +renderInputLine (lhs, rhs) = do +    --clearLine +    putStr $ "> " ++ pp lhs ++ pp rhs +    moveCursorLeft (length $ ppVis rhs) +    --hFlush stdout +  where +    pp = concat . map reform +    reform c = +      if isPrint c +        then [c] +        else +          "\x1b[35m" ++ ( +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) +          ) ++ "\x1b[m" + +    ppVis = concat . map reformVis +    reformVis c = +      if isPrint c +        then [c] +        else +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) + + + + + + +clearLine = +    putStr "\x1b[2K" >> +    moveCursorLeft 80 + + +ringBell = putStr "\x07" -- BEL '\a' + + +moveCursorLeft 0 = return () +moveCursorLeft i = putStr $ "\x1b[" ++ show i ++ "D" + +moveCursorRight 0 = return () +moveCursorRight i = putStr $ "\x1b[" ++ show i ++ "C" + +clearLineFromCursorRight = putStr "\x1b[0K" + + +-- TODO? charToCode c = "\\x" ++ showHex (ord c) +charToCode c = "\\x" ++ showIntAtBase 16 intToDigit (ord c) "" + + + + + +-- TODO pressing ESC, then F11 etc. is ugly +nmap = +  [ ("\x01", GotoBOL) +  , ("\x05", GotoEOL) +  , ("\x1b[3~", KillNextChar) +  , ("\x1b[C", MoveCursorRight) +  , ("\x1b[D", MoveCursorLeft) +  , ("\x16", InsertNextCharVerbatim) -- ^V +  , ("\x17", KillLastWord) -- ^W +  , ("\x0a", ExecuteInputBuffer) +  , ("\x7f", KillLastChar) -- Delete +  , ("\x08", KillLastChar) -- BackSpace +  ] +  ++ [unboundSequence "\x1b[2~" "<Insert>"] +  ++ [unboundSequence "\x1b[5~" "<Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6~" "<Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7~" "<Home>"] +  ++ [unboundSequence "\x1b[8~" "<End>"] +  ++ [unboundSequence "\x1b[2$" "<S-Insert>"] +  ++ [unboundSequence "\x1b[5$" "<S-Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6$" "<S-Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7$" "<S-Home>"] +  ++ [unboundSequence "\x1b[8$" "<S-End>"] +  ++ [unboundSequence "\x1b\x1b[2$" "<S-M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[5$" "<S-M-Prior>"]    -- page up +  ++ [unboundSequence "\x1b\x1b[6$" "<S-M-Next>"]     -- page dn +  ++ [unboundSequence "\x1b\x1b[7$" "<S-M-Home>"] +  ++ [unboundSequence "\x1b\x1b[8$" "<S-M-End>"] +  ++ [unboundSequence "\x1b\x1b[A" "<M-Up>"] +  ++ [unboundSequence "\x1b\x1b[B" "<M-Down>"] +  ++ [unboundSequence "\x1b\x1b[C" "<M-Right>"] +  ++ [unboundSequence "\x1b\x1b[D" "<M-Left>"] +  ++ [unboundSequence "\x1b\x1b[a" "<S-M-Up>"] +  ++ [unboundSequence "\x1b\x1b[b" "<S-M-Down>"] +  ++ [unboundSequence "\x1b\x1b[c" "<S-M-Right>"] +  ++ [unboundSequence "\x1b\x1b[d" "<S-M-Left>"] +  ++ [unboundSequence "\x1b[a" "<S-Up>"] +  ++ [unboundSequence "\x1b[b" "<S-Down>"] +  ++ [unboundSequence "\x1b[c" "<S-Right>"] +  ++ [unboundSequence "\x1b[d" "<S-Left>"] +  ++ [unboundSequence "\x1bOa" "<C-Up>"] +  ++ [unboundSequence "\x1bOb" "<C-Down>"] +  ++ [unboundSequence "\x1bOc" "<C-Right>"] +  ++ [unboundSequence "\x1bOd" "<C-Left>"] +  ++ [unboundSequence "\x1b\x1bOa" "<C-M-Up>"] +  ++ [unboundSequence "\x1b\x1bOb" "<C-M-Down>"] +  ++ [unboundSequence "\x1b\x1bOc" "<C-M-Right>"] +  ++ [unboundSequence "\x1b\x1bOd" "<C-M-Left>"] +  ++ [unboundSequence "\x1b[11~" "<F1>"] +  ++ [unboundSequence "\x1b[12~" "<F2>"] +  ++ [unboundSequence "\x1b[13~" "<F3>"] +  ++ [unboundSequence "\x1b[14~" "<F4>"] +  ++ [unboundSequence "\x1b[15~" "<F5>"] +  ++ [unboundSequence "\x1b[17~" "<F6>"] +  ++ [unboundSequence "\x1b[18~" "<F7>"] +  ++ [unboundSequence "\x1b[19~" "<F8>"] +  ++ [unboundSequence "\x1b[20~" "<F9>"] +  ++ [unboundSequence "\x1b[21~" "<F10>"] +  ++ [unboundSequence "\x1b[23~" "<F11>"] +  ++ [unboundSequence "\x1b[24~" "<F12>"] + +  ++ [unboundSequence "\x1b\x1b[2~" "<M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[3~" "<M-Delete>"] +  ++ map (\ i -> unboundSequence ("\x1b\x1b[" ++ show i ++ "~") +                                 ("<M-F" ++ show i ++ ">")) +          [11..24] +  ++ [unboundSequence "\x1b\x7f" "<M-BackSpace>"] +  ++ [unboundSequence "\x1b\x0a" "<M-Return>"] + + +unboundSequence seq name = +  (seq, UnboundSequence seq name) + + +data Mode +  = NormalMode [(String, Command)] +  | VerbatimMode + +instance  Show Mode  where +  show (NormalMode _) = "normal" +  show VerbatimMode = "verbatim" + + +getCommand :: Mode -> IO Command +getCommand (NormalMode map) = getMappedCommand map +getCommand VerbatimMode = verbatimKeymap + + +getMappedCommand :: [(String, Command)] -> IO Command +getMappedCommand xs = do +    c <- getChar +    if any (isPrefixOf [c] . fst) xs +      then rec [c] +      else +        if isPrint c +          then return $ InsertChar c +          else return $ AlertBadInput [c] +  where +    rec :: String -> IO Command +    rec s = +      case lookup s xs of +        Just c -> return c +        _ -> +          if any (isPrefixOf s . fst) xs +            then do +              c <- getChar +              rec $ s ++ [c] +            else +              return $ AlertBadInput s + + +verbatimKeymap :: IO Command +verbatimKeymap = do +  c <- getChar +  --return $ InsertCharThenChangeMode c defaultGetCommand +  return $ InsertCharThenChangeMode c (NormalMode nmap) + diff --git a/Main-kaputt-mit-mtl.hs b/Main-kaputt-mit-mtl.hs new file mode 100644 index 0000000..0167b9f --- /dev/null +++ b/Main-kaputt-mit-mtl.hs @@ -0,0 +1,396 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Main where + +import Control.Concurrent +import Control.Concurrent.MVar +import Control.Monad +import Data.Char +import Data.IORef +import Data.List +import Data.Time.Clock (getCurrentTime) +import Data.Time.Format (formatTime) +import Numeric (showIntAtBase) +import System.IO +import System.Locale (defaultTimeLocale, rfc822DateFormat) +--import System.Posix.Signal +--import Control.Monad.Trans.Class (lift) +--import Control.Monad.IO.Class (liftIO, MonadIO) +--import Control.Monad.Trans.State.Lazy +import Control.Monad.State +import Control.Monad.Reader +import Data.Typeable +import Control.Applicative + + +newtype VT a = VT (StateT VTState IO a) +    deriving (Functor, Monad, MonadIO, MonadState VTState) + + +instance Applicative VT where +    pure = return +    (<*>) = ap + + +data VTState = VTState +  { mode :: Mode +  , buffer :: MVar Buffer +  } + +runVT :: VTState -> VT a -> IO (a, VTState) +runVT st (VT a) = runStateT a st + + + + +type Buffer = (String, String) + +emptyBuffer = ("", "") + + +main :: IO () +main = do +  hSetEcho stdin False +  hSetBuffering stdin NoBuffering + +  tid <- myThreadId + +  -- WINCH +  -- TODO installHandler 28 (Catch $ ioctl 0 ...) Nothing + +  lock <- newMVar emptyBuffer + +  renderInputLine emptyBuffer +  hFlush stdout + +  let st = VTState +        { mode = NormalMode nmap +        , buffer = lock +        } + +  forkIO $ runVT st (dateThread 1000000 lock) >> return () +  runVT st uiThread + +  return () + + +dateThread :: Int -> MVar Buffer -> VT () +dateThread delay lock = forever $ do +    t <- liftIO getCurrentTime +    liftIO $ withMVar lock $ \ buf -> do +      putLine $ formatTime defaultTimeLocale rfc822DateFormat t  +      renderInputLine buf +      hFlush stdout +    liftIO $ threadDelay delay +  + +--uiThread :: MVar Buffer -> VT () +--uiThread lock = do +--    c <- liftIO $ getCommand mode +--uiThread :: mvar buffer -> vt () +--uiThread lock = do +uiThread :: VT () +uiThread = forever $ do +    st <- get +    let m = mode st +    c <- liftIO $ getCommand m +    b <- liftIO $ readMVar (buffer st) +    execCommand c b +    --mbMode <- liftIO $ modifyMVar (buffer st) (execCommand c) +    --case mbMode of +    --    Nothing -> return () +    --    Just mode' -> do +    --        put $ st { mode = mode' } + + +data Command +  = AlertBadInput String +  | InsertChar Char +  | InsertNextCharVerbatim +  | InsertCharThenChangeMode Char Mode +  | MoveCursorRight +  | MoveCursorLeft +  | KillLastWord +  | KillLastChar +  | KillNextChar +  | ExecuteInputBuffer +  | UnboundSequence String String +  | GotoBOL +  | GotoEOL + + +--finishCommand :: Buffer -> IO (Buffer, Maybe Mode) +finishCommand :: Buffer -> VT () +finishCommand buf = do +    b <- gets buffer +    liftIO $ do +        clearLine +        renderInputLine buf +        hFlush stdout +        putMVar b buf +    --modify $ \ st -> st { buffer = buf } +    --return (buf, Nothing) + +--finishCommandChangeMode :: Buffer -> Mode -> IO (Buffer, Maybe Mode) +finishCommandChangeMode :: Buffer -> Mode -> VT () +finishCommandChangeMode buf mode = do +    b <- gets buffer +    liftIO $ do +        clearLine +        putStrLn $ "change mode: " ++ (show mode) +        renderInputLine buf +        hFlush stdout +        putMVar b buf +    --return (buf, Just mode) +    --modify $ \ st -> st { buffer = buf } + + +--execCommand :: Command -> Buffer -> IO (Buffer, Maybe Mode) +execCommand :: Command -> Buffer -> VT () + +execCommand GotoBOL (lhs, rhs) = +    finishCommand ("", lhs ++ rhs) + +execCommand GotoEOL (lhs, rhs) = +    finishCommand (lhs ++ rhs, "") + +execCommand MoveCursorLeft buf@(lhs@(_:_),rhs) = do +    finishCommand (init lhs, last lhs : rhs) + +execCommand MoveCursorRight (lhs,rhs@(_:_)) = do +    finishCommand (lhs ++ [head rhs], tail rhs) + +execCommand (InsertChar c) (lhs,rhs) = do +    finishCommand (lhs ++ [c], rhs) + +execCommand (InsertCharThenChangeMode c m) (lhs, rhs) = do +    finishCommandChangeMode (lhs ++ [c], rhs) m + +execCommand InsertNextCharVerbatim buf = do +    finishCommandChangeMode buf VerbatimMode + +execCommand ExecuteInputBuffer (lhs,rhs) = do +    liftIO $ putLine $ "input: <" ++ (concat $ map (reform 32) $ lhs ++ rhs) ++ ">" +    finishCommand emptyBuffer + +execCommand KillNextChar buf@(lhs,_:rhs') = do +    finishCommand (lhs, rhs') + +execCommand KillLastChar (lhs@(_:_),rhs) = do +    finishCommand (init lhs, rhs) + +execCommand KillLastWord (lhs@(_:_),rhs) = do +    finishCommand (foldr dropWhileEnd lhs [not . isSpace, isSpace], rhs) + +execCommand (AlertBadInput s) buf@(lhs,rhs) = do +    liftIO $ putLine $ "unhandled input: <" ++ (concat $ map (reform 31) s) ++ ">" +    finishCommand buf + +execCommand (UnboundSequence s n) buf@(lhs,rhs) = do +    liftIO $ putLine $ "unbound sequence: <" ++ (concat $ map (reform 31) s) ++ "> " +              ++ (special 31 n) +    finishCommand buf + +execCommand _ buf = do +    liftIO $ do +        ringBell +        hFlush stdout +    --return (buf, Nothing) + + + + +putLine s = do +    clearLine -- TODO this renders finishCommand's clearLine redundant +    putStrLn s + + + +reform colorCode c = +    if isPrint c +      then normal colorCode [c] +      else +        special colorCode $ +          case ord c of +            27 -> "^[" +            _ -> charToCode c + +normal colorCode s = "\x1b[" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" +special colorCode s = "\x1b[1;" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" + + + +-- XXX assumes that the cursor is already at the input line +renderInputLine :: Buffer -> IO () +renderInputLine (lhs, rhs) = do +    --clearLine +    putStr $ "> " ++ pp lhs ++ pp rhs +    moveCursorLeft (length $ ppVis rhs) +    --hFlush stdout +  where +    pp = concat . map reform +    reform c = +      if isPrint c +        then [c] +        else +          "\x1b[35m" ++ ( +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) +          ) ++ "\x1b[m" + +    ppVis = concat . map reformVis +    reformVis c = +      if isPrint c +        then [c] +        else +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) + + + + + + +clearLine = +    putStr "\x1b[2K" >> +    moveCursorLeft 80 + + +ringBell = putStr "\x07" -- BEL '\a' + + +moveCursorLeft 0 = return () +moveCursorLeft i = putStr $ "\x1b[" ++ show i ++ "D" + +moveCursorRight 0 = return () +moveCursorRight i = putStr $ "\x1b[" ++ show i ++ "C" + +clearLineFromCursorRight = putStr "\x1b[0K" + + +-- TODO? charToCode c = "\\x" ++ showHex (ord c) +charToCode c = "\\x" ++ showIntAtBase 16 intToDigit (ord c) "" + + + + + +-- TODO pressing ESC, then F11 etc. is ugly +nmap = +  [ ("\x01", GotoBOL) +  , ("\x05", GotoEOL) +  , ("\x1b[3~", KillNextChar) +  , ("\x1b[C", MoveCursorRight) +  , ("\x1b[D", MoveCursorLeft) +  , ("\x16", InsertNextCharVerbatim) -- ^V +  , ("\x17", KillLastWord) -- ^W +  , ("\x0a", ExecuteInputBuffer) +  , ("\x7f", KillLastChar) -- Delete +  , ("\x08", KillLastChar) -- BackSpace +  ] +  ++ [unboundSequence "\x1b[2~" "<Insert>"] +  ++ [unboundSequence "\x1b[5~" "<Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6~" "<Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7~" "<Home>"] +  ++ [unboundSequence "\x1b[8~" "<End>"] +  ++ [unboundSequence "\x1b[2$" "<S-Insert>"] +  ++ [unboundSequence "\x1b[5$" "<S-Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6$" "<S-Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7$" "<S-Home>"] +  ++ [unboundSequence "\x1b[8$" "<S-End>"] +  ++ [unboundSequence "\x1b\x1b[2$" "<S-M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[5$" "<S-M-Prior>"]    -- page up +  ++ [unboundSequence "\x1b\x1b[6$" "<S-M-Next>"]     -- page dn +  ++ [unboundSequence "\x1b\x1b[7$" "<S-M-Home>"] +  ++ [unboundSequence "\x1b\x1b[8$" "<S-M-End>"] +  ++ [unboundSequence "\x1b\x1b[A" "<M-Up>"] +  ++ [unboundSequence "\x1b\x1b[B" "<M-Down>"] +  ++ [unboundSequence "\x1b\x1b[C" "<M-Right>"] +  ++ [unboundSequence "\x1b\x1b[D" "<M-Left>"] +  ++ [unboundSequence "\x1b\x1b[a" "<S-M-Up>"] +  ++ [unboundSequence "\x1b\x1b[b" "<S-M-Down>"] +  ++ [unboundSequence "\x1b\x1b[c" "<S-M-Right>"] +  ++ [unboundSequence "\x1b\x1b[d" "<S-M-Left>"] +  ++ [unboundSequence "\x1b[a" "<S-Up>"] +  ++ [unboundSequence "\x1b[b" "<S-Down>"] +  ++ [unboundSequence "\x1b[c" "<S-Right>"] +  ++ [unboundSequence "\x1b[d" "<S-Left>"] +  ++ [unboundSequence "\x1bOa" "<C-Up>"] +  ++ [unboundSequence "\x1bOb" "<C-Down>"] +  ++ [unboundSequence "\x1bOc" "<C-Right>"] +  ++ [unboundSequence "\x1bOd" "<C-Left>"] +  ++ [unboundSequence "\x1b\x1bOa" "<C-M-Up>"] +  ++ [unboundSequence "\x1b\x1bOb" "<C-M-Down>"] +  ++ [unboundSequence "\x1b\x1bOc" "<C-M-Right>"] +  ++ [unboundSequence "\x1b\x1bOd" "<C-M-Left>"] +  ++ [unboundSequence "\x1b[11~" "<F1>"] +  ++ [unboundSequence "\x1b[12~" "<F2>"] +  ++ [unboundSequence "\x1b[13~" "<F3>"] +  ++ [unboundSequence "\x1b[14~" "<F4>"] +  ++ [unboundSequence "\x1b[15~" "<F5>"] +  ++ [unboundSequence "\x1b[17~" "<F6>"] +  ++ [unboundSequence "\x1b[18~" "<F7>"] +  ++ [unboundSequence "\x1b[19~" "<F8>"] +  ++ [unboundSequence "\x1b[20~" "<F9>"] +  ++ [unboundSequence "\x1b[21~" "<F10>"] +  ++ [unboundSequence "\x1b[23~" "<F11>"] +  ++ [unboundSequence "\x1b[24~" "<F12>"] + +  ++ [unboundSequence "\x1b\x1b[2~" "<M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[3~" "<M-Delete>"] +  ++ map (\ i -> unboundSequence ("\x1b\x1b[" ++ show i ++ "~") +                                 ("<M-F" ++ show i ++ ">")) +          [11..24] +  ++ [unboundSequence "\x1b\x7f" "<M-BackSpace>"] +  ++ [unboundSequence "\x1b\x0a" "<M-Return>"] + + +unboundSequence seq name = +  (seq, UnboundSequence seq name) + + +data Mode +  = NormalMode [(String, Command)] +  | VerbatimMode + +instance  Show Mode  where +  show (NormalMode _) = "normal" +  show VerbatimMode = "verbatim" + + +getCommand :: Mode -> IO Command +getCommand (NormalMode map) = getMappedCommand map +getCommand VerbatimMode = verbatimKeymap + + +getMappedCommand :: [(String, Command)] -> IO Command +getMappedCommand xs = do +    c <- getChar +    if any (isPrefixOf [c] . fst) xs +      then rec [c] +      else +        if isPrint c +          then return $ InsertChar c +          else return $ AlertBadInput [c] +  where +    rec :: String -> IO Command +    rec s = +      case lookup s xs of +        Just c -> return c +        _ -> +          if any (isPrefixOf s . fst) xs +            then do +              c <- getChar +              rec $ s ++ [c] +            else +              return $ AlertBadInput s + + +verbatimKeymap :: IO Command +verbatimKeymap = do +  c <- getChar +  --return $ InsertCharThenChangeMode c defaultGetCommand +  return $ InsertCharThenChangeMode c (NormalMode nmap) + @@ -0,0 +1,400 @@ +{-# LANGUAGE OverloadedStrings #-} +module Main where + +import Control.Concurrent +import Control.Concurrent.MVar +import Control.Monad +import Data.Char +import Data.IORef +import Data.List +import Data.Time.Clock (getCurrentTime) +import Data.Time.Format (formatTime) +import Numeric (showIntAtBase) +import System.IO +import System.Locale (defaultTimeLocale, rfc822DateFormat) +--import System.Posix.Signals + + +data VTState = VTState +    { buffer :: Buffer +    , mode :: Mode +    } + +emptyState = VTState emptyBuffer (NormalMode nmap) + + +type Buffer = (String, String) + +emptyBuffer = ("", "") + + +main :: IO () +main = do +  hSetEcho stdin False +  hSetBuffering stdin NoBuffering + +  tid <- myThreadId + +  -- WINCH +  -- TODO installHandler 28 (Catch $ ioctl 0 ...) Nothing + +  lock <- newMVar emptyBuffer + +  renderInputLine emptyBuffer +  hFlush stdout + +  forkIO $ (dateThread 1000000) lock +  uiThread (NormalMode nmap) lock + + +dateThread delay lock = forever $ do +    t <- getCurrentTime +    withMVar lock $ \ buf -> do +      putLine $ formatTime defaultTimeLocale rfc822DateFormat t  +      renderInputLine buf +      hFlush stdout +    threadDelay delay +  + +uiThread mod lock = do +    c <- getCommand mod +    --mbMode <- modifyMVar lock (execCommand c) +    --case mbMode of +    --    Nothing -> +    --        uiThread mode lock +    --    Just mode' ->  +    --        uiThread mode' lock +    mod' <- modifyMVar lock $ \ buf -> do +        let st = VTState +                { mode = mod +                , buffer = buf +                } +        mbst' <- execCommand c st + +        case mbst' of +          Nothing -> do +            ringBell +            hFlush stdout +            return (buf, mod) +          Just st' -> do +            clearLine +            when (show (mode st) /= show (mode st')) $ do +                putStrLn $ "change mode: " ++ (show $ mode st') +            renderInputLine (buffer st') +            hFlush stdout + +            return (buffer st', mode st') + +    uiThread mod' lock + + +data Command +  = AlertBadInput String +  | InsertChar Char +  | InsertNextCharVerbatim +  | InsertCharThenChangeMode Char Mode +  | MoveCursorRight +  | MoveCursorLeft +  | KillLastWord +  | KillLastChar +  | KillNextChar +  | ExecuteInputBuffer +  | UnboundSequence String String +  | GotoBOL +  | GotoEOL + + +--finishCommand :: Buffer -> IO (Buffer, Maybe Mode) +--finishCommand buf = do +--    clearLine +--    renderInputLine buf +--    hFlush stdout +--    return (buf, Nothing) +-- +--finishCommandChangeMode :: Buffer -> Mode -> IO (Buffer, Maybe Mode) +--finishCommandChangeMode buf mode = do +--    clearLine +--    putStrLn $ "change mode: " ++ (show mode) +--    renderInputLine buf +--    hFlush stdout +--    return (buf, Just mode) + +-- TODO execCommand :: Command -> VTState -> VTState + +--execCommand :: Command -> Buffer -> IO (Buffer, Maybe Mode) + + +-- TODO instead of propagating Maybe to caller, use +--      something like Writer monad to generate bell +modifyBuffer :: (Buffer -> Maybe Buffer) -> VTState -> Maybe VTState +modifyBuffer f st = +  case f (buffer st) of +    Nothing -> Nothing +    Just b' -> Just st { buffer = b' } + +-- TODO instance Show Buffer (w/newtype Buffer) + +showBuffer :: Buffer -> String +showBuffer (lhs, rhs) = lhs ++ rhs + + +execCommand :: Command -> VTState -> IO (Maybe VTState) + +execCommand GotoBOL q = +    return . modifyBuffer (\(lhs, rhs) -> Just ("", lhs ++ rhs)) $ q + +execCommand GotoEOL q = +    return . modifyBuffer (\(lhs, rhs) -> Just (lhs ++ rhs, "")) $ q + +execCommand MoveCursorLeft q = +    return . modifyBuffer (\(lhs, rhs) -> +      if null lhs then Nothing else Just (init lhs, last lhs : rhs) +                          ) $ q + +execCommand MoveCursorRight q = +    return . modifyBuffer (\(lhs, rhs) -> +      if null lhs then Nothing else Just (lhs ++ [head rhs], tail rhs) +                          ) $ q + +execCommand (InsertChar c) q = +    return . modifyBuffer (\(lhs, rhs) -> Just (lhs ++ [c], rhs)) $ q + +execCommand (InsertCharThenChangeMode c m) q = +    execCommand (InsertChar c) q { mode = m } + +execCommand InsertNextCharVerbatim q = +    return . modifyBuffer Just $ q { mode = VerbatimMode } + +execCommand ExecuteInputBuffer q = do +    -- TODO Writer monad? +    putLine $ concat +      [ "input: <", concat $ map (reform 32) $ showBuffer . buffer $ q, ">" +      ] +    return . modifyBuffer (const $ Just emptyBuffer) $ q + +execCommand KillNextChar q = +    return . modifyBuffer (\(lhs, _:rhs') -> Just (lhs, rhs')) $ q + +execCommand KillLastChar q = +    return . modifyBuffer (\(lhs, rhs) -> +      if null lhs then Nothing else Just (init lhs, rhs) +                          ) $ q + +execCommand KillLastWord q = +    return . modifyBuffer (\(lhs, rhs) -> +      if null lhs then Nothing +          else Just (foldr dropWhileEnd lhs [not . isSpace, isSpace], rhs) +                          ) $ q + +execCommand (AlertBadInput s) q = do +    putLine $ "unhandled input: <" ++ (concat $ map (reform 31) s) ++ ">" +    return Nothing +    --return . Just $ q + +execCommand (UnboundSequence s n) q = do +    putLine $ "unbound sequence: <" ++ (concat $ map (reform 31) s) ++ "> " +              ++ (special 31 n) +    --return . Just $ q +    return Nothing + +--execCommand _ q = do +--    ringBell +--    hFlush stdout +--    return q + + + +putLine s = do +    clearLine -- TODO this renders finishCommand's clearLine redundant +    putStrLn s + + + +reform colorCode c = +    if isPrint c +      then normal colorCode [c] +      else +        special colorCode $ +          case ord c of +            27 -> "^[" +            _ -> charToCode c + +normal colorCode s = "\x1b[" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" +special colorCode s = "\x1b[1;" ++ show colorCode ++ "m" ++ s ++ "\x1b[m" + + + +-- XXX assumes that the cursor is already at the input line +renderInputLine :: Buffer -> IO () +renderInputLine (lhs, rhs) = do +    --clearLine +    putStr $ "> " ++ pp lhs ++ pp rhs +    moveCursorLeft (length $ ppVis rhs) +    --hFlush stdout +  where +    pp = concat . map reform +    reform c = +      if isPrint c +        then [c] +        else +          "\x1b[35m" ++ ( +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) +          ) ++ "\x1b[m" + +    ppVis = concat . map reformVis +    reformVis c = +      if isPrint c +        then [c] +        else +            case ord c of +              27 -> "^[" +              _ -> "\\" ++ show (ord c) + + + + + + +clearLine = +    putStr "\x1b[2K" >> +    moveCursorLeft 80 + + +ringBell = putStr "\x07" -- BEL '\a' + + +moveCursorLeft 0 = return () +moveCursorLeft i = putStr $ "\x1b[" ++ show i ++ "D" + +moveCursorRight 0 = return () +moveCursorRight i = putStr $ "\x1b[" ++ show i ++ "C" + +clearLineFromCursorRight = putStr "\x1b[0K" + + +-- TODO? charToCode c = "\\x" ++ showHex (ord c) +charToCode c = "\\x" ++ showIntAtBase 16 intToDigit (ord c) "" + + + + + +-- TODO pressing ESC, then F11 etc. is ugly +nmap = +  [ ("\x01", GotoBOL) +  , ("\x05", GotoEOL) +  , ("\x1b[3~", KillNextChar) +  , ("\x1b[C", MoveCursorRight) +  , ("\x1b[D", MoveCursorLeft) +  , ("\x16", InsertNextCharVerbatim) -- ^V +  , ("\x17", KillLastWord) -- ^W +  , ("\x0a", ExecuteInputBuffer) +  , ("\x7f", KillLastChar) -- Delete +  , ("\x08", KillLastChar) -- BackSpace +  ] +  ++ [unboundSequence "\x1b[2~" "<Insert>"] +  ++ [unboundSequence "\x1b[5~" "<Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6~" "<Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7~" "<Home>"] +  ++ [unboundSequence "\x1b[8~" "<End>"] +  ++ [unboundSequence "\x1b[2$" "<S-Insert>"] +  ++ [unboundSequence "\x1b[5$" "<S-Prior>"]    -- page up +  ++ [unboundSequence "\x1b[6$" "<S-Next>"]     -- page dn +  ++ [unboundSequence "\x1b[7$" "<S-Home>"] +  ++ [unboundSequence "\x1b[8$" "<S-End>"] +  ++ [unboundSequence "\x1b\x1b[2$" "<S-M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[5$" "<S-M-Prior>"]    -- page up +  ++ [unboundSequence "\x1b\x1b[6$" "<S-M-Next>"]     -- page dn +  ++ [unboundSequence "\x1b\x1b[7$" "<S-M-Home>"] +  ++ [unboundSequence "\x1b\x1b[8$" "<S-M-End>"] +  ++ [unboundSequence "\x1b\x1b[A" "<M-Up>"] +  ++ [unboundSequence "\x1b\x1b[B" "<M-Down>"] +  ++ [unboundSequence "\x1b\x1b[C" "<M-Right>"] +  ++ [unboundSequence "\x1b\x1b[D" "<M-Left>"] +  ++ [unboundSequence "\x1b\x1b[a" "<S-M-Up>"] +  ++ [unboundSequence "\x1b\x1b[b" "<S-M-Down>"] +  ++ [unboundSequence "\x1b\x1b[c" "<S-M-Right>"] +  ++ [unboundSequence "\x1b\x1b[d" "<S-M-Left>"] +  ++ [unboundSequence "\x1b[a" "<S-Up>"] +  ++ [unboundSequence "\x1b[b" "<S-Down>"] +  ++ [unboundSequence "\x1b[c" "<S-Right>"] +  ++ [unboundSequence "\x1b[d" "<S-Left>"] +  ++ [unboundSequence "\x1bOa" "<C-Up>"] +  ++ [unboundSequence "\x1bOb" "<C-Down>"] +  ++ [unboundSequence "\x1bOc" "<C-Right>"] +  ++ [unboundSequence "\x1bOd" "<C-Left>"] +  ++ [unboundSequence "\x1b\x1bOa" "<C-M-Up>"] +  ++ [unboundSequence "\x1b\x1bOb" "<C-M-Down>"] +  ++ [unboundSequence "\x1b\x1bOc" "<C-M-Right>"] +  ++ [unboundSequence "\x1b\x1bOd" "<C-M-Left>"] +  ++ [unboundSequence "\x1b[11~" "<F1>"] +  ++ [unboundSequence "\x1b[12~" "<F2>"] +  ++ [unboundSequence "\x1b[13~" "<F3>"] +  ++ [unboundSequence "\x1b[14~" "<F4>"] +  ++ [unboundSequence "\x1b[15~" "<F5>"] +  ++ [unboundSequence "\x1b[17~" "<F6>"] +  ++ [unboundSequence "\x1b[18~" "<F7>"] +  ++ [unboundSequence "\x1b[19~" "<F8>"] +  ++ [unboundSequence "\x1b[20~" "<F9>"] +  ++ [unboundSequence "\x1b[21~" "<F10>"] +  ++ [unboundSequence "\x1b[23~" "<F11>"] +  ++ [unboundSequence "\x1b[24~" "<F12>"] + +  ++ [unboundSequence "\x1b\x1b[2~" "<M-Insert>"] +  ++ [unboundSequence "\x1b\x1b[3~" "<M-Delete>"] +  ++ map (\ i -> unboundSequence ("\x1b\x1b[" ++ show i ++ "~") +                                 ("<M-F" ++ show i ++ ">")) +          [11..24] +  ++ [unboundSequence "\x1b\x7f" "<M-BackSpace>"] +  ++ [unboundSequence "\x1b\x0a" "<M-Return>"] + + +unboundSequence seq name = +  (seq, UnboundSequence seq name) + + +data Mode +  = NormalMode [(String, Command)] +  | VerbatimMode + +instance  Show Mode  where +  show (NormalMode _) = "normal" +  show VerbatimMode = "verbatim" + + +getCommand :: Mode -> IO Command +getCommand (NormalMode map) = getMappedCommand map +getCommand VerbatimMode = verbatimKeymap + + +getMappedCommand :: [(String, Command)] -> IO Command +getMappedCommand xs = do +    c <- getChar +    if any (isPrefixOf [c] . fst) xs +      then rec [c] +      else +        if isPrint c +          then return $ InsertChar c +          else return $ AlertBadInput [c] +  where +    rec :: String -> IO Command +    rec s = +      case lookup s xs of | 
