App crashes due to hover renderer shrink property
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
Need suggestions on how to handle the sizing of hover popup properly.
Notes
- hover popup
- hover renderer (uses tkhtml)
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
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 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!
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!
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!
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 .
@Andereoo Also, thanks for adding the <details> tag support. It is working really well. <3
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!