prompt-toolkit / python-prompt-toolkit

Library for building powerful interactive command line applications in Python
https://python-prompt-toolkit.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
9.32k stars 714 forks source link

Dynamically updating prompt #1286

Open asmeurer opened 3 years ago

asmeurer commented 3 years ago

Is it possible to make a prompt that updates dynamically based on an asynchronous callback? This seems like it should be doable, but I'm not sure because it looks like the layout code might assume that the prompt is fixed once it is computed. For example, I might want to make a shell prompt that includes the git branch name that automatically updates when the branch is changed, even if it is changed in another terminal window.

jonathanslenders commented 3 years ago

Hi @asmeurer,

It is possible indeed. Have a look at this example, for instance: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/examples/prompts/clock-input.py Would that work?

asmeurer commented 3 years ago

Thanks. That looks like it would work. I'm not sure if a refresh interval is the best way to do it for every case, but it looks like I can use the same method that refresh_interval uses to register an async callback (for instance, a callback based on a signal or some IO event).

jonathanslenders commented 3 years ago

You can indeed call invalidate by hand and not use the refresh interval.

jnoortheen commented 3 years ago

passing that as a function and have a refresh_interval would call the function multiple times i.e. every time it redraws. The better approach would be to update the prompt_session.message = some_str somewhere and then call invalidate to redraw the prompt.

jonathanslenders commented 3 years ago

@jnoortheen : That would also be possible indeed!

You have to call PromptSession.app.invalidate() for this.

solarjoe commented 2 years ago

I also needed this functionality and put together a simple example:

import asyncio
from prompt_toolkit import PromptSession
import datetime
from prompt_toolkit.patch_stdout import patch_stdout

session = PromptSession()

def prompt_string():
    now = datetime.datetime.now()
    msg = f"{now.hour}:{now.minute}:{now.second}"
    return msg + " >"

async def another_coroutine():
    while True:
        await asyncio.sleep(0.5)
        session.message = prompt_string() 
        session.app.invalidate()

async def prompt_coroutine():
    while True:
        # just in case your coroutine print stuff
        with patch_stdout():
            result = await session.prompt_async(prompt_string())

async def main():

    task1 = another_coroutine()
    task2 = prompt_coroutine()
    L = await asyncio.gather(task1, task2)
    print(L)
solarjoe commented 2 years ago

I would also modify the labels on dialogs dynamically. When creating a application using app = message_dialog() from prompt_toolkit.shortcuts, is there a reasonable way to reach the attributes Dialog.title and Dialog.body in that application?

I got it working by setting up the dialog from scratch like

dialog = Dialog(...)
app = _create_app(dialog)
dialog.title = prompt_string()
app.invalidate()

but would like to use the convenience functions if possible.