Search code examples
pythonconsolekeyboardinterrupt

General way to interrupt scrolling text in console interface


i am trying to make a game in the console and want to have scrolling text. i want to be able to hit a key/type enter and skip the scrolling and print the rest. so far i tried using pygame (out of the picture due to having to have a display surface active), asyncio with sys.stdin.read(1)(blocked the run on cmd and didnt query user in async based ide's).

this was my latest attempts at this.

import asyncio,time,sys

global skip 
immutablesleep = 0.04
mutablesleep = [immutablesleep]

async def aprintl(string,sep="",end="\n",sleep=mutablesleep):
    global skip
    for letter in string+end:
        print(letter+sep,end="",flush=True)
        await asyncio.sleep(sleep[0])
    skip = True

async def break_print():
    global skip
    while not skip:
        ch = sys.stdin.read(1)
        if len(ch)>0:
            mutablesleep[0]=0
            skip = True
        await asyncio.sleep(0.1)    


def printl(*args):
    global skip
    skip = False
    mutablesleep[0] = immutablesleep
    asyncio.gather(aprintl(*args),break_print())

keep in mind when suggesting modules that i want both os system independant code, and something that can be easly hooked into when freezing modules into exe.

Update:

currently this functions fairly well in terms of interrupting the slow print, but two issues persist:
1: the interruption by pressing enter is cutting through the printed line, making it unreadable
2: thread is still waiting for enter even after the print finished.

async def break_print():
    global skip, ch
    thread = Thread(target=t)
    thread.start()
    thread.join(timeout=0.1)
    while not skip:
        if len(ch) > 0:
            mutablesleep[0]=0
            skip = True
            ch = ''
        await asyncio.sleep(0.1)


def t():
    """Needed to read from stdin async"""
    global ch
    ch = sys.stdin.readline()

Solution

  • I believe your problem has to do with the last line

    asyncio.gather(aprintl(*args),break_print())
    

    Looking at the docs, the function signature looks like this: awaitable asyncio.gather(*aws, loop=None, return_exceptions=False). The .gather call is likely not working as expected because you are not passing a list of callables, you're instead passing aprintl(*args) to *aws and break_print() is being passed to the loop argument

    Change the line to the below, and see if it works as you're expecting.

    asyncio.gather([aprintl(*args),break_print()])
    

    Update

    I got your code to work, with some caveats

    import asyncio
    import sys
    from threading import Thread
    
    global skip
    ch = ''
    immutablesleep = 0.04
    mutablesleep = [immutablesleep]
    
    
    async def aprintl(string,sep="",end="\n",sleep=mutablesleep):
        global skip
        for letter in string+[end]:
            if not skip:
                print(letter+sep,end="",flush=True)
                await asyncio.sleep(sleep[0])
        skip = True
    
    
    async def break_print():
        global skip, ch
    
        while not skip:
            thread = Thread(target=t)
            thread.start()
            thread.join(timeout=.1)
    
            if len(ch) > 0:
                mutablesleep[0]=0
                skip = True
                ch = ''
            await asyncio.sleep(0.1)
    
    
    def t():
        """Needed to read from stdin async"""
        global ch
        ch = sys.stdin.readline()
    
    
    async def printl(*args):
        global skip
        skip = False
        mutablesleep[0] = immutablesleep
        await asyncio.gather(aprintl(*args), break_print())
    
    
    if __name__ == '__main__':
        x = ['asdf ', 'asdf']*5000
        asyncio.run(printl(x))
    

    What was changed

    • Added t() which runs in a Thread for .1 seconds every time break_print runs -- this was required as I believe the reason your initial code isn't running is because it's hanging at the sys.stdin.read(1) line
    • Run printl() via asyncio.run()
    • Added an if not skip: check in aprintl(), otherwise it will print the entire input once skipped

    Caveats

    • You must hit enter to stop the printout -- even with .read() you must hit enter. I use readline() because it will return any characters input before the enter key is hit (meaning, you could check to make sure the user input some character before hitting enter: len(ch.strip() > 0): do ...
    • The script doesn't exit even after skip == True -- this is likely because break_print() doesn't exit when skip == True, it will simply continue to loop.

    I realize this may not work in your use case, but I hope it at least gives you some ideas.