Lua Events

A Lua script can keep running and react to what happens in Rex: a terminal changes directory, a program exits, a window closes, a session is created. A script asks for an event with rex.on.

-- label.lua: label each terminal with the directory it is in
rex.on("terminal.pwd_changed", function(ctx, ev)
  local dir = ev.pwd:match("([^/]+)/?$")
  rex.session.set_block_label{
    block_id = ctx.block_id,
    label    = dir,
  }
end)
$ rex do label.lua
listening: 1 handler, session work; Ctrl-C to stop

Change directory in any terminal in the session and its label will update.

Handlers

rex.on takes the name of an event and a function to call each time that event happens:

rex.on(event, function(ctx, ev)
  -- ...
end)

A script that calls rex.on doesn’t exit when it reaches its last line. rex do keeps it running and calls its handlers as events arrive, one at a time.

Handlers are registered while the script first runs. Calling rex.on later, from inside a handler, is an error.

ev holds the event’s fields, and ctx says where it happened:

FieldDescription
ctx.session_idThe session the event happened in.
ctx.block_idThe block the event came from, for a block event.

API calls inside a handler default to the event’s session rather than the one the script was started in, so one handler works across every session it hears.

Event Names

There are three kinds of event name:

NameMatches
block_closed, window_created, …A session or server event, by its type.
terminal.pwd_changed, terminal.bell, …One kind of event from a block.
block_eventEvery event from every block.

A block event is named for the kind of block and the event, joined by a dot. terminal.bell is short for com.superlogical.terminal.bell. Either spelling works.

These are common events:

EventWhenFields on ev
terminal.pwd_changedA terminal changes directory.pwd, a file:// URL
terminal.title_changedA terminal’s title changes.title
terminal.bellA terminal rings its bell.
terminal.child_exitedA terminal’s program exits.exit_code
block_created, block_closedA block is created or closed.block_id
window_created, window_closedA window is created or closed.window_id
session_created, session_destroyedA session is created or destroyed.

The events reference has the full list.

Rex doesn’t check an event name against a list, so a misspelled name is not an error. The handler is registered and never called.

Event Scope

By default, a script reacts to events of one session: the one it was run in, or the one named with -s.

rex do label.lua            # the current session
rex do -s build label.lua   # the session labeled "build"

Pass --all to hear every session, including ones created after the script starts:

rex do label.lua --all

Stopping

A script with handlers runs until one of these happens:

  • A handler calls rex.stop(code). rex do exits with that status, or 0 when it is left out.
  • The time given with --for runs out, such as --for 10m.
  • The session it hears is destroyed. A script run with --all never stops this way.
  • You press Ctrl+C.
-- Exit with the status of the first program to fail
rex.on("terminal.child_exited", function(ctx, ev)
  if ev.exit_code ~= 0 then rex.stop(ev.exit_code) end
end)

Handler Failures

An error in a handler is printed and the script keeps running, so one bad event doesn’t end a long-lived script:

Error: watch.lua:7: attempt to index a nil value

Pass --fail-fast to exit with status 1 at the first failure instead.

Remember that a failed API call returns nil and a message rather than raising an error. Check the second return value where it matters.

Handlers or rex.wait

rex.wait and rex.on both receive events, and suit different scripts.

  • Use rex.wait when the script does one thing after another: start a command, wait for it to exit, then act on the result.
  • Use rex.on when the script should respond every time something happens, for as long as it runs.

rex.wait and rex.sleep also work inside a handler. No other event is handled until they return.

Invalid in Configuration Files

rex.on only works in a script run with rex do. The configuration file is never sent events, so a handler registered there is reported as a warning and never called.

To have a handler running all the time, start the script when you start your session, for example from a terminal you keep open for it.

Example: Notify on Bell

Many programs ring the terminal bell when they finish or need attention. This script turns a bell in any terminal, in any session, into a macOS notification that names the session:

-- notify.lua
local function notify(title, text)
  local script = string.format(
    "display notification %q with title %q", text, title)
  os.execute("osascript -e '" .. script .. "'")
end

rex.on("terminal.bell", function(ctx, ev)
  local view = rex.session.view{}
  notify("Rex", "Bell in " .. view.label)
end)
rex do notify.lua --all

rex.session.view is called without a session_id, so it describes the session the bell rang in. That is how one handler can name whichever session needs you.