changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/cli/tools/tmux.lisp

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