Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5dda8d2
py(deps[dev]): Add syrupy for snapshot testing
tony Dec 6, 2025
3fcbfe9
tests(textframe): Add TextFrame ASCII frame prototype
tony Dec 6, 2025
c8f5e17
tests(textframe): Add snapshot baselines
tony Dec 6, 2025
c6d02c5
TextFrame(feat[__post_init__]): Add dimension and fill_char validation
tony Dec 7, 2025
bf87a3c
TextFrame(feat[overflow_behavior]): Add truncate mode for content ove…
tony Dec 7, 2025
404553f
tests(textframe): Add truncate behavior test cases
tony Dec 7, 2025
5cd7f60
tests(textframe): Add snapshot baselines for truncate tests
tony Dec 7, 2025
021f844
tests(textframe): Add pytest_assertrepr_compare hook
tony Dec 7, 2025
1d3c55b
tests(textframe): Switch to SingleFileSnapshotExtension
tony Dec 7, 2025
0006b28
tests(textframe): Remove old .ambr snapshot file
tony Dec 7, 2025
e78e74f
tests(textframe): Add .frame snapshot baselines
tony Dec 7, 2025
1f4c538
docs(textframe): Document assertion customization patterns
tony Dec 7, 2025
fd13026
libtmux(textframe): Move core module to src/libtmux/textframe/
tony Dec 7, 2025
2b6bf88
libtmux(textframe): Add pytest plugin with hooks and fixtures
tony Dec 7, 2025
6e8c279
py(deps): Add textframe extras with syrupy dependency
tony Dec 7, 2025
9242c98
docs(textframe): Update for distributable plugin
tony Dec 7, 2025
2cabba3
py(deps): Update lockfile for textframe extras
tony Dec 7, 2025
18f567b
docs(CHANGES): Document TextFrame features for 0.52.x (#613)
tony Dec 7, 2025
92dab52
Pane(feat[capture_frame]): Add capture_frame() method
tony Dec 7, 2025
4d738c1
tests(pane): Add capture_frame() integration tests
tony Dec 7, 2025
15ac3bb
tests(pane): Add capture_frame snapshot baseline
tony Dec 7, 2025
f972626
docs(textframe): Document capture_frame() integration
tony Dec 7, 2025
992f060
tests(pane): Add exhaustive capture_frame() snapshot tests
tony Dec 7, 2025
fe88a74
tests(pane): Add capture_frame snapshot baselines
tony Dec 7, 2025
f3def30
Pane(docs[capture_frame]): Fix doctest to work without SKIP
tony Dec 7, 2025
649e5a4
Pane(feat[capture_frame]): Forward capture_pane() flags
tony Dec 7, 2025
cc7a061
tests(pane): Add capture_frame() flag forwarding tests
tony Dec 7, 2025
e1c0c48
TextFrame(feat[display]): Add interactive curses viewer
tony Dec 8, 2025
f3cc82b
docs(textframe): Document display() method
tony Dec 8, 2025
819e714
TextFrame(fix[display]): Use shutil for terminal size detection
tony Dec 8, 2025
5b148cc
tests(textframe): Add shutil terminal size detection test
tony Dec 8, 2025
25358fe
textframe(fix): Make TextFrameExtension import conditional
tony Dec 8, 2025
8e9b241
textframe(fix): Inherit ContentOverflowError from LibTmuxException
tony Dec 8, 2025
3d3df3d
textframe(style): Use namespace import for difflib
tony Dec 8, 2025
0981745
tests(textframe): Replace patch() with monkeypatch.setattr()
tony Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,128 @@ $ uvx --from 'libtmux' --prerelease allow python
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### New features

#### TextFrame primitive (#613)

New {class}`~libtmux.textframe.TextFrame` dataclass for testing terminal UI output.
Provides a fixed-size ASCII frame simulator with overflow detection - useful for
validating `capture_pane()` output and terminal rendering in tests.

```python
from libtmux.textframe import TextFrame

frame = TextFrame(content_width=10, content_height=2)
frame.set_content(["hello", "world"])
print(frame.render())
# +----------+
# |hello |
# |world |
# +----------+
```

**Features:**

- Configurable dimensions with `content_width` and `content_height`
- Overflow handling: `overflow_behavior="error"` raises {class}`~libtmux.textframe.ContentOverflowError`
with visual diagnostic, `overflow_behavior="truncate"` clips content silently
- Dimension validation via `__post_init__`
- Interactive curses viewer via `display()` for exploring large frames

#### pytest assertion hook for TextFrame (#613)

Rich assertion output when comparing {class}`~libtmux.textframe.TextFrame` objects.
Shows dimension mismatches and line-by-line content diffs using `difflib.ndiff`.

#### syrupy snapshot extension for TextFrame (#613)

{class}`~libtmux.textframe.TextFrameExtension` stores snapshots as `.frame` files —
one file per test for cleaner git diffs.

```console
$ pip install libtmux[textframe]
```

```python
from libtmux.textframe import TextFrame

def test_pane_output(textframe_snapshot):
frame = TextFrame(content_width=20, content_height=5)
frame.set_content(["Hello", "World"])
assert frame == textframe_snapshot
```

The `textframe_snapshot` fixture and assertion hooks are auto-discovered via
pytest's `pytest11` entry point when the `textframe` extra is installed.

#### Pane.capture_frame() (#613)

New {meth}`~libtmux.pane.Pane.capture_frame` method that wraps
{meth}`~libtmux.pane.Pane.capture_pane` and returns a
{class}`~libtmux.textframe.TextFrame` for visualization and snapshot testing.

**Basic usage:**

```python
pane.send_keys('echo "Hello, TextFrame!"', enter=True)
frame = pane.capture_frame(content_width=30, content_height=5)
print(frame.render())
# +------------------------------+
# |$ echo "Hello, TextFrame!" |
# |Hello, TextFrame! |
# |$ |
# | |
# | |
# +------------------------------+
```

**Multiline output:**

```python
pane.send_keys('printf "a\\nb\\nc\\n"', enter=True)
frame = pane.capture_frame(content_width=20, content_height=6)
print(frame.render())
# +--------------------+
# |$ printf "a\nb\nc\n"|
# |a |
# |b |
# |c |
# |$ |
# | |
# +--------------------+
```

**Truncation (long lines clipped to frame width):**

```python
pane.send_keys('echo "' + "x" * 50 + '"', enter=True)
frame = pane.capture_frame(content_width=15, content_height=4)
print(frame.render())
# +---------------+
# |$ echo "xxxxxxx|
# |xxxxxxxxxxxxxxx|
# |$ |
# | |
# +---------------+
```

**Snapshot testing:**

```python
def test_cli_output(pane, textframe_snapshot):
pane.send_keys("echo 'Hello'", enter=True)
frame = pane.capture_frame(content_width=40, content_height=10)
assert frame == textframe_snapshot
```

**Features:**

- Defaults to pane dimensions when `content_width` / `content_height` not specified
- Uses `overflow_behavior="truncate"` by default for CI robustness
- Accepts same `start` / `end` parameters as `capture_pane()`
- Forwards all `capture_pane()` flags: `escape_sequences`, `escape_non_printable`,
`join_wrapped`, `preserve_trailing`, `trim_trailing`

## libtmux 0.56.0 (2026-05-10)

### What's new
Expand Down
1 change: 1 addition & 0 deletions docs/internals/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ api/libtmux._internal.dataclasses
api/libtmux._internal.query_list
api/libtmux._internal.constants
api/libtmux._internal.sparse_array
textframe
```

## Environmental variables
Expand Down
Loading
Loading