I have a function that has a continual loop asking the user for their input using python's builtin input(prompt). I also have a separate thread doing some work, and when a certain condition is met in that thread, the input prompt should change.
So, say at startup the prompt is "Input: " but then in the middle of the thread's work, a condition is met, so the prompt should be switched to "Go Ahead, Type: ". Now if the user doesn't enter anything at that first prompt, but the thread reaches the point where the prompt is switched, then we are still stuck on that first blocking input call that has "Input: " as it's prompt.
# Start the thread that does some work and eventually changes the prompt
myThread.start()
#Start looping to ask for user input, get the prompt from the thread
while True:
userInput = input(myThread.get_prompt())
<do something with input>
I know I can accomplish this with select([stdin],[],[],0.0) to poll stdin before doing a stdin.readline() and then just print the prompt again if 1) we got user input or 2) if the prompt changed.
However, I'd like to find a solution that uses Python's builtin input() function so that I can set tab completion with python's readline module.
I tried playing around with a signal that would basically interrupt input() every couple seconds. With this I would need to make it appear seamless that a new input() call was made without it reprinting the prompt. So something like:
myThread.start()
while True:
userInput = None
signal(SIGALRM, handler)
signal.alarm(3)
try:
userInput = input("\r" + myThread.get_prompt())
except TimeoutError:
pass
finally:
signal.alarm(0)
<do something with input>
def handler(signum, frame):
raise TimeoutError
Which is messy, but then when it times out, and the new input() is called, the current line buffer gets printed but the cursor is at the front of it. So if I type "aaa" then it reprints the "aaa" but the cursor is not at the end of that buffer, it is at the beginning.
Any suggestions?
Update: I can certainly try to play around more with the signal option. It seems like that might be my best option so far. I just can't seem to get the cursor to move to the end of the input buffer if the user has already started typing something. I don't want the user to be aware of the call timing out and being called again.
Ok I figured it out. And it's way better than using the signal alarm to timeout.
In case anyone in the future stumbles across this specific problem, here is essentially what I did:
import threading
import time
import readline
import sys
class MyThread(threading.Thread):
def __init__(self):
super(MyThread, self).__init__()
# Set the initial prompt
self.prompt = "Input: "
def run(self):
time.sleep(6)
# Now we want to change the prompt so that next time input loop asks for prompt it is correct
self.set_prompt("Go ahead, type: ")
# Now overwrite what was there in the prompt
sys.stdout.write("\r" + self.get_prompt())
sys.stdout.flush()
# Get the current line buffer and reprint it, in case some input had started to be entered when the prompt was switched
sys.stdout.write(readline.get_line_buffer())
sys.stdout.flush()
def get_prompt(self):
return self.prompt
def set_prompt(self, new_prompt):
self.prompt = new_prompt
# Create and start thread
myThread = MyThread()
myThread.start()
# Input loop
while True:
userInput = input(myThread.get_prompt())
print("Got: " + userInput)
If you are using tab complete (which I didn't show here), you may notice that if you try to tab complete after the first time that the prompt switches (without accepting any input from the first prompt), then tab complete still thinks we are reading from the first prompt and will reprint that prompt. In that case, you will need to do the following:
readline.set_completion_display_matches_hook(display)
Where display is a function that does something like what our thread had to do to rewrite the correct prompt:
def display(substitution, matches, longest_match_length):
# Print the matches
print('')
buffer = ''
for match in matches:
buffer += match + " "
print(buffer)
# Rewrite the NEW prompt
sys.stdout.write(<prompt>)
sys.stdout.flush()
sys.stdout.write(readline.get_line_buffer())
sys.stdout.flush()