Skip to content

Scroll, text-editing and link keys are declared operations, not action names - #244

Merged
trissim merged 1 commit into
mainfrom
scroll-keys-are-commands
Nov 6, 2026
Merged

trissim merged 1 commit into
mainfrom
scroll-keys-are-commands

Conversation

@trissim

@trissim trissim commented Nov 6, 2026

Copy link
Copy Markdown

1 commit, 8 files, +216/-209. Three of Textual's string-dispatched key protocols become declared families. That lets Toad's keymap guard go from "9 tolerated" to 0 (companion Toad PR follows).

Scroll keys

  • Before: ScrollableContainer.BINDINGS had ten Binding("up", "scroll_up", …) strings reaching Widget.action_scroll_* by name. Toad's HistoryWindow overrode five of those names.
  • After: a ScrollCommand family in textual.navigation (ScrollUp/Down/Left/Right/Home/End, PageLeft/PageRight). PageUp and PageDown are ListCommand and ScrollCommand at once, one key and one operation rather than a second pair. The ten bodies move from Widget into ScrollsView, the contract Widget inherits. ScrollableContainer declares key_families=(ScrollCommand,), and its BINDINGS and the docstring table restating them are deleted.
  • Navigable(DOMNode) now owns accepts_navigation/apply_navigation. Hanging ScrollsView under Navigation made every (MovesCursor, Widget) class unlinearizable.

Text editing

  • TextArea's twelve string BINDINGS become @MethodBinding.declare(...) on the methods they run, the same mechanism its cursor keys already use.
  • action_delete_to_end_of_line_or_delete_line was async only to await self.run_action(action), with action a string picked by an if chain (IMPL-1). It now calls action_delete_line/action_delete_right/action_delete_to_end_of_line directly.

Links

  • textual.actions.StyleAction: a style's handler meta can carry the operation itself. HandlerArguments.decode is the one boundary, and the string form becomes ParsedAction.
  • Markdown links and images carry MarkdownLink(href) instead of f"link({href!r})". The three action_link copies (MarkdownBlock, MarkdownTableCellContents, MarkdownTableContent, IMPL-12) become one contract, OpensMarkdownLinks.open_link.

Proof

  • Key, binding, container, data table, option list, tree, text area, markdown and links tests: 660 passed. The 3 tests/text_area/test_editor_state.py reds fail identically on clean main.
  • tests/test_markdown.py:237 pinned the old string meta. It now asserts MarkdownLink.

🤖 Generated with Claude Code

…n names

Toad's keymap guard tolerated 9 `def action_*` methods reached by string name; every one was
an override of a Textual operation Textual itself only reached by a string:

- ScrollableContainer's ten BINDINGS ("scroll_up", "page_down", ...) become the ScrollCommand
  family in textual.navigation. The ten action bodies move out of Widget into ScrollsView, the
  contract Widget inherits; ScrollableContainer declares key_families=(ScrollCommand,).
  PageUp/PageDown are ListCommand and ScrollCommand at once: one key, one operation.
- Navigable is the root a node answering navigation commands needs, so Widget can inherit the
  scroll contract without Navigation breaking every (MovesCursor, Widget) MRO.
- TextArea's twelve string BINDINGS become MethodBinding.declare on the methods they run.
  The ctrl+k kill action stops choosing an action name in a string and awaiting run_action;
  it calls its three operations directly, synchronously.
- A style's handler meta can carry a StyleAction object; HandlerArguments decodes the meta
  once into one, and the string form is ParsedAction. Markdown links carry a MarkdownLink, and
  the three action_link copies (block, table cell, table content) become one contract,
  OpensMarkdownLinks.open_link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 05:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim
trissim merged commit 2d331c6 into main Nov 6, 2026
3 of 23 checks passed
trissim added a commit to OpenHCSDev/toad that referenced this pull request Nov 6, 2026
Every KeymapMeasure asserts zero and `ceiling` is gone: a tolerated count of keys written
outside their owner was a ratchet on slop. ActionMethods exempts exactly the operations
Textual declares (a navigation contract's methods, a MethodBinding's), read from the classes.

With OpenHCSDev/textual#244, the eleven `def action_*` sites are operations, not names:
HistoryWindow overrides ScrollsView, TargetTree implements MovesHorizontalCursor, the prompt
overrides TextArea's declared editing methods. The two link actions are deleted:
- a drawn link's style meta carries OpenLineLink(link), a StyleAction in the new
  toad/link_click.py; LineLink.click and its "open" action name leave line_blocks, which
  declares that nothing in it imports a frontend;
- OpensLinks inherits Textual's OpensMarkdownLinks, and open_link decodes a toad-link href
  once; the right-click menu reads OpenLineLink.link_of, and the app's inline import is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants