biscuit icon indicating copy to clipboard operation
biscuit copied to clipboard

App crashes due to hover renderer shrink property

Open tomlin7 opened this issue 1 year ago • 1 comments

Bug

The shrink property of tkhtml3 causes crash: https://github.com/Andereoo/TkinterWeb/blob/main/tkinterweb/bindings.py#L393

  • shrink is used to make the hover popup size wrt the content size
  • when multiple multiline docs of varying size are hovered fast, internal tk error occurs (uncatchable) due to ambiguity occurred in sizing the window, and app crashes.

Temporarily resolved this by disabling shrink. Hover docs will now take a fixed size irrespective of the doc length. Temporary fixes: #411

image

Need suggestions on how to handle the sizing of hover popup properly.

Notes

From tkhtml3 docs: http://tkhtml.tcl.tk/tkhtml.html

Command-Line Name: -shrink Database Name: shrink Database Class: Shrink

This boolean option governs the way the widgets requested width and height are calculated. If it is set to false (the default), then the requested width and height are set by the -width and -height options as per usual.

If this option is set to true, then the widgets requested width and height are determined by the current document. Each time the document layout is calculated, the widgets requested height and width are set to the size of the document layout. If the widget is unmapped when the layout is calculated, then the value of the -width option is used to determine the width of the initial containing block for the layout. Otherwise, the current window width is used.

System Information

  • OS: Windows
  • Python Version: 3.12.5

tomlin7 avatar Oct 11 '24 16:10 tomlin7

Hi!

I'm the developer of TkinterWeb. Assuming you are still having this issue, I was just wondering if you had some isolated sample code you could send me? I have some ideas, and I'd be happy to play around and see if I can find any workarounds.

Thanks, Andrew

Andereoo avatar Jan 06 '26 10:01 Andereoo

@Andereoo Hey, I'm so surprised that you came to find this specific issue! Sorry for the delayed response. I will send you a reproducible example soon.

Meanwhile I wanted to ask about the possibility of introducing <details> tag support in TkinterWeb (my bad if that is already a thing :P I haven't been following updates lately).

The thing is, I've been trying to set up the agent panel with the help of TkinterWeb mainly, it just worked out of the box!

Image

So, you see I'd like to add some "expandable"/"collapsible" components within this render as well, so that users can collapse these thoughts/edited file content/search results, etc.

Let me know your thoughts!

tomlin7 avatar Feb 02 '26 21:02 tomlin7

Hey! No worries at all...I'm in no rush!

I was bored one day and was looking at projects using TkinterWeb to see what folks are using it for and figured I'd search the issue trackers for TkinterWeb in case anything comes up. Your package looked really well done and I thought it might be interesting to take a look at the bug!

I just added <details> tag support. If you upgrade to the latest version it should work as expected. Consider upgrading via pip install tkinterweb[recommended]. This installs the tkinterweb-tkhtml-extras package, which on Windows and Linux introduces HTML5 support (as well as border-radius support, bug fixes, and some other stuff). Without that the <details> tag still works, but with some quirks (i.e. it can't be added to the document and its state can't be changed programmatically after loading).

The default styling is:

DETAILS[open] SUMMARY:before {
    content: "▾";
    margin-right: 5px;
}
DETAILS SUMMARY:before, DETAILS[open="false"] SUMMARY:before {
    content: "▸";
    margin-right: 5px;
}

Because of Tkhtml's limitations it's not perfect CSS3 <details> tag styling, but nonetheless you can load your own CSS to change the arrow's looks or location.

Hopefully this helps. 

Cheers!

Andereoo avatar Feb 06 '26 16:02 Andereoo

Here's a reproducible example:

import sys
import os
import random
import tkinter as tk
import re

try:
    from biscuit.app import App
    from biscuit.language.data import HoverResponse
except ImportError as e:
    print(f"Requires biscuit installed.")
    sys.exit(1)

# Pool of mock contents
DOCS_POOL = [
    ("Identifier Info", "This is a standard symbol identified in the current Python scope."),
    ("Class Definition", "```python\nclass DataModel:\n    def __init__(self):\n        \"\"\"Initialize the data model.\"\"\"\n        self.data = []\n```"),
    ("Long Documentation", "#### Documentation\n" + "This documentation block is extremely verbose to trigger layout updates. " * 40),
    ("Short Meta", "Type: `function` | Return: `None`"),
]

class MockLSPManager:
    def __init__(self, base):
        self.base = base
        self.word_map = {}

    def request_hover(self, tab):
        index = tab.index(tk.CURRENT)
        word = tab.get(index + " wordstart", index + " wordend").strip()
        
        if not word or not re.match(r'^[a-zA-Z_]\w*$', word):
            return
            
        if word not in self.word_map:
            self.word_map[word] = random.choice(DOCS_POOL)
        
        info, docs = self.word_map[word]
        response = HoverResponse(
            location=index,
            text=["python", f"({info}) {word}"],
            docs=docs
        )
        
        tab.after(10, lambda: tab.lsp_hover(response))

    def tab_opened(self, tab): return None
    def request_removal(self, tab): pass
    def tab_closed(self, tab): pass
    def content_changed(self, tab): pass
    def request_outline(self, tab): pass
    def request_client_instance(self, tab): return None

def run():
    os.environ["ENVIRONMENT"] = "test"
    app = App(os.getcwd())
    
    if not hasattr(app, 'extensions_manager'):
        app.extensions_manager = type('Mock', (), {'stop_server': lambda: None})
    
    app.language_server_manager = MockLSPManager(app)
    
    def on_app_ready():
        editor = app.editorsmanager.open_editor(__file__, exists=True)
        text_widget = editor.content.text
        text_widget.lsp = True
        
        print("\nready")
        print("- hover over words rapidly")

    app.after(1000, on_app_ready)
    app.run()

if __name__ == "__main__":
    run()

Hover on the words you see in editor for some time, and you can reproduce the crash.

Use the unreleased version of biscuit. Clone the repo and then go with:

pip install -e .

tomlin7 avatar Feb 07 '26 07:02 tomlin7

@Andereoo Also, thanks for adding the <details> tag support. It is working really well. <3

Image

tomlin7 avatar Feb 07 '26 07:02 tomlin7

So glad to hear!

I opened renderer.py and added shrink=True to line 76. Nothing is crashing for me. Is there anything else I need to set?

Also, as far as you know, does this affect all operating systems, or only specific ones (I'm using Ubuntu)? I assume you still have this issue with the latest TkinterWeb version (4.18.1)?

Thanks!

Andereoo avatar Feb 07 '26 12:02 Andereoo