3

I'm writing a multithreaded program with an interactive console:

def console()
    import readline
    while True:
        data = input()
        do_whatever(data.split())

However, a library I'm using runs my callbacks from a different thread. The callback needs to print to the console. Thus I want to clear the command line, re-display the prompt, and re-show the command line.

How do I do that, short of re-implementing readline?

Matthias Urlichs
  • 2,301
  • 19
  • 29
  • Do you need your callback to print to stdout/stderr? One quick and ugly hack is to print many 'new lines' to refresh the whole screen. or execute system command like "clear" or "cls" based on os. – thuyein Sep 24 '18 at 07:58
  • I can't do whole-screen refresh because I need the scrollback buffer. Also, that doesn't tell me how to re-write the readline prompt and whatever the user is in the middle of entering. – Matthias Urlichs Sep 24 '18 at 08:05
  • Have you found a solution for your problem? An out-of-the box solution might be to use `rlwrap` around your program instead of using readline from Python. – mic_e Dec 17 '20 at 14:12

1 Answers1

2

Starting from this answer that solves the issue for C, I arrived at the following Python code, which works well enough for my purposes:

import os
import readline
import signal
import sys
import time
import threading


print_lock = threading.Lock()

def print_safely(text):
    with print_lock:
        sys.stdout.write(f'\r\x1b[K{text}\n')
        os.kill(os.getpid(), signal.SIGWINCH)

def background_thread():
    while True:
        time.sleep(0.5)
        print_safely('x')

threading.Thread(target=background_thread, daemon=True).start()

while True:
    try:
        inp = input(f'> ')
        with print_lock:
            print(repr(inp))
    except (KeyboardInterrupt, EOFError):
        print('')
        break

print_safely

  • uses \r\x1b[K (CR + CSI code EL) to remove the existing Readline prompt
  • prints its payload line
  • sends SIGWINCH to the thread that runs readline (in my case, readline runs in the main thread so os.getpid() gives the right target PID); this causes readline to re-draw its full prompt.

Possible remaining issues:

  • As mentioned in the other answer, if the readline prompt is longer than one line, the line-clearing will most likely fail.
  • You need to acquire the print_lock whenever printing in the main thread (this is true independent of the readline issue)
  • There might be some racy behaviours when the user presses a key at the exact same moment when the background thread prints.
mic_e
  • 5,594
  • 4
  • 34
  • 49