Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ Server <libtmux.server>
Session <libtmux.session>
Window <libtmux.window>
Pane <libtmux.pane>
Snapshot <libtmux.snapshot>
Common <libtmux.common>
Neo <libtmux.neo>
Options <libtmux.options>
Expand Down
104 changes: 104 additions & 0 deletions docs/api/snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
(snapshot)=

# Snapshots

The snapshot module provides functionality for capturing and analyzing the state of tmux panes.

## Core Classes

```{eval-rst}
.. autoclass:: libtmux.snapshot.PaneSnapshot
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

.. autoclass:: libtmux.snapshot.PaneRecording
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource
```

## Output Adapters

```{eval-rst}
.. autoclass:: libtmux.snapshot.SnapshotOutputAdapter
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

.. autoclass:: libtmux.snapshot.TerminalOutputAdapter
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

.. autoclass:: libtmux.snapshot.CLIOutputAdapter
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

.. autoclass:: libtmux.snapshot.PytestDiffAdapter
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource

.. autoclass:: libtmux.snapshot.SyrupySnapshotAdapter
:members:
:inherited-members:
:show-inheritance:
:member-order: bysource
```

## Examples

### Basic Snapshot

```python
>>> snapshot = pane.snapshot()
>>> snapshot.pane_id # doctest: +ELLIPSIS
'%...'
>>> isinstance(snapshot.content_str, str)
True
```

### Recording Activity

```python
>>> recording = pane.record()
>>> recording.add_snapshot(pane)
>>> pane.send_keys("echo 'Hello'")
>>> recording.add_snapshot(pane)
>>> isinstance(recording.latest.content_str, str)
True
>>> len(recording) >= 2
True
```

### Using Output Adapters

```python
>>> from libtmux.snapshot import TerminalOutputAdapter
>>> snapshot = pane.snapshot()
>>> formatted = snapshot.format(TerminalOutputAdapter())
>>> '=== Pane Snapshot ===' in formatted
True
>>> '=== Content ===' in formatted
True
```

### Custom Adapter

```python
>>> from libtmux.snapshot import SnapshotOutputAdapter, PaneSnapshot
>>> snapshot = pane.snapshot()
>>> class MyAdapter(SnapshotOutputAdapter):
... def format(self, snapshot: PaneSnapshot) -> str:
... return f"Content: {snapshot.content_str[:20]}"
>>> snapshot.format(MyAdapter()).startswith('Content:')
True
```
1 change: 1 addition & 0 deletions docs/topics/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ workspace_setup
automation_patterns
context_managers
options_and_hooks
snapshots
```
131 changes: 131 additions & 0 deletions docs/topics/snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
(snapshots)=

# Snapshots and Recordings

libtmux provides functionality to capture and analyze the state of tmux panes through snapshots and recordings.

## Taking Snapshots

A snapshot captures the content and metadata of a pane at a specific point in time:

```python
>>> snapshot = pane.snapshot()
>>> snapshot.pane_id # doctest: +ELLIPSIS
'%...'
>>> isinstance(snapshot.content_str, str)
True
```

Snapshots are immutable and include:
- Pane content
- Timestamp (in UTC)
- Pane, window, session, and server IDs
- All tmux pane metadata

You can also capture specific ranges of the pane history:

```python
>>> # Capture lines 1-3 only
>>> snapshot = pane.snapshot(start=1, end=3)
>>> isinstance(snapshot.content_str, str)
True

>>> # Capture from start of history
>>> snapshot = pane.snapshot(start="-")
>>> isinstance(snapshot.content_str, str)
True

>>> # Capture up to current view
>>> snapshot = pane.snapshot(end="-")
>>> isinstance(snapshot.content_str, str)
True
```

## Recording Pane Activity

To track changes in a pane over time, use recordings:

```python
>>> recording = pane.record()
>>> recording.add_snapshot(pane)
>>> pane.send_keys("echo 'Hello'")
>>> recording.add_snapshot(pane)
>>> pane.send_keys("echo 'World'")
>>> recording.add_snapshot(pane)

>>> # Access snapshots
>>> isinstance(recording[0].content_str, str)
True
>>> isinstance(recording.latest.content_str, str)
True

>>> # Filter by time
>>> import datetime
>>> recent = recording.get_snapshots_between(
... start_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(minutes=5),
... end_time=datetime.datetime.now(datetime.timezone.utc),
... )
>>> isinstance(recent, list)
True
```

## Output Formats

Snapshots can be formatted in different ways for various use cases:

### Terminal Output

```python
>>> from libtmux.snapshot import TerminalOutputAdapter
>>> snapshot = pane.snapshot()
>>> formatted = snapshot.format(TerminalOutputAdapter())
>>> '=== Pane Snapshot ===' in formatted
True
>>> '=== Content ===' in formatted
True
```

### CLI Output (No Colors)

```python
>>> from libtmux.snapshot import CLIOutputAdapter
>>> snapshot = pane.snapshot()
>>> formatted = snapshot.format(CLIOutputAdapter())
>>> '=== Pane Snapshot ===' in formatted
True
```

### Pytest Assertion Diffs

```python
>>> from libtmux.snapshot import PytestDiffAdapter
>>> snapshot = pane.snapshot()
>>> formatted = snapshot.format(PytestDiffAdapter())
>>> 'PaneSnapshot(' in formatted
True
```

### Syrupy Snapshot Testing

```python
>>> from libtmux.snapshot import SyrupySnapshotAdapter
>>> snapshot = pane.snapshot()
>>> formatted = snapshot.format(SyrupySnapshotAdapter())
>>> 'pane_id' in formatted
True
```

## Custom Output Formats

You can create custom output formats by implementing the `SnapshotOutputAdapter` interface:

```python
>>> from libtmux.snapshot import SnapshotOutputAdapter, PaneSnapshot
>>> snapshot = pane.snapshot()
>>> class MyCustomAdapter(SnapshotOutputAdapter):
... def format(self, snapshot: PaneSnapshot) -> str:
... return f"Custom format: {snapshot.content_str[:20]}"
>>> result = snapshot.format(MyCustomAdapter())
>>> result.startswith('Custom format:')
True
```
38 changes: 38 additions & 0 deletions src/libtmux/pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from libtmux.hooks import HooksMixin
from libtmux.neo import Obj, fetch_obj
from libtmux.options import OptionsMixin
from libtmux.snapshot import PaneRecording, PaneSnapshot

if t.TYPE_CHECKING:
import sys
Expand Down Expand Up @@ -420,6 +421,43 @@ def capture_pane(
)
return self.cmd(*cmd).stdout

def snapshot(
self,
start: t.Literal["-"] | int | None = None,
end: t.Literal["-"] | int | None = None,
) -> PaneSnapshot:
"""Create a snapshot of the pane's current state.

This is a convenience method that creates a :class:`PaneSnapshot` instance
from the current pane state.

Parameters
----------
start : int | "-" | None
Start line for capture_pane
end : int | "-" | None
End line for capture_pane

Returns
-------
PaneSnapshot
A frozen snapshot of the pane's current state
"""
return PaneSnapshot.from_pane(self, start=start, end=end)

def record(self) -> PaneRecording:
"""Create a new recording for this pane.

This is a convenience method that creates a :class:`PaneRecording` instance
for recording snapshots of this pane.

Returns
-------
PaneRecording
A new recording instance for this pane
"""
return PaneRecording()

def send_keys(
self,
cmd: str,
Expand Down
Loading
Loading