Coverage report: /home/ellis/comp/core/lib/cli/tools/tmux.lisp
Kind | Covered | All | % |
expression | 0 | 182 | 0.0 |
branch | 0 | 8 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; cli/tmux.lisp --- Tmux Tools
3
;; Control Tmux from Lisp
7
;; ref: https://github.com/tmux/tmux/wiki/Getting-Started#getting-started
9
;; By default tmux tries to open a TTY and errors when it can't, so normally
10
;; you'd want to use SPAWN-TMUX to allocate a terminal first.
12
;; There is however a control-mode available which provides a text-based
13
;; channel without needing a TTY.
15
;; To use this mode call RUN-TMUX with the "-C" arg.
17
;; ref: https://github.com/tmux/tmux/wiki/Control-Mode#control-mode
20
(in-package :cli/tools/tmux)
22
(defparameter *tmux-user-config-path* (merge-pathnames ".tmux.conf" (user-homedir-pathname)))
23
(defparameter *tmux-system-config-path* (merge-pathnames "tmux.conf" "/etc/"))
25
(defparameter *tmux* (find-exe "tmux"))
27
(defparameter *default-tmux-tmpdir* (pathname (format nil "/tmp/tmux-~A/" (sb-posix:getuid))))
28
(defparameter *default-tmux-socket* (merge-pathnames "default" *default-tmux-tmpdir*))
31
(define-cli-tool :tmux (&rest args)
32
(let ((proc (sb-ext:run-program *tmux* (or args nil) :output t)))
33
(unless (eq 0 (sb-ext:process-exit-code proc))
34
(tmux-error "tmux command failed: ~A ~A" args))))
36
(defun spawn-tmux (&rest args)
37
(run-term (append (list "-e" "tmux") args)))
39
;;; Session > Window > Pane
40
(defstruct tmux-session
43
(windows nil :type list))
45
(defstruct tmux-window
48
(panes nil :type list)
56
(defstruct tmux-controller
57
(input nil :type (or null sb-sys:fd-stream))
58
(output nil :type (or null sb-sys:fd-stream))
59
(silent nil :type boolean))
61
(defun run-tmux-controller (&rest args)
62
(sb-ext:run-program *tmux* (or args nil) :output :stream :input :stream))
64
(defun init-tmux-controller (ctrl &rest args)
67
(if (tmux-controller-silent ctrl) "-CC" "-C")
69
(setf (tmux-controller-output ctrl) (sb-ext:process-output proc)
70
(tmux-controller-input ctrl) (sb-ext:process-input proc))
73
(defun write-tmux-line (ctrl string)
74
(write-line string (tmux-controller-input ctrl)))
76
(defun read-tmux-line (ctrl)
77
(read-line (tmux-controller-output ctrl)))
79
(defstruct tmux-command name flags args)
81
(defun parse-tmux-command (str)
82
"Parse a single TMUX-COMMAND from a string."
83
(let ((words (split-sequence #\space str)))
84
;; TODO 2024-08-06: parse for real
85
(make-tmux-command :name (car words) :args (cdr words))))
87
(defconfig tmux-config ()
88
((commands :initform nil)
89
(server-options :type hash-table)
90
(session-options :type hash-table)
91
(window-options :type hash-table)
92
(keys :type hash-table))
93
(:documentation "A CONFIG object containing the parsed content of a tmux configuration file."))
95
(defmethod make-config ((obj (eql :tmux)) &key commands server session window keys)
96
(let ((config (make-instance 'tmux-config)))
97
(when commands (setf (slot-value config 'commands) commands))
98
(when server (setf (slot-value config 'server-options) server))
99
(when session (setf (slot-value config 'session-options) session))
100
(when window (setf (slot-value config 'window-options) window))
101
(when keys (setf (slot-value config 'keys) keys))
104
(defmethod find-config ((obj (eql :tmux)) &key system user)
105
"Find a tmux configuration and load it.
107
When SYSTEM is non-nil, skip check for user config.
109
When USER is non-nil it should be the name of a user whose config will be loaded
110
from /home/USER/.tmux.conf."
112
(system (probe-file *tmux-system-config-path*))
113
(user (probe-file (format nil "/home/~A/.tmux.conf" user)))
114
(t (or (probe-file *tmux-user-config-path*) (probe-file *tmux-system-config-path*)))))
115
(obj (make-config :tmux :commands nil)))
116
(with-open-file (file path)
117
(with-output-to-string (str)
118
(loop for l = (read-line file nil nil)
120
unless (or (zerop (length l)) (equal (char l 0) #\#))
121
do (push (parse-tmux-command l) (slot-value obj 'commands)))))
124
;; (describe (find-config :tmux))
127
(defun format-tmux-string (dst fmt &rest args)
128
(apply #'format dst fmt (mapcar (lambda (a) (format nil "#{~A}" a)) args)))
130
(defvar *tmux-var-table* (make-hash-table))
132
(defmacro tmux-format (dst fmt &rest args)
133
"Format a tmux string, replacing symbols in ARGS that match a member of
134
*TMUX-VARIABLES* with their corresponding lower-case name."
135
`(format-tmux-string ,dst ,fmt
136
,@(mapcar (lambda (a)
137
(gethash (symbolicate a) *tmux-var-table* a))
139
(declaim ((vector symbol) *tmux-variables*))
140
(defvar *tmux-variables*
141
#(:active-window-index ;; Index of active window in session
142
:alternate-on ;; 1 if pane is in alternate screen
143
:alternate-saved-x ;; Saved cursor X in alternate screen
144
:alternate-saved-y ;; Saved cursor Y in alternate screen
145
:buffer-created ;; Time buffer created
146
:buffer-name ;; Name of buffer
147
:buffer-sample ;; Sample of start of buffer
148
:buffer-size ;; Size of the specified buffer in bytes
149
:client-activity ;; Time client last had activity
150
:client-cell-height ;; Height of each client cell in pixels
151
:client-cell-width ;; Width of each client cell in pixels
152
:client-control-mode ;; 1 if client is in control mode
153
:client-created ;; Time client created
154
:client-discarded ;; Bytes discarded when client behind
155
:client-flags ;; List of client flags
156
:client-height ;; Height of client
157
:client-key-table ;; Current key table
158
:client-last-session ;; Name of the client's last session
159
:client-name ;; Name of client
160
:client-pid ;; PID of client process
161
:client-prefix ;; 1 if prefix key has been pressed
162
:client-readonly ;; 1 if client is read-only
163
:client-session ;; Name of the client's session
164
:client-termfeatures ;; Terminal features of client, if any
165
:client-termname ;; Terminal name of client
166
:client-termtype ;; Terminal type of client, if available
167
:client-tty ;; Pseudo terminal of client
168
:client-uid ;; UID of client process
169
:client-user ;; User of client process
170
:client-utf8 ;; 1 if client supports UTF-8
171
:client-width ;; Width of client
172
:client-written ;; Bytes written to client
173
:command ;; Name of command in use, if any
174
:command-list-alias ;; Command alias if listing commands
175
:command-list-name ;; Command name if listing commands
176
:command-list-usage ;; Command usage if listing commands
177
:config-files ;; List of configuration files loaded
178
:copy-cursor-line ;; Line the cursor is on in copy mode
179
:copy-cursor-word ;; Word under cursor in copy mode
180
:copy-cursor-x ;; Cursor X position in copy mode
181
:copy-cursor-y ;; Cursor Y position in copy mode
182
:current-file ;; Current configuration file
183
:cursor-character ;; Character at cursor in pane
184
:cursor-flag ;; Pane cursor flag
185
:cursor-x ;; Cursor X position in pane
186
:cursor-y ;; Cursor Y position in pane
187
:history-bytes ;; Number of bytes in window history
188
:history-limit ;; Maximum window history lines
189
:history-size ;; Size of history in lines
190
:hook ;; Name of running hook, if any
191
:hook-client ;; Name of client where hook was run, if any
192
:hook-pane ;; ID of pane where hook was run, if any
193
:hook-session ;; ID of session where hook was run, if any
194
:hook-session-name ;; Name of session where hook was run, if any
195
:hook-window ;; ID of window where hook was run, if any
196
:hook-window-name ;; Name of window where hook was run, if any
197
:host ;; H Hostname of local host
198
:host-short ;; h Hostname of local host (no domain name)
199
:insert-flag ;; Pane insert flag
200
:keypad-cursor-flag ;; Pane keypad cursor flag
201
:keypad-flag ;; Pane keypad flag
202
:last-window-index ;; Index of last window in session
203
:line ;; Line number in the list
204
:mouse-all-flag ;; Pane mouse all flag
205
:mouse-any-flag ;; Pane mouse any flag
206
:mouse-button-flag ;; Pane mouse button flag
207
:mouse-hyperlink ;; Hyperlink under mouse, if any
208
:mouse-line ;; Line under mouse, if any
209
:mouse-sgr-flag ;; Pane mouse SGR flag
210
:mouse-standard-flag ;; Pane mouse standard flag
211
:mouse-status-line ;; Status line on which mouse event took place
212
:mouse-status-range ;; Range type or argument of mouse event on status line
213
:mouse-utf8-flag ;; Pane mouse UTF-8 flag
214
:mouse-word ;; Word under mouse, if any
215
:mouse-x ;; Mouse X position, if any
216
:mouse-y ;; Mouse Y position, if any
217
:next-session-id ;; Unique session ID for next new session
218
:origin-flag ;; Pane origin flag
219
:pane-active ;; 1 if active pane
220
:pane-at-bottom ;; 1 if pane is at the bottom of window
221
:pane-at-left ;; 1 if pane is at the left of window
222
:pane-at-right ;; 1 if pane is at the right of window
223
:pane-at-top ;; 1 if pane is at the top of window
224
:pane-bg ;; Pane background colour
225
:pane-bottom ;; Bottom of pane
226
:pane-current-command ;; Current command if available
227
:pane-current-path ;; Current path if available
228
:pane-dead ;; 1 if pane is dead
229
:pane-dead-signal ;; Exit signal of process in dead pane
230
:pane-dead-status ;; Exit status of process in dead pane
231
:pane-dead-time ;; Exit time of process in dead pane
232
:pane-fg ;; Pane foreground colour
233
:pane-format ;; 1 if format is for a pane
234
:pane-height ;; Height of pane
235
:pane-id ;; D Unique pane ID
236
:pane-in-mode ;; 1 if pane is in a mode
237
:pane-index ;; P Index of pane
238
:pane-input-off ;; 1 if input to pane is disabled
239
:pane-last ;; 1 if last pane
240
:pane-left ;; Left of pane
241
:pane-marked ;; 1 if this is the marked pane
242
:pane-marked-set ;; 1 if a marked pane is set
243
:pane-mode ;; Name of pane mode, if any
244
:pane-path ;; Path of pane (can be set by application)
245
:pane-pid ;; PID of first process in pane
246
:pane-pipe ;; 1 if pane is being piped
247
:pane-right ;; Right of pane
248
:pane-search-string ;; Last search string in copy mode
249
:pane-start-command ;; Command pane started with
250
:pane-start-path ;; Path pane started with
251
:pane-synchronized ;; 1 if pane is synchronized
252
:pane-tabs ;; Pane tab positions
253
:pane-title ;; T Title of pane (can be set by application)
254
:pane-top ;; Top of pane
255
:pane-tty ;; Pseudo terminal of pane
256
:pane-unseen-changes ;; 1 if there were changes in pane while in mode
257
:pane-width ;; Width of pane
259
:rectangle-toggle ;; 1 if rectangle selection is activated
260
:scroll-position ;; Scroll position in copy mode
261
:scroll-region-lower ;; Bottom of scroll region in pane
262
:scroll-region-upper ;; Top of scroll region in pane
263
:search-match ;; Search match if any
264
:search-present ;; 1 if search started in copy mode
265
:selection-active ;; 1 if selection started and changes with the cursor in copy mode
266
:selection-end-x ;; X position of the end of the selection
267
:selection-end-y ;; Y position of the end of the selection
268
:selection-present ;; 1 if selection started in copy mode
269
:selection-start-x ;; X position of the start of the selection
270
:selection-start-y ;; Y position of the start of the selection
271
:server-sessions ;; Number of sessions
272
:session-activity ;; Time of session last activity
273
:session-alerts ;; List of window indexes with alerts
274
:session-attached ;; Number of clients session is attached to
275
:session-attached-list ;; List of clients session is attached to
276
:session-created ;; Time session created
277
:session-format ;; 1 if format is for a session
278
:session-group ;; Name of session group
279
:session-group-attached ;; Number of clients sessions in group are attached to
280
:session-group-attached-list ;; List of clients sessions in group are attached to
281
:session-group-list ;; List of sessions in group
282
:session-group-many-attached ;; 1 if multiple clients attached to sessions in group
283
:session-group-size ;; Size of session group
284
:session-grouped ;; 1 if session in a group
285
:session-id ;; Unique session ID
286
:session-last-attached ;; Time session last attached
287
:session-many-attached ;; 1 if multiple clients attached
288
:session-marked ;; 1 if this session contains the marked pane
289
:session-name ;; S Name of session
290
:session-path ;; Working directory of session
291
:session-stack ;; Window indexes in most recent order
292
:session-windows ;; Number of windows in session
293
:socket-path ;; Server socket path
294
:start-time ;; Server start time
297
:version ;; Server version
298
:window-active ;; 1 if window active
299
:window-active-clients ;; Number of clients viewing this window
300
:window-active-clients-list ;; List of clients viewing this window
301
:window-active-sessions ;; Number of sessions on which this window is active
302
:window-active-sessions-list ;; List of sessions on which this window is active
303
:window-activity ;; Time of window last activity
304
:window-activity-flag ;; 1 if window has activity
305
:window-bell-flag ;; 1 if window has bell
306
:window-bigger ;; 1 if window is larger than client
307
:window-cell-height ;; Height of each cell in pixels
308
:window-cell-width ;; Width of each cell in pixels
309
:window-end-flag ;; 1 if window has the highest index
310
:window-flags ;; F Window flags with # escaped as ##
311
:window-format ;; 1 if format is for a window
312
:window-height ;; Height of window
313
:window-id ;; Unique window ID
314
:window-index ;; I Index of window
315
:window-last-flag ;; 1 if window is the last used
316
:window-layout ;; Window layout description, ignoring zoomed window panes
317
:window-linked ;; 1 if window is linked across sessions
318
:window-linked-sessions ;; Number of sessions this window is linked to
319
:window-linked-sessions-list ;; List of sessions this window is linked to
320
:window-marked-flag ;; 1 if window contains the marked pane
321
:window-name ;; W Name of window
322
:window-offset-x ;; X offset into window if larger than client
323
:window-offset-y ;; Y offset into window if larger than client
324
:window-panes ;; Number of panes in window
325
:window-raw-flags ;; Window flags with nothing escaped
326
:window-silence-flag ;; 1 if window has silence alert
327
:window-stack-index ;; Index in session most recent stack
328
:window-start-flag ;; 1 if window has the lowest index
329
:window-visible-layout ;; Window layout description, respecting zoomed window panes
330
:window-width ;; Width of window
331
:window-zoomed-flag ;; 1 if window is zoomed
332
:wrap-flag ;; Pane wrap flag
334
:popup-centre-x ;; Centered in the client
335
:popup-centre-y ;; entered in the client
336
:popup-height ;; eight of menu or popup
337
:popup-mouse-bottom ;; ottom of at the mouse
338
:popup-mouse-centre-x ;; orizontal centre at the mouse
339
:popup-mouse-centre-y ;; ertical centre at the mouse
340
:popup-mouse-top ;; op at the mouse
341
:popup-mouse-x ;; ouse X position
342
:popup-mouse-y ;; ouse Y position
343
:popup-pane-bottom ;; ottom of the pane
344
:popup-pane-left ;; eft of the pane
345
:popup-pane-right ;; ight of the pane
346
:popup-pane-top ;; op of the pane
347
:popup-status-line-y ;; bove or below the status line
348
:popup-width ;; idth of menu or popup
349
:popup-window-status-line-x ;; t the window position in status line
350
:popup-window-status-line-y)) ;; t the status line showing the window
352
(defvar *tmux-variable-names*
354
(loop for v across *tmux-variables*
355
collect (string-downcase (substitute #\_ #\- (symbol-name v))))