在Haskell程序中运行vi(处理ptys)

8
我正在尝试编写一个记录shell,例如捕获以结构化格式运行的命令数据。为此,我使用readline读取命令,然后在子shell中执行它们,同时捕获诸如花费时间、环境、退出状态等内容。
到目前为止还不错。然而,在这个记录shell中运行像viless之类的初始尝试失败了。调查表明,需要做的是建立一个伪终端并将子shell连接到其中,而不是普通管道。这样可以防止vi抱怨没有连接到终端,但仍然失败了——我会在屏幕上打印一些无意义的东西,并且命令会作为编辑器中的字符打印出来,例如“ESC”只会显示^[
我认为我需要做的是将pty设置为raw模式。为此,我尝试了以下操作:
  pty <- do
    parentTerminal <- getControllingTerminalName >>= 
                      \a -> openFd a ReadWrite Nothing defaultFileFlags
    sttyp <- getTerminalAttributes parentTerminal
    (a, b) <- openPseudoTerminal
    let rawModes = [ProcessInput, KeyboardInterrupts, ExtendedFunctions, 
                    EnableEcho, InterruptOnBreak, MapCRtoLF, IgnoreBreak, 
                    IgnoreCR, MapLFtoCR, CheckParity, StripHighBit, 
                    StartStopOutput, MarkParityErrors, ProcessOutput]
        sttym = withoutModes rawModes sttyp
        withoutModes modes tty = foldl withoutMode tty modes
    setTerminalAttributes b sttym Immediately
    setTerminalAttributes a sttym Immediately
    a' <- fdToHandle a
    b' <- fdToHandle b
    return (a',b')

例如,我们获取父终端的属性,去除我认为对应于将tty设置为原始模式的各种标志(基于this codeSystem.Posix.Terminal的哈达克),然后在pty的两侧设置这些标志。
然后,我使用createProcess在shell中启动一个进程,并使用waitForProcess连接到它,为子进程的stdin和stdout句柄提供pty的从属端。
eval :: (Handle, Handle) -> String -> IO ()
eval pty command = do
    let (ptym, ptys) = pty
    (_, _, hErr, ph) <- createProcess $ (shell command) { 
          delegate_ctlc = True
        , std_err = CreatePipe
        , std_out = UseHandle ptys
        , std_in = UseHandle ptys
      }
    snipOut <- tee ptym stdout
    snipErr <- sequence $ fmap (\h -> tee h stderr) hErr
    exitCode <- waitForProcess ph
    return ()
  where tee :: Handle -> Handle -> IO B.ByteString
        tee from to = DCB.sourceHandle from
            $= DCB.conduitHandle to -- Sink contents to out Handle
            $$ DCB.take 256 -- Pull off the start of the stream

这肯定会改变终端设置(使用stty确认),但并不能解决问题。我是不是漏掉了什么?还有其他设备需要设置属性吗?
编辑:完整可运行代码可以在https://github.com/nc6/tabula找到——我为了这篇文章简化了一些内容。
2个回答

1

@jamshidh 指出我实际上没有将标准输入连接到伪终端的主端口,因此我遇到的问题与vi或终端模式无关,完全是因为没有传入任何输入!


1
这是创建 vi 进程的方法:
(_, _, hErr, ph) <- createProcess $ (shell command) { 

那些返回值是 stdin/stdout/stderr。你会丢弃 stdin/stdout(并保留 stderr)。你需要它们来与 vi 通信。基本上,当你输入 ESC 时,它甚至没有到达进程。
作为一个更大的架构注释 - 你不仅要重写终端代码,还要重写完整的 REPL/shell 脚本....这是一个比你想象中更大的项目(去读一下 bash 手册,看看他们需要实现所有的东西)。你可能需要考虑包装一个用户可选择的 shell 脚本(如 bash)。Unix 在这方面非常模块化,这就是为什么 xterm、ssh、命令提示符等都以相同的方式工作-它们代理所选的 shell 脚本,而不是每个人都编写自己的原因。

丢弃这些句柄是正确的 - 它们附加在一个pty的从属端上,而我持有主端。但是,您无意中指出我实际上没有将我的stdin连接到主控端!所以这可能是个问题 :-) - Impredicative

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接