# Coroutines and flows

Source: https://libtmux.org/en/kotlin/latest/guides/coroutines/

> Coroutines and flows: io.github.libtmux:libtmux-kotlin documentation.

## Wrapper classes over the Java handles

[`Server`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server/>), [`Session`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-session/>), [`Window`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-window/>), [`Pane`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane/>), [`Client`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-client/>), and [`ControlClient`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient/>) in
[`io.github.libtmux.kotlin`](<https://libtmux.org/en/kotlin/latest/reference/#io.github.libtmux.kotlin>) are hand-written Kotlin classes, not the Java types
directly and not extensions on them. A same-named extension is silently
shadowed by a Java member with only a compiler warning, so a member on a real
wrapper class is what makes a generated suspend mirror safe to add later
without an accidental collision.

Every operation that may contact tmux is [`suspend`](<https://kotlinlang.org/docs/coroutines-basics.html#suspending-functions>). Captured state — an id, a
name, a size — is a plain, non-suspending property. Query fields live on the
handle type's companion ([`Pane.command`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane-companion-command/>)), never as an instance member, so
[`Pane.command`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane-companion-command/>) (the field) and [`pane.currentCommand`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane-currentcommand/>) (the captured value)
never collide.

A tmux subsystem an accessor answers — [`Pane.options()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane-options/>), [`Server.hooks()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-hooks/>),
[`Server.shell()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-shell/>), [`Server.commands()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-commands/>), [`Server.buffers()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-buffers/>),
[`Session.environment()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-session-environment/>), [`Server.messageLog()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-messagelog/>), [`Server.prompt()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-prompt/>),
[`Server.keys()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-keys/>), [`Server.batch()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-batch/>), [`Server.chain()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-chain/>) — is wrapped the same way:
a small Kotlin class over the Java handle, whose own operations are [`suspend`](<https://kotlinlang.org/docs/coroutines-basics.html#suspending-functions>)
in turn.

```kotlin
// Given: config: ServerConfig
withServer(config) { server ->
    val session = server.newSession("guide-demo")

    session.name                          // → guide-demo
}
```

Each wrapper answers the Java handle it holds as `asJava`, the same object
rather than a copy, for a Java API that takes one. [`Server.fromJava`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-companion-fromjava/>) goes the
other way, over a server opened in Java, such as a test fixture's; closing the
wrapper closes that server. The two sets of classes share simple names, so a
file that needs both imports one under an alias:
`import io.github.libtmux.Server as JavaServer`.

## `ExecutionPolicy`: two independently sized pools

[`ExecutionPolicy.commands`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-commands/>) backs every [`suspend`](<https://kotlinlang.org/docs/coroutines-basics.html#suspending-functions>) operation this module
generates or hand-writes, sized from [`ServerConfig.maxConcurrentCommands()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-serverconfig-serverconfig-maxconcurrentcommands/>) —
the transport's real admission bound, not a guessed constant.
[`ExecutionPolicy.streamReads`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-streamreads/>) backs only what still genuinely blocks a
thread: [`Server.liveState`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-livestate/>)'s background pump. [`ControlClient.output`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-output/>)/[`events`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-events/>)
hold no thread from either pool; they are built on the non-blocking
[`EventSubscription.poll`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-poll/>)/[`onReady`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-onready/>) path, not a parked [`next()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-next/>).

[`ExecutionPolicy.default(config)`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-companion-default/>) sizes both pools; pass a differently-sized
one to [`Server.open`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-companion-open/>) or [`withServer`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-withserver/>) when a program opens more than the
default 16 concurrent [`liveState`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-livestate/>) watches.

## The cold `Flow` bridge, and why it is not `callbackFlow`

[`ControlClient.output`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-output/>)/[`events`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-events/>) return a plain `flow {}` that calls
[`EventSubscription.poll()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-poll/>) from the collector itself, suspending on the
subscription's one-shot [`onReady`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-onready/>) callback via `suspendCancellableCoroutine`
when nothing is buffered, and [`clearReady()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-clearready/>) on cancellation. A fresh Java
subscription opens per [`collect()`](<https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html>), so two concurrent collections never share
one subscription's buffer or gap accounting.

A `callbackFlow` over the same subscription would have its readiness callback
drain [`poll()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-poll/>) straight into the flow's channel, where a full channel's
`trySend` failing discards the polled item with no [`Gap`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-delivery-delivery-gap/>) recorded. `flow {}`
calls [`poll()`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-eventsubscription-eventsubscription-poll/>) only from the collector itself, so an overflow can only happen
inside the subscription's own buffer, which is exactly what turns into a
[`Gap`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-control-delivery-delivery-gap/>).

## Blocking calls from a coroutine

Every libtmux call blocks its thread until tmux answers. This module already
wraps each one in [`runInterruptible`](<https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-interruptible.html>), dispatched on [`ExecutionPolicy.commands`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-commands/>)
or [`.streamReads`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-streamreads/>), so cancelling the coroutine interrupts the wait rather than
leaving it running past the point nothing is listening for its result.

- An ordinary suspend call — [`newSession`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-newsession/>), [`capture`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-pane-capture/>), `sendLine` — already
  runs on `policy.commands`, sized from the server's own admission bound.
- [`Server.liveState`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-server-livestate/>)'s background pump runs on [`policy.streamReads`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-executionpolicy-streamreads/>).
- [`ControlClient.output`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-output/>)/[`events`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-controlclient-events/>) hold no thread at all; only collecting the
  returned [`Flow`](<https://docs.oracle.com/en/java/javase/21/docs/api/java/util/concurrent/Flow.html>) reads.

## The DSL, and the compile error the first draft had

`@DslMarker` marks [`SessionBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-sessionbuilder/>), [`WindowBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-windowbuilder/>), and [`SplitBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder/>) so an
inner block cannot reach an outer block's receiver by accident. Directory is
settable at all three levels for exactly this reason: writing `directory = x`
inside a nested `split { }` resolves to the innermost builder, and reaching
the outer one on purpose needs `this@newSession.directory = x` written out.

```kotlin
// Given: config: ServerConfig
withServer(config) { server ->
    val session = server.newSession {
        name = "layout-demo"
        window { name = "editor" }
        window {
            name = "shell"
            split { toRight(); percent(30) }
        }
    }

    session.windows.size                  // → 2
}
```

tmux's [`SplitSpec.Builder`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-splitspec-splitspec-builder-kfol/>) spells direction and size as method calls —
[`below()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-below/>)/[`above()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-above/>)/[`toRight()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-toright/>)/[`toLeft()`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-toleft/>), [`cells(n)`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-cells/>)/[`percent(n)`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder-percent/>) — not an
enum. The DSL forwards to that real vocabulary directly rather than inventing
a parallel [`SplitDirection`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-splitdirection-splitdirection/>)/[`PaneSize`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-panesize-panesize/>) type.

`env(name, value)` is on [`SessionBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-sessionbuilder/>), [`WindowBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-windowbuilder/>), and [`SplitBuilder`](<https://libtmux.org/en/kotlin/latest/reference/io-github-libtmux-kotlin-splitbuilder/>)
alike, matching the Java [`SessionSpec.Builder`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-sessionspec-sessionspec-builder-3lof/>)/[`WindowSpec.Builder`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-windowspec-windowspec-builder-1f6n/>)/
[`SplitSpec.Builder`](<https://libtmux.org/en/java/latest/reference/io-github-libtmux-splitspec-splitspec-builder-kfol/>) each has, and sets a variable in the environment the new
session, window, or pane's process starts with.

## Why nothing in Java may depend on this

Nothing written in Java may depend on `libtmux-kotlin`, and the build fails if
it does. Per the JSpecify specification a class carrying `@kotlin.Metadata` is
*not* null-marked, because the Kotlin compiler does not yet emit full
nullness into binaries
([KT-47417](https://youtrack.jetbrains.com/projects/KT/issues/KT-47417/Emit-jspecify-annotations-for-types-in-Kotlin-binaries)).
A Kotlin-authored API would therefore be worse for a Java caller and invisible
to NullAway. The dependency runs one way only.

See the [module README](https://libtmux.org/en/kotlin/latest/guides/getting-started/) for the full call-site
tour, including the query DSL, the exhaustive `when` over sealed failures, and
[`StateFlow`](<https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/>).
