Search code examples
rustcommon-lispsbcl

Capture prompt from stdout


I am starting a Command, and writing to its stdin and reading its stdout, to basically pipe it into another terminal (xterm.js) The command in case is long running and interactive (it's a Lisp REPL). My code works, but I have one problem: How can I know if the Command is prompting for input instead of just printing out?

If I read > without a newline at the end from the command's stdout, its probably the process prompting the user for input. But it could also be that its a long running evaluation, that just happens to print > in the middle, but is not finished yet. How can I distinguish these cases?

    // using the os_pipe crate to create a new pipe with both, stdout and stderr,
    // to capture both, in correct order
    let (reader, writer_out) = os_pipe::pipe().unwrap();
    let writer_err = writer_out.try_clone().unwrap();
  
    let mut c = Command::new("path/to/sbcl");
    let mut child = 
        c
        .stdin(Stdio::piped())
        .stdout(writer_out)
        .stderr(writer_err)
        .spawn()
        .expect("command failed to start");
    
    let (sender, receiver) = bounded::<String>(0);

    let mut f = BufReader::new(reader);
    thread::spawn(move || {
        
        let mut buf = String::new();
        // using the utf8_chars crate to iterate over chars of the bufreader
        for c in f.chars().map(|x| x.unwrap()) {
            buf.push(c);
            if c == '\n' {
                sender.send(buf.clone()).expect("Failed to send.");
                buf.clear();
            } 
            // matches "> " or similar
            // if a "> " was sent without newline, I still want to capture it 
            else if RE_PROMPT.is_match(&buf) {
                sender.send(buf.clone()).expect("Failed to send.");
                buf.clear();
            }
        }
    });
    

Solution

  • It is obvious that no mechanism based on merely inspecting what a program prints can reliably tell if it has just printed a prompt and is now waiting for input: whatever it is looking for can simply be printed by the program to fool it.

    The only trick you could possibly do is to somehow know then whatever is on the other end of the pipe is trying to read from it, which would indicate that it is expecting input and thus what it just printed might be a prompt. It may be that you can do that with pseudoterminals on *nix. Even this is not completely reliable.

    However the right way to do this is not to screen-scrape output at all: it's to build a protocol whereby the system you are talking to tells you when it has done whatever you asked and is waiting for you to ask it. That is what swank does, for instance.