Skip to content

✨ TextArea widget + shortcut menu#57

Open
juftin wants to merge 58 commits intomainfrom
feature/textarea-integration
Open

✨ TextArea widget + shortcut menu#57
juftin wants to merge 58 commits intomainfrom
feature/textarea-integration

Conversation

@juftin
Copy link
Copy Markdown
Owner

@juftin juftin commented Mar 25, 2026

Summary

Integrates Textual's native TextArea widget as the primary code viewer in browsr, replacing the static Syntax-based view for text and JSON files, and adds a new interactive Keyboard Shortcuts overlay accessible via ?.

Context

Two major features land in this PR:

  1. TextArea Integration — The static Syntax widget (via Rich) was read-only and non-interactive. Replacing it with TextArea unlocks native syntax highlighting, vim-style cursor navigation, text selection, clipboard copy, and soft-wrap toggling — all within the existing browsr UI architecture.

  2. Keyboard Shortcuts Widget — A new ?-triggered overlay that dynamically lists all active bindings, filtered to a trusted action list, and sorted alphabetically. Built on a reusable BasePopUp/BaseOverlay pattern that also benefits the existing ConfirmationWindow.

Changes

Code Changes

  • browsr/widgets/windows.py

    • Added TextWindow(TextArea, BaseCodeWindow) widget with read-only mode, vim key bindings (hjkl), smart language detection, theme mapping, and clipboard copy support via pyperclip.
    • Added FileToStringResult NamedTuple carrying both the decoded text and an error_occurred flag, enabling graceful fallback to StaticWindow on load failure.
    • Added ThemeVisibleMixin and LinenosVisibleMixin to share reactive state cleanly between StaticWindow, TextWindow, and WindowSwitcher.
    • Updated WindowSwitcher to compose and route files to TextWindow (code/JSON) or StaticWindow (images/markdown/errors), with unified linenos, theme, and dark-mode reactive watchers.
    • Added _update_subtitle to keep the app subtitle consistent (with theme annotation) across all window types.
    • Rewrote next_theme into _next_textarea_theme / _next_rich_theme helpers to cycle TextArea themes (from textarea_theme_map) and Rich themes (for StaticWindow) independently.
    • Added watch_dark to WindowSwitcher to propagate dark-mode changes to TextWindow.apply_smart_theme.
  • browsr/config.py

    • Added textarea_default_theme (defaults to vscode_dark).
    • Added textarea_theme_map (OrderedDict mapping Rich theme names to equivalent TextArea theme names for coordinated cycling).
    • Added language_map (file extension → TextArea language string).
    • Added filename_map (special-case filenames like Dockerfile, uv.lock, Makefile → language).
  • browsr/widgets/base.py (new file)

    • BasePopUp(Container): focusable popup base with escape binding and a Toggle message for communicating display state to a parent overlay.
    • BaseOverlay(Container): overlay container that listens for BasePopUp.Toggle, manages display, auto-focuses on show, and hides on mount.
  • browsr/widgets/shortcuts.py (new file)

    • ShortcutsPopUp(BasePopUp): dynamically reads app.active_bindings, filters to TRUSTED_ACTIONS and ignores IGNORED_KEYS (e.g. ctrl+q), and presents a sorted DataTable of key → description.
    • ShortcutsWindow(BaseOverlay): overlay container wrapping ShortcutsPopUp.
  • browsr/widgets/confirmation.py

    • Refactored ConfirmationPopUp and ConfirmationWindow to inherit from BasePopUp / BaseOverlay, removing duplicated display-toggle and escape-handling logic.
  • browsr/screens/code_browser.py

    • Added ? binding for action_toggle_shortcuts — opens/closes ShortcutsWindow and refreshes the DataTable.
    • Added w binding for action_toggle_wrap — toggles TextWindow.soft_wrap.
    • Added LAYERS = ["default", "overlay"] to support true overlay rendering for ShortcutsWindow and ConfirmationWindow.
    • Updated BINDING_WEIGHTS to accommodate new w, C, and ? entries.
    • Updated action_linenos to set linenos on WindowSwitcher rather than directly on StaticWindow.
  • browsr/widgets/code_browser.py

    • Bound C (Shift+C) to copy_text action with key_display="shift+c" for footer display.
  • browsr/browsr.py

    • Added action_copy_text to delegate clipboard copy to text_window.copy_selected_text().
  • browsr/browsr.css

    • Added styles for ShortcutsWindow and ConfirmationWindow as true overlay layers with semi-transparent backgrounds.
  • pyproject.toml / uv.lock

    • Added textual[syntax] extra (enables Tree-sitter syntax highlighting in TextArea).
    • Added pyperclip for clipboard copy support.
  • tests/test_windows.py (new file)

    • Comprehensive tests for TextWindow: initialization, language detection, theme application, linenos sync, copy behavior, and WindowSwitcher routing logic.
  • tests/test_shortcuts.py (new file)

    • Tests for ShortcutsWindow mount and rendering.
  • tests/__snapshots__/

    • Updated visual snapshots to reflect TextArea-based rendering and new shortcuts overlay.

Test Plan

  • Widget inheritance and initialization verified.
  • Theme and dark mode synchronization verified.
  • Line number dynamicity and synchronization verified.
  • Smart language detection verified for various extensions and filenames.
  • Shift+C copy functionality verified.
  • Soft-wrap toggle (w) verified.
  • Graceful fallback to StaticWindow on file load error verified.
  • ShortcutsWindow renders active bindings filtered to trusted actions.
  • Escape closes both ShortcutsWindow and ConfirmationWindow.
  • Overlay layers render correctly over content.
  • Visual snapshots updated and verified.
  • Full test suite passes (uv run pytest tests/).
  • mypy passes with no errors.
  • ruff linting and formatting pass.
  • Manual smoke test: open various file types (.py, .json, .md, .csv, image) and verify correct window routing and theme cycling.
  • Manual smoke test: press ? to open shortcuts overlay, verify it shows the correct bindings and closes with escape or the Close button.

Behavior Diagram

graph TD
    subgraph WindowSwitcher
        A[file_path] --> B{extension?}
        B -->|datatable| C[DataTableWindow]
        B -->|image| D[StaticWindow - image]
        B -->|markdown| E[StaticWindow - markdown]
        B -->|json| F[TextWindow - json]
        B -->|text / error| G{load error?}
        G -->|yes| H[StaticWindow - error art]
        G -->|no| I[TextWindow - code]
    end

    subgraph Overlays
        J["? key"] --> K[ShortcutsWindow]
        K --> L[ShortcutsPopUp DataTable]
        M[download action] --> N[ConfirmationWindow]
        N --> O[ConfirmationPopUp]
    end
Loading

juftin added 24 commits March 24, 2026 21:48
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py00100% 
__main__.py110%5
base.py55590%50, 91, 97, 99, 101
browsr.py30390%69, 75, 81
cli.py381268%205–212, 219–220, 227–228
config.py13284%24, 26
exceptions.py10100% 
utils.py1054656%23–27, 34–36, 38–45, 72–77, 80–90, 131–132, 134–140, 151–152, 167–168
screens
   __init__.py20100% 
   code_browser.py903066%95, 128, 130, 137, 143, 149–152, 156, 159, 170, 176–178, 186–192, 195–198, 202–204, 215
tests
   __init__.py00100% 
   conftest.py26292%26, 34
   debug_app.py990%5, 7–8, 11–12, 16–19
   test_browsr.py60100% 
   test_cli.py50100% 
   test_config.py250100% 
   test_screenshots.py290100% 
   test_shortcuts.py150100% 
   test_windows.py1210100% 
widgets
   __init__.py00100% 
   base.py30970%32–33, 39, 57, 69–70, 77–78, 80
   code_browser.py1284564%100, 149–151, 165, 174–175, 182–185, 194–195, 209–211, 222–228, 232, 238–241, 248–257, 268–274
   confirmation.py28775%54, 65–66, 73–76
   double_click_directory_tree.py401757%47–48, 65, 69–70, 77–79, 85–91, 93–94
   files.py822075%35–36, 53, 67, 75–76, 87, 92, 94, 103, 110–111, 114, 118, 127, 131–134, 136
   shortcuts.py41295%58, 71
   universal_directory_tree.py29486%39, 42, 56, 60
   vim.py110100% 
   windows.py2998272%81–83, 96–97, 99–101, 110–112, 118–121, 124–128, 138, 145–146, 153–156, 188–189, 203–204, 211, 219, 221, 305, 319–324, 326–328, 354–364, 412, 427–428, 455–456, 482, 484, 491–493, 497–498, 512, 515, 519–521, 545–546, 560, 568–569, 579–580, 589–591
TOTAL125929676% 

Tests Skipped Failures Errors Time
18 0 💤 0 ❌ 0 🔥 46.452s ⏱️

@juftin juftin force-pushed the feature/textarea-integration branch from 932e823 to 355f67a Compare April 1, 2026 05:25
@juftin juftin changed the title feat: integrate TextArea for high-performance code viewing ✨ feat: integrate TextArea viewer and Keyboard Shortcuts widget Apr 2, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Integrates Textual’s TextArea as the primary interactive code/text viewer and adds a ?-triggered keyboard shortcuts overlay, along with supporting theme/language config and updated tests/snapshots.

Changes:

  • Replace the read-only Syntax-based code viewer with a read-only TextArea-based TextWindow and update WindowSwitcher routing/theme/lineno propagation.
  • Add a reusable overlay/popup base plus a new shortcuts overlay that discovers active bindings and displays them in a DataTable.
  • Update docs, CLI help text, dependencies, and expand test/snapshot coverage to match the new UI behavior.

Reviewed changes

Copilot reviewed 24 out of 26 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
browsr/widgets/windows.py Adds TextWindow, mixins, new WindowSwitcher routing, and theme/lineno synchronization logic.
browsr/config.py Introduces TextArea theme mapping and language/filename detection maps.
browsr/widgets/base.py Adds BasePopUp / BaseOverlay primitives for consistent overlay behavior.
browsr/widgets/shortcuts.py Implements shortcuts popup/window with active binding discovery and filtering.
browsr/widgets/confirmation.py Refactors confirmation UI to use the new popup/overlay base.
browsr/widgets/code_browser.py Adjusts key binding registration and confirmation workflow display restoration.
browsr/screens/code_browser.py Adds ? and w actions, overlay layer support, and shortcuts overlay wiring.
browsr/browsr.py Adds action_copy_text delegating to TextWindow selection-copy behavior.
browsr/browsr.css Styles shortcuts/confirmation as true overlays with semi-transparent backgrounds.
browsr/utils.py Adjusts image scaling math used for rendering images in-terminal.
browsr/cli.py Updates documented key bindings to match the new bindings and features.
pyproject.toml Enables textual[syntax] extra for Tree-sitter highlighting in TextArea.
README.md Updates marketing copy to mention TextArea-based syntax highlighting.
docs/index.md Mirrors README copy update for the documentation homepage.
docs/superpowers/summaries/2026-03-30-keyboard-shortcuts-implementation.md Adds implementation summary document for the feature.
docs/superpowers/specs/2026-03-30-keyboard-shortcuts-widget-design.md Adds design spec document for the shortcuts overlay.
docs/superpowers/plans/2026-03-30-keyboard-shortcuts-widget.md Adds implementation plan document for the shortcuts overlay.
tests/test_windows.py Adds unit/integration-style tests for TextWindow and WindowSwitcher behavior.
tests/test_shortcuts.py Adds a basic mount/render test for shortcut discovery.
tests/test_screenshots.py Updates screenshot test to open the shortcuts overlay during snapshot.
tests/cassettes/test_shortcuts_screenshot.yaml Adds VCR cassette data used by the new screenshot test.
tests/__snapshots__/test_screenshots/test_shortcuts_screenshot.raw Adds new snapshot output for the shortcuts overlay screenshot.
tests/__snapshots__/test_screenshots/test_github_screenshot.raw Removes an obsolete snapshot tied to the renamed screenshot test.
Comments suppressed due to low confidence (1)

browsr/widgets/confirmation.py:90

  • ConfirmationWindow.compose() currently returns immediately and contains an unreachable yield statement. This makes the method misleading and can result in an empty overlay if ConfirmationWindow is ever instantiated without children passed to the constructor. Either remove this override entirely (let Container handle children) or implement compose() to yield the expected ConfirmationPopUp content.
    def compose(self) -> ComposeResult:
        """
        Compose the Confirmation Window
        """
        return
        yield


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@juftin juftin changed the title ✨ feat: integrate TextArea viewer and Keyboard Shortcuts widget ✨ TextArea widget + shortcut menu Apr 3, 2026
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