Textualize / textual

The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.
https://textual.textualize.io/
MIT License
25.11k stars 766 forks source link

Live console widget #125

Closed hajdbo closed 1 year ago

hajdbo commented 2 years ago

It would be nice to have a widget that behaves like a console. At first, just able to print and auto-scroll, and scroll bar, with auto-refresh. console.print("another brick in the wall")

Maybe reuse Rich's Live View / Console?

Tinche commented 2 years ago

I am looking into this too. Could we emulate this with an autoscrolling ScrollView?

presedo93 commented 2 years ago

Did you get any further in this topic? I have a pytorch-lightning script that prints to console and I would like to encapsulate that printing inside a Panel (or a live console widget) so that the prints of the module won't be in top of the interface when it starts

ncwhale commented 2 years ago

When I try to using rich.console inside textual, BOOM! everything broken.

DanielATucker commented 2 years ago

@willmcgugan Please implement this feature. I have an application that is heavily dependant on Rich, Rich Handlers, Rich Logging, Rich Console and now Textual (can you tell I like your work lol), and the last piece of the puzzle is getting Rich Console to work as a widget in Textual. I've been driving myself mad for days thinking the bug was in my code until I stumbled upon this thread. Please allow for a creation of a widget in Textual that can display a Rich Console utilizing the ScrollView or something similar.

yamatteo commented 2 years ago

@DanielATucker Can something like this work? I think the relevant parts are: using Console.capture() and ScrollView.animate("y", ...)

import random

from rich.text import Text

from textual import events
from textual.app import App
from textual.widgets import Header, Footer, Placeholder, ScrollView
from rich.console import Console

console = Console()

def random_content():
    what = random.randint(0, 2)
    if what == 0:
        return f"Some random number [blue] {random.random()}",
    elif what == 1:
        try:
            import requests
            x = requests.get("https://baconipsum.com/api/?type=meat-and-filler").json()
        except Exception:
            x = "   ...no, no content from internet."
        return f"Some json from the internet...", x
    elif what == 2:
        return f"Your globals...", globals()

class MyApp(App):
    console_outputs = Text("")

    async def on_load(self, event: events.Load) -> None:
        await self.bind("q", "quit", "Quit")
        await self.bind("p", "console", "Generate random content")

    async def action_console(self):
        pre_y = self.body.y
        with console.capture() as capture:
            for line in random_content():
                console.print(line)
        self.console_outputs.append(Text.from_ansi(capture.get()+"\n"))
        await self.body.update(self.console_outputs)
        self.body.y = pre_y
        self.body.animate("y", self.body.window.virtual_size.height, duration=1, easing="linear")

    async def on_mount(self, event: events.Mount) -> None:
        self.body = ScrollView(gutter=1)

        await self.view.dock(Header(), edge="top")
        await self.view.dock(Footer(), edge="bottom")

        await self.view.dock(self.body, edge="right")

MyApp.run(title="Press p to generate console content")
DanielATucker commented 2 years ago

@yamatteo, That's not exactly what I'm looking for. I have an application that uses console.input() and Rich logging to simulate a bash screen. When I try to add an input to the code you wrote, my terminal just hangs indefinitely with no response. Maybe if we could integrate https://github.com/sirfuzzalot/textual-inputs then have it displayed using your console_outputs.

If not, I understand, but could you look at my code and point me in the right direction of integration? https://github.com/DanielATucker/Brain. The main console logic is in Brain.py under def switchboard

yamatteo commented 2 years ago

@DanielATucker is it a REPL you want?

from rich.text import Text

from textual import events
from textual.app import App
from textual.widgets import Header, ScrollView
from rich.console import Console
from textual_inputs import TextInput

console = Console()

class OutConsole(ScrollView):
    prev = Text("")

    async def eval(self, text_input):
        pre_y = self.y
        with console.capture() as capture:
            try:
                console.print(eval(text_input))
            except Exception:
                console.print_exception(show_locals=True)
        self.prev.append(Text.from_ansi(capture.get() + "\n"))
        await self.update(self.prev)
        self.y = pre_y
        self.animate("y", self.window.virtual_size.height, duration=1, easing="linear")

class InConsole(TextInput):
    def __init__(self, out):
        super(InConsole, self).__init__()
        self.out = out

    async def on_key(self, event: events.Key) -> None:
        if event.key == "enter":
            await self.out.eval(self.value)
            self.value = ""

class GridTest(App):
    async def on_mount(self) -> None:
        output = OutConsole()
        in_put = InConsole(out=output)

        grid = await self.view.dock_grid(edge="left", name="left")
        grid.add_column(fraction=1, name="u")
        grid.add_row(fraction=1, name="top", min_size=3)
        grid.add_row(fraction=20, name="middle")
        grid.add_row(fraction=1, name="bottom", min_size=3)
        grid.add_areas(area1="u,top", area2="u,middle", area3="u,bottom")
        grid.place(area1=Header(), area2=output, area3=in_put, )

GridTest.run(title="Something like REPL")
DanielATucker commented 2 years ago

@yamatteo yes, your fix is exactly what I needed. Thanks for your help!

DanielATucker commented 2 years ago

@yamatteo How would add to the console from outside of the OutConsole. For example in the on_mount I am trying to add console.print("Hello") after both the InConsole and Outconsole, but when I run the code it never shows up, anything else I try ends with a series or errors. I have been trying for days to figure out how to print to the console from other parts of my program, eg. error messages and status updates and such.

yamatteo commented 2 years ago

@DanielATucker You can do as follow:

class GridTest(App):
    async def on_mount(self) -> None:
        output = OutConsole()
        in_put = InConsole(out=output)

        # ... same as before ...

        # You can use output.eval to add console output
        await output.eval(repr("Hello World!"))

        # You can change the value of output directly
        await output.update(output.prev[:-2] + "?")

Maybe I should point out that this is not the clean way to do it in textual. I'm sure the developer is cooking some proper widget that will work in harmony with the rest of the framework. This is just a dirty hack.

willmcgugan commented 1 year ago

https://github.com/Textualize/textual/wiki/Sorry-we-closed-your-issue

github-actions[bot] commented 1 year ago

Did we solve your problem?

Consider buying the Textualize developers a coffee to say thanks.

Textualize

erezsh commented 1 year ago

Request to re-open this issue!