3

I have a python script (serving as a command-line interface) that waits for an input query using the input function.

while True:
  query = input('Enter query: ')
  thread = Thread(target=exec_query, args=(query,))
  thread.start()

At the same time, a worker thread is executing such queries. The output of the queries is printed to the command-line using the print function.

def exec_query(query_data):
  # doing a complex data processing ...
  print('results of the query')

Thereby, the output of the print function is written behind the prefix 'Enter query: ' printed by the second execution of the print function in the main thread:

Enter query: {the query}
Enter query: results of the query

I would like to achieve that the output of the print function is inserted before the prefix 'Enter query: ' (or that it looks like this is the case):

Enter query: {the query}
results of the query
Enter query:

I already through about a few approaches but did not found a good solution. One workaround would be to erase the prefix by adding '\x1b[1A\x1b[2K' and write it back after printing the output of the query execution. The problem here is, that I don't know how to reconstruct the incomplete user input (query) that may already be inserted by the user at this point.

guenthermi
  • 53
  • 6
  • If you want to get the thread output then https://stackoverflow.com/questions/6893968/how-to-get-the-return-value-from-a-thread-in-python should do – Natthaphon Hongcharoen May 20 '20 at 12:28
  • Return the message and something like index and write it back to where it got excuted maybe? – Natthaphon Hongcharoen May 20 '20 at 12:32
  • Your main loop store the current excuted number, say 12, then the thread return message and index 10 so you just need to go back 2 lines – Natthaphon Hongcharoen May 20 '20 at 12:34
  • The problem is not about reading out the results of the thread. The problem is that I want to be able to type in a second query while the first query result is processed and if this thread produces output it should be represented before the new query and should not interrupt an incomplete query. – guenthermi May 20 '20 at 14:55

1 Answers1

2

use this Read 1 char from terminal

  • returns bytes
    • get str by bytes.decode()
  • doesn't echo (typing isn't visible)
    • instead of msvcrt.getch use .getche inside _GetchWindows.__call__
  • msvcrt is not globally imported

do something like

while input != '\r': # or some other return char, depends
    input += getch().decode()

and

# in front of erase
while msvcrt.kbhit(): # read while there is something to read
    remember_input += getch().decode()
print(erase)
# and than you should return remember_input to stdin somehow (try msvcrt.putch)

I haven't done it myself because of complexity (writing with threading (I'm new to it), in/output controlling (because of which my vsc terminal hated me every time), and probably more reasons I'm too tired to think of),
but I'm sure you won't quit

EDIT: Oh, yes
I forgot to mention you will also probably want to write your own print and input,
in which case useful thing would be input(prompt, remember_string)
the prompt would be unerasable by a backspace, and remember_string would

BIG UPDATE
I used curses module instead of msvcrt (as originally suggested)

This actually dwells with problem similar to yours (much simplified simulation),
but resolves the core of the problem

  1. receiving input
  2. deleting line when something happens & writing message in that line
  3. reinputing with already-written stuff there

This takes input as long as it is <= 3 chars.
If >= 4 chars written, it will do (3.) what was, to you, finishing query,
and then ask for input again with old input.
when pressed ENTER, finishes input

import curses
import curses.ascii as ascii

def getch(stdscr: curses.window):
    'return single char'
    a = stdscr.get_wch()
    stdscr.refresh()
    return a

def prompt(stdscr: curses.window):
    'write prompt for input'
    addstr(stdscr, "Enter query: ")

def addstr(stdscr: curses.window, str):
    'write string to window'
    stdscr.addstr(str)
    stdscr.refresh()

def del_line(stdscr: curses.window):
    'deletes line in which cursor is'
    row, col = stdscr.getyx()
    stdscr.move(row, 0)
    stdscr.clrtoeol()

@curses.wrapper
def main(stdscr: curses.window):
    # next 3 lines were on some tutorial so I left them be
    # Clear screen
    stdscr.clear()
    curses.echo()

    # I will use marks like #x.y to indicate places
    q = ''
    while True: #EDIT from `for (5)` to `while`
        prompt(stdscr)
        addstr(stdscr, q) # at the beginning & after enter is pressed, q==''
                          # else it is old input (that was >= 4 chars
        for i in range(4): # range 4 to take 4 or less chars
            a = getch(stdscr) #read charby char
            if ascii.isalnum(a): #letters & numbers
                q += a
            elif a == '\n' or a == '\r': # enter pressed == input finished
                stdscr.addstr(f"\nfinished input {q}\n")
                q = ''
                break
        else: # this block happens (in py) if no break occurred in loop
            # that means, in this case, that ENTER was not pressed, i.e. input is stillongoing
            # but since it is > 4 chars it is briefly interrupted to write message
            del_line(stdscr)
            addstr(stdscr, "Input interupted\n")

    return 

test
run this program (I recommend simply double-click the file to open std terminal, because other terminals may have something against this [program])
(E represents ENTER)
and type: abcE, abcdefE, abcdefghijE
to see what this does

P.S.

this may solve your problem, but the capabilities for this module are bigger,
and I didn't want to write to much to complex API.
solution for that would be to write API for easy managing more things like moving with arrows, but that is not in the scope of this question

Superior
  • 787
  • 3
  • 17