# libtmux for Scala > The Scala library (io.github.libtmux:libtmux-scala_3). These pages include its guides, tested examples and API documentation. - [Scala API reference](https://libtmux.org/en/scala/latest/reference/): every public symbol, generated from the source. Hosted on libtmux.org. --- # Concepts Source: https://libtmux.org/en/scala/latest/concepts/ > Understand Scala handles, filters, transports and layout ownership. Use these concepts to reason about Scala handles, selection and command execution. Each page includes complete programs with imports, project files, run commands and cleanup. For individual types and operations, open the [API reference](https://libtmux.org/en/scala/latest/reference/). For a first connection, start with [attaching to tmux](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/). - [Server, session, window, pane](https://libtmux.org/en/scala/latest/concepts/server-session-window-pane/): Navigate captured sessions, windows, and panes, and refresh their state. - [Filtering and queries](https://libtmux.org/en/scala/latest/concepts/queries/): Combine predicates, handle result counts, and match related windows. - [Commands and control mode](https://libtmux.org/en/scala/latest/concepts/transports/): Run bounded commands and manage a persistent control client. - [Layouts and repeated setup](https://libtmux.org/en/scala/latest/concepts/workspaces/): Create a split window and reuse a named window safely. --- # Server, session, window, pane Source: https://libtmux.org/en/scala/latest/concepts/server-session-window-pane/ > Traverse captured Scala handles, retain IDs and refresh after changes. Capture a hierarchy, then read its sessions, windows and panes locally. Handles retain the state they captured; a rename does not rewrite an older handle. Use a new capture to observe later changes. [`Server`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-server/) selects the socket and owns client resources. [`Session`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-session/), [`Window`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-window/) and [`Pane`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-pane/) hold captured state and send mutations through that server. The session list captures the hierarchy; walking its children reads that capture. A session holds window placements; a window holds panes. A linked window can appear in several sessions, so traversal counts placements rather than necessarily distinct physical windows. Names can change; retain IDs when identifying a target. ## Setup and run Use an empty directory on Linux with Git, tmux 3.2a or newer, and JDK 25. Save the project files and launcher below, then save any complete program on this page. Each program has its own imports and entry point. The pinned source checkout supplies the Gradle wrapper and the library dependency. ```kotlin title="settings.gradle.kts" rootProject.name = "connect" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` ```kotlin title="build.gradle.kts" val example = providers.gradleProperty("example").getOrElse("Local") plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("${example}.scala") } application { mainClass.set(example) } ``` ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` The launcher creates two sessions on a private socket: `work-one` with an `editor` window, and `work-two` with a `logs` window. Each pane runs `cat` so it stays alive. It stops only this server when the program finishes or fails. If shutdown fails, it reports the retained directory and exits with an error. ```sh title="run.sh" #!/bin/sh set -eu binary=$(command -v tmux) mkdir -p /tmp/libtmux-java-dev directory=$(mktemp -d /tmp/libtmux-java-dev/query.XXXXXX) socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" "$binary" -S "$socket" -f /dev/null new-session -d -s work-one -n editor /bin/cat "$binary" -S "$socket" new-session -d -s work-two -n logs /bin/cat "$@" "$binary" -S "$socket" has-session -t '=work-one' ``` Fetch the library revision used by these examples: ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout be1d62fbaa1aa634c687e1c50bceeb09ef0b8a15 ``` Each run starts from the same two-session fixture. Programs do not depend on another example having run first. Their assertions fail if the observed result differs. ## Walk sessions, windows and panes Flatten the captured children and verify the fixture contains two of each. The program also prints each session and its window. ```scala title="Hierarchy.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Hierarchy { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val sessions = server.sessions() val windows = sessions.flatMap(_.windows) val panes = windows.flatMap(_.panes) assert(sessions.size == 2 && windows.size == 2 && panes.size == 2) for (session <- sessions) { println(s"${session.name}: ${session.windows.head.name}") } println("2 sessions, 2 windows, 2 panes") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Hierarchy --console=plain --max-workers=2 ``` Expected program output: ```text work-one: editor work-two: logs 2 sessions, 2 windows, 2 panes ``` ## Observe a rename with a fresh read Rename the editor window. The old handle still says `editor`; the returned handle and a new server read say `renamed`. This distinction matters when a UI, another client or your own code changes tmux after a capture. ```scala title="Refresh.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Refresh { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val session = server.sessions().find(_.name == "work-one").get val before = session.windows.head val after = before.rename("renamed") assert(before.name == "editor") assert(after.name == "renamed") val readAgain = server.sessions().find(_.name == "work-one").get.windows.head assert(readAgain.name == "renamed") println(s"${before.name} -> ${readAgain.name}") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Refresh --console=plain --max-workers=2 ``` Expected program output: ```text editor -> renamed ``` ## Select a target before changing it A successful lookup does not reserve a tmux object. Another client can remove it before your next command. Handle a command failure at the mutation, and refresh before deciding what to do next. See [filtering and queries](https://libtmux.org/en/scala/latest/concepts/queries/) for missing and ambiguous selections, and [layouts](https://libtmux.org/en/scala/latest/concepts/workspaces/) for creation. --- # Filtering and queries Source: https://libtmux.org/en/scala/latest/concepts/queries/ > Compose Scala filters, distinguish zero and several matches, and query captured relations. Filter captured objects in Scala, handle result counts explicitly, and match sessions by their related windows. Local filters read captured values; they do not subscribe to changes or issue a fresh tmux query. [`Expr`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-query-expr-zfkn/) supports `&&`, `||` and `!`. The field companions expose equality, prefix, suffix, substring and pattern matching. Use [`.matching(expression)`]() for a typed query or `.filter(predicate)` for an ordinary Scala condition. ## Setup and run Use an empty directory on Linux with Git, tmux 3.2a or newer, and JDK 25. Save the project files and launcher below, then save any complete program on this page. Each program has its own imports and entry point. The pinned source checkout supplies the Gradle wrapper and the library dependency. ```kotlin title="settings.gradle.kts" rootProject.name = "connect" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` ```kotlin title="build.gradle.kts" val example = providers.gradleProperty("example").getOrElse("Local") plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("${example}.scala") } application { mainClass.set(example) } ``` ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` The launcher creates two sessions on a private socket: `work-one` with an `editor` window, and `work-two` with a `logs` window. Each pane runs `cat` so it stays alive. It stops only this server when the program finishes or fails. If shutdown fails, it reports the retained directory and exits with an error. ```sh title="run.sh" #!/bin/sh set -eu binary=$(command -v tmux) mkdir -p /tmp/libtmux-java-dev directory=$(mktemp -d /tmp/libtmux-java-dev/query.XXXXXX) socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" "$binary" -S "$socket" -f /dev/null new-session -d -s work-one -n editor /bin/cat "$binary" -S "$socket" new-session -d -s work-two -n logs /bin/cat "$@" "$binary" -S "$socket" has-session -t '=work-one' ``` Fetch the library revision used by these examples: ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout be1d62fbaa1aa634c687e1c50bceeb09ef0b8a15 ``` Each run starts from the same two-session fixture. Programs do not depend on another example having run first. Their assertions fail if the observed result differs. ## Match names and combine conditions Select the two names beginning with `work-`, then assert equality, AND, OR and exclusion against the same captured collection. Matching is case sensitive. The native collection predicate agrees with the typed prefix query. ```scala title="Local.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Local { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val sessions = server.sessions() val matching = sessions.matching(Session.name.startsWith("work-")) val names = matching.map(_.name).sorted assert(names == Vector("work-one", "work-two")) println(names.mkString(", ")) // Filtering the captured vector makes no new tmux calls. assert(sessions.filter(_.name.startsWith("work-")) == matching) val onlyOne = Session.name.startsWith("work-") && Session.name.endsWith("one") assert(sessions.matching(onlyOne).head.name == "work-one") val either = Session.name.is("work-one") || Session.name.is("work-two") assert(sessions.matching(either).size == 2) assert(sessions.matching(!Session.name.is("work-one")).head.name == "work-two") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Local --console=plain --max-workers=2 ``` Expected program output: ```text work-one, work-two ``` ## Distinguish missing and ambiguous results An exactly-one selection returns an [`Either`](): one handle on success, [`CardinalityError.NoMatch`]() for zero, or [`MultipleMatches`]() for several. An at-most-one selection also rejects ambiguity; it does not pick an arbitrary first result. ```scala title="Cardinality.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Cardinality { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val sessions = server.sessions() for (name <- Vector("work-one", "missing")) { sessions.matching(Session.name.is(name)).exactlyOne match { case Right(session) => println(s"${session.name}: selected") case Left(CardinalityError.NoMatch) => println(s"$name: absent") case Left(CardinalityError.MultipleMatches(count)) => throw new IllegalStateException(s"At least $count sessions named $name") } } val many = sessions.matching(Session.name.startsWith("work-")).atMostOne assert(many == Left(CardinalityError.MultipleMatches(2))) println("work-: ambiguous") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Cardinality --console=plain --max-workers=2 ``` Expected program output: ```text work-one: selected missing: absent work-: ambiguous ``` ## Filter through related windows Match sessions with any window named `editor`, then match sessions with none. For an empty captured relation, [`any`]() is false and [`none`]() is true; `all` is true. An uncaptured relation is a different condition and must be captured before filtering. ```scala title="Relations.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Relations { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val sessions = server.sessions() val editorWindow = Window.name.is("editor") val withEditor = sessions.matching(Session.windows.any(editorWindow)) val withoutEditor = sessions.matching(Session.windows.none(editorWindow)) assert(withEditor.map(_.name) == Vector("work-one")) assert(withoutEditor.map(_.name) == Vector("work-two")) println(s"editor: ${withEditor.head.name}") println(s"no editor: ${withoutEditor.head.name}") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Relations --console=plain --max-workers=2 ``` Expected program output: ```text editor: work-one no editor: work-two ``` ## Refresh before repeating a decision Reusing the same list repeats the same decision over old state. Capture again when you need to observe a rename, new window or closed pane. The [snapshot example](https://libtmux.org/en/scala/latest/concepts/server-session-window-pane/#observe-a-rename-with-a-fresh-read) shows the old and fresh values side by side. --- # Commands and control mode Source: https://libtmux.org/en/scala/latest/concepts/transports/ > Use Scala command calls and persistent control connections with explicit cleanup. Choose command calls for bounded operations and a control connection when you need a persistent tmux client. Closing a client releases its resources; it does not mean the tmux server should be stopped. The direct [`Server`](https://libtmux.org/en/scala/latest/reference/io-github-libtmux-scaladsl-server/) API blocks until the operation completes. [`Using.resource`]() closes its client resources. For effectful applications, the [execution guide](https://libtmux.org/en/scala/latest/guides/execution/) covers the Cats Effect and Ox packages. ## Setup and run Use an empty directory on Linux with Git, tmux 3.2a or newer, and JDK 25. Save the project files and launcher below, then save any complete program on this page. Each program has its own imports and entry point. The pinned source checkout supplies the Gradle wrapper and the library dependency. ```kotlin title="settings.gradle.kts" rootProject.name = "connect" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` ```kotlin title="build.gradle.kts" val example = providers.gradleProperty("example").getOrElse("Local") plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("${example}.scala") } application { mainClass.set(example) } ``` ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` The launcher creates two sessions on a private socket: `work-one` with an `editor` window, and `work-two` with a `logs` window. Each pane runs `cat` so it stays alive. It stops only this server when the program finishes or fails. If shutdown fails, it reports the retained directory and exits with an error. ```sh title="run.sh" #!/bin/sh set -eu binary=$(command -v tmux) mkdir -p /tmp/libtmux-java-dev directory=$(mktemp -d /tmp/libtmux-java-dev/query.XXXXXX) socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" "$binary" -S "$socket" -f /dev/null new-session -d -s work-one -n editor /bin/cat "$binary" -S "$socket" new-session -d -s work-two -n logs /bin/cat "$@" "$binary" -S "$socket" has-session -t '=work-one' ``` Fetch the library revision used by these examples: ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout be1d62fbaa1aa634c687e1c50bceeb09ef0b8a15 ``` Each run starts from the same two-session fixture. Programs do not depend on another example having run first. Their assertions fail if the observed result differs. ## Run a bounded command and inspect its result Read the session list and filter it locally. The connection uses a five-second timeout so an unavailable server fails visibly. ```scala title="Local.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Local { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val sessions = server.sessions() val matching = sessions.matching(Session.name.startsWith("work-")) val names = matching.map(_.name).sorted assert(names == Vector("work-one", "work-two")) println(names.mkString(", ")) // Filtering the captured vector makes no new tmux calls. assert(sessions.filter(_.name.startsWith("work-")) == matching) val onlyOne = Session.name.startsWith("work-") && Session.name.endsWith("one") assert(sessions.matching(onlyOne).head.name == "work-one") val either = Session.name.is("work-one") || Session.name.is("work-two") assert(sessions.matching(either).size == 2) assert(sessions.matching(!Session.name.is("work-one")).head.name == "work-two") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Local --console=plain --max-workers=2 ``` Expected program output: ```text work-one, work-two ``` ## Open and close a control client Send `list-sessions` over a persistent control connection, verify the response, then close the control client. A subsequent ordinary read verifies the tmux server still has both sessions. ```scala title="Control.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Control { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val session = server.sessions().find(_.name == "work-one").get Using.resource(server.control(session)) { control => val reply = control.send("list-sessions", "-F", "#{session_name}") require(reply.succeeded(), s"tmux rejected list-sessions: ${reply.outcome()}") val names = reply.lines().asScala.toVector.sorted assert(names == Vector("work-one", "work-two")) println(names.mkString(", ")) } assert(server.sessions().size == 2) println("control client closed; server still running") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Control --console=plain --max-workers=2 ``` Expected program output: ```text work-one, work-two control client closed; server still running ``` ## Timeouts and ownership An operation that times out may already have reached tmux. Read the current state before retrying a mutation such as creating a window. A cancellation signal does not roll back a completed tmux command. Here the launcher owns the private server and stops it after the program exits. An application connecting to an existing server should close its own client resources and leave that server running. See [attaching to tmux](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/) for the connection-only example. --- # Layouts and repeated setup Source: https://libtmux.org/en/scala/latest/concepts/workspaces/ > Create and reuse Scala tmux layouts while preserving running processes. Build a tmux layout from Scala by creating a window, splitting a pane and selecting a layout. Capture again to inspect the result. The examples use the library APIs directly and give every pane a command that stays alive. ## Setup and run Use an empty directory on Linux with Git, tmux 3.2a or newer, and JDK 25. Save the project files and launcher below, then save any complete program on this page. Each program has its own imports and entry point. The pinned source checkout supplies the Gradle wrapper and the library dependency. ```kotlin title="settings.gradle.kts" rootProject.name = "connect" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` ```kotlin title="build.gradle.kts" val example = providers.gradleProperty("example").getOrElse("Local") plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("${example}.scala") } application { mainClass.set(example) } ``` ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` The launcher creates two sessions on a private socket: `work-one` with an `editor` window, and `work-two` with a `logs` window. Each pane runs `cat` so it stays alive. It stops only this server when the program finishes or fails. If shutdown fails, it reports the retained directory and exits with an error. ```sh title="run.sh" #!/bin/sh set -eu binary=$(command -v tmux) mkdir -p /tmp/libtmux-java-dev directory=$(mktemp -d /tmp/libtmux-java-dev/query.XXXXXX) socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" "$binary" -S "$socket" -f /dev/null new-session -d -s work-one -n editor /bin/cat "$binary" -S "$socket" new-session -d -s work-two -n logs /bin/cat "$@" "$binary" -S "$socket" has-session -t '=work-one' ``` Fetch the library revision used by these examples: ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout be1d62fbaa1aa634c687e1c50bceeb09ef0b8a15 ``` Each run starts from the same two-session fixture. Programs do not depend on another example having run first. Their assertions fail if the observed result differs. ## Create a two-pane tools window Create `tools` in `work-one`, split its pane to the right, then choose `even-horizontal`. The final read verifies two panes. The previously captured session does not automatically gain the new window. ```scala title="Layout.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object Layout { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val session = server.sessions().find(_.name == "work-one").get val window = session.newWindow(io.github.libtmux.WindowSpec.builder() .named("tools").running("/bin/cat").build()) window.panes.head.split(io.github.libtmux.SplitSpec.builder() .toRight().running("/bin/cat").build()) window.selectLayout(io.github.libtmux.Layout.EVEN_HORIZONTAL) val refreshed = server.sessions().find(_.name == "work-one").get val tools = refreshed.windows.find(_.name == "tools").get assert(tools.panes.size == 2) println("tools: 2 panes") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=Layout --console=plain --max-workers=2 ``` Expected program output: ```text tools: 2 panes ``` ## Reuse a named window Look for `tools` before creating it. Two sequential calls return the same window ID, and the session has only `editor` and `tools`. This is useful for a setup command you run repeatedly. ```scala title="ReuseLayout.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import io.github.libtmux.scaladsl.query.* import java.nio.file.Path import java.time.Duration import scala.util.Using import scala.jdk.CollectionConverters.* object ReuseLayout { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => def ensureTools(): Window = { val session = server.sessions().find(_.name == "work-one").get session.windows.find(_.name == "tools").getOrElse { session.newWindow(io.github.libtmux.WindowSpec.builder() .named("tools").running("/bin/cat").build()) } } val first = ensureTools() val second = ensureTools() assert(first.id == second.id) assert(server.sessions().find(_.name == "work-one").get.windows.size == 2) println("one tools window after two calls") } } } ``` ```console $ sh run.sh ./libtmux-source/gradlew --project-dir . run \ -Pexample=ReuseLayout --console=plain --max-workers=2 ``` Expected program output: ```text one tools window after two calls ``` ## Decide what repeated setup means The reuse example preserves the existing window and its running processes. It does not reset its pane count, layout or commands. Choose that policy deliberately when building a reusable workspace command. This check-then-create sequence assumes one writer. Concurrent callers can both observe an absent window and create duplicates. Serialize setup in your application when several callers share a session. A later failure also leaves earlier successful mutations in place; tmux commands are not a transaction. Use [filtering and queries](https://libtmux.org/en/scala/latest/concepts/queries/) for stricter target selection and [captured handles](https://libtmux.org/en/scala/latest/concepts/server-session-window-pane/) to understand refresh behavior. --- # Examples Source: https://libtmux.org/en/scala/latest/examples/ > Tested Scala examples against an isolated tmux server. Start with [Capture pane output](https://libtmux.org/en/scala/latest/examples/capture-pane-output/) for a standalone program with imports, Gradle files, and private-server cleanup. For an existing server, use [Attaching to tmux](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/). That program includes the imports, build files, and launcher. It reads a session without stopping the server that owns it. --- # Capture pane output Source: https://libtmux.org/en/scala/latest/examples/capture-pane-output/ > Run a complete Scala program that captures output on an isolated tmux server. This complete Scala program starts a private tmux server, sends a command, and captures the line it prints. It includes imports, setup, and cleanup. You need tmux and a Unix environment; no existing session is required. ## Read what's on screen The leading newline puts the output on a fresh row. Matching the whole line avoids mistaking the echoed command for its output. ```scala title="Capture.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint, SessionSpec} import io.github.libtmux.scaladsl.* import java.nio.file.{Files, Path} import java.time.Duration import scala.util.Using object Capture { def main(args: Array[String]): Unit = { val root = Files.createDirectories(Path.of("/tmp/libtmux-java-dev")) val directory = Files.createTempDirectory(root, "capture-") val socket = directory.resolve("tmux.sock") val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(socket)) .configFile(Path.of("/dev/null")) .defaultTimeout(Duration.ofSeconds(1)) .build() var failure: Option[Throwable] = None try { Using.resource(Server.open(config)) { server => try { val session = server.newSession(SessionSpec.builder() .named("capture").running("/bin/sh").env("ENV", "/dev/null").build()) val pane = session.windows.head.panes.head pane.sendLine("printf '\\nlibtmux capture ready\\n'") val deadline = System.nanoTime() + Duration.ofSeconds(5).toNanos var captured = false while (!captured && System.nanoTime() < deadline) { captured = pane.capture().contains("libtmux capture ready") if (!captured) Thread.sleep(25) } if (!captured) throw new IllegalStateException("Output did not arrive within five seconds") println("libtmux capture ready") } catch { case error: Throwable => failure = Some(error) throw error } finally { try { if (Files.exists(socket)) server.killServer() Files.deleteIfExists(socket) Files.delete(directory) } catch { case cleanup: Throwable => failure match { case Some(original) => original.addSuppressed(cleanup) case None => throw cleanup } } } } } finally { // Opening a client can fail before the cleanup block is entered. if (!Files.exists(socket)) Files.deleteIfExists(directory) } } } ``` ## Wait for output or completion The Scala facade uses blocking calls. The loop checks complete captured lines against a monotonic deadline. [`Using.resource`](https://www.scala-lang.org/api/3.x/scala/util/Using$.html) closes the client; the `finally` block also stops the tmux server this program created. Cleanup errors stay visible, and a server that cannot be stopped keeps its socket. Capture reads screen state and scrollback, so output that has scrolled away may be absent. The program prints `libtmux capture ready` when its check passes and exits unsuccessfully if an operation fails. ## Setup and run Use an empty directory and save the files using the displayed names. You need JDK 25. The pinned repository supplies Gradle; the project files select Scala 3.9.0 and include the library build. Save `settings.gradle.kts` beside `Capture.scala`. ```kotlin title="settings.gradle.kts" rootProject.name = "capture" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` Save `build.gradle.kts` beside `Capture.scala`. ```kotlin title="build.gradle.kts" plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("Capture.scala") } application { mainClass.set("Capture") } ``` Save `gradle.properties` beside `Capture.scala`. ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` The commands pin the library revision used to run this program. ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 && ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2 ``` ## Where this comes from The displayed files were compiled or loaded with their native tools and run on Linux with tmux 3.2a and 3.7c. The rendering checks preserve those file bytes. --- # Guides Source: https://libtmux.org/en/scala/latest/guides/ > Guides for io.github.libtmux:libtmux-scala_3. Connect to tmux and work with its sessions, windows, and panes. - [Getting started](https://libtmux.org/en/scala/latest/guides/getting-started/): Read about getting started. - [Attaching to tmux](https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/): Connect to an existing socket and leave its server running. - [Installation and requirements](https://libtmux.org/en/scala/latest/guides/overview/): Read about libtmux-scala. - [Queries](https://libtmux.org/en/scala/latest/guides/query/): Read about queries. - [Ownership](https://libtmux.org/en/scala/latest/guides/ownership/): Read about ownership. - [Execution](https://libtmux.org/en/scala/latest/guides/execution/): Read about execution. - [Streaming](https://libtmux.org/en/scala/latest/guides/streaming/): Read about streaming. - [Compatibility](https://libtmux.org/en/scala/latest/guides/compatibility/): Read about compatibility. --- # Getting started Source: https://libtmux.org/en/scala/latest/guides/getting-started/ > Getting started: io.github.libtmux:libtmux-scala_3 documentation. The Scala facades need Scala 3.9 and JDK 25 or newer, and tmux 3.2a through 3.7c on the machine they drive. ## Install Three artifacts, all in group `io.github.libtmux` and all at the same version as `libtmux` itself. `%%` adds the `_3` suffix that marks a Scala 3 artifact. They are on Maven Central and release with the Java artifacts. - **`libtmux-scala_3`** — direct-style handles, collections and the typed query DSL. - **`libtmux-scala-cats_3`** — Cats Effect resources and FS2 observations. - **`libtmux-scala-ox_3`** — an Ox [`Flow`]() over subscriptions and live views. ```sbt libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "" ``` For Cats Effect and FS2, or for Ox, add the matching module: ```sbt libraryDependencies += "io.github.libtmux" %% "libtmux-scala-cats" % "" ``` ```sbt libraryDependencies += "io.github.libtmux" %% "libtmux-scala-ox" % "" ``` From Gradle or Maven, name the suffixed artifact directly: `io.github.libtmux:libtmux-scala_3:`. The core facade depends on `libtmux` and the Scala 3 library, and on nothing else. ## A first client The function below takes three caller-supplied values and constructs a Java [`ServerConfig`]() before opening the Scala client: | Parameter | Value to supply | | --- | --- | | `binary` | Absolute path to your tmux executable | | `socket` | An isolated socket you own under `/tmp/libtmux-java-dev/` | | `configFile` | Your tmux configuration file, or `/dev/null` for none | For example, choose `/tmp/libtmux-java-dev/scala-start/socket`; the function creates its parent directory. Opening the client does not create tmux; [`newSession`]() does. The operation creates a session, splits its window, verifies the resulting panes and kills that session. Closing the client is separate from session cleanup. An existing server's other sessions remain running; tmux normally exits when its final session closes. Call `firstClient` with your three values from your application. The final call in this tested snippet takes those inputs from the owned test fixture's `config`; the function builds and uses its own configuration. ```scala import io.github.libtmux.{ Layout, ServerConfig, ServerEndpoint, SessionSpec, SplitSpec } import io.github.libtmux.scaladsl.{config => _, *} import java.nio.file.{Files, Path} import java.time.Duration import scala.util.Using def firstClient(binary: String, socket: Path, configFile: Path): Unit = { require(Path.of(binary).isAbsolute, "supply an absolute tmux executable") val ownedSocket = socket.toAbsolutePath.normalize() require( ownedSocket.startsWith(Path.of("/tmp/libtmux-java-dev")) || ownedSocket.startsWith(Path.of("/tmp/libtmux-java-test")), "choose a socket under an owned libtmux Java directory" ) Files.createDirectories(ownedSocket.getParent) val selected = ServerConfig.builder() .binary(binary) .endpoint(ServerEndpoint.socketPath(ownedSocket)) .configFile(configFile) .defaultTimeout(Duration.ofMillis(800)) .build() Using.resource(Server.open(selected)) { server => val session = server.newSession( SessionSpec.builder().named("scala-start").running("cat", "-").build() ) try { val window = session.windows.head val second = window.split( SplitSpec.builder().running("cat", "-").build() ) window.selectLayout(Layout.EVEN_HORIZONTAL) second.select() // window.panes is CAPTURED: it answers from window's own frozen capture, taken before the // split, so this refreshes first rather than reading stale data. val panes = window.refresh().panes assert(panes.size == 2) assert(panes.exists(_.info.id().value() == second.info.id().value())) } finally session.kill() } } config.endpoint() match { case endpoint: ServerEndpoint.SocketPath => firstClient( config.binaryPath(), endpoint.path(), config.configFile().orElse(Path.of("/dev/null")) ) case _ => throw new IllegalArgumentException("an explicit socket is required") } ``` Timeouts are [`scala.concurrent.duration.FiniteDuration`]() at every public entry point, converted once at the boundary: `pane.awaitText("$", 5.seconds)` never surfaces [`java.time.Duration`]() to the caller. Follow with [queries](https://libtmux.org/en/scala/latest/guides/query/), [ownership](https://libtmux.org/en/scala/latest/guides/ownership/), then [execution](https://libtmux.org/en/scala/latest/guides/execution/). For immediate access without the facade, use the [direct Java guide](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/docs/guide/scala.md). ## Build from source The facades build with the rest of the repository, from its root: ```console $ ./gradlew :libtmux-scala:check :libtmux-scala-cats:check :libtmux-scala-ox:check ``` The real-tmux suites run with the Java ones in `integration-tests`: ```console $ ./gradlew :integration-tests:test --tests 'io.github.libtmux.scaladsl.*' ``` Format Scala sources before committing: ```console $ ./gradlew spotlessApply ``` The API documentation, with a browser for every source it documents, generated operations included, starts at `libtmux-scala/build/docs/scaladoc/index.html`: ```console $ ./gradlew :libtmux-scala:scaladocSite ``` Begin with [direct-style `Server`][server] or [Cats `Server`][cats-server]. [server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala/src/main/scala/io/github/libtmux/scaladsl/Server.scala [cats-server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Server.scala --- # Attaching to tmux Source: https://libtmux.org/en/scala/latest/guides/attaching-to-tmux/ > Connect to an existing tmux server and find a session with Scala. Connect a [`Server`]() to an explicit socket and find the existing `work` session. The program prints its name and leaves the tmux server running. It reports an error if the connection fails or the session is absent. This controls tmux from your program. To open a session in your terminal, use `tmux attach-session`; the [shared guide](https://libtmux.org/en/tmux/guides/attaching-to-tmux/) covers interactive attachment and detaching. ## Connect to an existing server Save the complete program as `Connect.scala`. `LIBTMUX_SOCKET_PATH` selects the existing server. The launcher below supplies a private socket for trying the example. ```scala title="Connect.scala" import io.github.libtmux.{ServerConfig, ServerEndpoint} import io.github.libtmux.scaladsl.* import java.nio.file.Path import java.time.Duration import scala.util.Using object Connect { def main(args: Array[String]): Unit = { val socket = sys.env.getOrElse("LIBTMUX_SOCKET_PATH", throw new IllegalArgumentException("Set LIBTMUX_SOCKET_PATH to an existing socket")) val config = ServerConfig.builder() .endpoint(ServerEndpoint.socketPath(Path.of(socket))) .defaultTimeout(Duration.ofSeconds(5)) .build() Using.resource(Server.open(config)) { server => val session = server.sessions().find(_.name == "work").getOrElse( throw new IllegalStateException("The work session does not exist")) println(session.name) } } } ``` ## Setup and run Use an empty directory on Linux with Git and tmux 3.2a or newer installed. This example was checked with JDK 25.0.3, Scala 3.9.0. Save this file beside the program using the displayed filename. ```kotlin title="build.gradle.kts" plugins { application scala } repositories { mavenCentral() } dependencies { implementation("org.scala-lang:scala3-library_3:3.9.0") implementation("io.github.libtmux:libtmux-scala_3:0.0.1-alpha.17-SNAPSHOT") } java { toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } } sourceSets.main { scala.srcDir("."); scala.include("Connect.scala") } application { mainClass.set("Connect") } ``` Save this file beside the program using the displayed filename. ```kotlin title="settings.gradle.kts" rootProject.name = "connect" includeBuild("libtmux-source") { dependencySubstitution { substitute(module("io.github.libtmux:libtmux-scala_3")) .using(project(":libtmux-scala")) } } ``` Save this file beside the program using the displayed filename. ```properties title="gradle.properties" org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx2g org.gradle.workers.max=2 ``` Save the launcher as `run.sh`. It starts an isolated tmux server, runs the program, checks that the session still exists, then stops only that server. Cleanup runs after failures too. A failed shutdown keeps its socket directory and prints its location for inspection. ```sh title="run.sh" #!/bin/sh set -eu binary=$(command -v tmux) directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-scala-attach.XXXXXX") socket="$directory/tmux.sock" cleanup() { status=$? trap - 0 HUP INT TERM if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then printf 'Cannot stop tmux; kept %s\n' "$directory" >&2 exit 1 fi rm -rf "$directory" || exit 1 exit "$status" } trap cleanup 0 trap 'exit 1' HUP INT TERM unset TMUX TMUX_PANE export LIBTMUX_SOCKET_PATH="$socket" TMUX_BIN="$binary" "$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat "$@" "$binary" -S "$socket" has-session -t '=work' ``` Fetch the verified library revision, build, and run: ```console $ git clone https://github.com/libtmux/libtmux-java libtmux-source && git -C libtmux-source checkout 85ebf6955e56703c5be74e2afd34a18309044741 && sh run.sh ./libtmux-source/gradlew --project-dir . run --console=plain --max-workers=2 ``` The program prints `work`. To use an existing server of your own, set `LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher. That launcher is responsible for the demonstration server's lifetime. ## Find or create a session The example only looks up a session. If your application creates a session after an unsuccessful lookup, another client may create the same name between those operations. Handle the creation error instead of assuming the lookup reserves the name. For a complete program that starts and owns its server, see [Capture pane output](https://libtmux.org/en/tmux/examples/capture-pane-output/). --- # Installation and requirements Source: https://libtmux.org/en/scala/latest/guides/overview/ > Installation and requirements: io.github.libtmux:libtmux-scala_3 documentation. **Scala 3 collections and opaque handles over libtmux for Java.** Use Scala collections and explicit effects to inspect and operate tmux through libtmux for Java. The direct-style facade's handles are opaque aliases of the Java ones — lossless by construction, never a hand-copied parallel type — and return immutable [`Vector`]() and [`Option`]() values. The [separate Cats module](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/README.md) supplies scoped effects and FS2 observations. Both retain the original Java handles, targeting rules and command engine. **This project is alpha.** Releases carry an `-alpha` prerelease tag. The API is not settled, and any release may change or remove exported identifiers without a deprecation period. Pin an exact version rather than a range. Not recommended for production. ## Requirements Scala 3.9 and JDK 25 or newer; there is no Scala 2.13 build. tmux 3.2a through 3.7c, which the Java library supports and every tmux lane in CI runs these suites against. See [Compatibility](https://libtmux.org/en/scala/latest/guides/compatibility/). ## Installation ```sbt libraryDependencies += "io.github.libtmux" %% "libtmux-scala" % "" ``` From Gradle or Maven the coordinate is `io.github.libtmux:libtmux-scala_3`. The version is always `libtmux`'s own: the Scala artifacts are on Maven Central and release with the Java ones. Core depends on `libtmux` and the Scala 3 library only; Cats, FS2 and Ox arrive through [`libtmux-scala-cats`](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/) and [`libtmux-scala-ox`](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-ox/). ## Inspect captured panes Start with [a first client](https://libtmux.org/en/scala/latest/guides/getting-started/#a-first-client) to construct [`ServerConfig`]() from your tmux executable, owned socket and configuration file. Here `config` selects an existing server. Opening the client does not create a session. This operation closes its owned client while leaving the daemon running. ```scala import io.github.libtmux.scaladsl.{config => _, *} import scala.util.Using Using.resource(Server.open(config)) { server => val panes = server.panes() val named = panes.filter(_.info.title().nonEmpty) val commands = named.map(_.info.currentCommand()) assert(commands.size <= panes.size) assert(panes.forall(p => p.window.info.context().equals(p.info.context()))) } ``` [`server.panes()`]() acquires state. Reading `info`, traversing `window`, or filtering the captured vector performs no further tmux commands. `refresh()` returns a new capture. A failed acquisition raises the original Java error; it does not become an empty vector. ## Documentation - [Getting started](https://libtmux.org/en/scala/latest/guides/getting-started/): installation, a first owned client, and building from source. - [Queries](https://libtmux.org/en/scala/latest/guides/query/): native collections, the typed field DSL and strict cardinality. - [Ownership](https://libtmux.org/en/scala/latest/guides/ownership/): captured handles, borrowing and cleanup. - [Execution](https://libtmux.org/en/scala/latest/guides/execution/): direct-style calls, bounded effects and command outcomes. - [Streaming](https://libtmux.org/en/scala/latest/guides/streaming/): subscriptions, cancellation and visible loss. - [Compatibility](https://libtmux.org/en/scala/latest/guides/compatibility/): compilers, runtimes and what CI runs. - [Examples](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/examples/): runnable programs, each executed against a real tmux. The [Java guide](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/docs/guide/scala.md) shows direct Java use without the facade. Source contracts live beside [direct-style operations][server] and [Cats resources][cats-server]. For changes to existing APIs, see the repository's [migration notes](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/MIGRATION.md). [server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala/src/main/scala/io/github/libtmux/scaladsl/Server.scala [cats-server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Server.scala --- # Queries Source: https://libtmux.org/en/scala/latest/guides/query/ > Queries: io.github.libtmux:libtmux-scala_3 documentation. Acquire once, then use Scala collections. A handle's captured fields (`.info`, returning the real Java [`PaneState`]()/[`WindowState`]()/[`SessionState`]()/[`ClientState`]() record) describe state as of that capture. A `filter`, `find`, `collect`, sort or comprehension over a [`Vector`]() of handles does not acquire another snapshot. A custom predicate can still perform whatever work its author puts inside it. ## Native predicates `config` identifies the server. Keep the capture while deriving several views of the same data. A view defers local computation; it does not refresh tmux. ```scala import io.github.libtmux.scaladsl.{config => _, *} import scala.util.Using Using.resource(Server.open(config)) { server => val panes = server.panes() val named = panes.filter(_.info.title().nonEmpty) val paths = panes.collect { case pane if pane.info.pid().isPresent => pane.info.currentPath() } val firstActive = panes.find(_.info.active()) val deferred = panes.view.filter(_.info.title().nonEmpty) assert(deferred.toVector == named) assert(paths.size <= panes.size) assert(firstActive.forall(_.info.active())) } ``` Missing metadata answers through the record's own Java accessor ([`Optional`](), [`OptionalLong`]()): a missing PID is different from PID zero, and unavailable floating-pane metadata is different from [`Optional.of(false)`](). ## The typed field DSL and strict cardinality Fields hang on the handle type's own companion ([`Pane.command`](), [`Pane.active`](), ...), generated one-line forwards to Java's own field metamodel ([`Pane_.command()`]()). [`.matching`]() filters a captured [`Vector`]() locally; `&&`, `||` and `!` compose expressions, alongside their named forms [`.and`](), [`.or`]() and [`.not`](). [`.exactlyOne`]()/[`.atMostOne`]() give the strict cardinality `headOption` and `find` do not: both return `Either[CardinalityError, ...]`, mirroring Java's own [`CardinalityException`]() leaves losslessly, including [`MultipleMatches`]()'s [`atLeast`]() count. ```scala import io.github.libtmux.scaladsl.query.* assert(Vector.empty[Int].atMostOne == Right(None)) assert(Vector("selected").exactlyOne == Right("selected")) val ambiguous = Vector(1, 2).atMostOne assert(ambiguous == Left(CardinalityError.MultipleMatches(2))) ``` ## Symbolic and named operators ```scala import io.github.libtmux.scaladsl.{config => _, *} import io.github.libtmux.scaladsl.query._ import scala.util.Using Using.resource(Server.open(config)) { server => val panes = server.panes() val expression = Pane.command.is("cat") && Pane.width.atLeast(1) val selected = panes.matching(expression) val native = panes.filter(p => p.info.currentCommand() == "cat" && p.info.size().width() >= 1) assert(selected == native) } ``` The Cats module reads the same expressions over a captured [`Vector`](), inside the effect's own `map`: `server.panes.map(_.filter(...))` (`Vector[Pane[F]]` does not itself carry [`.matching`]() — apply the predicate to each pane's own `.info` field instead, or to `.underlying.asJava` for a Java expression). This is still local evaluation over a captured vector; it does not run a Java command. An invalid field/operator combination must fail at compilation: ```scala import io.github.libtmux.scaladsl.Pane Pane.active.contains("yes") ``` ## Relationships and identity Java's [`Session_`](), [`Window_`](), [`Pane_`]() and [`Client_`]() expose the canonical fields; the Scala field companions are one-line forwards to them, never a second, hand-copied metamodel. Use their existing [`any`](), `all`, [`none`]() and to-one `is` relations, exposed through [`Fields.ToManyRef`]()/[`Fields.ToOneRef`](). `all` over an empty relation is true. Applying [`any`]() to a conjunction requires one related object satisfying both conditions; conjoining two separate [`any`]() expressions allows two different related objects. Window equality includes its session and index placement. Pane equality is physical, while traversal retains each occurrence's window context. A vector can therefore contain equal pane handles reached through different links. Deduplicate only when that is the intended question, retaining server identity when mixing endpoints. See [ownership](https://libtmux.org/en/scala/latest/guides/ownership/). ## Serialization and regular expressions Opted-in serialization uses the separate Java `libtmux-jackson` artifact and its `libtmux.filter/1` schema, over the raw Java [`FilterExpr`]() an `Expr[T]` wraps (`.asJava`). Keep the canonical Java field and relation objects; rebuilding an accessor with the same name does not establish model authority. Unknown schemas, fields, operators and relations remain errors. Arbitrary Scala closures are not serialized. Java regex matching uses [`java.util.regex.Pattern`]() and substring search through [`Matcher.find`](). Supply a Scala regex's `.pattern` explicitly. Neither the expression adapter nor serialization implies tmux `-f` compilation or an MCP query parameter. --- # Ownership Source: https://libtmux.org/en/scala/latest/guides/ownership/ > Ownership: io.github.libtmux:libtmux-scala_3 documentation. Opening a Scala server owns a Java client. Closing that client releases its transport and local workers; it does not kill the tmux daemon. [`killServer`]() is an explicit, separate operation. A borrowed facade leaves the original Java client under its existing owner's control. ## Direct-style scopes [`Server.open`][server] returns an opaque [`Server`](), bounded by [`AutoCloseable`](), for [`scala.util.Using.resource`]() or an explicit `try`/`finally` scope — the bound is the type's own, so [`close()`]() needs no forwarding of its own. Its [`close`]() is idempotent. [`Server.fromJava`]() borrows a client instead of owning one; closing that facade closes the Java client too, so a caller who only means to borrow keeps the original owner's [`close()`]() as the one that matters. An opaque handle carries no Scala-side scope state of its own — no owned/closed/parent bookkeeping to keep in sync with Java's. Whether a call through it fails after the owning [`Server`]() closes is exactly Java's own use-after-close contract ([`ServerClosedException`]()), not a second policy this facade adds. ## Effect scopes [`cats.Server.resource`][cats-server] acquires the client when its [`Resource`]() runs. [`cats.Server.fromJava`]() also returns a [`Resource`](), with borrowing semantics. Both scope the operations submitted through the returned facade. Release rejects new work, cancels queued and running operations, waits for their finalizers, and then closes the owned client if there is one. Returning a handle or an unevaluated effect from the resource body does not extend its lifetime. The handle's captured data remains available; executing its effect after release fails. Release cancels operations still running; an operation can finish before that cancellation reaches it. A caller that masks cancellation can receive a scope-closed failure instead of waiting indefinitely for a canceled child operation. Partial acquisition failure and failure in the resource body still release what the resource acquired. Cancellation also waits for cleanup. Borrowing changes which client is closed, not the requirement to release the facade's own work. If an external owner closes a borrowed transport while a request is dispatched, that request can fail with Java's `UNKNOWN` dispatch outcome. The facade preserves that failure; it does not turn owner closure into a successful empty result or an automatic retry. Here `config` selects an existing server with a session. The outer resource owns Java. The inner Scala scope borrows it, and the original client still answers after that scope ends: ```scala import _root_.cats.effect.{IO, Resource} import io.github.libtmux.{Server => JavaServer} import io.github.libtmux.scaladsl.cats.{config => _, *} import io.github.libtmux.scaladsl.cats.{Server => ScalaServer} Resource .make(IO.blocking(JavaServer.open(config)))(java => IO.blocking(java.close())) .use { java => ScalaServer.fromJava[IO](java).use { server => server.sessions().flatMap(values => IO(assert(values.nonEmpty))) }.flatMap(_ => IO.blocking(assert(java.isAlive()))) } ``` ## Java identity and one escape hatch Every direct-style handle is opaque over its Java one — the same object, not a copy — so equality and hash code are Java's own. A pane's identity is physical; a window's identity includes its captured session and window index. Borrowing does not replace that identity by reacquiring the same textual ID. Refresh follows the Java contract and can return a pane through a different window occurrence. `.asJava` is the one escape hatch, on every handle, always public: opaque wrapping is free, so it is a zero-cost coercion, never a copy and never a second, more "unsafe" name to reach for. Code using the raw Java handle must still follow its ownership and threading contract. Reaching `.asJava` from an owned scope does not keep that client open past the Scala facade's own [`close()`](); reaching it from a borrowed scope does not make the Scala facade its owner. ## Control attachments [`Control.attach`][control] on a captured session owns a separate process attachment to that capture's process, started by the session's transport. [`Control.attachUnfenced`]() on a config and session id does not check the process. Releasing either stops its admitted requests before closing the attachment and preserves the daemon. Acquire an output or event subscription inside the attachment's resource and before starting its producer. Release the subscription before the attachment. An [observation][observation] allows one active stream consumer; canceled consumption releases that slot. Releasing the observation closes its Java subscription and discards buffered events. Keep command and observation attachments separate when canceling a command must not end observation. See [execution](https://libtmux.org/en/scala/latest/guides/execution/) for admission bounds, cancellation uncertainty, and the difference between a control acknowledgement and command completion. [server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala/src/main/scala/io/github/libtmux/scaladsl/Server.scala [cats-server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Server.scala [control]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Control.scala [observation]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Observation.scala --- # Execution Source: https://libtmux.org/en/scala/latest/guides/execution/ > Execution: io.github.libtmux:libtmux-scala_3 documentation. The direct-style facade runs operations immediately. The Cats facade constructs lazy effects: creating a read or mutation effect performs no tmux I/O, and evaluating it again repeats the operation. Both delegate commands and transport behavior to Java. Choose the [direct-style server][server] for ordinary synchronous code or the [Cats server][cats-server] inside a managed effect scope. Here `config` is a Java [`ServerConfig`]() for an existing tmux server. The same read effect observes the value present at each evaluation: ```scala import _root_.cats.effect.IO import io.github.libtmux.scaladsl.cats.{config => _, *} import io.github.libtmux.scaladsl.cats.{Server => ScalaServer} ScalaServer.resource[IO](config).use { server => val name = "scala-guide-session" val read = server.hasSession(name) for { before <- read _ <- server.newSession(name) after <- read _ <- IO { assert(!before) assert(after) } } yield () } ``` Captured `info`, session windows, window panes, and client attachments are pure reads: generated as plain values in both layers, never wrapped in `F` on the Cats side, since the catalog marks them [`CAPTURED`](). A captured accessor that answers a tmux subsystem instead — [`Server.hooks`](), [`Pane.options`](), [`Server.shell`](), and the rest — is CAPTURED for the same reason, its own construction touches no tmux state, but on the Cats side it answers that subsystem's own wrapper class rather than the raw Java handle direct style still returns; the subsystem's own reads and mutations then run through `F` exactly as every other operation does. Filtering their immutable collections does not refresh them. Listing, refresh, format expansion, pane mode inspection, and mutation perform I/O. [`Pane.awaitText`]() polls captured text. ## Admission and blocking work Cats operations run through an [interruptible blocking boundary][execution], including a server or control client's own acquisition step and every subsystem operation reached through [`Server.hooks`](), [`Pane.options`](), and the rest. An owned server accepts `maxConcurrentCalls` from one through four. A borrowed server accepts a positive bound, but that bound covers only calls through that facade. Its owner must account for other users and the underlying transport's capacity. [`Pane.awaitText`]() and [`Pane.await`]() reserve one facade call for work that can release the wait (`Execution.waiting`), and require capacity of at least two. [`Pane.run`]() uses ordinary admission: its private completion channel is normally signalled by the pane's shell. This bounds admitted calls; it does not make Java I/O thread-free. Waiting operations occupy blocking workers, and process transport uses its own workers to service child processes. The limit does not bound the number of fibers a caller may queue. Use bounded effect traversal when submitting a large collection. Ordered traversal preserves result order, while concurrent tmux mutations can still reach the daemon in a different order. Prompt cancellation depends on a borrowed transport honoring interruption and completing its cleanup. Command timeouts start when Java receives the operation, after Scala admission. An effect timeout can bound admission and execution together, but cancellation still waits for the operation's cleanup. A timeout is not a promise that a dispatched mutation had no effect. ## Failures and cancellation Ordinary failures preserve Java's exception and dispatch certainty: the sealed [`LibTmuxException`]() tree matches directly from Scala (see [errors](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/docs/guide/scala.md)), and `DispatchException#safeToRetry` answers the question a caller usually has without a catalog lookup. A [`NOT_DISPATCHED`]() transport failure differs from `UNKNOWN`: the latter may have changed tmux. Raw [`Server.cmd`]() returns Java's own [`transport.CommandResult`](), so a nonzero exit remains data with its stdout and stderr. Cats cancellation interrupts local work, waits for owned cleanup, and ends in `Outcome.Canceled` whether or not the command reached tmux. Cancellation carries no error, so the facade does not turn it into one. To learn whether a canceled command was dispatched, set an [`OperationObserver`]() on the Java [`ServerConfig`](): the process transport reports `UNKNOWN` for a command it interrupted after starting. Treat a canceled mutation as possibly dispatched; the facade does not retry it, roll it back, or switch transports. A daemon-side shell job can continue after its requesting client is canceled. Closing a borrowed transport from its owner can instead produce an ordinary Java `UNKNOWN` failure. ## Control replies [`Control.acknowledge`]() reports a [protocol reply][control]. [`accepted`]() means a successful reply frame arrived; deferred tmux work can still be running. Use a separate completion signal for that work. Canceling an active control request can close its attachment and affect queued requests and observations, so use separate attachments when their lifetimes must be independent. [`Control.isAlive`]() reports whether that process is still running. [`Control.standardError`]() is the text that process wrote to its error stream, at most 4096 bytes. [`standardErrorTruncated`]() says the stream continued past that bound. [`Control.watch`]() asks tmux to push a format when its value changes, and [`unwatch`]() removes that name. A target that is not a pane or window id watches the attached session. [server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala/src/main/scala/io/github/libtmux/scaladsl/Server.scala [cats-server]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Server.scala [execution]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Execution.scala [control]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Control.scala --- # Streaming Source: https://libtmux.org/en/scala/latest/guides/streaming/ > Streaming: io.github.libtmux:libtmux-scala_3 documentation. The Cats module adapts Java control subscriptions to FS2. Acquire an attachment, then an observation, then start the producer. An observation has one active stream consumer; use FS2's explicit broadcast operations if several consumers need the same values. Starting another reader on the same observation fails. ## Observe a state change Here `config` selects an existing server with a session. The subscription is registered before the rename. Evaluating the returned [`IO`]() performs the work; constructing it alone does not. ```scala import _root_.cats.effect.IO import io.github.libtmux.control.Notification import io.github.libtmux.scaladsl.cats.{config => _, *} import scala.concurrent.duration._ Server.resource[IO](config).use { server => server.sessions().flatMap { sessions => val session = sessions.head Control.attach[IO](session).use { control => control.events(8).use { observation => for { renamed <- session.rename("scala-streamed") event <- observation.stream .map(Observation.value) .unNone .map(_.notification()) .collect { case value: Notification.SessionRenamed => value } .filter(_.name() == renamed.info.name()) .take(1) .compile.lastOrError.timeout(5.seconds) drops <- observation.droppedCount _ <- IO { assert(event.session().value() == session.info.id().value()) assert(drops == 0L) } } yield () } } } } ``` [`events`]() retains Java's typed notifications and its unknown notification variant. [`output`]() yields pane-attributed [`PaneOutput`]() values. Their `data` is decoded terminal text, not exact bytes or a captured screen. A marker may span several values; retain the necessary suffix while matching it. ## Loss and state reconciliation Each subscription has a bounded queue. Overflow drops the oldest buffered value. The stream then emits a [`Delivery.Gap`]() ahead of what survived. [`Observation.value`]() drops that gap, so a pipeline that keeps only values cannot see where the loss sat. [`Observation.kept`]() fails the read instead. [`droppedCount`]() is the cumulative total. Closing a subscription discards queued values without counting them as overflow. FS2 demand does not make tmux obey backpressure, and the facade never mutes pane output to imitate it. Read the counter when completeness matters. If it increases, reacquire a snapshot before making decisions about current object state. A snapshot cannot reconstruct the dropped terminal output. The [ObserveChanges example](https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/examples/src/main/scala/io/github/libtmux/scaladsl/examples/ObserveChanges.scala) demonstrates the live view's own reconciliation over a real rename. ## Cancellation and closure The Cats stream suspends the *fiber*, not a platform thread, while idle: it polls first — nothing suspends when a step is already buffered — and only arms the subscription's one-shot readiness callback ([`onReady`]()) when nothing is, disarming it ([`clearReady`]()) if the fiber is cancelled first. No [`ExecutionContext`]() sized for blocking stream reads is needed, and no thread is parked per subscription. Canceling a reader releases its consumer slot. Releasing the observation closes its subscription and disarms any pending wakeup. Deliberate Scala observation or attachment closure ends the stream. If the control client ends the subscription, the stream fails with that cause. [`Observation.UnknownCause`]() is only the remaining case: the subscription ended, this side did not close it, and Java recorded no cause. Do not label every unexpected end as a timeout or a server crash. The direct-style module reads the same subscription through its own `Observation`, blocking in Java's [`next()`]() per read and guarding against a second, overlapping [`read`]() on the same instance with a scoped CAS — the direct-style analogue of the Cats module's stream ownership. Raw [`Control.acknowledge`]() calls use bounded, supervised admission. Their timeout begins after Scala admission. Canceling a genuinely dispatched request can close the attachment and affect queued requests and observations. Use a separate attachment when an observation must survive command cancellation. An accepted reply is an acknowledgement, not proof that deferred tmux work finished. See [execution](https://libtmux.org/en/scala/latest/guides/execution/) for completion signals and uncertainty. The [Control source][control] and [Observation source][observation] specify the resource and stream boundaries. [control]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Control.scala [observation]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux-scala-cats/src/main/scala/io/github/libtmux/scaladsl/cats/Observation.scala --- # Compatibility Source: https://libtmux.org/en/scala/latest/guides/compatibility/ > Compatibility: io.github.libtmux:libtmux-scala_3 documentation. The Scala facades build, test and publish with the rest of the repository, so they meet the same gates the Java library does. ## Compilers and runtimes Scala 3.9 only — there is no 2.13 build — compiled to JDK 25 bytecode. CI runs the facades' unit suites, their real-tmux suites, the documented Scala examples and the runnable programs on JDK 25 and 27 on Linux, and on macOS. Every lane of the [tmux matrix][tmux-matrix] runs the real-tmux suites against its own tmux: `3.2a`, `3.3`, `3.3a`, `3.4`, `3.5`, `3.6`, `3.7`, `3.7a`, `3.7b` and `3.7c`. The Scala fixture takes the lane's tmux the same way the Java one does. ## Artifacts - **`libtmux-scala_3`** — direct-style operations and the typed query DSL. - **`libtmux-scala-cats_3`** — Cats Effect resources and FS2 observations. - **`libtmux-scala-ox_3`** — an Ox [`Flow`]() over subscriptions and live views. Core's runtime dependencies are the Scala 3 library and `libtmux`. Cats Effect and FS2 belong to `libtmux-scala-cats`, Ox to `libtmux-scala-ox`. None depends on Jackson, JUnit, Kotlin, MCP or the workspace packages. The Scala artifacts release with the Java ones, at the same version, in the same Central deployment, signed and attested alike. `libtmux-bom` manages them too, so one BOM version selects a matching Java and Scala set. ## The generated and handwritten surface Every direct-style extension method and Cats per-operation forward that reaches tmux is generated from the operation catalog `libtmux` ships, by [`ScalaOperationGenerator`][generator] — never hand-copied per method, so it cannot drift from what Java exposes. [`WAIT`](), [`STREAM`]() and [`LIFECYCLE`]() operations are written by hand, for their cancellation or resource scoping: [`Server.open`]()/`fromJava`/[`within`]()/[`control`](), [`Pane.awaitText`]()/`run`/`await`, [`LiveView`](), [`LiveServer`](), and both modules' `Observation`. Both facades generate from the same catalog through the same owner-and-kind branching, and the [generator's tests][generator-tests] hold the two to it: a captured operation forwards purely on both, and a mutation is effect-wrapped on the Cats side only. Nothing Scala has shipped yet, so there is no earlier release for MiMa or `tasty-mima` to compare against. ## Inherited feature boundaries The facade preserves Java's version guards. [Named buffer deletion][buffers] rejects tmux before 3.4 because those versions can delete the wrong buffer. [`Shell.capturing`][java-shell] rejects tmux 3.3a and 3.4, which lose the requested output. Java's tmux matrix asserts these unsupported results rather than skip the contract or substitute an empty successful result. Capture and buffer reads retain Java's normalized text, including its handling of trailing empty lines. Observations retain decoded text chunks. These APIs do not promise arbitrary-byte round trips. Sparse hook listings preserve command order without inventing their original indices. Basic copy-mode entry, inspection, and exit are wrapped; advanced commands use explicit raw access. See [execution](https://libtmux.org/en/scala/latest/guides/execution/) for control acknowledgement limits, and [ownership](https://libtmux.org/en/scala/latest/guides/ownership/) for resource lifetimes. [generator]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/build-logic/codegen/src/main/kotlin/io/github/libtmux/codegen/scala/ScalaOperationGenerator.kt [generator-tests]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/build-logic/codegen/src/test/kotlin/io/github/libtmux/codegen/scala/ScalaOperationGeneratorTest.kt [tmux-matrix]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/build-logic/conventions/src/main/kotlin/libtmux.tmux-matrix.gradle.kts [java-options]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux/src/main/java/io/github/libtmux/Options.java [buffers]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux/src/main/java/io/github/libtmux/Buffers.java [java-shell]: https://github.com/libtmux/libtmux-java/blob/f56392b5d9bc7f1f9c1631842333fb90f3d82d40/libtmux/src/main/java/io/github/libtmux/Shell.java