Preserve interactive typeahead during commands
ober
e5f8689c582c304b3dd3623e418d6b8d3b27a173
--- a/main.ss +++ b/main.ss @@ -26,8 +26,7 @@ :jsh/startup :jsh/arithmetic :jsh/ffi - (only (std os platform) platform-cpu-count) - (only (compat gambit) tty-mode-set!)) + (only (std os platform) platform-cpu-count)) ;;; --- Session state parameters --- @@ -391,12 +390,10 @@ (let ([action (trap-get "ERR")]) (when (string? action) (execute-input action env)))))))))))) - ;; Restore terminal to sane state after command - ;; (programs like top/vim may leave echo off or raw mode on). - ;; Must use Gambit's tty-mode-set! to keep port state in sync; - ;; raw tcsetattr behind Gambit's back desynchronizes its cache. + ;; Restore terminal to sane state after command without flushing + ;; queued typeahead from the terminal input buffer. (when (= (ffi-isatty 0) 1) - (tty-mode-set! (current-input-port) #t #t #f #f 0)) + (ffi-termios-restore 0 0)) (loop (+ cmd-num 1)))))))) (def (output-to-string proc) --- a/test/test-binary.sh +++ b/test/test-binary.sh @@ -140,6 +140,65 @@ PY fail=$((fail + 1)) printf 'FAIL: interactive REPL multi-line continuation (for loop)\n' fi + + if python3 - "$BINARY" <<'PY' +import os, pty, re, select, sys, time + +binary = sys.argv[1] +master, slave = pty.openpty() +pid = os.fork() +if pid == 0: + os.close(master) + os.setsid() + os.dup2(slave, 0) + os.dup2(slave, 1) + os.dup2(slave, 2) + os.close(slave) + env = os.environ.copy() + env['PS1'] = 'jsh-test> ' + env['PS2'] = 'jsh-more> ' + os.execve(binary, [binary, '-i'], env) + sys.exit(1) +os.close(slave) + +out = b'' + +def read_for(timeout): + global out + end = time.time() + timeout + while time.time() < end: + r, _, _ = select.select([master], [], [], 0.05) + if not r: + continue + try: + data = os.read(master, 4096) + except OSError: + break + if not data: + break + out += data + +read_for(1.0) +os.write(master, b'sleep 1\n') +time.sleep(0.15) +os.write(master, b'x=TYPEAHEAD_OK; printf "ran:%s\\n" "$x"\n') +read_for(2.5) +os.write(master, b'exit\n') +read_for(0.5) +_, status = os.waitpid(pid, 0) + +text = re.sub(r'\x1b\[[0-9;?]*[a-zA-Z]', '', out.decode('utf-8', errors='replace')) +if 'ran:TYPEAHEAD_OK' in text and os.WIFEXITED(status): + sys.exit(0) +print(text, file=sys.stderr) +sys.exit(1) +PY + then + pass=$((pass + 1)) + else + fail=$((fail + 1)) + printf 'FAIL: interactive REPL preserves typeahead while foreground command runs\n' + fi else echo "SKIP: interactive REPL tests require python3" fi