From 1008b877e7733b57c8e81e400100dc83dbb0c9ef Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 04:25:52 -0400 Subject: [PATCH 1/3] Order native pointer targeting after original handler completion --- .../pointer-capture-order-20261006/README.md | 32 + .../before.json | 857 ++++++++++++++++++ src/textual/app.py | 6 +- src/textual/driver.py | 5 +- src/textual/message.py | 19 +- src/textual/message_pump.py | 40 +- src/textual/pilot.py | 2 +- src/textual/screen.py | 24 +- src/textual/widget.py | 6 +- tests/test_selection.py | 4 +- 10 files changed, 969 insertions(+), 26 deletions(-) create mode 100644 evidence/pointer-capture-order-20261006/README.md create mode 100644 evidence/pointer-capture-order-20261006/before.json diff --git a/evidence/pointer-capture-order-20261006/README.md b/evidence/pointer-capture-order-20261006/README.md new file mode 100644 index 00000000000..1d272cec5b1 --- /dev/null +++ b/evidence/pointer-capture-order-20261006/README.md @@ -0,0 +1,32 @@ +# Ordered native pointer admission + +App originally selected every pointer target before the queued MouseDown handler +could establish capture. A queued move and release could therefore miss the press +owner, then its late handler left capture active. This is a source defect; it is +not proven to be the interleaving in closed physical02. + +Message owns one pending dispatch completion. MessagePump posts the original +message to its existing FIFO and releases completion only from the receiver which +still owns it. Bubbling transfers that custody through the original DOM queues; +the originating pump retains its own bubbled copy for later dispatch. A retiring +receiver releases its original delivery without cancelling children or replay. +App awaits this admission before resolving the next pointer packet. Screen and +Widget return the existing AwaitComplete resource; Pilot awaits the same contract. +Capture, release, selection, hit geometry, broker actions and disabled input keep +their existing owners. No second input queue, capture state or timed delay exists. + +Native Input, TextArea and ScrollBar capture from their queued handlers. Toad +SidebarResizeHandle, SidebarSlider, GoalBar and Mandelbrot do the same. Ancestor +handlers retain ordinary bubbling. The awaited chain stops before the originating +App pump, which cannot await itself; its forwarded application-level event remains +on its original FIFO. This is target/DOM admission, not synchronous application +notification delivery. + +Existing refactor-audit Package parsed 249 native production / 461 test modules +and 288 Toad production / 400 test modules, with zero omissions. before.json +contains 118 lexical owner/consumer sites. Dynamic overrides/factories remain a +semantic resolution limitation, not zero-by-omission proof. + +Source checkpoint; changed-path checks have not run. Parent owns recorder motion +acceptance; no physical run, installed pin, package or public runtime is changed. +Frozen e15 / physical02 evidence remains unchanged. diff --git a/evidence/pointer-capture-order-20261006/before.json b/evidence/pointer-capture-order-20261006/before.json new file mode 100644 index 00000000000..ac63419d1ce --- /dev/null +++ b/evidence/pointer-capture-order-20261006/before.json @@ -0,0 +1,857 @@ +{ + "coverage": [ + { + "repository": "/home/ts/wt/textual-frame-publication-20261006", + "root": "src/textual", + "parsed": 249, + "omissions": [] + }, + { + "repository": "/home/ts/wt/textual-frame-publication-20261006", + "root": "tests", + "parsed": 461, + "omissions": [] + }, + { + "repository": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "root": "src/toad", + "parsed": 288, + "omissions": [] + }, + { + "repository": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "root": "tests", + "parsed": 400, + "omissions": [] + } + ], + "sites": [ + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/_types.py", + "line": 14, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/_types.py", + "line": 20, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3242, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 656, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3255, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3027, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3234, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3251, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3253, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 2878, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 2951, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3235, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 3254, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4126, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4184, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4188, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4190, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4182, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4118, + "kind": "Attribute", + "name": "prepare_input" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/app.py", + "line": 4161, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/headless_driver.py", + "line": 55, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/linux_driver.py", + "line": 244, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/linux_driver.py", + "line": 317, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 197, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/web_driver.py", + "line": 165, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/web_driver.py", + "line": 147, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/drivers/win32.py", + "line": 304, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/message.py", + "line": 151, + "kind": "FunctionDef", + "name": "_bubble_to" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/message_pump.py", + "line": 905, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/message_pump.py", + "line": 898, + "kind": "Attribute", + "name": "_bubble_to" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/pilot.py", + "line": 468, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 1805, + "kind": "FunctionDef", + "name": "_handle_mouse_move" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2000, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 1808, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2146, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 1809, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 1833, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2010, + "kind": "Attribute", + "name": "_handle_mouse_move" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2106, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2107, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2066, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/screen.py", + "line": 2128, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/scrollbar.py", + "line": 352, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/scrollbar.py", + "line": 356, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_up" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/scrollbar.py", + "line": 350, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/scrollbar.py", + "line": 327, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/scrollbar.py", + "line": 358, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4549, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4881, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4891, + "kind": "FunctionDef", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4950, + "kind": "AsyncFunctionDef", + "name": "prepare_input" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4967, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4970, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_up" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 1795, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4889, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4896, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 2160, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widget.py", + "line": 4897, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_footer.py", + "line": 156, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_input.py", + "line": 786, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_input.py", + "line": 803, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_up" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_input.py", + "line": 791, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_input.py", + "line": 797, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_markdown.py", + "line": 1642, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_text_area.py", + "line": 1997, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_text_area.py", + "line": 2023, + "kind": "AsyncFunctionDef", + "name": "_on_mouse_up" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_text_area.py", + "line": 2004, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "src/textual/widgets/_text_area.py", + "line": 2019, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/input/test_input_messages.py", + "line": 73, + "kind": "Attribute", + "name": "_post_message" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_footer_projection_lifetime.py", + "line": 72, + "kind": "Attribute", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_footer_projection_lifetime.py", + "line": 77, + "kind": "Attribute", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_footer_projection_lifetime.py", + "line": 83, + "kind": "Attribute", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_footer_projection_lifetime.py", + "line": 130, + "kind": "Attribute", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_removed_scene_ownership.py", + "line": 75, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_removed_scene_ownership.py", + "line": 79, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_reparent.py", + "line": 145, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_reparent.py", + "line": 149, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_reparent.py", + "line": 150, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_selection.py", + "line": 254, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/textual-frame-publication-20261006", + "path": "tests/test_selection.py", + "line": 259, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/comms_menu.py", + "line": 36, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/comms_menu.py", + "line": 41, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/comms_sidebar.py", + "line": 362, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/conversation.py", + "line": 1822, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/goal_bar.py", + "line": 241, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/goal_bar.py", + "line": 220, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/goal_bar.py", + "line": 256, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/mandelbrot.py", + "line": 166, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/mandelbrot.py", + "line": 174, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/mandelbrot.py", + "line": 172, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/mandelbrot.py", + "line": 175, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/prompt.py", + "line": 109, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 443, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 461, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 553, + "kind": "FunctionDef", + "name": "on_mouse_down" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 566, + "kind": "FunctionDef", + "name": "on_mouse_up" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 453, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 466, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 559, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/side_bar.py", + "line": 571, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "src/toad/widgets/viewport_body.py", + "line": 570, + "kind": "AsyncFunctionDef", + "name": "prepare_input" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/goal_resize_pilot.py", + "line": 87, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/goal_resize_pilot.py", + "line": 43, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/goal_resize_pilot.py", + "line": 90, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/goal_resize_pilot.py", + "line": 47, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/scroll_select_latency_pilot.py", + "line": 65, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/scroll_select_latency_pilot.py", + "line": 209, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/scroll_select_latency_pilot.py", + "line": 317, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/selection_hover_pilot.py", + "line": 32, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/selection_hover_pilot.py", + "line": 39, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/shared_channels_pilot.py", + "line": 122, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/shared_channels_pilot.py", + "line": 126, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/sidebar_drag_resize_pilot.py", + "line": 68, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/sidebar_edge_anchor_installed_pilot.py", + "line": 87, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/stale_selection_pilot.py", + "line": 35, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/viewport_selection_diff_pilot.py", + "line": 50, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/viewport_selection_diff_pilot.py", + "line": 51, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "repo": "/home/ts/wt/toad-right-sidebar-latency-20261006", + "path": "tests/viewport_selection_diff_pilot.py", + "line": 79, + "kind": "Attribute", + "name": "_forward_event" + } + ], + "limits": "Lexical references do not resolve dynamic handler dispatch or factories." +} diff --git a/src/textual/app.py b/src/textual/app.py index 50bde78d376..0e775485548 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -4123,7 +4123,9 @@ async def on_event(self, event: events.Event) -> None: # Shouldn't occur, since at the very least this will find the Screen self._mouse_down_widget = None - self.screen._forward_event(event) + # The next pointer packet must use capture and geometry decided + # by this delivery's original widget / ancestor queues. + await self.screen._forward_event(event) # If a MouseUp occurs at the same widget as a MouseDown, then we should # consider it a click, and produce a Click event. @@ -4158,7 +4160,7 @@ async def on_event(self, event: events.Event) -> None: self._click_chain_last_time = event.time self._click_chain_last_offset = screen_offset - self.screen._forward_event(click_event) + await self.screen._forward_event(click_event) except NoWidget: pass diff --git a/src/textual/driver.py b/src/textual/driver.py index ea4756b0fe6..25b1d31b30c 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -70,9 +70,8 @@ def send_message(self, message: messages.Message) -> None: Args: message: A message. """ - # Queue on the owning loop directly. The async _post_message adapter - # only calls post_message, so wrapping it in a task added another ready - # queue turn (and an unused cross-thread Future) before input arrived. + # Queue raw input on its original ingress owner. App owns ordered target + # delivery; this thread must not inspect or change mouse capture. self._loop.call_soon_threadsafe(self._app.post_message, message) def process_message(self, message: messages.Message) -> None: diff --git a/src/textual/message.py b/src/textual/message.py index cc69ca1b654..db023b9157a 100644 --- a/src/textual/message.py +++ b/src/textual/message.py @@ -15,6 +15,7 @@ from textual.case import camel_to_snake if TYPE_CHECKING: + from asyncio import Future, Task from textual.dom import DOMNode from textual.message_pump import MessagePump @@ -30,6 +31,7 @@ class Message: "_no_default_action", "_stop_propagation", "_prevent", + "_dispatch_completion", ] ALLOW_SELECTOR_MATCH: ClassVar[set[str]] = set() @@ -55,6 +57,7 @@ def __post_init__(self) -> None: self._no_default_action = False self._stop_propagation = False self._prevent: set[type[Message]] = set() + self._dispatch_completion: tuple[MessagePump, Task | None, Future] | None = None def __rich_repr__(self) -> rich.repr.Result: yield from () @@ -155,4 +158,18 @@ def _bubble_to(self, widget: MessagePump) -> None: widget: Target of bubble. """ self._no_default_action = False - widget.post_message(self) + if widget.post_message(self) and self._dispatch_completion is not None: + _, caller, completion = self._dispatch_completion + # The originating pump is awaiting this delivery. Its bubbled copy + # remains on its original queue, after the routed DOM handlers finish. + if widget._task is not caller: + self._dispatch_completion = (widget, caller, completion) + + def _complete_dispatch(self, receiver: MessagePump) -> None: + """Release this delivery only from the queue which still owns it.""" + if self._dispatch_completion is not None: + owner, _, completion = self._dispatch_completion + if owner is receiver: + self._dispatch_completion = None + if not completion.done(): + completion.set_result(None) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 05a0c558184..773ea709f70 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -50,6 +50,7 @@ from typing_extensions import TypeAlias from textual.app import App + from textual.await_complete import AwaitComplete from textual.css.model import SelectorSet from textual.dom import DOMNode from textual.screen import Screen @@ -721,6 +722,7 @@ async def _process_messages_loop(self) -> None: self.app._handle_exception(error) break finally: + message._complete_dispatch(self) # Subscription access initializes this optional signal. Do not # allocate its weak-key subscriber machinery for quiet pumps. message_signal = self.__dict__.get("message_signal") @@ -902,19 +904,43 @@ def check_idle(self) -> None: if self._running and self._message_queue.empty(): self.post_message(messages.Prompt()) - async def _post_message(self, message: Message) -> bool: - """Post a message or an event to this message pump. - - This is an internal method for use where a coroutine is required. + def _post_message(self, message: Message) -> AwaitComplete: + """Post to the original queue and await its routed handler completion. Args: message: A message object. Returns: - True if the messages was posted successfully, False if the message was not posted - (because the message pump was in the process of closing). + Optional completion of this delivery, including its DOM bubbling. + The originating pump processes its own bubbled copy after admission. """ - return self.post_message(message) + from textual.await_complete import AwaitComplete + + if not self.post_message(message) or self._task is None: + return AwaitComplete.nothing() + completion = asyncio.get_running_loop().create_future() + message._dispatch_completion = (self, asyncio.current_task(), completion) + + async def wait_for_dispatch() -> None: + # A removed receiver may stop before reaching this message. Borrow + # its original task; never cancel that task or replay the delivery. + while not completion.done(): + owner, _, _ = message._dispatch_completion + task = owner._task + if task is None: + message._complete_dispatch(owner) + break + await asyncio.wait( + (completion, task), return_when=asyncio.FIRST_COMPLETED + ) + if task.done() and not completion.done(): + message._complete_dispatch(owner) + + def check_self_wait() -> None: + if asyncio.current_task() is self._task: + raise RuntimeError("A message pump cannot await its own queued delivery") + + return AwaitComplete(wait_for_dispatch(), pre_await=check_self_wait) def post_message(self, message: Message) -> bool: """Posts a message on to this widget's queue. diff --git a/src/textual/pilot.py b/src/textual/pilot.py index 76adae9d594..a4e37b9db2b 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -465,7 +465,7 @@ async def _post_mouse_events( # that's useful to other things (tooltip handling, for example), # we patch the offset in there as well. app.mouse_position = offset - screen._forward_event(event) + await screen._forward_event(event) await self.pause() return widget is None or widget_at is target_widget diff --git a/src/textual/screen.py b/src/textual/screen.py index dde0c10e19d..bc6cd3412d8 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -1802,7 +1802,8 @@ def _handle_tooltip_timer(self, widget: Widget) -> None: tooltip.absolute_offset = self.app.mouse_position tooltip.update(tooltip_content) - def _handle_mouse_move(self, event: events.MouseMove) -> None: + def _handle_mouse_move(self, event: events.MouseMove) -> AwaitComplete: + completion = AwaitComplete.nothing() hover_widget: Widget | None = None try: if self.app.mouse_captured: @@ -1826,11 +1827,11 @@ def _handle_mouse_move(self, event: events.MouseMove) -> None: self.update_pointer_shape() widget.hover_style = event.style if widget is self: - self.post_message(event) + completion = self._post_message(event) else: mouse_event = self._translate_mouse_move_event(event, widget, region) mouse_event._set_forwarded() - widget._forward_event(mouse_event) + completion = widget._forward_event(mouse_event) if not self.app._disable_tooltips: try: @@ -1851,6 +1852,7 @@ def _handle_mouse_move(self, event: events.MouseMove) -> None: else: tooltip.display = False self.screen.update_pointer_shape() + return completion @staticmethod def _translate_mouse_move_event( @@ -1997,17 +1999,18 @@ def _update_select(self) -> None: """Update select for a screen-space offset (typically the mouse position).""" self._watch__select_state(self._select_state) - def _forward_event(self, event: events.Event) -> None: + def _forward_event(self, event: events.Event) -> AwaitComplete: if event.is_forwarded: - return + return AwaitComplete.nothing() event._set_forwarded() + completion = AwaitComplete.nothing() if isinstance(event, (events.Enter, events.Leave)): self.post_message(event) elif isinstance(event, events.MouseMove): event.style = self.get_style_at(event.screen_x, event.screen_y) - self._handle_mouse_move(event) + completion = self._handle_mouse_move(event) if self._selecting and self._select_state is not None: @@ -2120,16 +2123,19 @@ def _forward_event(self, event: events.Event) -> None: self.set_focus(focusable_widget, scroll_visible=False) event.style = self.get_style_at(event.screen_x, event.screen_y) if widget.loading: - return + return completion if widget is self: event._set_forwarded() - self.post_message(event) + completion = self._post_message(event) else: - widget._forward_event(event._apply_offset(-region.x, -region.y)) + completion = widget._forward_event( + event._apply_offset(-region.x, -region.y) + ) else: self.post_message(event) self.update_pointer_shape() + return completion def _key_escape(self) -> None: self.clear_selection() diff --git a/src/textual/widget.py b/src/textual/widget.py index af44498da02..0f23175d0e4 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -64,6 +64,7 @@ from textual._styles_cache import StylesCache from textual._types import AnimationLevel from textual.actions import SkipAction +from textual.await_complete import AwaitComplete from textual.await_remove import AwaitRemove from textual.box_model import BoxModel from textual.cache import FIFOCache, LRUCache @@ -4546,9 +4547,12 @@ def suppress_click(self) -> None: """ self.app._mouse_down_widget = None - def _forward_event(self, event: events.Event) -> None: + def _forward_event(self, event: events.Event) -> AwaitComplete: event._set_forwarded() + if isinstance(event, events.MouseEvent): + return self._post_message(event) self.post_message(event) + return AwaitComplete.nothing() def _refresh_scroll(self) -> None: """Refreshes the scroll position.""" diff --git a/tests/test_selection.py b/tests/test_selection.py index 1932f2428a8..bbad95017ce 100644 --- a/tests/test_selection.py +++ b/tests/test_selection.py @@ -251,12 +251,12 @@ def compose(self) -> ComposeResult: last = app.query_one("#record-504", Counted) assert await pilot.mouse_down(first, offset=(0, 0)) x, y = last.region.x + 2, last.region.y - app.screen._forward_event(MouseMove(None, x, y, 2, 1, 1, False, False, False)) + await app.screen._forward_event(MouseMove(None, x, y, 2, 1, 1, False, False, False)) await pilot.pause() assert interior in app.screen.selections updates = interior.selection_updates assert updates == 1 - app.screen._forward_event(MouseMove(None, x + 1, y, 1, 0, 1, False, False, False)) + await app.screen._forward_event(MouseMove(None, x + 1, y, 1, 0, 1, False, False, False)) await pilot.pause() assert interior.selection_updates == updates assert last.selection_updates > 1 From b51453fd5e0a5eac30e00267aed86950043b64bb Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 04:29:09 -0400 Subject: [PATCH 2/3] Preserve driver coroutine admission and own routed delivery separately --- evidence/pointer-capture-order-20261006/README.md | 11 ++++++++++- src/textual/message_pump.py | 6 +++++- src/textual/screen.py | 4 ++-- src/textual/widget.py | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/evidence/pointer-capture-order-20261006/README.md b/evidence/pointer-capture-order-20261006/README.md index 1d272cec5b1..2d33233bdde 100644 --- a/evidence/pointer-capture-order-20261006/README.md +++ b/evidence/pointer-capture-order-20261006/README.md @@ -5,7 +5,7 @@ could establish capture. A queued move and release could therefore miss the pres owner, then its late handler left capture active. This is a source defect; it is not proven to be the interleaving in closed physical02. -Message owns one pending dispatch completion. MessagePump posts the original +Message owns one pending dispatch completion. MessagePump._post_message_and_wait posts the original message to its existing FIFO and releases completion only from the receiver which still owns it. Bubbling transfers that custody through the original DOM queues; the originating pump retains its own bubbled copy for later dispatch. A retiring @@ -30,3 +30,12 @@ semantic resolution limitation, not zero-by-omission proof. Source checkpoint; changed-path checks have not run. Parent owns recorder motion acceptance; no physical run, installed pin, package or public runtime is changed. Frozen e15 / physical02 evidence remains unchanged. + +The first checkpoint broke the drivers' coroutine scheduling contract by changing +_post_message to a synchronous return. That negative is retained in Parent's +mounted.stderr. The original async _post_message coroutine and bool admission +contract are restored for all Headless/Linux/inline/Web/Win32 driver callers, +MessageTarget/EventTarget protocols, Markdown self-publication and input tests. +Awaited target completion is a distinct MessagePump operation on the same FIFO; +it does not change raw driver admission or add a queue. These callers are present +in before.json; the first checkpoint failed to read their scheduling requirements. diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 773ea709f70..f2a70941644 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -904,7 +904,11 @@ def check_idle(self) -> None: if self._running and self._message_queue.empty(): self.post_message(messages.Prompt()) - def _post_message(self, message: Message) -> AwaitComplete: + async def _post_message(self, message: Message) -> bool: + """Coroutine admission to the original queue, including driver threads.""" + return self.post_message(message) + + def _post_message_and_wait(self, message: Message) -> AwaitComplete: """Post to the original queue and await its routed handler completion. Args: diff --git a/src/textual/screen.py b/src/textual/screen.py index bc6cd3412d8..fcdc663b2b2 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -1827,7 +1827,7 @@ def _handle_mouse_move(self, event: events.MouseMove) -> AwaitComplete: self.update_pointer_shape() widget.hover_style = event.style if widget is self: - completion = self._post_message(event) + completion = self._post_message_and_wait(event) else: mouse_event = self._translate_mouse_move_event(event, widget, region) mouse_event._set_forwarded() @@ -2126,7 +2126,7 @@ def _forward_event(self, event: events.Event) -> AwaitComplete: return completion if widget is self: event._set_forwarded() - completion = self._post_message(event) + completion = self._post_message_and_wait(event) else: completion = widget._forward_event( event._apply_offset(-region.x, -region.y) diff --git a/src/textual/widget.py b/src/textual/widget.py index 0f23175d0e4..7c212f56da6 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4550,7 +4550,7 @@ def suppress_click(self) -> None: def _forward_event(self, event: events.Event) -> AwaitComplete: event._set_forwarded() if isinstance(event, events.MouseEvent): - return self._post_message(event) + return self._post_message_and_wait(event) self.post_message(event) return AwaitComplete.nothing() From 3e73a51ab881d8d9cddfc6f60a57ee4453854dfc Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 04:39:22 -0400 Subject: [PATCH 3/3] Preserve selection publication boundaries during ordered pointer admission --- .../pointer-capture-order-20261006/README.md | 18 +- .../pointer-capture-order-20261006/after.json | 3689 +++++++++++++++++ .../final-checks.log | 11 + .../qualified-checks.log | 2 + src/textual/screen.py | 9 +- tests/test_pointer_capture_order.py | 181 + 6 files changed, 3904 insertions(+), 6 deletions(-) create mode 100644 evidence/pointer-capture-order-20261006/after.json create mode 100644 evidence/pointer-capture-order-20261006/final-checks.log create mode 100644 evidence/pointer-capture-order-20261006/qualified-checks.log create mode 100644 tests/test_pointer_capture_order.py diff --git a/evidence/pointer-capture-order-20261006/README.md b/evidence/pointer-capture-order-20261006/README.md index 2d33233bdde..e4952cbd96a 100644 --- a/evidence/pointer-capture-order-20261006/README.md +++ b/evidence/pointer-capture-order-20261006/README.md @@ -27,7 +27,19 @@ and 288 Toad production / 400 test modules, with zero omissions. before.json contains 118 lexical owner/consumer sites. Dynamic overrides/factories remain a semantic resolution limitation, not zero-by-omission proof. -Source checkpoint; changed-path checks have not run. Parent owns recorder motion +Changed native checks passed: 33 in 10.45s through actual App/HeadlessDriver +packet ingress, direct and ancestor capture, delayed press, release-before-wheel, +Input/TextArea selection, filtered delivery, self-removal, original input messages +and queued selection/copy behavior. The first selection batch caught 80 redundant +selection rebuilds: original call_next flushed every newly awaited DOM delivery. +That early fallback is deleted; original copy/release/paint/refresh-callback owners +still flush the single pending projection. Final source retains all 80 mouse events +and exact mid-burst/final copy values. No new timer or selection state exists. + +Two initial authored editor assertions incorrectly assumed an unpanned/unwrapped +substring; their expectation is now movement selection, preserved original text +and ended capture. Initial negatives stay in the tool transcript; final-checks.log +retains the coalescing refusal and qualified-checks.log the completed result. Parent owns recorder motion acceptance; no physical run, installed pin, package or public runtime is changed. Frozen e15 / physical02 evidence remains unchanged. @@ -39,3 +51,7 @@ MessageTarget/EventTarget protocols, Markdown self-publication and input tests. Awaited target completion is a distinct MessagePump operation on the same FIFO; it does not change raw driver admission or add a queue. These callers are present in before.json; the first checkpoint failed to read their scheduling requirements. + +No physical02 causality or repaired physical motion is claimed. Parent owns the +matched application/recorder acceptance. All local checks are terminal, with no +provider, installed package, pin or public runtime operation. diff --git a/evidence/pointer-capture-order-20261006/after.json b/evidence/pointer-capture-order-20261006/after.json new file mode 100644 index 00000000000..a226507a156 --- /dev/null +++ b/evidence/pointer-capture-order-20261006/after.json @@ -0,0 +1,3689 @@ +{ + "before": "4e3a877f179d8c3e695a01f458f9b3cdf9185319", + "after_tree": "ac056b2675e43ae812e0edda0f1a49d4e64426f2", + "limits": "Lexical source only. Dynamic factories, custom handlers and external plugins are not exhaustively resolved.", + "before_scope": { + "coverage": [ + { + "root": "src/textual", + "parsed": 249, + "omissions": [] + }, + { + "root": "tests", + "parsed": 461, + "omissions": [] + } + ], + "owners": [ + { + "path": "src/textual/_types.py", + "line": 11, + "class": "MessageTarget", + "bases": [ + "Protocol" + ], + "members": [ + "_post_message", + "post_message" + ], + "annotations": [] + }, + { + "path": "src/textual/_types.py", + "line": 19, + "class": "EventTarget", + "bases": [ + "Protocol" + ], + "members": [ + "_post_message", + "post_message" + ], + "annotations": [] + }, + { + "path": "src/textual/app.py", + "line": 296, + "class": "App", + "bases": [ + "Generic[ReturnType]", + "DOMNode" + ], + "members": [ + "frame_interval", + "__init__", + "get_line_filters", + "_is_devtools_connected", + "_exception_event", + "__init_subclass__", + "_thread_init", + "_get_dom_base", + "validate_title", + "validate_sub_title", + "default_screen", + "workers", + "return_value", + "return_code", + "children", + "clipboard", + "_realtime_animation_begin", + "_realtime_animation_complete", + "format_title", + "batch_update", + "_begin_batch", + "_end_batch", + "delay_update", + "_context", + "_watch_ansi_color", + "animate", + "stop_animation", + "is_dom_root", + "is_attached", + "debug", + "is_headless", + "is_inline", + "is_web", + "screen_stack", + "_screen_stack", + "current_mode", + "console_options", + "get_screen_stack", + "exit", + "focused", + "active_bindings", + "get_system_commands", + "get_default_screen", + "compose", + "get_theme_variable_defaults", + "get_css_variables", + "get_theme", + "register_theme", + "unregister_theme", + "available_themes", + "current_theme", + "_validate_theme", + "_watch_theme", + "_invalidate_css", + "watch_ansi_theme_dark", + "watch_ansi_theme_light", + "ansi_theme", + "native_ansi_color", + "_refresh_truecolor_filter", + "get_driver_class", + "__rich_repr__", + "animator", + "screen", + "_background_screens", + "size", + "viewport_size", + "_get_inline_height", + "log", + "_log", + "get_loading_widget", + "copy_to_clipboard", + "call_from_thread", + "action_change_theme", + "action_screenshot", + "export_screenshot", + "save_screenshot", + "deliver_screenshot", + "search_commands", + "search_themes", + "bind", + "get_key_display", + "_press_keys", + "_flush", + "_print", + "begin_capture_print", + "end_capture_print", + "run_test", + "run_async", + "run", + "_on_css_change", + "render", + "get_child_by_id", + "get_widget_by_id", + "get_child_by_type", + "update_styles", + "mount", + "mount_all", + "_init_mode", + "switch_mode", + "add_mode", + "remove_mode", + "is_screen_installed", + "get_screen", + "get_screen", + "get_screen", + "get_screen", + "get_screen", + "_get_screen", + "_load_screen_css", + "_replace_screen", + "push_screen", + "push_screen_wait", + "switch_screen", + "install_screen", + "uninstall_screen", + "pop_screen", + "_pop_to_screen", + "set_focus", + "_set_mouse_over", + "_update_mouse_over", + "_retire_pointer_widgets", + "capture_mouse", + "panic", + "_handle_exception", + "_fatal_error", + "_print_error_renderables", + "_build_driver", + "_init_devtools", + "_process_messages", + "_pre_process", + "_ready", + "_on_compose", + "_check_recompose", + "recompose", + "_register_child", + "_register", + "_unregister", + "_disconnect_devtools", + "_start_widget", + "is_mounted", + "_close_all", + "_shutdown", + "_on_exit_app", + "_request_layout", + "refresh", + "refresh_css", + "_display", + "post_display_hook", + "get_widget_at", + "bell", + "_set_pointer_shape", + "_binding_chain", + "simulate_key", + "_check_bindings", + "action_help_quit", + "_normalize_keymap", + "set_keymap", + "update_keymap", + "handle_bindings_clash", + "on_event", + "escape_to_minimize", + "_parse_action", + "_check_action_state", + "run_action", + "_dispatch_action", + "_broker_event", + "_on_update", + "_on_layout", + "_on_key", + "_on_resize", + "_on_app_focus", + "_on_app_blur", + "_watch_app_focus", + "_pseudo_classes_cache_key", + "action_simulate_key", + "action_quit", + "action_bell", + "action_focus", + "action_switch_screen", + "action_push_screen", + "action_pop_screen", + "action_switch_mode", + "action_back", + "action_add_class", + "action_remove_class", + "action_toggle_class", + "action_toggle_dark", + "action_focus_next", + "action_focus_previous", + "action_hide_help_panel", + "action_show_help_panel", + "action_notify", + "_on_terminal_supports_synchronized_output", + "_begin_update", + "_end_update", + "_refresh_notifications", + "clear_selection", + "notify", + "_on_notify", + "_unnotify", + "clear_notifications", + "action_command_palette", + "_suspend_signal", + "_resume_signal", + "suspend", + "action_suspend_process", + "open_url", + "deliver_text", + "deliver_binary", + "_deliver_binary", + "_on_delivery_complete", + "_on_delivery_failed", + "_on_in_band_window_resize", + "_on_idle", + "_check_resize" + ], + "annotations": [ + "CSS: ClassVar[str] = ''", + "DEFAULT_CSS: ClassVar[str]", + "MODES: ClassVar[dict[str, str | Callable[[], Screen]]] = {}", + "DEFAULT_MODE: ClassVar[str] = '_default'", + "MAX_FPS: ClassVar[int] = constants._get_environ_int('TEXTUAL_FPS', 144, minimum=1)", + "SCREENS: ClassVar[dict[str, Callable[[], Screen[Any]]]] = {}", + "AUTO_FOCUS: ClassVar[str | None] = '*'", + "ALLOW_SELECT: ClassVar[bool] = True", + "_BASE_PATH: str | None = None", + "CSS_PATH: ClassVar[CSSPathType | None] = None", + "TITLE: str | None = None", + "SUB_TITLE: str | None = None", + "ENABLE_COMMAND_PALETTE: ClassVar[bool] = True", + "NOTIFICATION_TIMEOUT: ClassVar[float] = 5", + "COMMANDS: ClassVar[set[type[Provider] | Callable[[], type[Provider]]]] = {get_system_commands_provider}", + "COMMAND_PALETTE_BINDING: ClassVar[str] = 'ctrl+p'", + "COMMAND_PALETTE_DISPLAY: ClassVar[str | None] = None", + "ALLOW_IN_MAXIMIZED_VIEW: ClassVar[str] = 'Footer'", + "CLICK_CHAIN_TIME_THRESHOLD: ClassVar[float] = 0.5", + "BINDINGS: ClassVar[list[BindingType]] = [Binding('ctrl+q', 'quit', 'Quit', tooltip='Quit the app and return to the command prompt.', show=False, priority=True), Binding('ctrl+c', 'help_quit', show=False, system=True)]", + "CLOSE_TIMEOUT: float | None = 5.0", + "TOOLTIP_DELAY: float = 0.5", + "BINDING_GROUP_TITLE: str | None = None", + "ESCAPE_TO_MINIMIZE: ClassVar[bool] = True", + "INLINE_PADDING: ClassVar[int] = 1", + "SUSPENDED_SCREEN_CLASS: ClassVar[str] = ''", + "HORIZONTAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = []", + "VERTICAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = []", + "PAUSE_GC_ON_SCROLL: ClassVar[bool] = False", + "ENABLE_SELECT_AUTO_SCROLL: ClassVar[bool] = True", + "SELECT_AUTO_SCROLL_LINES: ClassVar[int] = 3", + "SELECT_AUTO_SCROLL_SPEED: ClassVar[float] = 60.0", + "_PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = {'focus': lambda app: app.app_focus, 'blur': lambda app: not app.app_focus, 'dark': lambda app: app.current_theme.dark, 'light': lambda app: not app.current_theme.dark, 'inline': lambda app: app.is_inline, 'ansi': lambda app: app.native_ansi_color, 'nocolor': lambda app: app.no_color}", + "title: Reactive[str] = Reactive('', compute=False)", + "sub_title: Reactive[str] = Reactive('', compute=False)", + "theme: Reactive[str] = Reactive(constants.DEFAULT_THEME)", + "ansi_color: Reactive[bool | None] = Reactive(None)" + ] + }, + { + "path": "src/textual/driver.py", + "line": 17, + "class": "Driver", + "bases": [ + "ABC" + ], + "members": [ + "__init__", + "is_headless", + "is_inline", + "is_web", + "can_suspend", + "send_message", + "process_message", + "write", + "flush", + "_enable_application_keypad", + "_disable_application_keypad", + "start_application_mode", + "disable_input", + "stop_application_mode", + "suspend_application_mode", + "resume_application_mode", + "no_automatic_restart", + "close", + "open_url", + "deliver_binary", + "_delivery_complete", + "_delivery_failed" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 10, + "class": "HeadlessDriver", + "bases": [ + "Driver" + ], + "members": [ + "is_headless", + "_get_terminal_size", + "write", + "start_application_mode", + "disable_input", + "stop_application_mode" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 38, + "class": "LinuxDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "_sigtstp_application", + "_sigcont_application", + "can_suspend", + "__rich_repr__", + "_get_terminal_size", + "_enable_mouse_support", + "_enable_mouse_pixels", + "_enable_bracketed_paste", + "_query_in_band_window_resize", + "_enable_in_band_window_resize", + "_enable_line_wrap", + "_disable_line_wrap", + "_disable_in_band_window_resize", + "_disable_bracketed_paste", + "_disable_mouse_support", + "write", + "call_after_flush", + "start_application_mode", + "_request_terminal_sync_mode_support", + "_patch_lflag", + "_patch_iflag", + "disable_input", + "stop_application_mode", + "close", + "_run_input_thread", + "run_input_thread", + "process_message" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 28, + "class": "LinuxInlineDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "__rich_repr__", + "is_inline", + "_enable_bracketed_paste", + "_disable_bracketed_paste", + "_get_terminal_size", + "_enable_mouse_support", + "_disable_mouse_support", + "write", + "_run_input_thread", + "run_input_thread", + "start_application_mode", + "_request_terminal_sync_mode_support", + "_patch_lflag", + "_patch_iflag", + "disable_input", + "flush", + "stop_application_mode" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 41, + "class": "WebDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "is_web", + "write", + "write_meta", + "write_binary_encoded", + "flush", + "_enable_mouse_support", + "_enable_bracketed_paste", + "_disable_bracketed_paste", + "_disable_mouse_support", + "_request_terminal_sync_mode_support", + "start_application_mode", + "disable_input", + "stop_application_mode", + "run_input_thread", + "_on_meta", + "on_meta", + "open_url", + "deliver_binary", + "_deliver_file" + ], + "annotations": [] + }, + { + "path": "src/textual/message.py", + "line": 23, + "class": "Message", + "bases": [], + "members": [ + "__init__", + "__post_init__", + "__rich_repr__", + "__init_subclass__", + "control", + "is_forwarded", + "_set_forwarded", + "set_sender", + "can_replace", + "prevent_default", + "stop", + "_bubble_to" + ], + "annotations": [ + "ALLOW_SELECTOR_MATCH: ClassVar[set[str]] = set()", + "bubble: ClassVar[bool] = True", + "verbose: ClassVar[bool] = False", + "no_dispatch: ClassVar[bool] = False", + "namespace: ClassVar[str] = ''", + "handler_name: ClassVar[str]" + ] + }, + { + "path": "src/textual/message_pump.py", + "line": 122, + "class": "MessagePump", + "bases": [], + "members": [ + "__init__", + "_disabled_messages", + "_timers", + "_next_callbacks", + "message_signal", + "_register_signal_subscription", + "_unregister_signal_subscription", + "_clear_signal_subscriptions", + "_parent", + "_parent", + "_message_queue", + "_mounted_event", + "_prevent_message_types_stack", + "_thread_init", + "_get_prevented_messages", + "_is_prevented", + "prevent", + "task", + "has_parent", + "message_queue_size", + "is_dom_root", + "is_attached", + "is_parent_active", + "is_running", + "log", + "_attach", + "_detach", + "check_message_enabled", + "disable_messages", + "enable_messages", + "_get_message", + "_peek_message", + "set_timer", + "set_interval", + "_after_refresh_pending", + "call_after_refresh", + "wait_for_refresh", + "call_later", + "call_next", + "_on_invoke_later", + "_close_messages", + "_start_messages", + "_process_messages", + "_message_loop_exit", + "_pre_process", + "_post_mount", + "_close_messages_no_wait", + "_context", + "_on_close_messages", + "_process_messages_loop", + "_flush_next_callbacks", + "_dispatch_message", + "_get_dispatch_methods", + "on_event", + "_on_message", + "check_idle", + "_post_message", + "post_message", + "on_callback", + "on_timer" + ], + "annotations": [] + }, + { + "path": "src/textual/pilot.py", + "line": 62, + "class": "Pilot", + "bases": [ + "Generic[ReturnType]" + ], + "members": [ + "__init__", + "__rich_repr__", + "app", + "press", + "resize_terminal", + "mouse_down", + "mouse_up", + "click", + "double_click", + "triple_click", + "hover", + "_post_mouse_events", + "_wait_for_screen", + "pause", + "wait_for_animation", + "wait_for_scheduled_animations", + "exit" + ], + "annotations": [] + }, + { + "path": "src/textual/screen.py", + "line": 144, + "class": "Screen", + "bases": [ + "Generic[ScreenResultType]", + "Widget" + ], + "members": [ + "__init__", + "is_modal", + "is_current", + "_update_timer", + "layers", + "size", + "_watch_focused", + "_watch_stack_updates", + "_watch_selections", + "refresh_bindings", + "_watch_maximized", + "_binding_chain", + "_modal_binding_chain", + "active_bindings", + "arrange", + "is_active", + "allow_select", + "get_loading_widget", + "_watch__pointer_shape", + "update_pointer_shape", + "render", + "get_offset", + "get_widget_at", + "get_hover_widgets_at", + "get_widgets_at", + "get_focusable_widget_at", + "get_style_at", + "get_widget_and_offset_at", + "find_widget", + "clear_selection", + "_select_all_in_widget", + "focus_chain", + "_move_focus", + "focus_next", + "focus_previous", + "maximize", + "minimize", + "get_selected_text", + "action_copy_text", + "action_maximize", + "action_minimize", + "action_blur", + "action_focus", + "_reset_focus", + "_update_focus_styles", + "set_focus", + "_extend_compose", + "_on_mount", + "_refresh_pending", + "_on_idle", + "_prepare_compositor_refresh", + "_layout_mutation_roots", + "_on_frame_published", + "_prepare_visible_screens", + "_after_refresh_pending", + "_compositor_refresh", + "_on_timer_update", + "_sender_refresh_pending", + "_invoke_and_clear_callbacks", + "_invoke_later", + "_push_result_callback", + "_message_loop_exit", + "_pop_result_callback", + "_use_viewport_layout", + "_layout_geometry_targets", + "_refresh_layout", + "_on_update", + "_on_layout", + "_on_update_scroll", + "_forget_pruned_widgets", + "_retire_selection", + "_forget_widget_geometry", + "_get_inline_height", + "_screen_resized", + "_on_screen_resume", + "_compose", + "_update_auto_focus", + "_on_screen_suspend", + "_retire_inactive_paint", + "_retire_inactive_presentation", + "_on_resize", + "_get_breakpoint_classes", + "_update_tooltip", + "_clear_tooltip", + "_maybe_clear_tooltip", + "_handle_tooltip_timer", + "_handle_mouse_move", + "_translate_mouse_move_event", + "_start_auto_scroll", + "_stop_auto_scroll", + "_check_auto_scroll", + "_update_select", + "_forward_event", + "_key_escape", + "_watch__selecting", + "_interaction_widgets", + "_watch__select_state", + "_flush_pending_selection", + "_apply_selection_state", + "dismiss", + "pop_until_active", + "action_dismiss", + "can_view_entire", + "can_view_partial", + "validate_title", + "validate_sub_title" + ], + "annotations": [ + "AUTO_FOCUS: ClassVar[str | None] = None", + "CSS: ClassVar[str] = ''", + "CSS_PATH: ClassVar[CSSPathType | None] = None", + "TITLE: ClassVar[str | None] = None", + "SUB_TITLE: ClassVar[str | None] = None", + "HORIZONTAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = None", + "VERTICAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = None", + "focused: Reactive[Widget | None] = Reactive(None)", + "stack_updates: Reactive[int] = Reactive(0, repaint=False)", + "sub_title: Reactive[str | None] = Reactive(None, compute=False)", + "title: Reactive[str | None] = Reactive(None, compute=False)", + "COMMANDS: ClassVar[set[type[Provider] | Callable[[], type[Provider]]]] = set()", + "ALLOW_IN_MAXIMIZED_VIEW: ClassVar[str | None] = None", + "ESCAPE_TO_MINIMIZE: ClassVar[bool | None] = None", + "maximized: Reactive[Widget | None] = Reactive(None, layout=True)", + "selections: var[dict[Widget, Selection]] = var(dict)", + "_select_state: Reactive[SelectState | None] = Reactive(None, repaint=False)", + "_mouse_down_offset: var[Offset | None] = var(None)", + "_pointer_shape: var[PointerShape] = var('default')", + "RETAIN_INACTIVE_PAINT: ClassVar[bool] = True", + "RETAIN_INACTIVE_PRESENTATION: ClassVar[bool] = True", + "SUBTREE_GEOMETRY_CACHE_ENTRIES: ClassVar[int] = Compositor.DEFAULT_SUBTREE_GEOMETRY_CACHE_ENTRIES" + ] + }, + { + "path": "src/textual/widget.py", + "line": 303, + "class": "Widget", + "bases": [ + "DOMNode" + ], + "members": [ + "_render_styles_sensitive", + "_style_geometry_updated", + "_measurement_updated", + "_invalidate_layout", + "__init__", + "is_mounted", + "siblings", + "visible_siblings", + "allow_vertical_scroll", + "allow_horizontal_scroll", + "_allow_scroll", + "allow_maximize", + "offset", + "offset", + "opacity", + "is_anchored", + "is_mouse_over", + "is_maximized", + "is_in_maximized_view", + "uses_screen_coordinates", + "subtree_geometry_resource", + "_render_widget", + "text_selection", + "get_common_ancestor", + "focus_on_click", + "get_line_filters", + "preflight_checks", + "pre_render", + "_cover", + "process_layout", + "_uncover", + "anchor", + "release_anchor", + "_check_anchor", + "_check_disabled", + "tooltip", + "tooltip", + "with_tooltip", + "allow_focus", + "allow_focus_children", + "compose_add_child", + "is_disabled", + "has_focus_within", + "first_of_type", + "last_of_type", + "first_child", + "last_child", + "is_odd", + "is_even", + "__enter__", + "__exit__", + "clear_cached_dimensions", + "get_loading_widget", + "set_loading", + "_watch_loading", + "get_child_by_id", + "get_widget_by_id", + "get_child_by_type", + "get_component_rich_style", + "get_visual_style", + "_get_style", + "render_str", + "render_str", + "render_str", + "_after_refresh_pending", + "arrange", + "_clear_arrangement_cache", + "_child_nodes_removed", + "_get_virtual_dom", + "_find_mount_point", + "mount", + "_refresh_styles", + "mount_all", + "mount_compose", + "move_child", + "reparent", + "compose", + "_check_recompose", + "recompose", + "_post_register", + "_arrangement_depends_on_available_height", + "_box_depends_on_available_height", + "_get_box_model", + "get_content_width", + "get_content_height", + "watch_hover_style", + "watch_scroll_x", + "watch_scroll_y", + "validate_scroll_x", + "validate_scroll_target_x", + "validate_scroll_y", + "validate_scroll_target_y", + "max_scroll_x", + "max_scroll_y", + "is_vertical_scroll_end", + "is_horizontal_scroll_end", + "is_vertical_scrollbar_grabbed", + "is_horizontal_scrollbar_grabbed", + "scrollbar_corner", + "vertical_scrollbar", + "horizontal_scrollbar", + "_refresh_scrollbars", + "scrollbars_enabled", + "scrollbars_space", + "scrollbar_size_vertical", + "scrollbar_size_horizontal", + "scrollbar_gutter", + "gutter", + "size", + "scrollable_size", + "outer_size", + "container_size", + "content_region", + "scrollable_content_region", + "content_offset", + "content_size", + "_selection_order", + "region", + "dock_gutter", + "container_viewport", + "virtual_region", + "window_region", + "virtual_region_with_margin", + "_self_or_ancestors_disabled", + "focusable", + "_focus_sort_key", + "scroll_offset", + "container_scroll_offset", + "_console", + "_has_relative_children_width", + "_has_relative_children_height", + "is_on_screen", + "_resolve_extrema", + "animate", + "stop_animation", + "layout", + "is_container", + "is_scrollable", + "is_scrolling", + "layer", + "layers", + "_get_layer_order", + "link_style", + "link_style_hover", + "select_container", + "_set_dirty", + "_exchange_repaint_regions", + "_scroll_to", + "allow_select", + "pre_layout", + "set_scroll", + "scroll_to", + "scroll_relative", + "scroll_home", + "scroll_end", + "scroll_left", + "_scroll_left_for_pointer", + "scroll_right", + "_scroll_right_for_pointer", + "scroll_down", + "_scroll_down_for_pointer", + "scroll_up", + "_scroll_up_for_pointer", + "scroll_page_up", + "scroll_page_down", + "scroll_page_left", + "scroll_page_right", + "scroll_to_widget", + "scroll_to_region", + "scroll_visible", + "scroll_to_center", + "can_view_entire", + "can_view_partial", + "__init_subclass__", + "__rich_repr__", + "_get_scrollable_region", + "_arrange_scrollbars", + "get_pseudo_class_state", + "_pseudo_classes_cache_key", + "_get_justify_method", + "post_render", + "watch_has_focus", + "watch_disabled", + "_measured_virtual_size_requires_layout", + "_size_updated", + "_scroll_update", + "visual_style", + "get_selection", + "selection_updated", + "_render_content", + "render_line", + "render_lines", + "get_style_at", + "suppress_click", + "_forward_event", + "_refresh_scroll", + "refresh", + "remove", + "remove_children", + "batch", + "render", + "_render", + "run_action", + "post_message", + "on_prune", + "_message_loop_exit", + "_release_paint", + "_release_presentation", + "_on_idle", + "_check_refresh", + "focus", + "blur", + "capture_mouse", + "release_mouse", + "text_select_all", + "begin_capture_print", + "end_capture_print", + "check_message_enabled", + "broker_event", + "prepare_input", + "_request_layout", + "notify_style_update", + "_on_mouse_down", + "_on_mouse_up", + "_on_click", + "_on_key", + "handle_key", + "_on_compose", + "_compose", + "mount_composed_widgets", + "_extend_compose", + "_on_mount", + "_on_leave", + "_on_enter", + "_on_focus", + "_on_blur", + "_on_mouse_scroll_down", + "_on_mouse_scroll_up", + "_on_mouse_scroll_right", + "_on_mouse_scroll_left", + "_on_scroll_to", + "_on_scroll_up", + "_on_scroll_down", + "_on_scroll_left", + "_on_scroll_right", + "_on_show", + "_on_hide", + "_on_scroll_to_region", + "_on_unmount", + "action_scroll_home", + "action_scroll_end", + "action_scroll_left", + "action_scroll_right", + "action_scroll_up", + "action_scroll_down", + "action_page_down", + "action_page_up", + "action_page_left", + "action_page_right", + "notify", + "action_notify" + ], + "annotations": [ + "CACHE_SUBTREE_GEOMETRY: ClassVar[bool] = False", + "_content_height_dependency: ClassVar[HeightDependency] = NATIVE_WIDGET_HEIGHT", + "_content_width_dependency: ClassVar[HeightDependency] = NATIVE_WIDGET_WIDTH", + "_native_box_measurement: ClassVar[bool] = True", + "_native_measurement_layout_hooks: ClassVar[bool] = True", + "COMPONENT_CLASSES: ClassVar[set[str]] = set()", + "BORDER_TITLE: ClassVar[str] = ''", + "BORDER_SUBTITLE: ClassVar[str] = ''", + "ALLOW_MAXIMIZE: ClassVar[bool | None] = None", + "ALLOW_SELECT: ClassVar[bool] = True", + "FOCUS_ON_CLICK: ClassVar[bool] = True", + "BLANK: ClassVar[bool] = False", + "can_focus: bool = False", + "can_focus_children: bool = True", + "expand: Reactive[bool] = Reactive(False)", + "shrink: Reactive[bool] = Reactive(True)", + "auto_links: Reactive[bool] = Reactive(True)", + "disabled: Reactive[bool] = Reactive(False)", + "hover_style: Reactive[Style] = Reactive(Style, repaint=False)", + "highlight_link_id: Reactive[str] = Reactive('')", + "loading: Reactive[bool] = Reactive(False)", + "has_focus: Reactive[bool] = Reactive(False, repaint=False)", + "mouse_hover: Reactive[bool] = Reactive(False, repaint=False)", + "scroll_x: Reactive[float] = Reactive(0.0, repaint=False, layout=False)", + "scroll_y: Reactive[float] = Reactive(0.0, repaint=False, layout=False)", + "show_vertical_scrollbar: Reactive[bool] = Reactive(False, layout=True)", + "show_horizontal_scrollbar: Reactive[bool] = Reactive(False, layout=True)", + "_sort_order: ClassVar[int] = 0", + "_PSEUDO_CLASSES: ClassVar[dict[str, Callable[[Widget], bool]]] = {'hover': lambda widget: widget.mouse_hover, 'focus': lambda widget: widget.has_focus, 'blur': lambda widget: not widget.has_focus, 'can-focus': lambda widget: widget.allow_focus(), 'disabled': lambda widget: widget.is_disabled, 'enabled': lambda widget: not widget.is_disabled, 'dark': lambda widget: widget.app.current_theme.dark, 'light': lambda widget: not widget.app.current_theme.dark, 'focus-within': lambda widget: widget.has_focus_within, 'inline': lambda widget: widget.app.is_inline, 'ansi': lambda widget: widget.app.native_ansi_color, 'nocolor': lambda widget: widget.app.no_color, 'first-of-type': lambda widget: widget.first_of_type, 'last-of-type': lambda widget: widget.last_of_type, 'first-child': lambda widget: widget.first_child, 'last-child': lambda widget: widget.last_child, 'odd': lambda widget: widget.is_odd, 'even': lambda widget: widget.is_even, 'empty': lambda widget: widget.is_empty}" + ] + }, + { + "path": "tests/test_query.py", + "line": 40, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_query.py", + "line": 163, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_query.py", + "line": 229, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_selection.py", + "line": 355, + "class": "Message", + "bases": [ + "VerticalGroup" + ], + "members": [ + "displayed_and_visible_children" + ], + "annotations": [] + } + ], + "sites": [ + { + "path": "src/textual/_time.py", + "line": 28, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/_types.py", + "line": 14, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/_types.py", + "line": 20, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/app.py", + "line": 3242, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 656, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 1844, + "scheduling": "asyncio.run_coroutine_threadsafe(run_callback(), loop=self._loop)" + }, + { + "path": "src/textual/app.py", + "line": 3255, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 1844, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/app.py", + "line": 3027, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3234, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3251, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3253, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 2878, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 2951, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3235, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3254, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 4126, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4184, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4188, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4190, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4182, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4118, + "kind": "Attribute", + "name": "prepare_input" + }, + { + "path": "src/textual/app.py", + "line": 4161, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/await_remove.py", + "line": 102, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 54, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 54, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 55, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 243, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 316, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(self.SignalResume()), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 243, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 316, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 244, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 317, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 199, + "scheduling": "asyncio.run_coroutine_threadsafe(update_size(), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 199, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 197, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 164, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 146, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(messages.ExitApp()), loop=loop)" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 164, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 146, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 165, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 147, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/win32.py", + "line": 304, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/message.py", + "line": 151, + "kind": "FunctionDef", + "name": "_bubble_to" + }, + { + "path": "src/textual/message_pump.py", + "line": 905, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/message_pump.py", + "line": 898, + "kind": "Attribute", + "name": "_bubble_to" + }, + { + "path": "src/textual/pilot.py", + "line": 468, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/pilot.py", + "line": 510, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/pilot.py", + "line": 511, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/screen.py", + "line": 1805, + "kind": "FunctionDef", + "name": "_handle_mouse_move" + }, + { + "path": "src/textual/screen.py", + "line": 2000, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "path": "src/textual/screen.py", + "line": 2183, + "kind": "FunctionDef", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 292, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2180, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2185, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 983, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1808, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2146, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2186, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 1292, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1809, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 1833, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/screen.py", + "line": 2010, + "kind": "Attribute", + "name": "_handle_mouse_move" + }, + { + "path": "src/textual/screen.py", + "line": 2175, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2176, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2106, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2177, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2047, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2107, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2066, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2128, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/scrollbar.py", + "line": 350, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/scrollbar.py", + "line": 327, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/scrollbar.py", + "line": 358, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4549, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "path": "src/textual/widget.py", + "line": 4881, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4891, + "kind": "FunctionDef", + "name": "release_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4950, + "kind": "AsyncFunctionDef", + "name": "prepare_input" + }, + { + "path": "src/textual/widget.py", + "line": 1795, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4889, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4896, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 2160, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4897, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 791, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 797, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 70, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1642, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2004, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2019, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/worker.py", + "line": 423, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/input/test_input_messages.py", + "line": 73, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "tests/test_message_pump.py", + "line": 101, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 37, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 38, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 54, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 55, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 10, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 27, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 28, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 45, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 46, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 55, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 66, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 73, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 96, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 75, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 79, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 83, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "tests/test_reparent.py", + "line": 145, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_reparent.py", + "line": 149, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_reparent.py", + "line": 150, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "tests/test_rlock.py", + "line": 47, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_rlock.py", + "line": 49, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_selection.py", + "line": 488, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "tests/test_selection.py", + "line": 254, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "tests/test_selection.py", + "line": 259, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "tests/test_selection.py", + "line": 480, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "tests/test_selection.py", + "line": 492, + "kind": "Attribute", + "name": "_selection_update_pending" + } + ] + }, + "after_scope": { + "coverage": [ + { + "root": "src/textual", + "parsed": 249, + "omissions": [] + }, + { + "root": "tests", + "parsed": 462, + "omissions": [] + } + ], + "owners": [ + { + "path": "src/textual/_types.py", + "line": 11, + "class": "MessageTarget", + "bases": [ + "Protocol" + ], + "members": [ + "_post_message", + "post_message" + ], + "annotations": [] + }, + { + "path": "src/textual/_types.py", + "line": 19, + "class": "EventTarget", + "bases": [ + "Protocol" + ], + "members": [ + "_post_message", + "post_message" + ], + "annotations": [] + }, + { + "path": "src/textual/app.py", + "line": 296, + "class": "App", + "bases": [ + "Generic[ReturnType]", + "DOMNode" + ], + "members": [ + "frame_interval", + "__init__", + "get_line_filters", + "_is_devtools_connected", + "_exception_event", + "__init_subclass__", + "_thread_init", + "_get_dom_base", + "validate_title", + "validate_sub_title", + "default_screen", + "workers", + "return_value", + "return_code", + "children", + "clipboard", + "_realtime_animation_begin", + "_realtime_animation_complete", + "format_title", + "batch_update", + "_begin_batch", + "_end_batch", + "delay_update", + "_context", + "_watch_ansi_color", + "animate", + "stop_animation", + "is_dom_root", + "is_attached", + "debug", + "is_headless", + "is_inline", + "is_web", + "screen_stack", + "_screen_stack", + "current_mode", + "console_options", + "get_screen_stack", + "exit", + "focused", + "active_bindings", + "get_system_commands", + "get_default_screen", + "compose", + "get_theme_variable_defaults", + "get_css_variables", + "get_theme", + "register_theme", + "unregister_theme", + "available_themes", + "current_theme", + "_validate_theme", + "_watch_theme", + "_invalidate_css", + "watch_ansi_theme_dark", + "watch_ansi_theme_light", + "ansi_theme", + "native_ansi_color", + "_refresh_truecolor_filter", + "get_driver_class", + "__rich_repr__", + "animator", + "screen", + "_background_screens", + "size", + "viewport_size", + "_get_inline_height", + "log", + "_log", + "get_loading_widget", + "copy_to_clipboard", + "call_from_thread", + "action_change_theme", + "action_screenshot", + "export_screenshot", + "save_screenshot", + "deliver_screenshot", + "search_commands", + "search_themes", + "bind", + "get_key_display", + "_press_keys", + "_flush", + "_print", + "begin_capture_print", + "end_capture_print", + "run_test", + "run_async", + "run", + "_on_css_change", + "render", + "get_child_by_id", + "get_widget_by_id", + "get_child_by_type", + "update_styles", + "mount", + "mount_all", + "_init_mode", + "switch_mode", + "add_mode", + "remove_mode", + "is_screen_installed", + "get_screen", + "get_screen", + "get_screen", + "get_screen", + "get_screen", + "_get_screen", + "_load_screen_css", + "_replace_screen", + "push_screen", + "push_screen_wait", + "switch_screen", + "install_screen", + "uninstall_screen", + "pop_screen", + "_pop_to_screen", + "set_focus", + "_set_mouse_over", + "_update_mouse_over", + "_retire_pointer_widgets", + "capture_mouse", + "panic", + "_handle_exception", + "_fatal_error", + "_print_error_renderables", + "_build_driver", + "_init_devtools", + "_process_messages", + "_pre_process", + "_ready", + "_on_compose", + "_check_recompose", + "recompose", + "_register_child", + "_register", + "_unregister", + "_disconnect_devtools", + "_start_widget", + "is_mounted", + "_close_all", + "_shutdown", + "_on_exit_app", + "_request_layout", + "refresh", + "refresh_css", + "_display", + "post_display_hook", + "get_widget_at", + "bell", + "_set_pointer_shape", + "_binding_chain", + "simulate_key", + "_check_bindings", + "action_help_quit", + "_normalize_keymap", + "set_keymap", + "update_keymap", + "handle_bindings_clash", + "on_event", + "escape_to_minimize", + "_parse_action", + "_check_action_state", + "run_action", + "_dispatch_action", + "_broker_event", + "_on_update", + "_on_layout", + "_on_key", + "_on_resize", + "_on_app_focus", + "_on_app_blur", + "_watch_app_focus", + "_pseudo_classes_cache_key", + "action_simulate_key", + "action_quit", + "action_bell", + "action_focus", + "action_switch_screen", + "action_push_screen", + "action_pop_screen", + "action_switch_mode", + "action_back", + "action_add_class", + "action_remove_class", + "action_toggle_class", + "action_toggle_dark", + "action_focus_next", + "action_focus_previous", + "action_hide_help_panel", + "action_show_help_panel", + "action_notify", + "_on_terminal_supports_synchronized_output", + "_begin_update", + "_end_update", + "_refresh_notifications", + "clear_selection", + "notify", + "_on_notify", + "_unnotify", + "clear_notifications", + "action_command_palette", + "_suspend_signal", + "_resume_signal", + "suspend", + "action_suspend_process", + "open_url", + "deliver_text", + "deliver_binary", + "_deliver_binary", + "_on_delivery_complete", + "_on_delivery_failed", + "_on_in_band_window_resize", + "_on_idle", + "_check_resize" + ], + "annotations": [ + "CSS: ClassVar[str] = ''", + "DEFAULT_CSS: ClassVar[str]", + "MODES: ClassVar[dict[str, str | Callable[[], Screen]]] = {}", + "DEFAULT_MODE: ClassVar[str] = '_default'", + "MAX_FPS: ClassVar[int] = constants._get_environ_int('TEXTUAL_FPS', 144, minimum=1)", + "SCREENS: ClassVar[dict[str, Callable[[], Screen[Any]]]] = {}", + "AUTO_FOCUS: ClassVar[str | None] = '*'", + "ALLOW_SELECT: ClassVar[bool] = True", + "_BASE_PATH: str | None = None", + "CSS_PATH: ClassVar[CSSPathType | None] = None", + "TITLE: str | None = None", + "SUB_TITLE: str | None = None", + "ENABLE_COMMAND_PALETTE: ClassVar[bool] = True", + "NOTIFICATION_TIMEOUT: ClassVar[float] = 5", + "COMMANDS: ClassVar[set[type[Provider] | Callable[[], type[Provider]]]] = {get_system_commands_provider}", + "COMMAND_PALETTE_BINDING: ClassVar[str] = 'ctrl+p'", + "COMMAND_PALETTE_DISPLAY: ClassVar[str | None] = None", + "ALLOW_IN_MAXIMIZED_VIEW: ClassVar[str] = 'Footer'", + "CLICK_CHAIN_TIME_THRESHOLD: ClassVar[float] = 0.5", + "BINDINGS: ClassVar[list[BindingType]] = [Binding('ctrl+q', 'quit', 'Quit', tooltip='Quit the app and return to the command prompt.', show=False, priority=True), Binding('ctrl+c', 'help_quit', show=False, system=True)]", + "CLOSE_TIMEOUT: float | None = 5.0", + "TOOLTIP_DELAY: float = 0.5", + "BINDING_GROUP_TITLE: str | None = None", + "ESCAPE_TO_MINIMIZE: ClassVar[bool] = True", + "INLINE_PADDING: ClassVar[int] = 1", + "SUSPENDED_SCREEN_CLASS: ClassVar[str] = ''", + "HORIZONTAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = []", + "VERTICAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = []", + "PAUSE_GC_ON_SCROLL: ClassVar[bool] = False", + "ENABLE_SELECT_AUTO_SCROLL: ClassVar[bool] = True", + "SELECT_AUTO_SCROLL_LINES: ClassVar[int] = 3", + "SELECT_AUTO_SCROLL_SPEED: ClassVar[float] = 60.0", + "_PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = {'focus': lambda app: app.app_focus, 'blur': lambda app: not app.app_focus, 'dark': lambda app: app.current_theme.dark, 'light': lambda app: not app.current_theme.dark, 'inline': lambda app: app.is_inline, 'ansi': lambda app: app.native_ansi_color, 'nocolor': lambda app: app.no_color}", + "title: Reactive[str] = Reactive('', compute=False)", + "sub_title: Reactive[str] = Reactive('', compute=False)", + "theme: Reactive[str] = Reactive(constants.DEFAULT_THEME)", + "ansi_color: Reactive[bool | None] = Reactive(None)" + ] + }, + { + "path": "src/textual/driver.py", + "line": 17, + "class": "Driver", + "bases": [ + "ABC" + ], + "members": [ + "__init__", + "is_headless", + "is_inline", + "is_web", + "can_suspend", + "send_message", + "process_message", + "write", + "flush", + "_enable_application_keypad", + "_disable_application_keypad", + "start_application_mode", + "disable_input", + "stop_application_mode", + "suspend_application_mode", + "resume_application_mode", + "no_automatic_restart", + "close", + "open_url", + "deliver_binary", + "_delivery_complete", + "_delivery_failed" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 10, + "class": "HeadlessDriver", + "bases": [ + "Driver" + ], + "members": [ + "is_headless", + "_get_terminal_size", + "write", + "start_application_mode", + "disable_input", + "stop_application_mode" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 38, + "class": "LinuxDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "_sigtstp_application", + "_sigcont_application", + "can_suspend", + "__rich_repr__", + "_get_terminal_size", + "_enable_mouse_support", + "_enable_mouse_pixels", + "_enable_bracketed_paste", + "_query_in_band_window_resize", + "_enable_in_band_window_resize", + "_enable_line_wrap", + "_disable_line_wrap", + "_disable_in_band_window_resize", + "_disable_bracketed_paste", + "_disable_mouse_support", + "write", + "call_after_flush", + "start_application_mode", + "_request_terminal_sync_mode_support", + "_patch_lflag", + "_patch_iflag", + "disable_input", + "stop_application_mode", + "close", + "_run_input_thread", + "run_input_thread", + "process_message" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 28, + "class": "LinuxInlineDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "__rich_repr__", + "is_inline", + "_enable_bracketed_paste", + "_disable_bracketed_paste", + "_get_terminal_size", + "_enable_mouse_support", + "_disable_mouse_support", + "write", + "_run_input_thread", + "run_input_thread", + "start_application_mode", + "_request_terminal_sync_mode_support", + "_patch_lflag", + "_patch_iflag", + "disable_input", + "flush", + "stop_application_mode" + ], + "annotations": [] + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 41, + "class": "WebDriver", + "bases": [ + "Driver" + ], + "members": [ + "__init__", + "is_web", + "write", + "write_meta", + "write_binary_encoded", + "flush", + "_enable_mouse_support", + "_enable_bracketed_paste", + "_disable_bracketed_paste", + "_disable_mouse_support", + "_request_terminal_sync_mode_support", + "start_application_mode", + "disable_input", + "stop_application_mode", + "run_input_thread", + "_on_meta", + "on_meta", + "open_url", + "deliver_binary", + "_deliver_file" + ], + "annotations": [] + }, + { + "path": "src/textual/message.py", + "line": 24, + "class": "Message", + "bases": [], + "members": [ + "__init__", + "__post_init__", + "__rich_repr__", + "__init_subclass__", + "control", + "is_forwarded", + "_set_forwarded", + "set_sender", + "can_replace", + "prevent_default", + "stop", + "_bubble_to", + "_complete_dispatch" + ], + "annotations": [ + "ALLOW_SELECTOR_MATCH: ClassVar[set[str]] = set()", + "bubble: ClassVar[bool] = True", + "verbose: ClassVar[bool] = False", + "no_dispatch: ClassVar[bool] = False", + "namespace: ClassVar[str] = ''", + "handler_name: ClassVar[str]" + ] + }, + { + "path": "src/textual/message_pump.py", + "line": 123, + "class": "MessagePump", + "bases": [], + "members": [ + "__init__", + "_disabled_messages", + "_timers", + "_next_callbacks", + "message_signal", + "_register_signal_subscription", + "_unregister_signal_subscription", + "_clear_signal_subscriptions", + "_parent", + "_parent", + "_message_queue", + "_mounted_event", + "_prevent_message_types_stack", + "_thread_init", + "_get_prevented_messages", + "_is_prevented", + "prevent", + "task", + "has_parent", + "message_queue_size", + "is_dom_root", + "is_attached", + "is_parent_active", + "is_running", + "log", + "_attach", + "_detach", + "check_message_enabled", + "disable_messages", + "enable_messages", + "_get_message", + "_peek_message", + "set_timer", + "set_interval", + "_after_refresh_pending", + "call_after_refresh", + "wait_for_refresh", + "call_later", + "call_next", + "_on_invoke_later", + "_close_messages", + "_start_messages", + "_process_messages", + "_message_loop_exit", + "_pre_process", + "_post_mount", + "_close_messages_no_wait", + "_context", + "_on_close_messages", + "_process_messages_loop", + "_flush_next_callbacks", + "_dispatch_message", + "_get_dispatch_methods", + "on_event", + "_on_message", + "check_idle", + "_post_message", + "_post_message_and_wait", + "post_message", + "on_callback", + "on_timer" + ], + "annotations": [] + }, + { + "path": "src/textual/pilot.py", + "line": 62, + "class": "Pilot", + "bases": [ + "Generic[ReturnType]" + ], + "members": [ + "__init__", + "__rich_repr__", + "app", + "press", + "resize_terminal", + "mouse_down", + "mouse_up", + "click", + "double_click", + "triple_click", + "hover", + "_post_mouse_events", + "_wait_for_screen", + "pause", + "wait_for_animation", + "wait_for_scheduled_animations", + "exit" + ], + "annotations": [] + }, + { + "path": "src/textual/screen.py", + "line": 144, + "class": "Screen", + "bases": [ + "Generic[ScreenResultType]", + "Widget" + ], + "members": [ + "__init__", + "is_modal", + "is_current", + "_update_timer", + "layers", + "size", + "_watch_focused", + "_watch_stack_updates", + "_watch_selections", + "refresh_bindings", + "_watch_maximized", + "_binding_chain", + "_modal_binding_chain", + "active_bindings", + "arrange", + "is_active", + "allow_select", + "get_loading_widget", + "_watch__pointer_shape", + "update_pointer_shape", + "render", + "get_offset", + "get_widget_at", + "get_hover_widgets_at", + "get_widgets_at", + "get_focusable_widget_at", + "get_style_at", + "get_widget_and_offset_at", + "find_widget", + "clear_selection", + "_select_all_in_widget", + "focus_chain", + "_move_focus", + "focus_next", + "focus_previous", + "maximize", + "minimize", + "get_selected_text", + "action_copy_text", + "action_maximize", + "action_minimize", + "action_blur", + "action_focus", + "_reset_focus", + "_update_focus_styles", + "set_focus", + "_extend_compose", + "_on_mount", + "_refresh_pending", + "_on_idle", + "_prepare_compositor_refresh", + "_layout_mutation_roots", + "_on_frame_published", + "_prepare_visible_screens", + "_after_refresh_pending", + "_compositor_refresh", + "_on_timer_update", + "_sender_refresh_pending", + "_invoke_and_clear_callbacks", + "_invoke_later", + "_push_result_callback", + "_message_loop_exit", + "_pop_result_callback", + "_use_viewport_layout", + "_layout_geometry_targets", + "_refresh_layout", + "_on_update", + "_on_layout", + "_on_update_scroll", + "_forget_pruned_widgets", + "_retire_selection", + "_forget_widget_geometry", + "_get_inline_height", + "_screen_resized", + "_on_screen_resume", + "_compose", + "_update_auto_focus", + "_on_screen_suspend", + "_retire_inactive_paint", + "_retire_inactive_presentation", + "_on_resize", + "_get_breakpoint_classes", + "_update_tooltip", + "_clear_tooltip", + "_maybe_clear_tooltip", + "_handle_tooltip_timer", + "_handle_mouse_move", + "_translate_mouse_move_event", + "_start_auto_scroll", + "_stop_auto_scroll", + "_check_auto_scroll", + "_update_select", + "_forward_event", + "_key_escape", + "_watch__selecting", + "_interaction_widgets", + "_watch__select_state", + "_flush_pending_selection", + "_apply_selection_state", + "dismiss", + "pop_until_active", + "action_dismiss", + "can_view_entire", + "can_view_partial", + "validate_title", + "validate_sub_title" + ], + "annotations": [ + "AUTO_FOCUS: ClassVar[str | None] = None", + "CSS: ClassVar[str] = ''", + "CSS_PATH: ClassVar[CSSPathType | None] = None", + "TITLE: ClassVar[str | None] = None", + "SUB_TITLE: ClassVar[str | None] = None", + "HORIZONTAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = None", + "VERTICAL_BREAKPOINTS: ClassVar[list[tuple[int, str]]] | None = None", + "focused: Reactive[Widget | None] = Reactive(None)", + "stack_updates: Reactive[int] = Reactive(0, repaint=False)", + "sub_title: Reactive[str | None] = Reactive(None, compute=False)", + "title: Reactive[str | None] = Reactive(None, compute=False)", + "COMMANDS: ClassVar[set[type[Provider] | Callable[[], type[Provider]]]] = set()", + "ALLOW_IN_MAXIMIZED_VIEW: ClassVar[str | None] = None", + "ESCAPE_TO_MINIMIZE: ClassVar[bool | None] = None", + "maximized: Reactive[Widget | None] = Reactive(None, layout=True)", + "selections: var[dict[Widget, Selection]] = var(dict)", + "_select_state: Reactive[SelectState | None] = Reactive(None, repaint=False)", + "_mouse_down_offset: var[Offset | None] = var(None)", + "_pointer_shape: var[PointerShape] = var('default')", + "RETAIN_INACTIVE_PAINT: ClassVar[bool] = True", + "RETAIN_INACTIVE_PRESENTATION: ClassVar[bool] = True", + "SUBTREE_GEOMETRY_CACHE_ENTRIES: ClassVar[int] = Compositor.DEFAULT_SUBTREE_GEOMETRY_CACHE_ENTRIES" + ] + }, + { + "path": "src/textual/widget.py", + "line": 304, + "class": "Widget", + "bases": [ + "DOMNode" + ], + "members": [ + "_render_styles_sensitive", + "_style_geometry_updated", + "_measurement_updated", + "_invalidate_layout", + "__init__", + "is_mounted", + "siblings", + "visible_siblings", + "allow_vertical_scroll", + "allow_horizontal_scroll", + "_allow_scroll", + "allow_maximize", + "offset", + "offset", + "opacity", + "is_anchored", + "is_mouse_over", + "is_maximized", + "is_in_maximized_view", + "uses_screen_coordinates", + "subtree_geometry_resource", + "_render_widget", + "text_selection", + "get_common_ancestor", + "focus_on_click", + "get_line_filters", + "preflight_checks", + "pre_render", + "_cover", + "process_layout", + "_uncover", + "anchor", + "release_anchor", + "_check_anchor", + "_check_disabled", + "tooltip", + "tooltip", + "with_tooltip", + "allow_focus", + "allow_focus_children", + "compose_add_child", + "is_disabled", + "has_focus_within", + "first_of_type", + "last_of_type", + "first_child", + "last_child", + "is_odd", + "is_even", + "__enter__", + "__exit__", + "clear_cached_dimensions", + "get_loading_widget", + "set_loading", + "_watch_loading", + "get_child_by_id", + "get_widget_by_id", + "get_child_by_type", + "get_component_rich_style", + "get_visual_style", + "_get_style", + "render_str", + "render_str", + "render_str", + "_after_refresh_pending", + "arrange", + "_clear_arrangement_cache", + "_child_nodes_removed", + "_get_virtual_dom", + "_find_mount_point", + "mount", + "_refresh_styles", + "mount_all", + "mount_compose", + "move_child", + "reparent", + "compose", + "_check_recompose", + "recompose", + "_post_register", + "_arrangement_depends_on_available_height", + "_box_depends_on_available_height", + "_get_box_model", + "get_content_width", + "get_content_height", + "watch_hover_style", + "watch_scroll_x", + "watch_scroll_y", + "validate_scroll_x", + "validate_scroll_target_x", + "validate_scroll_y", + "validate_scroll_target_y", + "max_scroll_x", + "max_scroll_y", + "is_vertical_scroll_end", + "is_horizontal_scroll_end", + "is_vertical_scrollbar_grabbed", + "is_horizontal_scrollbar_grabbed", + "scrollbar_corner", + "vertical_scrollbar", + "horizontal_scrollbar", + "_refresh_scrollbars", + "scrollbars_enabled", + "scrollbars_space", + "scrollbar_size_vertical", + "scrollbar_size_horizontal", + "scrollbar_gutter", + "gutter", + "size", + "scrollable_size", + "outer_size", + "container_size", + "content_region", + "scrollable_content_region", + "content_offset", + "content_size", + "_selection_order", + "region", + "dock_gutter", + "container_viewport", + "virtual_region", + "window_region", + "virtual_region_with_margin", + "_self_or_ancestors_disabled", + "focusable", + "_focus_sort_key", + "scroll_offset", + "container_scroll_offset", + "_console", + "_has_relative_children_width", + "_has_relative_children_height", + "is_on_screen", + "_resolve_extrema", + "animate", + "stop_animation", + "layout", + "is_container", + "is_scrollable", + "is_scrolling", + "layer", + "layers", + "_get_layer_order", + "link_style", + "link_style_hover", + "select_container", + "_set_dirty", + "_exchange_repaint_regions", + "_scroll_to", + "allow_select", + "pre_layout", + "set_scroll", + "scroll_to", + "scroll_relative", + "scroll_home", + "scroll_end", + "scroll_left", + "_scroll_left_for_pointer", + "scroll_right", + "_scroll_right_for_pointer", + "scroll_down", + "_scroll_down_for_pointer", + "scroll_up", + "_scroll_up_for_pointer", + "scroll_page_up", + "scroll_page_down", + "scroll_page_left", + "scroll_page_right", + "scroll_to_widget", + "scroll_to_region", + "scroll_visible", + "scroll_to_center", + "can_view_entire", + "can_view_partial", + "__init_subclass__", + "__rich_repr__", + "_get_scrollable_region", + "_arrange_scrollbars", + "get_pseudo_class_state", + "_pseudo_classes_cache_key", + "_get_justify_method", + "post_render", + "watch_has_focus", + "watch_disabled", + "_measured_virtual_size_requires_layout", + "_size_updated", + "_scroll_update", + "visual_style", + "get_selection", + "selection_updated", + "_render_content", + "render_line", + "render_lines", + "get_style_at", + "suppress_click", + "_forward_event", + "_refresh_scroll", + "refresh", + "remove", + "remove_children", + "batch", + "render", + "_render", + "run_action", + "post_message", + "on_prune", + "_message_loop_exit", + "_release_paint", + "_release_presentation", + "_on_idle", + "_check_refresh", + "focus", + "blur", + "capture_mouse", + "release_mouse", + "text_select_all", + "begin_capture_print", + "end_capture_print", + "check_message_enabled", + "broker_event", + "prepare_input", + "_request_layout", + "notify_style_update", + "_on_mouse_down", + "_on_mouse_up", + "_on_click", + "_on_key", + "handle_key", + "_on_compose", + "_compose", + "mount_composed_widgets", + "_extend_compose", + "_on_mount", + "_on_leave", + "_on_enter", + "_on_focus", + "_on_blur", + "_on_mouse_scroll_down", + "_on_mouse_scroll_up", + "_on_mouse_scroll_right", + "_on_mouse_scroll_left", + "_on_scroll_to", + "_on_scroll_up", + "_on_scroll_down", + "_on_scroll_left", + "_on_scroll_right", + "_on_show", + "_on_hide", + "_on_scroll_to_region", + "_on_unmount", + "action_scroll_home", + "action_scroll_end", + "action_scroll_left", + "action_scroll_right", + "action_scroll_up", + "action_scroll_down", + "action_page_down", + "action_page_up", + "action_page_left", + "action_page_right", + "notify", + "action_notify" + ], + "annotations": [ + "CACHE_SUBTREE_GEOMETRY: ClassVar[bool] = False", + "_content_height_dependency: ClassVar[HeightDependency] = NATIVE_WIDGET_HEIGHT", + "_content_width_dependency: ClassVar[HeightDependency] = NATIVE_WIDGET_WIDTH", + "_native_box_measurement: ClassVar[bool] = True", + "_native_measurement_layout_hooks: ClassVar[bool] = True", + "COMPONENT_CLASSES: ClassVar[set[str]] = set()", + "BORDER_TITLE: ClassVar[str] = ''", + "BORDER_SUBTITLE: ClassVar[str] = ''", + "ALLOW_MAXIMIZE: ClassVar[bool | None] = None", + "ALLOW_SELECT: ClassVar[bool] = True", + "FOCUS_ON_CLICK: ClassVar[bool] = True", + "BLANK: ClassVar[bool] = False", + "can_focus: bool = False", + "can_focus_children: bool = True", + "expand: Reactive[bool] = Reactive(False)", + "shrink: Reactive[bool] = Reactive(True)", + "auto_links: Reactive[bool] = Reactive(True)", + "disabled: Reactive[bool] = Reactive(False)", + "hover_style: Reactive[Style] = Reactive(Style, repaint=False)", + "highlight_link_id: Reactive[str] = Reactive('')", + "loading: Reactive[bool] = Reactive(False)", + "has_focus: Reactive[bool] = Reactive(False, repaint=False)", + "mouse_hover: Reactive[bool] = Reactive(False, repaint=False)", + "scroll_x: Reactive[float] = Reactive(0.0, repaint=False, layout=False)", + "scroll_y: Reactive[float] = Reactive(0.0, repaint=False, layout=False)", + "show_vertical_scrollbar: Reactive[bool] = Reactive(False, layout=True)", + "show_horizontal_scrollbar: Reactive[bool] = Reactive(False, layout=True)", + "_sort_order: ClassVar[int] = 0", + "_PSEUDO_CLASSES: ClassVar[dict[str, Callable[[Widget], bool]]] = {'hover': lambda widget: widget.mouse_hover, 'focus': lambda widget: widget.has_focus, 'blur': lambda widget: not widget.has_focus, 'can-focus': lambda widget: widget.allow_focus(), 'disabled': lambda widget: widget.is_disabled, 'enabled': lambda widget: not widget.is_disabled, 'dark': lambda widget: widget.app.current_theme.dark, 'light': lambda widget: not widget.app.current_theme.dark, 'focus-within': lambda widget: widget.has_focus_within, 'inline': lambda widget: widget.app.is_inline, 'ansi': lambda widget: widget.app.native_ansi_color, 'nocolor': lambda widget: widget.app.no_color, 'first-of-type': lambda widget: widget.first_of_type, 'last-of-type': lambda widget: widget.last_of_type, 'first-child': lambda widget: widget.first_child, 'last-child': lambda widget: widget.last_child, 'odd': lambda widget: widget.is_odd, 'even': lambda widget: widget.is_even, 'empty': lambda widget: widget.is_empty}" + ] + }, + { + "path": "tests/test_query.py", + "line": 40, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_query.py", + "line": 163, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_query.py", + "line": 229, + "class": "App", + "bases": [ + "Widget" + ], + "members": [], + "annotations": [] + }, + { + "path": "tests/test_selection.py", + "line": 355, + "class": "Message", + "bases": [ + "VerticalGroup" + ], + "members": [ + "displayed_and_visible_children" + ], + "annotations": [] + } + ], + "sites": [ + { + "path": "src/textual/_time.py", + "line": 28, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/_types.py", + "line": 14, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/_types.py", + "line": 20, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/app.py", + "line": 3242, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 656, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 1844, + "scheduling": "asyncio.run_coroutine_threadsafe(run_callback(), loop=self._loop)" + }, + { + "path": "src/textual/app.py", + "line": 3255, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 1844, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/app.py", + "line": 3027, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3234, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3251, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 3253, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 2878, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 2951, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3235, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/app.py", + "line": 3254, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/app.py", + "line": 4128, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4186, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4190, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4192, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4184, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/app.py", + "line": 4118, + "kind": "Attribute", + "name": "prepare_input" + }, + { + "path": "src/textual/app.py", + "line": 4163, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/await_remove.py", + "line": 102, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 54, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 54, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/headless_driver.py", + "line": 55, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 243, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 316, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(self.SignalResume()), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 243, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 316, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 244, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_driver.py", + "line": 317, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 199, + "scheduling": "asyncio.run_coroutine_threadsafe(update_size(), loop=loop)" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 199, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/linux_inline_driver.py", + "line": 197, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 164, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(event), loop=loop)" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 146, + "scheduling": "asyncio.run_coroutine_threadsafe(self._app._post_message(messages.ExitApp()), loop=loop)" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 164, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 146, + "kind": "Attribute", + "name": "run_coroutine_threadsafe" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 165, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/web_driver.py", + "line": 147, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/drivers/win32.py", + "line": 304, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/message.py", + "line": 154, + "kind": "FunctionDef", + "name": "_bubble_to" + }, + { + "path": "src/textual/message.py", + "line": 168, + "kind": "FunctionDef", + "name": "_complete_dispatch" + }, + { + "path": "src/textual/message.py", + "line": 60, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 162, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 170, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 171, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 161, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 166, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message.py", + "line": 173, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message_pump.py", + "line": 907, + "kind": "AsyncFunctionDef", + "name": "_post_message" + }, + { + "path": "src/textual/message_pump.py", + "line": 911, + "kind": "FunctionDef", + "name": "_post_message_and_wait" + }, + { + "path": "src/textual/message_pump.py", + "line": 926, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message_pump.py", + "line": 932, + "kind": "Attribute", + "name": "_dispatch_completion" + }, + { + "path": "src/textual/message_pump.py", + "line": 725, + "kind": "Attribute", + "name": "_complete_dispatch" + }, + { + "path": "src/textual/message_pump.py", + "line": 900, + "kind": "Attribute", + "name": "_bubble_to" + }, + { + "path": "src/textual/message_pump.py", + "line": 935, + "kind": "Attribute", + "name": "_complete_dispatch" + }, + { + "path": "src/textual/message_pump.py", + "line": 941, + "kind": "Attribute", + "name": "_complete_dispatch" + }, + { + "path": "src/textual/pilot.py", + "line": 510, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/pilot.py", + "line": 511, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/pilot.py", + "line": 468, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/screen.py", + "line": 1805, + "kind": "FunctionDef", + "name": "_handle_mouse_move" + }, + { + "path": "src/textual/screen.py", + "line": 2002, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "path": "src/textual/screen.py", + "line": 2188, + "kind": "FunctionDef", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 292, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2185, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 2190, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 983, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1809, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2152, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2191, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 1292, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 1810, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2182, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "src/textual/screen.py", + "line": 1830, + "kind": "Attribute", + "name": "_post_message_and_wait" + }, + { + "path": "src/textual/screen.py", + "line": 1834, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/screen.py", + "line": 2013, + "kind": "Attribute", + "name": "_handle_mouse_move" + }, + { + "path": "src/textual/screen.py", + "line": 2109, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2050, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "src/textual/screen.py", + "line": 2110, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2069, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/screen.py", + "line": 2129, + "kind": "Attribute", + "name": "_post_message_and_wait" + }, + { + "path": "src/textual/screen.py", + "line": 2131, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "src/textual/scrollbar.py", + "line": 350, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/scrollbar.py", + "line": 327, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/scrollbar.py", + "line": 358, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4550, + "kind": "FunctionDef", + "name": "_forward_event" + }, + { + "path": "src/textual/widget.py", + "line": 4885, + "kind": "FunctionDef", + "name": "capture_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4895, + "kind": "FunctionDef", + "name": "release_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4954, + "kind": "AsyncFunctionDef", + "name": "prepare_input" + }, + { + "path": "src/textual/widget.py", + "line": 1796, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4893, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widget.py", + "line": 4900, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 2161, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "src/textual/widget.py", + "line": 4553, + "kind": "Attribute", + "name": "_post_message_and_wait" + }, + { + "path": "src/textual/widget.py", + "line": 4901, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 791, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 797, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 70, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 1642, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2004, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 2019, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "src/textual/worker.py", + "line": 423, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/input/test_input_messages.py", + "line": 73, + "kind": "Attribute", + "name": "_post_message" + }, + { + "path": "tests/test_message_pump.py", + "line": 101, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 37, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 38, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 54, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_mount.py", + "line": 55, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 73, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 115, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 140, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 158, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 181, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 28, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 38, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 84, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_pointer_capture_order.py", + "line": 93, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "tests/test_queue.py", + "line": 10, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 27, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 28, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 45, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_queue.py", + "line": 46, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 55, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 66, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 73, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_remove_transaction.py", + "line": 96, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 75, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 79, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_removed_scene_ownership.py", + "line": 83, + "kind": "Attribute", + "name": "_flush_pending_selection" + }, + { + "path": "tests/test_reparent.py", + "line": 145, + "kind": "Attribute", + "name": "capture_mouse" + }, + { + "path": "tests/test_reparent.py", + "line": 149, + "kind": "Attribute", + "name": "mouse_captured" + }, + { + "path": "tests/test_reparent.py", + "line": 150, + "kind": "Attribute", + "name": "release_mouse" + }, + { + "path": "tests/test_rlock.py", + "line": 47, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_rlock.py", + "line": 49, + "kind": "Attribute", + "name": "create_task" + }, + { + "path": "tests/test_selection.py", + "line": 488, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "tests/test_selection.py", + "line": 480, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "tests/test_selection.py", + "line": 492, + "kind": "Attribute", + "name": "_selection_update_pending" + }, + { + "path": "tests/test_selection.py", + "line": 254, + "kind": "Attribute", + "name": "_forward_event" + }, + { + "path": "tests/test_selection.py", + "line": 259, + "kind": "Attribute", + "name": "_forward_event" + } + ] + } +} diff --git a/evidence/pointer-capture-order-20261006/final-checks.log b/evidence/pointer-capture-order-20261006/final-checks.log new file mode 100644 index 00000000000..8a490b54eeb --- /dev/null +++ b/evidence/pointer-capture-order-20261006/final-checks.log @@ -0,0 +1,11 @@ +................................F [100%] +=================================== FAILURES =================================== +_____ test_queued_drag_keeps_events_and_copy_but_coalesces_selection_work ______ +tests/test_selection.py:475: in test_queued_drag_keeps_events_and_copy_but_coalesces_selection_work + assert apply.call_count < len(moves) // 4 +E AssertionError: assert 80 < (80 // 4) +E + where 80 = .call_count +E + and 80 = len([MouseMove(None, x=2, y=4, pointer_x=2, pointer_y=4, delta_x=1, button=1, style=Style(color=Color('#e0e0e0', ColorType...Color('#3b3b3b', ColorType.TRUECOLOR, triplet=ColorTriplet(red=59, green=59, blue=59)), meta={'offset': (0, 0)})), ...]) +=========================== short test summary info ============================ +FAILED tests/test_selection.py::test_queued_drag_keeps_events_and_copy_but_coalesces_selection_work +1 failed, 32 passed in 10.32s diff --git a/evidence/pointer-capture-order-20261006/qualified-checks.log b/evidence/pointer-capture-order-20261006/qualified-checks.log new file mode 100644 index 00000000000..66426282581 --- /dev/null +++ b/evidence/pointer-capture-order-20261006/qualified-checks.log @@ -0,0 +1,2 @@ +................................. [100%] +33 passed in 10.45s diff --git a/src/textual/screen.py b/src/textual/screen.py index fcdc663b2b2..b96c3833102 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -2167,8 +2167,9 @@ def _watch__select_state(self, select_state: SelectState | None) -> None: # The application may already have another raw drag position # queued. Deliver all mouse events, but don't repeatedly traverse # thousands of selected widgets for positions superseded before - # painting. A callback, copy, mouse-up or paint flushes the latest - # state even if the following event doesn't update this screen. + # painting. The original callback, copy, mouse-up and paint + # boundaries flush this projection. Routed handler completion is + # not a selection publication boundary. try: pending = self.app._peek_message() except MessagePumpClosed: @@ -2178,9 +2179,7 @@ def _watch__select_state(self, select_state: SelectState | None) -> None: and pending.button == 1 and not pending.is_forwarded ): - if not self._selection_update_pending: - self._selection_update_pending = True - self.call_next(self._flush_pending_selection) + self._selection_update_pending = True return self._selection_update_pending = False diff --git a/tests/test_pointer_capture_order.py b/tests/test_pointer_capture_order.py new file mode 100644 index 00000000000..ef0164a5fc1 --- /dev/null +++ b/tests/test_pointer_capture_order.py @@ -0,0 +1,181 @@ +import asyncio + +import pytest + +from textual import events +from textual.app import App, ComposeResult +from textual.containers import Horizontal, VerticalGroup +from textual.geometry import Offset +from textual.widgets import Input, Static, TextArea + + +def send_pointer(app, kind, point, button=1): + # Exercise real driver ingress, including sender and button ownership. + app._driver.process_message(kind(None, *point, 0, 0, button, False, False, False)) + + +async def test_driver_burst_waits_for_press_capture_and_routes_release_before_wheel(): + entered, release = asyncio.Event(), asyncio.Event() + delivered = [] + + class Handle(Static): + ALLOW_SELECT = False + + async def on_mouse_down(self, event): + event.stop() + entered.set() + await release.wait() + self.capture_mouse() + delivered.append(("down", event.screen_offset)) + + def on_mouse_move(self, event): + event.stop() + delivered.append(("move", event.screen_offset)) + + def on_mouse_up(self, event): + event.stop() + delivered.append(("up", event.screen_offset)) + self.release_mouse() + + class Body(Static): + def on_mouse_scroll_up(self, event): + event.stop() + delivered.append(("wheel", event.screen_offset)) + + class PointerApp(App): + CSS = "Handle {width: 6; height: 3;} Body {width: 20; height: 3;}" + + def compose(self) -> ComposeResult: + with Horizontal(): + yield Handle("handle") + yield Body("body") + + app = PointerApp() + async with app.run_test(size=(40, 8)) as pilot: + await pilot.pause() + handle = app.query_one(Handle) + body = app.query_one(Body) + down = handle.region.offset + Offset(1, 1) + move = body.region.offset + Offset(2, 1) + up = move + Offset(1, 0) + send_pointer(app, events.MouseDown, down) + send_pointer(app, events.MouseMove, move) + send_pointer(app, events.MouseUp, up) + send_pointer(app, events.MouseScrollUp, move, button=0) + try: + await asyncio.wait_for(entered.wait(), 5) + assert not delivered + assert app.mouse_position == down + finally: + release.set() + await pilot.pause() + assert delivered == [("down", down), ("move", move), ("up", up), ("wheel", move)] + assert app.mouse_captured is None + + +async def test_driver_burst_waits_for_bubbled_parent_capture(): + delivered = [] + + class CaptureParent(VerticalGroup): + ALLOW_SELECT = False + + def on_mouse_down(self, event): + event.stop() + self.capture_mouse() + delivered.append("down") + + def on_mouse_move(self, event): + event.stop() + delivered.append("move") + + def on_mouse_up(self, event): + event.stop() + self.release_mouse() + delivered.append("up") + + class PointerApp(App): + CSS = "CaptureParent {width: 6; height: 3;} #outside {width: 20; height: 3;}" + + def compose(self) -> ComposeResult: + with Horizontal(): + with CaptureParent(): + yield Static("child") + yield Static("outside", id="outside") + + app = PointerApp() + async with app.run_test(size=(40, 8)) as pilot: + await pilot.pause() + parent = app.query_one(CaptureParent) + outside = app.query_one("#outside") + send_pointer(app, events.MouseDown, parent.region.offset + Offset(1, 0)) + send_pointer(app, events.MouseMove, outside.region.offset + Offset(2, 1)) + send_pointer(app, events.MouseUp, outside.region.offset + Offset(3, 1)) + await pilot.pause() + assert delivered == ["down", "move", "up"] + assert app.mouse_captured is None + + +@pytest.mark.parametrize("editor", [Input, TextArea]) +async def test_original_editor_capture_releases_after_driver_burst(editor): + class EditorApp(App): + CSS = "#editor {width: 12; height: 3;} #outside {width: 20; height: 3;}" + + def compose(self) -> ComposeResult: + with Horizontal(): + yield editor("original text", id="editor") + yield Static("outside", id="outside") + + app = EditorApp() + async with app.run_test(size=(40, 8)) as pilot: + await pilot.pause() + target = app.query_one("#editor") + outside = app.query_one("#outside") + down = target.content_region.offset + Offset(1, 0) + away = outside.content_region.offset + Offset(3, 0) + send_pointer(app, events.MouseDown, down) + send_pointer(app, events.MouseMove, away) + send_pointer(app, events.MouseUp, away) + await pilot.pause() + assert not target._selecting + assert app.mouse_captured is None + # Initial Input panning and TextArea wrapping own the exact endpoints. + # A routed drag must select text and terminate without editing it. + assert target.selected_text + assert (target.value if editor is Input else target.text) == "original text" + + +async def test_filtered_pointer_delivery_does_not_hold_ingress(): + app = App() + async with app.run_test(size=(40, 8)) as pilot: + disabled = Static("disabled", disabled=True) + await app.mount(disabled) + await pilot.pause() + point = disabled.region.offset + send_pointer(app, events.MouseDown, point) + send_pointer(app, events.MouseUp, point) + await pilot.pause() + assert app.mouse_position == point + assert app.mouse_captured is None + + +async def test_pointer_receiver_self_removal_releases_original_delivery(): + class Retiring(Static): + ALLOW_SELECT = False + + async def on_mouse_down(self, event): + event.stop() + await self.remove() + + app = App() + async with app.run_test(size=(40, 8)) as pilot: + target = Retiring("retiring") + await app.mount(target) + await pilot.pause() + send_pointer(app, events.MouseDown, target.region.offset) + send_pointer(app, events.MouseMove, Offset(20, 4)) + send_pointer(app, events.MouseUp, Offset(21, 4)) + await pilot.pause() + assert not target.is_attached + assert target._task is None + assert app.mouse_position == Offset(21, 4) + assert app.mouse_captured is None