Haskell读取原始键盘输入
我正在用Haskell编写一个终端模式程序。我如何阅读原始按键信息 特别是,在Haskell之上似乎有一些提供行编辑功能的东西。如果我执行Haskell读取原始键盘输入,haskell,io,terminal,Haskell,Io,Terminal,我正在用Haskell编写一个终端模式程序。我如何阅读原始按键信息 特别是,在Haskell之上似乎有一些提供行编辑功能的东西。如果我执行getLine,我似乎能够使用向上箭头来获取前几行,编辑文本,并且只有当我按Enter键时,文本才会对Haskell应用程序本身可见 我所追求的是阅读单个按键的能力,所以我可以自己实现行编辑功能 也许我的问题不清楚。基本上,我想构建一些类似Vi或Emacs(or)的东西。我已经知道有一些终端绑定可以让我进行奇特的控制台模式打印,所以输出端不应该是个问题。我只
getLine
,我似乎能够使用向上箭头来获取前几行,编辑文本,并且只有当我按Enter键时,文本才会对Haskell应用程序本身可见
我所追求的是阅读单个按键的能力,所以我可以自己实现行编辑功能
也许我的问题不清楚。基本上,我想构建一些类似Vi或Emacs(or)的东西。我已经知道有一些终端绑定可以让我进行奇特的控制台模式打印,所以输出端不应该是个问题。我只是在寻找一种获取原始按键输入的方法,这样我就可以在用户按下字母K时将K添加到当前文本行中,或者在用户按下Ctrl+S时将文件保存到磁盘中。听起来您需要readline支持。有几个包可以做到这一点,但可能是最容易使用的最受支持的平台
import Control.Monad.Trans
import System.Console.Haskeline
type Repl a = InputT IO a
process :: String -> IO ()
process = putStrLn
repl :: Repl ()
repl = do
minput <- getInputLine "> "
case minput of
Nothing -> outputStrLn "Goodbye."
Just input -> (liftIO $ process input) >> repl
main :: IO ()
main = runInputT defaultSettings repl
import Control.Monad.Trans
导入System.Console.Haskeline
类型Repl a=输入IO a
进程::字符串->IO()
进程=putStrLn
repl::repl()
repl=do
minput outputStrLn“再见”
只需输入->(liftIO$流程输入)>>repl
main::IO()
main=runinput defaultSettings repl
我想你在找。默认情况下,StdIn是行缓冲的,但是
你想马上收到钥匙 不完整:
import System.IO (stdin, hSetEcho, hSetBuffering, NoBuffering)
import Control.Monad (when)
-- Simple menu controller
main = do
hSetBuffering stdin NoBuffering
hSetEcho stdin False
key <- getKey
when (key /= "\ESC") $ do
case key of
"\ESC[A" -> putStr "↑"
"\ESC[B" -> putStr "↓"
"\ESC[C" -> putStr "→"
"\ESC[D" -> putStr "←"
"\n" -> putStr "⎆"
"\DEL" -> putStr "⎋"
_ -> return ()
main
经过几个小时的网上冲浪,我可以报告如下:
有一个巨大的界面,几乎没有任何文档。从函数名和类型签名,你也许可以猜出这些东西是做什么的。。。但这绝非小事。无论如何,这个库似乎提供了一个高级编辑界面——这正是我试图自己实现的。我需要更低级的东西readline
- 在涉猎了haskeline的源代码之后,它似乎有一个巨大而复杂的底层代码,分别针对Win32和POSIX。如果有一种简单的方法来进行控制台I/O,那么这个库不会演示它。这段代码似乎是如此紧密地集成在一起,并且高度特定于
,以至于我怀疑我能否重用其中任何一段。但也许通过阅读,我可以学到足够的东西来写我自己的haskeline
- 易是。。。惊人的巨大。阴谋集团文件列出了超过150个暴露的模块。(!!)不过,它下面似乎使用了一个名为
的包,该包仅限于POSIX。(我想知道易建联到底是如何在Windows上工作的?vty
看起来它可能对我直接有用,无需进一步修改。(但同样,不是在Windows上。)vty
已。。。基本上没什么有趣的。它有很多东西可以在终端上进行设置,但绝对没有任何东西可以从终端上读取。(除了可能检查echo是否打开等,按键没有任何问题。)unix
绝对没有什么值得关注的unix compat
termios
有一定的了解,它由System.Posix.Terminal
模块反映出来
在gnu.org上有一个很好的页面,介绍了如何使用termios
设置终端,您可以使用System.Posix.terminal
来实现这一点
下面是我的解决方案,它将IO
中的计算转换为使用非规范模式:
{- from unix library -}
import System.Posix.Terminal
import System.Posix.IO (fdRead, stdInput)
{- from base -}
import System.IO (hFlush, stdout)
import Control.Exception (finally, catch, IOException)
{- run an application in raw input / non-canonical mode with given
- VMIN and VTIME settings. for a description of these, see:
- http://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
- as well as `man termios`.
-}
withRawInput :: Int -> Int -> IO a -> IO a
withRawInput vmin vtime application = do
{- retrieve current settings -}
oldTermSettings <- getTerminalAttributes stdInput
{- modify settings -}
let newTermSettings =
flip withoutMode EnableEcho . -- don't echo keystrokes
flip withoutMode ProcessInput . -- turn on non-canonical mode
flip withTime vtime . -- wait at most vtime decisecs per read
flip withMinInput vmin $ -- wait for >= vmin bytes per read
oldTermSettings
{- install new settings -}
setTerminalAttributes stdInput newTermSettings Immediately
{- restore old settings no matter what; this prevents the terminal
- from becoming borked if the application halts with an exception
-}
application
`finally` setTerminalAttributes stdInput oldTermSettings Immediately
{- sample raw input method -}
tryGetArrow = (do
(str, bytes) <- fdRead stdInput 3
case str of
"\ESC[A" -> putStrLn "\nUp"
"\ESC[B" -> putStrLn "\nDown"
"\ESC[C" -> putStrLn "\nRight"
"\ESC[D" -> putStrLn "\nLeft"
_ -> return ()
) `catch` (
{- if vmin bytes have not been read by vtime, fdRead will fail
- with an EOF exception. catch this case and do nothing.
- The type signature is necessary to allow other exceptions
- to get through.
-}
(const $ return ()) :: IOException -> IO ()
)
{- sample application -}
loop = do
tryGetArrow
putStr "." >> hFlush stdout
loop
{- run with:
- VMIN = 0 (don't wait for a fixed number of bytes)
- VTIME = 1 (wait for at most 1/10 sec before fdRead returns)
-}
main = withRawInput 0 1 $ loop
{-来自unix库-}
导入System.Posix.Terminal
导入System.Posix.IO(fdRead、stdInput)
{-自基-}
导入System.IO(hFlush、stdout)
导入控制.Exception(最后是catch,IOException)
{-在原始输入/非规范模式下以给定的
-VMIN和VTIME设置。有关这些设置的说明,请参阅:
- http://www.gnu.org/software/libc/manual/html_node/Noncanonical-Input.html
-还有“男人的termios”。
-}
withRawInput::Int->Int->IO a->IO a
withRawInput vmin vtime application=do
{-检索当前设置-}
oldTermSettings这可能是最简单的解决方案,类似于其他编程语言中的典型代码:
import System.IO(标准输入法,线程)
getKey::IO[Char]
getKey=反向getKey''
其中getKey'chars=do
char一个选项是使用。一个极简主义的例子:
import Control.Monad
import UI.NCurses
main :: IO ()
main = runCurses $ do
w <- defaultWindow
forever $ do
e <- getEvent w Nothing
updateWindow w $ do
moveCursor 0 0
drawString (show e)
render
import-Control.Monad
导入UI.NCurses
main::IO()
main=runCurses$do
也许是软件包?Haskeline并不是我想要的,但看起来我可以通过阅读源代码得到我想要的…我错误地认为Haskell RTS做了一些奇怪的事情。然而,在编写了执行原始系统调用的C程序之后,我得到了类似的行为。似乎终端本身确实在缓冲按键,除非您将其置于正确的模式(这大概是readline
所做的)。