changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/cli/ansi.lisp

changeset 698: 96958d3eb5b0
parent: 282991a71fe5
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 ;;; lib/cli/ansi.lisp --- ANSI X3.64 Control Sequences
2 
3 ;; based on https://github.com/McParen/croatoan/tree/master
4 
5 ;; the tests for the original source are at the bottom of the file
6 ;; (interactive).
7 
8 ;;; Code:
9 (in-package :cli/ansi)
10 
11 ;;; Basic terminal control functions based on 7bit escape sequences
12 ;;; according to ANSI X3.64 / ECMA 48 / ISO/IEC 6429 / VT10X / XTerm
13 
14 ;; ECMA-6: 7bit character set 0-127
15 ;; ECMA-35: Bit notation 01/07
16 ;; ECMA-48: ANSI escape sequences
17 
18 ;; 1-char 7bit controls C0
19 ;; 1-char 8bit controls C1
20 ;; escape sequences
21 ;; 7bit CSI sequences
22 ;; 8bit CSI sequences
23 
24 ;; Acronym Character Decimal Octal Hexadecimal Code
25 ;; DEL #\rubout 127 #o177 #x7f 07/15
26 ;; ESC #\esc 27 #o33 #x1b 01/11
27 ;; SP #\space 32 #o40 #x20 02/00
28 
29 ;; code x/y = column/line
30 ;; 7bit code table = x-column 0-7 / y-line 0-15
31 
32 ;; x/y: x y
33 ;; Bit: 7 6 5 4 3 2 1
34 ;; Weight: 4 2 1 8 4 2 1
35 
36 ;; 200530 add a stream argument to every function
37 ;; add windows as gray streams
38 
39 ;;(defmacro define-control-function ())
40 ;;(defmacro define-control-sequence (name args))
41 
42 ;; ESC [ Pn1 ; Pn2 H
43 ;; CSI Pn1 ; Pn2 H
44 ;; CSI n ; m H
45 ;; CUP
46 ;; cursor-position
47 
48 ;; TODO 200530 write csi in terms of esc?
49 ;; no because CSI params are separated with ; while esc params arent separated
50 
51 ;; See 5.4 for the overall format of control sequences
52 
53 ;; Set: C1
54 ;; Section: 8.3.16
55 ;; Name: Control Sequence Introducer
56 ;; Mnemonic: CSI
57 ;; 7bit Chars: ESC [
58 ;; 7bit Byte: 01/11 05/11
59 ;; 8bit Byte: 09/11 (not used here)
60 (defparameter *csi* (coerce (list #\esc #\[) 'string)
61  "A two-character string representing the 7bit control sequence introducer CSI.")
62 
63 (defun esc (&rest params)
64  "Write an ESC control sequence. The parameters are not separated."
65  (format t "~A~{~A~}" #\esc params))
66 
67 (defun csi (final-char &rest params)
68  "Write a CSI control sequence. The params are separated by a semicolon."
69  ;; only the params are separated with ; the other chars are not separated.
70  ;; ~^; = add ; to every list item except the last
71  (format t "~A~{~A~^;~}~A" *csi* params final-char))
72 
73 ;; Sequence Syntax
74 ;; C A single character
75 ;; Ps A single numeric parameter
76 ;; Pm Several numeric parameters Ps separated by a semicolon ;
77 
78 ;;; ESC sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
79 
80 ;; Name: Reset to initial state
81 ;; Mnemonic: RIS
82 ;; Final char: c
83 ;; Final byte: 06/03
84 ;; Sequence: ESC c
85 ;; Parameters: none
86 ;; Default: none
87 ;; Reference: ANSI 5.72, ECMA 8.3.105
88 (defun reset-to-initial-state ()
89  "Reset the terminal to its initial state.
90 
91 In particular, turn on cooked and echo modes and newline translation,
92 turn off raw and cbreak modes, reset any unset special characters.
93 
94 A reset is useful after a program crashes and leaves the terminal in
95 an undefined, unusable state."
96  (esc "c"))
97 
98 (setf (fdefinition '.ris) #'reset-to-initial-state)
99 
100 ;;; CSI sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
101 
102 ;;; Cursor control functions
103 
104 ;; Name: Cursor up
105 ;; Mnemonic: CUU
106 ;; Final char: A
107 ;; Final byte: 04/01
108 ;; Sequence: CSI Pn A
109 ;; Parameters: Pn = m
110 ;; Default: Pn = 1
111 ;; Reference: ANSI 5.17, ECMA 8.3.22
112 (defun cursor-up (&optional (m 1))
113  "Move the cursor m lines up."
114  (csi "A" m))
115 
116 (setf (fdefinition '.cuu) #'cursor-up)
117 
118 ;; Name: Cursor down
119 ;; Mnemonic: CUD
120 ;; Final char: B
121 ;; Final byte: 04/02
122 ;; Sequence: CSI Pn B
123 ;; Parameters: Pn = m
124 ;; Default: Pn = 1
125 ;; Reference: ANSI 5.14, ECMA 8.3.19
126 (defun cursor-down (&optional (m 1))
127  "Move the cursor m lines down."
128  (csi "B" m))
129 
130 (setf (fdefinition '.cud) #'cursor-down)
131 
132 ;; Name: Cursor forward
133 ;; Mnemonic: CUF
134 ;; Final char: C
135 ;; Final byte: 04/03
136 ;; Sequence: CSI Pn C
137 ;; Parameters: Pn = n
138 ;; Default: Pn = 1
139 ;; Reference: ANSI 5.15, ECMA 8.3.20
140 ;; Notice: ECMA name: Cursor right
141 (defun cursor-forward (&optional (n 1))
142  "Move the cursor n columns in the forward direction (to the right)."
143  (csi "C" n))
144 
145 (setf (fdefinition '.cuf) #'cursor-forward)
146 
147 ;; Name: Cursor backward
148 ;; Mnemonic: CUB
149 ;; Final char: D
150 ;; Final byte: 04/04
151 ;; Sequence: CSI Pn D
152 ;; Parameters: Pn = n
153 ;; Default: Pn = 1
154 ;; Reference: ANSI 5.13, ECMA 8.3.18
155 ;; Notice: ECMA name: Cursor left
156 (defun cursor-backward (&optional (n 1))
157  "Move the cursor n columns in the backward direction (to the left)."
158  (csi "D" n))
159 
160 (setf (fdefinition '.cub) #'cursor-backward)
161 
162 ;; Name: Cursor next line
163 ;; Mnemonic: CNL
164 ;; Final char: E
165 ;; Final byte: 04/05
166 ;; Sequence: CSI Pn E
167 ;; Parameters: Pn = m
168 ;; Default: Pn = 1
169 ;; Reference: ANSI 5.7, ECMA 8.3.12
170 (defun cursor-next-line (&optional (m 1))
171  "Move the cursor m columns down to column 1."
172  (csi "E" m))
173 
174 (setf (fdefinition '.cnl) #'cursor-next-line)
175 
176 ;; Name: Cursor preceding line
177 ;; Mnemonic: CPL
178 ;; Final char: F
179 ;; Final byte: 04/06
180 ;; Sequence: CSI Pn F
181 ;; Parameters: Pn = m
182 ;; Default: Pn = 1
183 ;; Reference: ANSI 5.8, ECMA 8.3.13
184 (defun cursor-preceding-line (&optional (m 1))
185  "Move the cursor m columns up to column 1."
186  (csi "F" m))
187 
188 (setf (fdefinition '.cpl) #'cursor-preceding-line)
189 
190 ;; Name: Cursor horizontal absolute
191 ;; Mnemonic: CHA
192 ;; Final char: G
193 ;; Final byte: 04/07
194 ;; Sequence: CSI Pn G
195 ;; Parameters: Pn = n
196 ;; Default: Pn = 1
197 ;; Reference: ANSI 5.5, ECMA 8.3.9
198 ;; Notice: ECMA name: Cursor character absolute
199 (defun cursor-horizontal-absolute (&optional (n 1))
200  "Set the cursor horizontal position to the n-th column in the current line."
201  (csi "G" n))
202 
203 (setf (fdefinition '.cha) #'cursor-horizontal-absolute)
204 
205 ;; Name: Cursor position
206 ;; Mnemonic: CUP
207 ;; Final char: H
208 ;; Final byte: 04/08
209 ;; Sequence: CSI Pn1 ; Pn2 H
210 ;; Parameters: Pn1 = m line, Pn2 = n column
211 ;; Defaults: Pn1 = 1; Pn2 = 1
212 ;; Reference: ANSI 5.16, ECMA 8.3.21
213 (defun cursor-position (&optional (line 1) (column 1))
214  "Move the cursor to m-th line and n-th column of the screen.
215 
216 The line and column numbering is one-based.
217 
218 Without arguments, the cursor is placed in the home position (1 1),
219 the top left corner."
220  (csi "H" line column))
221 
222 (setf (fdefinition '.cup) #'cursor-position)
223 
224 ;; Name: Vertical position absolute
225 ;; Mnemonic: VPA
226 ;; Final char: d
227 ;; Final byte: 06/04
228 ;; Sequence: CSI Pn d
229 ;; Parameters: Pn = m
230 ;; Default: Pn = 1
231 ;; Reference: ANSI 5.96, ECMA 8.3.158
232 ;; Notice: ECMA name: Line position absolute
233 (defun vertical-position-absolute (&optional (m 1))
234  "Set the cursor vertical position to the m-th line in the current column."
235  (csi "d" m))
236 
237 (setf (fdefinition '.vpa) #'vertical-position-absolute)
238 
239 ;; Name: Vertical position relative
240 ;; Mnemonic: VPR
241 ;; Final char: e
242 ;; Final byte: 06/05
243 ;; Sequence: CSI Pn e
244 ;; Parameters: Pn = m
245 ;; Default: Pn = 1
246 ;; Reference: ANSI 5.97, ECMA 8.3.160
247 ;; Notice: ECMA name: Line position forward
248 (defun vertical-position-relative (&optional (m 1))
249  "Move the cursor vertical position down by m lines in the current column.
250 
251 This has the same effect as cursor-down (cud)."
252  (csi "e" m))
253 
254 (setf (fdefinition '.vpr) #'vertical-position-relative)
255 
256 ;; Name: Vertical position backward
257 ;; Mnemonic: VPB
258 ;; Final char: k
259 ;; Final byte: 06/11
260 ;; Sequence: CSI Pn k
261 ;; Parameters: Pn = m
262 ;; Default: Pn = 1
263 ;; Reference: ECMA 8.3.159
264 ;; Notice: ECMA name: Line position backward
265 (defun vertical-position-backward (&optional (m 1))
266  "Move the cursor vertical position up by m lines in the current column.
267 
268 This has the same effect as cursor-up (cuu)."
269  (csi "k" m))
270 
271 (setf (fdefinition '.vpb) #'vertical-position-backward)
272 
273 (defun save-cursor-position ()
274  "Save cursor position. Move cursor to the saved position using restore-cursor-position."
275  (csi "s"))
276 
277 (setf (fdefinition '.scosc) #'save-cursor-position)
278 
279 (defun restore-cursor-position ()
280  "Move cursor to the position saved using save-cursor-position."
281  (csi "u"))
282 
283 (setf (fdefinition '.scorc) #'restore-cursor-position)
284 
285 ;; Name: Erase in display
286 ;; Mnemonic: ED
287 ;; Final char: J
288 ;; Final byte: 04/10
289 ;; Sequence: CSI Ps J
290 ;; Parameters: Ps = mode
291 ;; Defaults: Ps = 0
292 ;; Reference: ANSI 5.29, ECMA 8.3.39
293 ;; Notice: ECMA name: Erase in page
294 (defun erase-in-display (&optional (mode 0))
295  "Erase some or all characters on the screen depending on the selected mode.
296 
297 Mode 0 (erase-below, default) erases all characters from the cursor to
298 the end of the screen.
299 
300 Mode 1 (erase-above) erases all characters from the beginning of the
301 screen to the cursor.
302 
303 Mode 2 (erase) erases all characters on the screen.
304 
305 Mode 3 (erase-saved-lines, xterm) erases all characters on the screen
306 including the scrollback buffer."
307  (csi "J" mode))
308 
309 (setf (fdefinition '.ed) #'erase-in-display)
310 
311 (defun erase-below ()
312  "Erases all characters from the cursor to the end of the screen."
313  (erase-in-display 0))
314 
315 (defun erase-above ()
316  "Erases all characters from the beginning of the screen to the cursor."
317  (erase-in-display 1))
318 (eval-always
319  (defun erase ()
320  "Erase all characters on the screen."
321  (erase-in-display 2)))
322 
323 (defun erase-saved-lines ()
324  "Erase all characters on the screen including the scrollback buffer."
325  (erase-in-display 3))
326 
327 ;; Name: Erase in line
328 ;; Mnemonic: EL
329 ;; Final char: K
330 ;; Final byte: 04/11
331 ;; Sequence: CSI Ps K
332 ;; Parameters: Ps = mode
333 ;; Defaults: Ps = 0
334 ;; Reference: ANSI 5.31, ECMA 8.3.41
335 (defun erase-in-line (&optional (mode 0))
336  "Erase some or all characters on the current line depending on the selected mode.
337 
338 Mode 0 (erase-right, default) erases all characters from the cursor to
339 the end of the line.
340 
341 Mode 1 (erase-left) erases all characters from the beginning of the
342 line to the cursor.
343 
344 Mode 2 (erase-line) erases all characters on the line."
345  (csi "K" mode))
346 
347 (setf (fdefinition '.el) #'erase-in-line)
348 
349 (defun erase-right ()
350  "Erases all characters from the cursor to the end of the line."
351  (erase-in-line 0))
352 
353 (defun erase-left ()
354  "Erases all characters from the beginning of the line to the cursor."
355  (erase-in-line 1))
356 
357 (defun erase-line ()
358  "Erases all characters on the current line."
359  (erase-in-line 2))
360 
361 ;; Name: Select Graphic Rendition
362 ;; Mnemonic: SGR
363 ;; Final char: m
364 ;, Final byte: 06/13
365 ;; Sequence: CSI Pm m
366 ;; Parameters: See documentation string.
367 ;; Defaults: Pm = 0
368 ;; Reference: ANSI 5.77, ECMA 8.3.117
369 (defun select-graphic-rendition (&rest params)
370  "Set character attributes and foreground and background colors.
371 
372  0 turn off all previous attributes, set normal, default rendition
373 
374  1 bold, increased intensity
375  2 faint, dim, decreased intensity
376  3 italic, standout
377  4 single underline
378  5 slow blinking
379  6 rapid blinking
380  7 negative, reverse image
381  8 invisible, hidden, concealed
382  9 crossed-out
383 21 double underline
384 
385 22 turn off bold and faint/dim, set normal intensity
386 23 turn off italic, standout
387 24 turn off single, double underline
388 25 turn off blinking
389 27 turn off negative, reverse image
390 28 turn off hidden, invisible
391 29 turn off crossed-out
392 
393 Foreground colors:
394 
395 30 black
396 31 red
397 32 green
398 33 yellow
399 34 blue
400 35 magenta
401 36 cyan
402 37 white
403 39 default foreground color
404 
405 38 5 n set the color n from a default 256-color palette
406 38 2 r g b set the color by directly giving its RGB components
407 
408 Background colors:
409 
410 40 black
411 41 red
412 42 green
413 43 yellow
414 44 blue
415 45 magenta
416 46 cyan
417 47 white
418 49 default background color
419 
420 48 5 n set the color n from a default 256-color palette
421 48 2 r g b set the color by directly giving its RGB components"
422  (apply #'csi "m" params))
423 
424 (setf (fdefinition '.sgr) #'select-graphic-rendition)
425 
426 ;; Name: Device Status Report
427 ;; Mnemonic: DSR
428 ;; Final char: n
429 ;, Final byte: 06/14
430 ;; Sequence: CSI Ps n
431 ;; Parameters: Ps = status command to send to the terminal
432 ;; Defaults: n = 6
433 ;; Reference: ECMA 8.3.35
434 (defun device-status-report (&optional (n 6))
435  "The terminal responds by sending a Cursor Position Report (CPR) to the standard input
436 as if we read it through read-line from the user."
437  (csi "n" n))
438 
439 (setf (fdefinition '.dsr) #'device-status-report)
440 
441 ;; Name: Cursor Position Report
442 ;; Mnemonic: CPR
443 ;; Final char: R
444 ;, Final byte: 05/02
445 ;; Sequence: CSI Pm ; Pn R
446 ;; Parameters: Pm = line, Pn = column
447 ;; Defaults: Pm = 1, Pn = 1
448 ;; Reference: ECMA 8.3.14
449 ;; Description: Response of the terminal to a Device Status Report (DSR)
450 ;; sent to be read from the standard input.
451 
452 ;;; DEC private mode
453 
454 ;; Set (enable, turn on)
455 
456 (defun dec-private-mode-set (mode)
457  "Set (turn on, enable) a DEC private mode.
458 
459 Implemented modes:
460 
461  25 show or hide the cursor
462 1047 alternate or normal screen buffer"
463  (csi "h" "?" mode))
464 
465 (setf (fdefinition '.decset) #'dec-private-mode-set)
466 
467 (defun show-cursor ()
468  (dec-private-mode-set 25))
469 
470 (defun use-alternate-screen-buffer ()
471  (dec-private-mode-set 1047))
472 
473 ;; Reset (disable, turn off)
474 
475 (defun dec-private-mode-reset (mode)
476  "Reset (turn off, disable) a DEC private mode."
477  (csi "l" "?" mode))
478 
479 (setf (fdefinition '.decrst) #'dec-private-mode-reset)
480 
481 (defun hide-cursor ()
482  (dec-private-mode-reset 25))
483 
484 (defun use-normal-screen-buffer ()
485  (dec-private-mode-reset 1047))
486 
487 ;;; Common
488 (defun home ()
489  "Move the cursor to the home position, the top left corner."
490  (cursor-position))
491 
492 (defun clear ()
493  "Erase the whole screen, then move the cursor to the home position."
494  (erase)
495  (home))
496 
497 ;;; STTY
498 #|
499 
500 From /usr/include/x86_64-linux-gnu/bits/termios.h
501 
502 typedef unsigned char cc_t;
503 typedef unsigned int speed_t;
504 typedef unsigned int tcflag_t;
505 
506 #define NCCS 32
507 
508 struct termios
509  {
510  tcflag_t c_iflag; /* input mode flags */
511  tcflag_t c_oflag; /* output mode flags */
512  tcflag_t c_cflag; /* control mode flags */
513  tcflag_t c_lflag; /* local mode flags */
514  cc_t c_line; /* line discipline */
515  cc_t c_cc[NCCS]; /* control characters */
516  speed_t c_ispeed; /* input speed */
517  speed_t c_ospeed; /* output speed */
518  };
519 
520 |#
521 
522 (defun mode-type (mode)
523  "Return the keyword designating the type of the terminal mode:
524 
525 :input, :output, :control, :local, :character, :combination."
526  (let ((iflags
527  '(:IGNBRK :BRKINT :IGNPAR :PARMRK :INPCK :ISTRIP :INLCR :IGNCR
528  :ICRNL :IUCLC :IXON :IXANY :IXOFF :IMAXBEL :IUTF8))
529  (oflags
530  '(:OPOST :OLCUC :ONLCR :OCRNL :ONOCR :ONLRET :OFILL :OFDEL :NLDLY
531  :NL0 :NL1 :CRDLY :CR0 :CR1 :CR2 :CR3 :TABDLY :TAB0 :TAB1 :TAB2
532  :TAB3 :BSDLY :BS0 :BS1 :FFDLY :FF0 :FF1 :VTDLY :VT0 :VT1 :XTABS))
533  (cflags
534  '(:CBAUD :B0 :B50 :B75 :B110 :B134 :B150 :B200 :B300 :B600 :B1200
535  :B1800 :B2400 :B4800 :B9600 :B19200 :B38400 :CSIZE :CS5 :CS6
536  :CS7 :CS8 :CSTOPB :CREAD :PARENB :PARODD :HUPCL :CLOCAL :CBAUDEX
537  :B57600 :B115200 :B230400 :B460800 :B500000 :B576000 :B921600
538  :B1000000 :B1152000 :B1500000 :B2000000 :B2500000 :B3000000
539  :B3500000 :B4000000 :CIBAUD :CMSPAR :CRTSCTS))
540  (lflags
541  '(:ISIG :ICANON :XCASE :ECHO :ECHOE :ECHOK :ECHONL :NOFLSH :TOSTOP
542  :ECHOCTL :ECHOPRT :ECHOKE :FLUSHO :PENDIN :IEXTEN :EXTPROC))
543  (cc
544  '(:VINTR :VQUIT :VERASE :VKILL :VEOF :VTIME :VMIN :VSWTC :VSTART
545  :VSTOP :VSUSP :VEOL :VREPRINT :VDISCARD :VWERASE :VLNEXT :VEOL2))
546  (combination
547  '(:COOKED :RAW)))
548  (cond ((member mode iflags) :iflag)
549  ((member mode oflags) :oflag)
550  ((member mode cflags) :cflag)
551  ((member mode lflags) :lflag)
552  ((member mode cc) :cc)
553  ((member mode combination) :combination)
554  (t nil))))
555 
556 (defun mode-accessor (mode)
557  "Return the appropriate accessor depending on the mode type."
558  (case (mode-type mode)
559  (:iflag 'sb-posix:termios-iflag)
560  (:oflag 'sb-posix:termios-oflag)
561  (:cflag 'sb-posix:termios-cflag)
562  (:lflag 'sb-posix:termios-lflag)
563  (:cc 'sb-posix:termios-cc)
564  (t nil)))
565 
566 (defun stream-fd (stream)
567  "Return the posix file descriptor associated with the lisp stream."
568  (let ((stream (typecase stream
569  ;; *standard-input*, *standard-output*, *terminal-io*, etc.
570  (synonym-stream (symbol-value (synonym-stream-symbol stream)))
571  ;; sb-sys:*stdin*, *stdout*, *tty*, etc.
572  (sb-sys:fd-stream stream))))
573  ;; requires a fd-stream, not a synonym-stream
574  (sb-posix:file-descriptor stream)))
575 
576 ;; ncurses:
577 ;; cooked: ixon brkint parmrk
578 ;; raw = -cooked -icanon -isig -iexten
579 ;; noraw = cooked icanon isig iexten
580 
581 ;; stty:
582 ;; raw = -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost -isig -iuclc -ixany -imaxbel -xcase min 1 time 0
583 ;; cooked = brkint ignpar istrip icrnl ixon icanon opost isig eof ^D eol 0
584 
585 (defparameter *combinations*
586  '(((:raw t)
587  :ignbrk nil :brkint nil :ignpar nil :parmrk nil :inpck nil :istrip nil
588  :inlcr nil :igncr nil :icrnl nil :ixon nil :ixoff nil :icanon nil
589  :opost nil :isig nil
590  ;; not available in sb-posix:
591  ;;:iuclc nil :ixany nil :imaxbel nil :xcase nil
592  :iexten nil :csize nil :parenb nil :vmin 1 :vtime 0)
593  ((:raw nil)
594  :brkint t :ignpar t :istrip t :icrnl t :ixon t :icanon t :opost t
595  :isig t :veol 0)
596  ((:cooked t)
597  :raw nil)
598  ((:cooked nil)
599  :raw t)))
600 
601 (defun mode-combination (mode value)
602  "If mode is a combination, return its contents as a plist."
603  (cdr (assoc (list mode value) *combinations* :test #'equal)))
604 
605 (defun set-termios-flag (termios mode value)
606  "Take a termios struct, a flag and a value, update the termios struct in place."
607  (let* (;; get the appropriate accessor for the flag
608  (read-flag (fdefinition (mode-accessor mode)))
609  (write-flag (fdefinition (list 'setf (mode-accessor mode))))
610  ;; get the current bitmask
611  (old-flag (funcall read-flag termios))
612  ;; get the new mode bitmask from the constants in sb-posix
613  ;; TODO 200609: what to do with constants not available in sb-posix?
614  (new-flag (symbol-value (find-symbol (symbol-name mode) 'sb-posix))))
615  ;; write the new values to the termios struct
616  ;; (funcall #'(setf acc) val obj) = (setf (acc obj) val)
617  (funcall write-flag
618  ;; the value for a flag can be t or nil
619  (if value
620  ;; if t, add new flag to old flag
621  (logior old-flag new-flag)
622  ;; if nil, remove new flag from old
623  (logand old-flag (lognot new-flag)))
624  termios)))
625 
626 (defun set-termios-param (termios mode value)
627  "Take a termios struct, a cc key and a value, update the termios struct in place."
628  ;; the mode flags are 32bit unsigned integers
629  ;; get the cc array
630  (let ((cc-array (sb-posix:termios-cc termios))
631  ;; the param name translates to an array index
632  (cc-param (symbol-value (find-symbol (symbol-name mode) 'sb-posix))))
633  (setf (aref cc-array cc-param) value)))
634 
635 (defun update-termios (termios modes)
636  "Update the settings in the termios struct in place with the values in modes plist."
637  (loop for (mode value) on modes by #'cddr do
638  (case (mode-type mode)
639  (:combination
640  (update-termios termios (mode-combination mode value)))
641  ((:iflag :oflag :cflag :lflag)
642  (set-termios-flag termios mode value))
643  (:cc
644  (set-termios-param termios mode value)))))
645 
646 ;; Examples: t06, t07
647 (defun set-tty-mode (stream &rest modes)
648  "Enable or disable one or more tty modes."
649  (let* ((stream (if (eq stream t) *standard-input* stream))
650  (fd (stream-fd stream))
651  ;; get the current attributes in a termios object
652  (termios (sb-posix:tcgetattr fd)))
653  ;; Update the termios struct in place.
654  (update-termios termios modes)
655  ;; write the new termios struct to the fd of the tty now.
656  (sb-posix:tcsetattr fd sb-posix:tcsanow termios)))