# Observing output

Source: https://libtmux.org/en/julia/latest/topics/observations/

> Observing output: LibTmux documentation.

Use [`observe_output`](https://libtmux.org/en/julia/latest/reference/libtmux-observe_output/) for raw future terminal bytes. A screen capture
is a separate observation: [`capture_baseline`](https://libtmux.org/en/julia/latest/reference/libtmux-capture_baseline/) returns captured bytes
and an explicit `:reset` boundary. Its before/after cursors do not establish
an atomic relationship between the screen and the stream.

This program checks the exact bytes of terminal echo. Echo does not prove
that a command started, finished or succeeded.

```julia title="output_stream.jl"
using LibTmux

environment = Dict(
    "PATH" => get(ENV, "PATH", "/usr/bin:/bin"),
    "TERM" => "xterm-256color",
    "SHELL" => "/bin/sh",
)

with_server(; tmux=get(ENV, "LIBTMUX_TEST_TMUX", "tmux"), env=environment) do server
    created = new_session(server; name="stream", command=["/bin/cat"])
    pane = only(panes(snapshot(server))).ref
    open_control(server, created) do connection
        observe_output(connection, pane) do stream
            baseline = capture_baseline(stream)
            @assert baseline.continuity === :reset
            message = "observed terminal echo: λ"
            send_keys(server, pane, message; literal=true)
            bytes = UInt8[]
            started = time_ns()
            while length(bytes) < ncodeunits(message)
                remaining = 0.9 - (time_ns() - started) / 1e9
                remaining > 0 || error("terminal echo deadline expired")
                append!(bytes, take!(stream; timeout=remaining).bytes)
            end
            @assert bytes == codeunits(message)
            @assert observation_cursor(stream).pane == pane.id
            println(decode_text(bytes))
        end
    end
end
```

Each stream has one consumer and independent item/byte limits. A slow
consumer loses its own stream with [`ObservationLost`](https://libtmux.org/en/julia/latest/reference/libtmux-observationlost/); it cannot block
the shared control reader. `take!` accepts [`timeout`](<https://libtmux.org/en/julia/latest/reference/libtmux-deadlineexceeded-timeout/>) and `cancel`. Always use
the callback form or close explicitly, including after an iteration error.
An unreachable stream is eventually retired, but GC does not provide a cleanup
deadline.

[`observation_cursor`](https://libtmux.org/en/julia/latest/reference/libtmux-observation_cursor/) identifies the last consumed event. Replay with
`after=cursor` is limited to the same connection and retained history:
256 records or 4 MiB, shared across event types. Foreign, expired or
topology-invalidated cursors require a new stream and baseline. Retaining a
cursor does not retain history. Closing the connection invalidates replay.

[`notifications`](https://libtmux.org/en/julia/latest/reference/libtmux-notifications/) observes tmux notifications. [`subscribe_format`](https://libtmux.org/en/julia/latest/reference/libtmux-subscribe_format/)
observes sampled field changes and preserves window-link context. tmux's
roughly one-second subscription cadence cannot establish prompt readiness
or output quiescence. Pane movement or removal requires a new subscription.
Use [`format_value`](https://libtmux.org/en/julia/latest/reference/libtmux-format_value/) to decode a format update's escaped bytes locally;
literal tabs, newlines and backslashes are preserved.
