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
cb7a108
py(deps[dev]): Add syrupy for snapshot testing
tony Dec 6, 2025
3adafda
tests(textframe): Add TextFrame ASCII frame prototype
tony Dec 6, 2025
06f9cf4
tests(textframe): Add snapshot baselines
tony Dec 6, 2025
49792f2
TextFrame(feat[__post_init__]): Add dimension and fill_char validation
tony Dec 7, 2025
6ba5879
TextFrame(feat[overflow_behavior]): Add truncate mode for content ove…
tony Dec 7, 2025
c023d30
tests(textframe): Add truncate behavior test cases
tony Dec 7, 2025
35a9791
tests(textframe): Add snapshot baselines for truncate tests
tony Dec 7, 2025
218ef9e
tests(textframe): Add pytest_assertrepr_compare hook
tony Dec 7, 2025
e53e832
tests(textframe): Switch to SingleFileSnapshotExtension
tony Dec 7, 2025
6cb4a93
tests(textframe): Remove old .ambr snapshot file
tony Dec 7, 2025
bb6782c
tests(textframe): Add .frame snapshot baselines
tony Dec 7, 2025
6c45610
docs(textframe): Document assertion customization patterns
tony Dec 7, 2025
25b3f6c
libtmux(textframe): Move core module to src/libtmux/textframe/
tony Dec 7, 2025
3ce5066
libtmux(textframe): Add pytest plugin with hooks and fixtures
tony Dec 7, 2025
cbbfe95
py(deps): Add textframe extras with syrupy dependency
tony Dec 7, 2025
41af5d9
docs(textframe): Update for distributable plugin
tony Dec 7, 2025
109f373
py(deps): Update lockfile for textframe extras
tony Dec 7, 2025
afe6c98
docs(CHANGES): Document TextFrame features for 0.52.x (#613)
tony Dec 7, 2025
eb2296a
Pane(feat[capture_frame]): Add capture_frame() method
tony Dec 7, 2025
6e5b51d
tests(pane): Add capture_frame() integration tests
tony Dec 7, 2025
221d4ca
tests(pane): Add capture_frame snapshot baseline
tony Dec 7, 2025
1893256
docs(textframe): Document capture_frame() integration
tony Dec 7, 2025
a9db4aa
tests(pane): Add exhaustive capture_frame() snapshot tests
tony Dec 7, 2025
725e7e6
tests(pane): Add capture_frame snapshot baselines
tony Dec 7, 2025
d935d48
Pane(docs[capture_frame]): Fix doctest to work without SKIP
tony Dec 7, 2025
c100bba
Pane(feat[capture_frame]): Forward capture_pane() flags
tony Dec 7, 2025
aea6eb8
tests(pane): Add capture_frame() flag forwarding tests
tony Dec 7, 2025
3057234
TextFrame(feat[display]): Add interactive curses viewer
tony Dec 8, 2025
7218e37
docs(textframe): Document display() method
tony Dec 8, 2025
53bc1d7
TextFrame(fix[display]): Use shutil for terminal size detection
tony Dec 8, 2025
b628fa2
tests(textframe): Add shutil terminal size detection test
tony Dec 8, 2025
d8beadb
textframe(fix): Make TextFrameExtension import conditional
tony Dec 8, 2025
55dff5c
textframe(fix): Inherit ContentOverflowError from LibTmuxException
tony Dec 8, 2025
3a53675
textframe(style): Use namespace import for difflib
tony Dec 8, 2025
9791daa
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.55.1 (2026-04-19)

### Fixes
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