Packages

Elixir tmux client with explicit observations and operation outcomes

Current section

Files

Jump to
libtmux README.md
Raw

README.md

<!-- libtmux-logo -->
<div align="center">
  <picture>
    <source srcset="docs/assets/logo.svg" type="image/svg+xml">
    <img src="docs/assets/logo-512.png" width="128" height="128" alt="libtmux for Elixir">
  </picture>
</div>
<!-- /libtmux-logo -->

<div align="center">

# libtmux for Elixir

<p>
  <a href="https://hex.pm/packages/libtmux">Hex.pm</a> ·
  <a href="https://libtmux.org/en/elixir/latest/reference/">API reference</a> ·
  <a href="https://libtmux.hexdocs.pm/">HexDocs</a> ·
  <a href="https://libtmux.org/en/elixir/">Documentation</a>
</p>

</div>

Read tmux sessions, windows and panes from Elixir. Filter a snapshot, rename
a window, split a pane, send input, or capture its screen. Queries use the
captured snapshot; actions contact tmux explicitly.

[Capture and query](#capture-and-query) ·
[Rename a window](#rename-a-window) ·
[Split and send input](#split-a-pane-and-send-input) ·
[Guides](https://libtmux.org/en/elixir/latest/guides/)

## Status and requirements

**Alpha:** the API may change between releases.

| Requirement | Version or dependency |
| --- | --- |
| Elixir / OTP | Elixir 1.20.4 or later 1.20 patch, compiled for OTP 29; OTP 29.1.2 or later 29 patch |
| Runtime platform | Linux, with a C11 compiler and headers to build the external helper |
| tmux | 3.3 minimum; tested with 3.3, 3.3a, 3.4 and 3.8 |
| Dependencies | Zero third-party core runtime packages; no NIF |

## Installation

Add the published alpha to your Mix dependencies:

```elixir
{:libtmux, "== 0.0.1-alpha.3"}
```

Fetch dependencies:

```console
$ mix deps.get
```

## Run the examples

The snippets below form one [executable program](examples/readme.exs). From
this checkout, run it on a private tmux server:

```console
$ python3 examples/owned_server.py \
    --run "$(command -v tmux)" \
    mix run examples/readme.exs
```

The [runner](docs/owned-server.md) creates a private server and cleans it up
afterward. It supplies the executable, socket and session ID used below,
leaving your existing tmux sessions alone. The runner needs Python 3.9+.

Already have a server? Use the [capture-only program](docs/first-task.md),
which takes its socket explicitly and leaves the server running.

## Capture and query

[`Server.snapshot!/2`][snapshot] captures an immutable graph.
[`Selection.where/2`][where] accepts predicate data; ordinary `Enum` functions
work on the selection. These queries make no further tmux calls.

<!-- example: examples/readme.exs#capture -->

```elixir
alias LibTmux.{Selection, Server, Snapshot}

[executable, socket, session_id] = System.argv()

server =
  Server.new(
    executable: executable,
    socket: socket,
    env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
  )

snapshot = Server.snapshot!(server)

active_ids =
  snapshot
  |> Snapshot.panes()
  |> Selection.where(active: true)
  |> Enum.map(& &1.id)

```

<!-- /example -->

The fixture starts with one pane, so `active_ids` is `["%0"]`. To use an
Elixir function as the predicate, choose [`Selection.filter/2`][filter].
Capture again explicitly when you need fresh observations.

## Rename a window

Continue with the same [`snapshot`][snapshot-type] and [`server`][server-type].
[`Selection.one!/2`][one] requires exactly one match, and
[`Session.active_window/2`][active-window] reads the captured relationship.
[`Ref.from_observation/1`][ref] carries the observed identity into an action.

<!-- example: examples/readme.exs#rename -->

```elixir
alias LibTmux.{Ref, Selection, Session, Snapshot, Window}

session = snapshot |> Snapshot.sessions() |> Selection.one!(id: session_id)
window = Session.active_window(snapshot, session)
{:ok, session_ref} = Ref.from_observation(session)
{:ok, window_ref} = Ref.from_observation(window)

Window.rename!(server, window_ref, "editor", session: session_ref)
```

<!-- /example -->

[`Window.rename!/4`][rename] returns `:ok` when tmux acknowledges completion.
The `session` option chooses this window's session membership. The original
snapshot still contains the name captured before the action.

## Split a pane and send input

Continue with that [`window`][window-type] and [`session`][session-type].
[`Control.with_connection/2`][scope]
owns connection startup and cleanup around the callback. Its returned value
is available after connection retirement.

<!-- example: examples/readme.exs#control -->

```elixir
alias LibTmux.{Control, Pane, Ref, Selection, Snapshot}

pane = snapshot |> Snapshot.panes() |> Selection.one!(window_id: window.id, active: true)
{:ok, pane_ref} = Ref.from_observation(pane)

{:ok, created_id} =
  Control.with_connection(
    [server: server, session: {:existing, session.id}, lane: :host_qualified],
    fn control ->
      %{id: id, ref: created} =
        Pane.split!(control, pane_ref, axis: :horizontal, command: {:argv, ["/bin/cat"]})

      Pane.send_text!(control, created, "hello")
      Pane.send_keys!(control, created, ["Enter"])
      id
    end
  )

```

<!-- /example -->

[`Pane.split!/3`][split] returns the new pane's ID and bound reference on this
connection. [`Pane.send_text!/4`][text] sends literal text;
[`Pane.send_keys!/4`][keys] sends named keys. `{:argv, ["/bin/cat"]}` requests
a program directly; `{:shell, "command"}` requests shell interpretation.
Acknowledgement does not mean the program has finished processing its input.

The private runner qualifies this example's `lane: :host_qualified` declaration.
That option does not configure or qualify an arbitrary server. Read the
[managed-control guide](docs/managed-control.md) before using another host.
For one-shot screen reads, use [`Pane.capture!/3`][capture].

The complete program prints the active pane `%0`, the new pane `%1`, and the
window name `editor`. These examples run in CI. Each snippet shows its aliases;
they shorten module names and are not inferred.

## Errors and recovery

The domain actions shown above raise when they cannot complete. If a command
times out, it may still have taken effect. The [recovery guide](docs/recovery.md)
shows how to inspect the result before deciding what to do next.

## Concepts and reference

- [Filtering](docs/filtering.md): predicates, relationships and finding one match.
- [One-shot actions](docs/one-shot-actions.md): rename, split, input and screen capture.
- [Managed control](docs/managed-control.md): reuse a connection for multiple commands.
- [Mental model](docs/mental-model.md): snapshots, references and command results.
- [API reference](https://libtmux.org/en/elixir/latest/reference/): functions and types.

## Contributing

See [Contributing](.github/CONTRIBUTING.md) for development setup and checks,
and [Writing](.github/WRITING.md) for documentation conventions.

Related projects: [libtmux for Python](https://github.com/tmux-python/libtmux),
[libtmux ports](https://github.com/libtmux), and [tmux](https://github.com/tmux/tmux).
[Artwork](docs/branding.md). Licensed under the [MIT License](LICENSE).

[snapshot]: https://libtmux.org/en/elixir/latest/reference/libtmux-server-snapshot-bang-2/
[snapshot-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-snapshot/
[server-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-server/
[window-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-window/
[session-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-session/
[where]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-where-2/
[filter]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-filter-2/
[one]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-one-bang-2/
[active-window]: https://libtmux.org/en/elixir/latest/reference/libtmux-session-active_window-2/
[ref]: https://libtmux.org/en/elixir/latest/reference/libtmux-ref-from_observation-1/
[rename]: https://libtmux.org/en/elixir/latest/reference/libtmux-window-rename-bang-4/
[scope]: https://libtmux.org/en/elixir/latest/reference/libtmux-control-with_connection-2/
[split]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-split-bang-3/
[text]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-send_text-bang-4/
[keys]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-send_keys-bang-4/
[capture]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-capture-bang-3/