Textualize / textual-serve

Serve Textual apps locally
MIT License
186 stars 8 forks source link

Access the terminal environment #14

Closed Niatomi closed 3 months ago

Niatomi commented 3 months ago

Is there any way to suspend app to run shell command or at least run code on the server side?

Like

@on(w.Button.Pressed, '#ping-key')
def on_button_pressed(self, event: w.Button.Pressed) -> None:
    with self.app.suspend():
        code = system(...)
willmcgugan commented 3 months ago

Not with a UI. But you can use subprocesses to run commands.

https://docs.python.org/3/library/asyncio-subprocess.html

Niatomi commented 3 months ago

I partially did this, but still it works only in one way direction with endless output cmds like ping. SSH or any user input relying cmds causes to break textual, which causes a glitches of overlapping console request input. So, hope somebody found this usefull.

import textual.widgets as w
from textual.screen import ModalScreen
from textual.reactive import reactive
import asyncio

class ConsoleEmulator(ModalScreen):

    view_stdout = reactive(b'')
    BINDINGS = [("ctrl+e", "close", "Exit")]

    def __init__(self, cmd='ping localhost'):
        super().__init__()
        self.initial_cmd = cmd

    async def stdout_handle(self, source):
        while 1:
            self.view_stdout = await source.read(1)

    async def on_mount(self):
        self.log_view: w.Log = self.query_one(w.Log)
        self.proc = await asyncio.create_subprocess_shell(
            self.initial_cmd,
            stdout=asyncio.subprocess.PIPE,   
        )
        self.stdout_handle_task = asyncio.create_task(
            self.stdout_handle(self.proc.stdout))

    def watch_view_stdout(self):
        self.log_view.write(self.view_stdout.decode('utf-8'))

    def compose(self):
        yield w.Log(auto_scroll=True)
        yield w.Footer()

    def action_close(self):
        try:
            self.stdout_handle_task.cancel()
        except asyncio.CancelledError():
            ...
        self.proc.kill()
        self.app.pop_screen()