Running vi from within haskell (working with ptys)

I am trying to write a registry wrapper; for example, which captures data about commands that are executed in a structured format. To do this, I use readline to read in commands, and then execute them in a subshell, capturing things like time, environment, exit status, etc.

So far so good. However, initial attempts to run things like vi or less from this log shell failed. The study showed that the point is to install a pseudo-tty and connect a subshell to this, and not to a regular pipe. This stops vi, complaining that it is not connected to the terminal, but still fails - I get some kind of nonsense printed on the screen, and the commands print like characters in an editor - for example, "ESC" just displays ^[ .

I thought I needed to do pty in raw mode. For this, I tried the following:

  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') 

eg. we get the attributes of the parent terminal, remove the various flags, which, it seems to me, correspond to setting tty to raw mode (based on this code and haddock for System.Posix.Terminal ), and then set them on both sides of pty.

Then I start the process inside the shell using createProcess and use waitForProcess to connect to it, providing the pty subordinate for stdin and stdout descriptors for the child process:

 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 

This definitely changes the terminal settings (confirmed with stty ), but does not fix the problem. Am I missing something? Is there any other device for which I need to set attributes?

Edit: The full executable code is available at https://github.com/nc6/tabula - I have simplified a few things for this post.

+7
linux shell haskell pty
source share
2 answers

@jamshidh pointed out that I didn’t actually connect my stdin to the main side of pty, so the problems I was getting had nothing to do with vi or terminal modes and completely did not concern any input

0
source share

Here's how you create the vi process:

 (_, _, hErr, ph) <- createProcess $ (shell command) { 

Return Values stdin/stdout/stderr . You throw away stdin/stdout (and hold stderr ). You will need those who communicate with vi . Basically, when you enter ESC , it doesn't even get into the process.

As a larger architectural note: you are rewriting not only the terminal code, but also the full REPL / shell script .... This is a larger project than you probably want to get into (go through bash to see everything they needed to implement) . Perhaps you should consider wrapping around a custom shell script (e.g. bash). Thus, Unix is ​​pretty modular, so xterm, ssh, command line, etc. They work the same way - they proxy the selected shell script, and not each one of its own.

0
source share

All Articles