Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rich's print module breaks nearby scrollable boxes. #3921

Closed
KingKDot opened this issue Dec 22, 2023 · 15 comments
Closed

rich's print module breaks nearby scrollable boxes. #3921

KingKDot opened this issue Dec 22, 2023 · 15 comments

Comments

@KingKDot
Copy link

Textual Diagnostics

Versions

Name Value
Textual 0.37.1
Rich 13.7.0

Python

Name Value
Version 3.12.1
Implementation CPython
Compiler MSC v.1937 64 bit (AMD64)
Executable C:\Users\this1\AppData\Local\Programs\Python\Python312\python.exe

Operating System

Name Value
System Windows
Release 11
Version 10.0.22621

Terminal

Name Value
Terminal Application Unknown
TERM Not set
COLORTERM Not set
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=120, height=30
legacy_windows False
min_width 1
max_width 120
is_terminal True
encoding utf-8
max_height 30
justify None
overflow None
no_wrap False
highlight None
markup None
height None

SOMETIMES it breaks just by adding text to the LOG box on the left side, it will also ALWAYS break when resizing the screen.

show

The text from the LOG box is being added from a on_print function. The following code is provided below.

    def on_print(self, event: Print) -> None:
        self.query_one(Log).write(event.text)

    def on_mount(self) -> None:
        self.run_my_worker()

    @work(thread=True)
    def run_my_worker(self):
        self.begin_capture_print(self, True, True)

If you need any more of the code for context just let me know I can send the rest.

Copy link

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

@TomJGooding
Copy link
Contributor

TomJGooding commented Dec 23, 2023

Could you provide more detail about what you're printing here? If you're able to provide a MRE so someone can reproduce the issue, it would really help find a solution.

@KingKDot
Copy link
Author

Could you provide more detail about what you're printing here? If you're able to provide a MRE so someone can reproduce the issue, it would really help find a solution.

when you have the following code

from rich import print

then in a work thread (or any function/button event) you print anything that has COLORED text, your boxes will break. Here is all my code to better explain (I'm very bad at explaining)

import time

from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, ScrollableContainer
from textual.widgets import Footer, Header, Button, Static, Placeholder, Log, Pretty
from textual.widget import Widget
from textual.events import Print
from textual.reactive import Reactive
from textual.binding import Binding
from rich.syntax import Syntax
from rich import print
from textual import work
from dataclasses import dataclass


@dataclass
class settings:
    debug: bool = False


class Code_Console1(Static):
    code: str = Reactive("test")

    def compose(self) -> ComposeResult:
        yield Log()


class Code_Console2(Static):
    def compose(self) -> ComposeResult:
        with open("tests\\test_1.bat", "r") as f:
            code = f.read()
        yield Static(
            Syntax(code, "bat", theme="monokai", line_numbers=True, word_wrap=True),
        )


class Obfuscator(Static):
    def compose(self) -> ComposeResult:
        yield Button("Obfuscate", id="start", variant="success")


class SomalifuscatorV2(App):
    CSS_PATH = "style.tcss"
    BINDINGS = [Binding("d", "toggle_debug", "Toggle Debug")]

    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "start":
            print("Obfuscating")
        else:
            print("Unknown button pressed!")

    def compose(self) -> ComposeResult:
        yield Header()
        yield Footer()
        with Container(id="MainContainer"):
            yield Horizontal(
                ScrollableContainer(Code_Console1()),
                ScrollableContainer(Code_Console2()),
            )
            yield Obfuscator()

    def action_toggle_debug(self) -> None:
        settings.debug = not settings.debug
        print(f"Debug is now {settings.debug}")

    def on_print(self, event: Print) -> None:
        self.query_one(Log).write(event.text)

    def on_mount(self) -> None:
        self.run_my_worker()

    @work(thread=True)
    def run_my_worker(self):
        self.begin_capture_print(self, True, True)


if __name__ == "__main__":
    app = SomalifuscatorV2()
    app.run()

if you need anything else please let me know and thank you for the help.

@KingKDot
Copy link
Author

Here is a video of what I mean

Desktop.2023.12.22.-.19.29.59.01.mp4

@willmcgugan
Copy link
Collaborator

You will be capturing the escape sequences from Rich, which are going to throw off the width calculations. The solution would be to print Text.from_ansi(event.text)

@KingKDot
Copy link
Author

You will be capturing the escape sequences from Rich, which are going to throw off the width calculations. The solution would be to print Text.from_ansi(event.text)

from_ansi returns a Text object which can't be written into Log. Also wouldn't it remove the colors? If it does, is there a way I can keep the colors using something else such as a text area, etc?

@KingKDot
Copy link
Author

Something like TextArea would be perfect but it doesn't seem to be supported anymore.

@TomJGooding
Copy link
Contributor

I'd recommend having a look at the FAQ (https://textual.textualize.io/FAQ/#no-widget-called-textlog) and perhaps another read through the docs. There's a number of things I'm afraid I don't understand about your code above - perhaps there's some important context missing. Why do you need to capture print statements, as it looks like these aren't coming from 'outside'...?

@KingKDot
Copy link
Author

I'd recommend having a look at the FAQ (https://textual.textualize.io/FAQ/#no-widget-called-textlog) and perhaps another read through the docs. There's a number of things I'm afraid I don't understand about your code above - perhaps there's some important context missing. Why do you need to capture print statements, as it looks like these aren't coming from 'outside'...?

I'm capturing the print statements to redirect all terminal "terminal" text into the box to make it look cool. If you have any other questions let me know.

@TomJGooding
Copy link
Contributor

TomJGooding commented Dec 24, 2023

So it is purely for style, am I reading that correctly? There's not an external process you need to capture print from?

In any case, hopefully from the docs/FAQ you'll have discovered the RichLog which can write more than just text.

@KingKDot
Copy link
Author

So it is purely for style, am I reading that correctly? There's not an external process you need to capture print from?

In any case, hopefully from the docs/FAQ you'll have discovered the RichLog which can write more than just text.

bruh... that is exactly what I was looking for thank you. Also no, I'm not looking for any external processes or anything I'm doing all of this just for aesthetic

Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

@TomJGooding
Copy link
Contributor

TomJGooding commented Dec 24, 2023

Glad to have helped. In that case, you really don't need rich print at all, rather something like this?

from dataclasses import dataclass

from rich.syntax import Syntax
from rich.text import Text
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Center, Horizontal, ScrollableContainer
from textual.widgets import Button, Footer, Header, RichLog, Static


@dataclass
class settings:
    debug: bool = False


MOCK_BAT = """\
@echo off
title This is your first batch script!
echo Welcome to batch scripting!
pause\
"""


class CodeDisplay(ScrollableContainer):
    # Unfortunately I don't think there's currently a read-only mode for the
    # TextArea widget, but when that's released you could replace this!
    def compose(self) -> ComposeResult:
        yield Static(
            Syntax(
                MOCK_BAT,
                "bat",
                theme="monokai",
                line_numbers=True,
                word_wrap=True,
            )
        )


class SomalifuscatorV2(App):
    BINDINGS = [Binding("d", "toggle_debug", "Toggle Debug")]

    CSS = """
    CodeDisplay {
        border: solid $primary-background-lighten-3;
        overflow: scroll scroll;
    }

    CodeDisplay:focus {
        border: solid $accent;
    }

    RichLog {
        border: solid $primary-background-lighten-3;
        overflow: scroll scroll;
    }

    RichLog:focus {
        border: solid $accent;
    }

    Center {
        padding: 2;
    }
    """

    def compose(self) -> ComposeResult:
        yield Header()
        with Horizontal():
            yield RichLog()  # logs are already scrollable
            yield CodeDisplay()
        with Center():
            yield Button("Obfuscate", variant="success")
        yield Footer()

    def on_mount(self) -> None:
        self.query_one(RichLog).border_title = "Log"
        self.query_one(CodeDisplay).border_title = "Code"

        self.query_one(Button).focus()

    def on_button_pressed(self, _: Button.Pressed) -> None:
        self.query_one(RichLog).write("Obfuscating...")

    def action_toggle_debug(self) -> None:
        settings.debug = not settings.debug
        color = "red" if not settings.debug else "green"
        debug_msg = Text.from_markup(
            f"Debug is now [{color} italic]{settings.debug}[/]"
        )
        self.query_one(RichLog).write(debug_msg)


if __name__ == "__main__":
    app = SomalifuscatorV2()
    app.run()

@KingKDot
Copy link
Author

@TomJGooding Random question but does the rich progress count as a renderable? Or do I have to use the textual progress bar instead?

@TomJGooding
Copy link
Contributor

You don't need to log from Rich at all. Just use what is provided by Textual.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants