Class: Muxr::Pane

Inherits:
Object
  • Object
show all
Defined in:
lib/muxr/pane.rb

Overview

A Pane bundles a Terminal emulator buffer with the PTYProcess running the shell that feeds it. The Window keeps a list of panes; the Renderer asks each pane for its current grid contents and cursor position.

Each pane has a stable 6-hex id generated at creation. The id survives promote_to_master and other array reshuffles (since it lives on the Pane, not on the index) and is persisted in the session JSON so it also survives a full cold restart. The drawer pane uses the symbol ‘:drawer` as its id; the MCP control surface treats it specially and never lists it under `panes.list`.

Constant Summary collapse

READ_BUDGET =

Drain everything currently in the PTY’s kernel read buffer, feeding each chunk to the Terminal. Coalescing reads here means we render once per fully-formed output burst (fzf re-render, vim cursor+status redraw, etc.) instead of once per ~8 KiB chunk — the latter shows intermediate frames and is the main source of in-pane flicker. Bounded by a byte cap so a runaway producer can’t starve other panes on a single tick.

1 << 20

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id: nil, rows: 24, cols: 80, cwd: nil, command: nil, env_overrides: nil, process: nil) ⇒ Pane

Returns a new instance of Pane.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/muxr/pane.rb', line 23

def initialize(id: nil, rows: 24, cols: 80, cwd: nil, command: nil, env_overrides: nil, process: nil)
  @id = id || SecureRandom.hex(3)
  @rows = rows
  @cols = cols
  @terminal = Terminal.new(rows: rows, cols: cols)
  @process = process || PTYProcess.new(
    rows: rows,
    cols: cols,
    cwd: cwd,
    command: command,
    env_overrides: env_overrides || {}
  )
  @rect = nil
  @initial_cwd = cwd || @process.cwd
  @private_flag = false
  @foreground_command = nil
end

Instance Attribute Details

#foreground_commandObject

Last value written by Application’s foreground poller thread. nil when the shell itself is foreground (the common empty-prompt case) or when the lookup hasn’t run / couldn’t read. Renderer surfaces this in the pane title.



21
22
23
# File 'lib/muxr/pane.rb', line 21

def foreground_command
  @foreground_command
end

#idObject (readonly)

Returns the value of attribute id.



15
16
17
# File 'lib/muxr/pane.rb', line 15

def id
  @id
end

#processObject (readonly)

Returns the value of attribute process.



15
16
17
# File 'lib/muxr/pane.rb', line 15

def process
  @process
end

#rectObject

Returns the value of attribute rect.



16
17
18
# File 'lib/muxr/pane.rb', line 16

def rect
  @rect
end

#terminalObject (readonly)

Returns the value of attribute terminal.



15
16
17
# File 'lib/muxr/pane.rb', line 15

def terminal
  @terminal
end

Instance Method Details

#alive?Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/muxr/pane.rb', line 120

def alive?
  @process.alive?
end

#closeObject



128
129
130
# File 'lib/muxr/pane.rb', line 128

def close
  @process.close
end

#cwdObject



124
125
126
# File 'lib/muxr/pane.rb', line 124

def cwd
  @process.cwd || @initial_cwd
end

#drain_writesObject



78
79
80
# File 'lib/muxr/pane.rb', line 78

def drain_writes
  @process.drain
end

#ioObject



66
67
68
# File 'lib/muxr/pane.rb', line 66

def io
  @process.io
end

#mark_private!Object



54
55
56
# File 'lib/muxr/pane.rb', line 54

def mark_private!
  @private_flag = true
end

#mark_public!Object



58
59
60
# File 'lib/muxr/pane.rb', line 58

def mark_public!
  @private_flag = false
end

#pending_write?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/muxr/pane.rb', line 74

def pending_write?
  @process.pending_write?
end

#pidObject



41
42
43
# File 'lib/muxr/pane.rb', line 41

def pid
  @process.pid
end

#private?Boolean

Private panes are invisible to the MCP control surface — their cwd is stripped from panes.list and pane.read/send_input/run/subscribe/kill all refuse. Toggled by the human via Ctrl-a P or ‘:private`. Never settable from the control surface itself (so a misbehaving MCP client can’t unmark a pane it shouldn’t see).

Returns:

  • (Boolean)


50
51
52
# File 'lib/muxr/pane.rb', line 50

def private?
  @private_flag
end

#read_from_ptyObject

1 MiB



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/muxr/pane.rb', line 93

def read_from_pty
  total = 0
  while total < READ_BUDGET
    chunk = @process.read_nonblock
    break unless chunk
    @terminal.feed(chunk)
    total += chunk.bytesize
  end
  # The emulator may owe the inner program a reply (DSR / CPR — see
  # Terminal#take_pending_replies!). Ship it back through the PTY's
  # input side as if it had been typed. Failure here is non-fatal: the
  # process can have exited between read and write.
  if (reply = @terminal.take_pending_replies!)
    begin
      @process.write(reply)
    rescue Errno::EIO, Errno::EPIPE
    end
  end
  total.positive? ? total : nil
end

#resize(rows, cols) ⇒ Object



114
115
116
117
118
# File 'lib/muxr/pane.rb', line 114

def resize(rows, cols)
  return if rows == @terminal.rows && cols == @terminal.cols
  @terminal.resize(rows, cols)
  @process.resize(rows, cols)
end

#toggle_private!Object



62
63
64
# File 'lib/muxr/pane.rb', line 62

def toggle_private!
  @private_flag = !@private_flag
end

#write(data) ⇒ Object



82
83
84
# File 'lib/muxr/pane.rb', line 82

def write(data)
  @process.write(data)
end

#writer_ioObject



70
71
72
# File 'lib/muxr/pane.rb', line 70

def writer_io
  @process.writer_io
end