Skip to content

search_panes: visual-row capture misses wrap-spanning patterns #55

Description

@tony

Problem

search_panes doesn't join visually-wrapped lines before matching, so plain-text patterns that tmux wraps at the pane's column width split across two captured rows and miss the match. Long error messages (e.g. "Build failed: module not found in /very/long/path/to/module.py") are the canonical example — they wrap mid-string and re.search against each row in isolation never sees the full string.

wait_for_text shipped the wrap fix in #45 (commit 67eed15) by passing join_wrapped=True to pane.capture_pane() — which adds tmux's -J flag. search_panes was not part of that sweep.

Repro

Render a pane with a long line that wraps:

$ pytest tests/integration/test_pipeline_with_a_very_long_test_class_name_to_force_wrap.py::TestXYZ::test_specific_assertion_that_fails
FAILED

If the pane is ~80 cols wide, the path wraps. Then:

search_panes(pattern="test_specific_assertion_that_fails")

Returns the pane (the substring lives on row 2). But:

search_panes(pattern="tests/integration/test_pipeline_with_a_very_long_test_class_name_to_force_wrap.py::TestXYZ::test_specific_assertion_that_fails")

Returns no match — the pattern spans the wrap boundary.

Root cause

src/libtmux_mcp/tools/pane_tools/search.py slow path:

lines = pane.capture_pane(start=content_start, end=content_end)
matched_lines = [line for line in lines if compiled.search(line)]

No join_wrapped=True. Compare to src/libtmux_mcp/tools/pane_tools/wait.py:

lines = await asyncio.to_thread(
    pane.capture_pane,
    start=start_line,
    end=None,
    join_wrapped=True,
)

Nuance — the fast path is structurally wrap-blind

search_panes has two paths:

  1. Slow path (Python-regex over captured lines): the mechanical fix is join_wrapped=True. Drop-in.
  2. Fast path (#{C:pattern} format conditional): runs tmux's C-level window_pane_search() against the grid by visual row. There's no -J analog at the format-conditional level, so this path is wrap-blind by tmux design.

The fast path triggers when (a) no scrollback range is requested and (b) the pattern has no regex metacharacters or tmux format-injection characters. Long plain-text error messages from build output hit the fast path most of the time — exactly the worst case for wrap-blindness.

Proposed fix

  1. Slow path: add join_wrapped=True to the pane.capture_pane(...) call. Same trade-off wait_for_text accepts — matched_lines may contain strings longer than pane_width.
  2. Fast path: docstring note acknowledging that plain-text patterns spanning the pane's wrap column may miss matches; suggest passing regex=True or supplying scrollback range (content_start=-N) to force the slow path.
  3. Test: regression covering a wrap-spanning pattern, parallel to test_wait_for_text_matches_pattern_across_wrap.
  4. CHANGES entry under ### Fixes (Published-Release Test passes — search_panes shipped in 0.1.0a2).

Out of scope

  • Routing wrap-spanning patterns from the fast path to the slow path automatically. Requires tmux-side detection of pane width vs. pattern length, which is heuristic and adds complexity.
  • Extending -J to tmux's #{C:...} format conditional. That's an upstream-tmux change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions