Class: AgentsControl::Terminals::Tmux

Inherits:
Base
  • Object
show all
Defined in:
lib/agents_control/terminals/tmux.rb

Overview

tmux.

The only backend that works on both macOS and Linux, and also the fastest one: a full pane list comes back in 0.008s versus 0.9s for iTerm2 — a hundred-plus-fold difference. It's also the only one with scrollback and the only one that survives an ssh disconnect.

Inside iTerm2 it runs as tmux -CC: tmux windows become native tabs, and both backends operate on the same machine at once.

Constant Summary collapse

FORMAT_FIELDS =

%w doesn't interpolate — the literal tmux substitutions stay intact inside.

%w[
  #{pane_id}
  #{pane_tty}
  #{pane_current_command}
  #{pane_current_path}
  #{session_name}
  #{window_name}
].freeze
FIELDS =
%i[id tty command cwd session window].freeze
SHELLS =

Shells under which a pane counts as "at a prompt" rather than busy.

%w[zsh bash sh fish dash ksh tcsh].freeze

Instance Method Summary collapse

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from AgentsControl::Terminals::Base

Instance Method Details

#available?Boolean

Just having the binary is enough: the server might not be running yet, but we can still create a window in it.

Returns:

  • (Boolean)


34
# File 'lib/agents_control/terminals/tmux.rb', line 34

def available? = !binary.nil?

#capture(id, lines: 200) ⇒ Object

This is exactly what tmux is for: history, not just the visible screen.



78
79
80
81
# File 'lib/agents_control/terminals/tmux.rb', line 78

def capture(id, lines: 200)
  result = run("capture-pane", "-p", "-t", id, "-S", "-#{lines}")
  result.success? ? result.stdout.rstrip : ""
end

#close(id) ⇒ Object



87
# File 'lib/agents_control/terminals/tmux.rb', line 87

def close(id) = run("kill-pane", "-t", id).success?

#create_tab(cwd: nil, command: nil) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/agents_control/terminals/tmux.rb', line 62

def create_tab(cwd: nil, command: nil)
  args = ["new-window", "-P", "-F", "#{'#'}{pane_id}"]
  args += ["-c", cwd] if cwd && !cwd.empty?
  args << command if command && !command.empty?

  result = run(*args)
  result.success? ? result.stdout.strip : nil
end

#focus(id) ⇒ Object



83
84
85
# File 'lib/agents_control/terminals/tmux.rb', line 83

def focus(id)
  run("select-window", "-t", id).success? && run("select-pane", "-t", id).success?
end

#nameObject



30
# File 'lib/agents_control/terminals/tmux.rb', line 30

def name = :tmux

#send_text(id, text, newline: true) ⇒ Object



71
72
73
74
75
# File 'lib/agents_control/terminals/tmux.rb', line 71

def send_text(id, text, newline: true)
  args = ["send-keys", "-t", id, "--", text]
  args << "Enter" if newline
  run(*args).success?
end

#sessionsObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/agents_control/terminals/tmux.rb', line 36

def sessions
  return [] unless available?

  result = run("list-panes", "-a", "-F", format_string)
  # No server isn't an error, it just means there are no panes yet.
  return [] unless result.success?

  parse_records(result.stdout, FIELDS).map do |record|
    Session.new(
      id: record[:id],
      backend: name,
      tty: record[:tty],
      title: [record[:session], record[:window]].compact.join(":"),
      cwd: record[:cwd],
      # tmux has no equivalent of `is processing`, so this is nil —
      # "unknown", not "no". ProcessProbe determines busy-ness.
      processing: nil,
      at_shell_prompt: SHELLS.include?(record[:command].to_s),
      # tmux already knows what's running in the pane — this same
      # list-panes call hands back the process name for free, with
      # no trip through ProcessProbe needed.
      foreground_command: record[:command]
    )
  end
end