LibTmux
Source and package
- Qualified name
-
LibTmux - Package
- LibTmux
- Source
- lib/LibTmux.rakumod
-
Description#
LibTmux
Typed tmux snapshots, local selections and explicit commands for Raku programs.
Create a session and split a pane
Serverstarts and owns a private daemon. This program splits the first pane and titles both; each runscat.LEAVEcloses the owner on any exit.use LibTmux;my $server = Server.start(:name<work>, :shell-command('exec cat'));LEAVE $server.close if $server.defined;my $before = $server.snapshot;my $left = $before.panes.single.ref;my $right = $left.split(:horizontal, :shell-command('exec cat'));$left.set-title('editor');$right.set-title('logs');my $after = $server.snapshot;my $editor = $after.panes.filter(*.title eq 'editor').single;say "{$editor.title}: {$after.panes.elems} panes";It prints
editor: 2 panes.- The earlier snapshot still has one pane: mutation does not change an
observation.
-
.refbuilds a command target.snapshotperforms I/O; reading a
captured field or relationship stays local.
Follow the captured graph
A
Snapshotholds sessions, session groups, physical windows, window links, panes, clients and buffer metadata.- A physical window can appear at several indexes, even twice in one
session. Its
WindowLinkowns the session-relative index and active state.-
windowsandpaneslist each physical identity once;window-links
keeps every placement.
- A pane's
windowand a window'spanesresolve inside the snapshot.
Field reads, traversal and selection make no tmux calls, even after the acquisition owner closes. Use
.ref.inspector a new snapshot for fresh data.- References carry daemon incarnation evidence, so a replacement daemon that
reuses an ID does not inherit an old pane reference.
Query captured values
Combine validated query data with native Raku predicates and transformations:
use v6.d;use LibTmux;sub MAIN(Str:D :$tmux = 'tmux') {my $server = Server.start(:binary($tmux), :name<work>,:width(160), :height(40), :shell-command('exec cat'));LEAVE $server.close if $server.defined;my $editor = $server.snapshot.panes.single.ref;my $tests = $editor.split(:horizontal, :shell-command('exec cat'));my $logs = $tests.split(:shell-command('exec cat'));$editor.set-title('editor');$tests.set-title('tests');$logs.set-title('logs');my $snapshot = $server.snapshot;my @wide := $snapshot.panes.where(%(width => %(gte => 60)));my $selected = @wide.filter(*.title eq 'editor').single;say "Wide pane: {$selected.title}";my @others = $snapshot.panes.grep(*.id ne $selected.id).map(*.title).sort;say 'Other panes: ' ~ @others.join(', ');}It prints
Wide pane: editorandOther panes: logs, tests.- Bind with
:=to keep a typed selection. Array assignment copies
ordinary Raku values; scalar assignment itemizes the collection.
-
wherevalidates fields and operators even on an empty selection. -
filterruns its callback eagerly and returns the same selection type.
A
Failurereturned directly from the callback throws its wrapped exception before Boolean coercion.-
grep(including:k,:kvand:p) returns a nativeSeq.
map,sort,firstand junctions keep plain Raku behavior; mapping panes to titles gives strings, not a selection.-
singlewants exactly one member andsingle-or-nilallows none.
Several matches throw
Cardinality.onestays a junction.Compile reusable criteria with
Filter:-
ACCEPTSpermits native matching;to-jsonandfrom-jsonkeep the
bounded portable data grammar.
- Closures, regexes and junctions stay local Raku matchers. Data equality
follows field types, not generic smartmatch.
- Relations use
some,every,none,isandis-not. An unloaded
relation raises
MissingDatainstead of acting like an empty one.Choose where a query runs
query-panestakeswhereand apushdownpolicy:-
autohas tmux apply the restrictions it can match exactly and checks
the rest locally.
-
neverevaluates locally after acquisition. -
requirerefuses a query that cannot run exactly in tmux.
explain-panesdescribes projections, scope, pushed conditions and residual work. Arbitrary callbacks are never translated to tmux.use v6.d;use LibTmux;sub MAIN(Str:D :$tmux = 'tmux') {my $server = Server.start(:binary($tmux), :name<work>, :shell-command('exec cat'));LEAVE $server.close if $server.defined;$server.snapshot.panes.single.ref.set-title('editor');my %wide = width => %(gte => 60);my %titled = AND => [%wide, %(title => %(contains => 'edit'))];say 'width: fully pushed = ' ~ $server.explain-panes(:where(%wide)).fully-pushed;my $plan = $server.explain-panes(:where(%titled));say 'width and title: fully pushed = ' ~ $plan.fully-pushed;say 'tmux filter: ' ~ $plan.source-filter;say 'Matches: ' ~ $server.query-panes(:where(%titled)).map(*.title).join(', ');try {$server.query-panes(:where(%titled), :pushdown<require>);CATCH { when X::LibTmux::Unsupported { say 'require: refused, title stays local' } }}}It prints whether each query is fully pushed to tmux, the filter tmux gets, the match, and the refusal.
Choose an operation from capabilities
Capabilities describe the daemon, not the client executable. Ask
supportsbefore offering a newer operation, or callrequirefor a typed refusal with its reason. This program creates an emptystatuspane, floating when supported and tiled otherwise:use v6.d;use LibTmux;sub MAIN(Str:D :$tmux = 'tmux') {my $server = Server.start(:binary($tmux), :name<work>,:width(120), :height(36), :shell-command('exec cat'));LEAVE $server.close if $server.defined;my $snapshot = $server.snapshot;my $main = $snapshot.panes.single.ref;my $floating = $snapshot.capabilities.supports('pane-floating');my $auxiliary = $floating?? $main.new-floating(:width(40), :height(12), :x(2), :y(2), :empty)!! $main.split(:horizontal, :empty);$auxiliary.set-title('status');my $after = $server.snapshot;die 'The auxiliary pane was not captured' unless $after.panes.elems == 2;say 'Status pane: ' ~ ($floating ?? 'floating' !! 'tiled');say $after.panes.where(%(id => $auxiliary.id)).single.title;}It prints the placement, then
status.- An empty pane can have no child PID. Its
pidis then null, not a made-up
integer;
state('pid')tells known null from a field that was not acquired.- Unknown tmux releases are refused. Field, codec, layout and command
decisions come from one packaged catalogue.
React to terminal output
An
-asyncmethod returns aPromiseof the same result. AControlConnectionowns one persistent client; its eventSupplyis shared by subscribers. Nativereactandwheneverconsume output without polling:use v6.d;use LibTmux;use LibTmux::Control;sub MAIN(Str:D :$tmux = 'tmux') {my $server = Server.start(:binary($tmux), :name<output>, :shell-command('exec cat'));LEAVE $server.close if $server.defined;my $snapshot = await $server.snapshot-async;my $pane = $snapshot.panes.single.ref;my $connection = await $server.open-control-async(:session-id($snapshot.sessions.single.id));LEAVE $connection.close if $connection.defined;my $message = 'hello from Raku';my $received = '';react {whenever $connection.events.grep(PaneOutput) -> $event {if $event.pane-id eq $pane.id {$received ~= $event.data.decode('utf8-c8');die 'Pane output exceeded its bound' if $received.chars > 65536;done if $received.contains($message);}}whenever start { $pane.send-text($message); $pane.send-keys('Enter') } { }whenever Promise.in(2) { die 'Pane output timed out' }}die 'The captured screen did not contain the message'unless $pane.capture.decode('utf8').contains($message);say $message;say 'Screen capture confirmed';}It prints
hello from RakuandScreen capture confirmed.- Subscribe before sending input; the deadline bounds the wait.
-
send-textnever appends Enter. - Output stays bytes until you pick a decoder, so fragmented or invalid
UTF-8 survives. Unknown notification types keep their raw form.
Close each connection:
- Cancellation stops and joins owned client work. It does not undo commands
the daemon already ran.
- After reconnecting, take a fresh observation; events do not add up to a
complete current graph.
-
Attachmentis a separate terminal client owner.
Recover from partial completion
Batchreports each operation's completion scope and outcome. The batch recovery program showsComplete,FailedandSkipped, then removes the first effect itself. Acknowledgement alone does not prove an application effect, and a failed batch is not rolled back.The session workflow program combines creation, control output, capture, local filtering and an explained source query.
Ownership and operational errors
-
Server.startowns its private daemon.Server.newconfigures a
borrowed endpoint; closing that facade leaves the daemon running. Close both.
- A connection or terminal attachment has its own lifetime; close it too.
- An observation keeps values and the captured graph, not a live
acquisition process.
- Commands pass an argument vector. Literal pane text, key presses, FORMAT
expressions, tmux command templates and deliberate shell commands have separate entry points.
- Errors keep typed context and captured bytes. A missing optional lookup
never disguises a failed command.
- Fields keep known null, false, zero, empty text, unsupported, unavailable
and unloaded data as distinct states.
Members18#
Common operations1#
- Server class
Other members17#
- Attachment class
- Batch module
- Capabilities class
- Catalogue module Packaged, source-qualified fields, relationships, options and release profiles.
- Client module
- Command module
- Configuration module
- Control module
- Engine module
- Exceptions module Typed acquisition, decoding, query, ownership and command failures.
- Format module
- Layout module
- Model module
- Query module
- Selection module
- Transport module
- Types module