Coverage report: /home/ellis/comp/core/lib/cli/ansi.lisp
Kind | Covered | All | % |
expression | 14 | 275 | 5.1 |
branch | 0 | 14 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; lib/cli/ansi.lisp --- ANSI X3.64 Control Sequences
3
;; based on https://github.com/McParen/croatoan/tree/master
8
;;; Basic terminal control functions based on 7bit escape sequences
9
;;; according to ANSI X3.64 / ECMA 48 / ISO/IEC 6429 / VT10X / XTerm
11
;; ECMA-6: 7bit character set 0-127
12
;; ECMA-35: Bit notation 01/07
13
;; ECMA-48: ANSI escape sequences
15
;; 1-char 7bit controls C0
16
;; 1-char 8bit controls C1
21
;; Acronym Character Decimal Octal Hexadecimal Code
22
;; DEL #\rubout 127 #o177 #x7f 07/15
23
;; ESC #\esc 27 #o33 #x1b 01/11
24
;; SP #\space 32 #o40 #x20 02/00
26
;; code x/y = column/line
27
;; 7bit code table = x-column 0-7 / y-line 0-15
31
;; Weight: 4 2 1 8 4 2 1
39
;; See 5.4 for the overall format of control sequences
43
;; Name: Control Sequence Introducer
46
;; 7bit Byte: 01/11 05/11
47
;; 8bit Byte: 09/11 (not used here)
48
(defparameter *csi* (coerce (list #\esc #\[) 'string)
49
"A two-character string representing the 7bit control sequence introducer CSI.")
51
(defun esc (&rest params)
52
"Write an ESC control sequence. The parameters are not separated."
53
(format t "~A~{~A~}" #\esc params))
55
(defun csi (final-char &rest params)
56
"Write a CSI control sequence. The params are separated by a semicolon."
57
;; only the params are separated with ; the other chars are not separated.
58
;; ~^; = add ; to every list item except the last
59
(format t "~A~{~A~^;~}~A" *csi* params final-char))
62
;; C A single character
63
;; Ps A single numeric parameter
64
;; Pm Several numeric parameters Ps separated by a semicolon ;
66
;;; ESC sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
68
;; Name: Reset to initial state
75
;; Reference: ANSI 5.72, ECMA 8.3.105
77
"Reset the terminal to its initial state.
79
In particular, turn on cooked and echo modes and newline translation,
80
turn off raw and cbreak modes, reset any unset special characters.
82
A reset is useful after a program crashes and leaves the terminal in
83
an undefined, unusable state."
86
;;; CSI sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
88
;;; Cursor control functions
97
;; Reference: ANSI 5.17, ECMA 8.3.22
98
(defun .cuu (&optional (m 1))
99
"Move the cursor m lines up."
106
;; Sequence: CSI Pn B
107
;; Parameters: Pn = m
109
;; Reference: ANSI 5.14, ECMA 8.3.19
110
(defun .cud (&optional (m 1))
111
"Move the cursor m lines down."
114
;; Name: Cursor forward
118
;; Sequence: CSI Pn C
119
;; Parameters: Pn = n
121
;; Reference: ANSI 5.15, ECMA 8.3.20
122
;; Notice: ECMA name: Cursor right
123
(defun .cuf (&optional (n 1))
124
"Move the cursor n columns in the forward direction (to the right)."
127
;; Name: Cursor backward
131
;; Sequence: CSI Pn D
132
;; Parameters: Pn = n
134
;; Reference: ANSI 5.13, ECMA 8.3.18
135
;; Notice: ECMA name: Cursor left
136
(defun .cub (&optional (n 1))
137
"Move the cursor n columns in the backward direction (to the left)."
140
;; Name: Cursor next line
144
;; Sequence: CSI Pn E
145
;; Parameters: Pn = m
147
;; Reference: ANSI 5.7, ECMA 8.3.12
148
(defun .cnl (&optional (m 1))
149
"Move the cursor m columns down to column 1."
152
;; Name: Cursor preceding line
156
;; Sequence: CSI Pn F
157
;; Parameters: Pn = m
159
;; Reference: ANSI 5.8, ECMA 8.3.13
160
(defun .cpl (&optional (m 1))
161
"Move the cursor m columns up to column 1."
164
;; Name: Cursor horizontal absolute
168
;; Sequence: CSI Pn G
169
;; Parameters: Pn = n
171
;; Reference: ANSI 5.5, ECMA 8.3.9
172
;; Notice: ECMA name: Cursor character absolute
173
(defun .cha (&optional (n 1))
174
"Set the cursor horizontal position to the n-th column in the current line."
177
;; Name: Cursor position
181
;; Sequence: CSI Pn1 ; Pn2 H
182
;; Parameters: Pn1 = m line, Pn2 = n column
183
;; Defaults: Pn1 = 1; Pn2 = 1
184
;; Reference: ANSI 5.16, ECMA 8.3.21
185
(defun .cup (&optional (line 1) (column 1))
186
"Move the cursor to m-th line and n-th column of the screen.
188
The line and column numbering is one-based.
190
Without arguments, the cursor is placed in the home position (1 1),
191
the top left corner."
192
(csi "H" line column))
194
;; Name: Vertical position absolute
198
;; Sequence: CSI Pn d
199
;; Parameters: Pn = m
201
;; Reference: ANSI 5.96, ECMA 8.3.158
202
;; Notice: ECMA name: Line position absolute
203
(defun .vpa (&optional (m 1))
204
"Set the cursor vertical position to the m-th line in the current column."
207
;; Name: Vertical position relative
211
;; Sequence: CSI Pn e
212
;; Parameters: Pn = m
214
;; Reference: ANSI 5.97, ECMA 8.3.160
215
;; Notice: ECMA name: Line position forward
216
(defun .vpr (&optional (m 1))
217
"Move the cursor vertical position down by m lines in the current column.
219
This has the same effect as cursor-down (cud)."
222
;; Name: Vertical position backward
226
;; Sequence: CSI Pn k
227
;; Parameters: Pn = m
229
;; Reference: ECMA 8.3.159
230
;; Notice: ECMA name: Line position backward
231
(defun .vpb (&optional (m 1))
232
"Move the cursor vertical position up by m lines in the current column.
234
This has the same effect as cursor-up (cuu)."
238
"Save cursor position. Move cursor to the saved position using .SCORC."
242
"Move cursor to the position saved using .SCOSC."
245
;; Name: Erase in display
249
;; Sequence: CSI Ps J
250
;; Parameters: Ps = mode
252
;; Reference: ANSI 5.29, ECMA 8.3.39
253
;; Notice: ECMA name: Erase in page
254
(defun .ed (&optional (mode 0))
255
"Erase some or all characters on the screen depending on the selected mode.
257
Mode 0 (erase-below, default) erases all characters from the cursor to
258
the end of the screen.
260
Mode 1 (erase-above) erases all characters from the beginning of the
261
screen to the cursor.
263
Mode 2 (erase) erases all characters on the screen.
265
Mode 3 (erase-saved-lines, xterm) erases all characters on the screen
266
including the scrollback buffer."
269
(defun erase-below ()
270
"Erases all characters from the cursor to the end of the screen."
273
(defun erase-above ()
274
"Erases all characters from the beginning of the screen to the cursor."
278
"Erase all characters on the screen."
281
(defun erase-saved-lines ()
282
"Erase all characters on the screen including the scrollback buffer."
285
;; Name: Erase in line
289
;; Sequence: CSI Ps K
290
;; Parameters: Ps = mode
292
;; Reference: ANSI 5.31, ECMA 8.3.41
293
(defun .el (&optional (mode 0))
294
"Erase some or all characters on the current line depending on the selected mode.
296
Mode 0 (erase-right, default) erases all characters from the cursor to
299
Mode 1 (erase-left) erases all characters from the beginning of the
302
Mode 2 (erase-line) erases all characters on the line."
305
(defun erase-right ()
306
"Erases all characters from the cursor to the end of the line."
310
"Erases all characters from the beginning of the line to the cursor."
314
"Erases all characters on the current line."
317
;; Name: Select Graphic Rendition
321
;; Sequence: CSI Pm m
322
;; Parameters: See documentation string.
324
;; Reference: ANSI 5.77, ECMA 8.3.117
325
(defun .sgr (&rest params)
326
"Set character attributes and foreground and background colors.
328
0 turn off all previous attributes, set normal, default rendition
330
1 bold, increased intensity
331
2 faint, dim, decreased intensity
336
7 negative, reverse image
337
8 invisible, hidden, concealed
341
22 turn off bold and faint/dim, set normal intensity
342
23 turn off italic, standout
343
24 turn off single, double underline
345
27 turn off negative, reverse image
346
28 turn off hidden, invisible
347
29 turn off crossed-out
359
39 default foreground color
361
38 5 n set the color n from a default 256-color palette
362
38 2 r g b set the color by directly giving its RGB components
374
49 default background color
376
48 5 n set the color n from a default 256-color palette
377
48 2 r g b set the color by directly giving its RGB components"
378
(apply #'csi "m" params))
380
;; Name: Device Status Report
384
;; Sequence: CSI Ps n
385
;; Parameters: Ps = status command to send to the terminal
387
;; Reference: ECMA 8.3.35
388
(defun .dsr (&optional (n 6))
389
"The terminal responds by sending a Cursor Position Report (CPR) to the standard input
390
as if we read it through read-line from the user."
393
;; Name: Cursor Position Report
397
;; Sequence: CSI Pm ; Pn R
398
;; Parameters: Pm = line, Pn = column
399
;; Defaults: Pm = 1, Pn = 1
400
;; Reference: ECMA 8.3.14
401
;; Description: Response of the terminal to a Device Status Report (DSR)
402
;; sent to be read from the standard input.
406
;; Set (enable, turn on)
408
(defun .decset (mode)
409
"Set (turn on, enable) a DEC private mode.
413
25 show or hide the cursor
414
1047 alternate or normal screen buffer"
417
(defun show-cursor ()
420
(defun use-alternate-screen-buffer ()
423
;; Reset (disable, turn off)
425
(defun .decrst (mode)
426
"Reset (turn off, disable) a DEC private mode."
429
(defun hide-cursor ()
432
(defun use-normal-screen-buffer ()
437
"Move the cursor to the home position, the top left corner."
441
"Erase the whole screen, then move the cursor to the home position."
448
From /usr/include/x86_64-linux-gnu/bits/termios.h
450
typedef unsigned char cc_t;
451
typedef unsigned int speed_t;
452
typedef unsigned int tcflag_t;
458
tcflag_t c_iflag; /* input mode flags */
459
tcflag_t c_oflag; /* output mode flags */
460
tcflag_t c_cflag; /* control mode flags */
461
tcflag_t c_lflag; /* local mode flags */
462
cc_t c_line; /* line discipline */
463
cc_t c_cc[NCCS]; /* control characters */
464
speed_t c_ispeed; /* input speed */
465
speed_t c_ospeed; /* output speed */
470
(defun mode-type (mode)
471
"Return the keyword designating the type of the terminal mode:
473
:input, :output, :control, :local, :character, :combination."
475
'(:IGNBRK :BRKINT :IGNPAR :PARMRK :INPCK :ISTRIP :INLCR :IGNCR
476
:ICRNL :IUCLC :IXON :IXANY :IXOFF :IMAXBEL :IUTF8))
478
'(:OPOST :OLCUC :ONLCR :OCRNL :ONOCR :ONLRET :OFILL :OFDEL :NLDLY
479
:NL0 :NL1 :CRDLY :CR0 :CR1 :CR2 :CR3 :TABDLY :TAB0 :TAB1 :TAB2
480
:TAB3 :BSDLY :BS0 :BS1 :FFDLY :FF0 :FF1 :VTDLY :VT0 :VT1 :XTABS))
482
'(:CBAUD :B0 :B50 :B75 :B110 :B134 :B150 :B200 :B300 :B600 :B1200
483
:B1800 :B2400 :B4800 :B9600 :B19200 :B38400 :CSIZE :CS5 :CS6
484
:CS7 :CS8 :CSTOPB :CREAD :PARENB :PARODD :HUPCL :CLOCAL :CBAUDEX
485
:B57600 :B115200 :B230400 :B460800 :B500000 :B576000 :B921600
486
:B1000000 :B1152000 :B1500000 :B2000000 :B2500000 :B3000000
487
:B3500000 :B4000000 :CIBAUD :CMSPAR :CRTSCTS))
489
'(:ISIG :ICANON :XCASE :ECHO :ECHOE :ECHOK :ECHONL :NOFLSH :TOSTOP
490
:ECHOCTL :ECHOPRT :ECHOKE :FLUSHO :PENDIN :IEXTEN :EXTPROC))
492
'(:VINTR :VQUIT :VERASE :VKILL :VEOF :VTIME :VMIN :VSWTC :VSTART
493
:VSTOP :VSUSP :VEOL :VREPRINT :VDISCARD :VWERASE :VLNEXT :VEOL2))
496
(cond ((member mode iflags) :iflag)
497
((member mode oflags) :oflag)
498
((member mode cflags) :cflag)
499
((member mode lflags) :lflag)
500
((member mode cc) :cc)
501
((member mode combination) :combination)
504
(defun mode-accessor (mode)
505
"Return the appropriate accessor depending on the mode type."
506
(case (mode-type mode)
507
(:iflag 'sb-posix:termios-iflag)
508
(:oflag 'sb-posix:termios-oflag)
509
(:cflag 'sb-posix:termios-cflag)
510
(:lflag 'sb-posix:termios-lflag)
511
(:cc 'sb-posix:termios-cc)
514
(defun stream-fd (stream)
515
"Return the posix file descriptor associated with the lisp stream."
518
;; *standard-input*, *standard-output*, *terminal-io*, etc.
519
(synonym-stream (sb-sys:fd-stream-fd (symbol-value (synonym-stream-symbol stream))))
520
;; sb-sys:*stdin*, *stdout*, *tty*, etc.
521
(file-stream (sb-sys:fd-stream-fd stream))
522
(t (sb-sys:fd-stream-fd (symbol-value (synonym-stream-symbol *standard-input*))))))
525
;; cooked: ixon brkint parmrk
526
;; raw = -cooked -icanon -isig -iexten
527
;; noraw = cooked icanon isig iexten
530
;; raw = -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -icanon -opost -isig -iuclc -ixany -imaxbel -xcase min 1 time 0
531
;; cooked = brkint ignpar istrip icrnl ixon icanon opost isig eof ^D eol 0
533
(defparameter *combinations*
535
:ignbrk nil :brkint nil :ignpar nil :parmrk nil :inpck nil :istrip nil
536
:inlcr nil :igncr nil :icrnl nil :ixon nil :ixoff nil :icanon nil
538
;; not available in sb-posix:
539
;;:iuclc nil :ixany nil :imaxbel nil :xcase nil
540
:iexten nil :csize nil :parenb nil :vmin 1 :vtime 0)
542
:brkint t :ignpar t :istrip t :icrnl t :ixon t :icanon t :opost t
549
(defun mode-combination (mode value)
550
"If mode is a combination, return its contents as a plist."
551
(cdr (assoc (list mode value) *combinations* :test #'equal)))
553
(defun set-termios-flag (termios mode value)
554
"Take a termios struct, a flag and a value, update the termios struct in place."
555
(let* (;; get the appropriate accessor for the flag
556
(read-flag (fdefinition (mode-accessor mode)))
557
(write-flag (fdefinition (list 'setf (mode-accessor mode))))
558
;; get the current bitmask
559
(old-flag (funcall read-flag termios))
560
;; get the new mode bitmask from the constants in sb-posix
561
;; TODO 200609: what to do with constants not available in sb-posix?
562
(new-flag (symbol-value (find-symbol (symbol-name mode) 'sb-posix))))
563
;; write the new values to the termios struct
564
;; (funcall #'(setf acc) val obj) = (setf (acc obj) val)
566
;; the value for a flag can be t or nil
568
;; if t, add new flag to old flag
569
(logior old-flag new-flag)
570
;; if nil, remove new flag from old
571
(logand old-flag (lognot new-flag)))
574
(defun set-termios-param (termios mode value)
575
"Take a termios struct, a cc key and a value, update the termios struct in place."
576
;; the mode flags are 32bit unsigned integers
578
(let ((cc-array (sb-posix:termios-cc termios))
579
;; the param name translates to an array index
580
(cc-param (symbol-value (find-symbol (symbol-name mode) 'sb-posix))))
581
(setf (aref cc-array cc-param) value)))
583
(defun update-termios (termios modes)
584
"Update the settings in the termios struct in place with the values in modes plist."
585
(loop for (mode value) on modes by #'cddr do
586
(case (mode-type mode)
588
(update-termios termios (mode-combination mode value)))
589
((:iflag :oflag :cflag :lflag)
590
(set-termios-flag termios mode value))
592
(set-termios-param termios mode value)))))
594
;; Examples: t06, t07
595
(defun set-tty-mode (stream &rest modes)
596
"Enable or disable one or more tty modes."
597
(let* ((stream (if (eq stream t) *standard-input* stream))
598
(fd (stream-fd stream))
599
;; get the current attributes in a termios object
600
(termios (sb-posix:tcgetattr fd)))
601
;; Update the termios struct in place.
602
(print (update-termios termios modes))
603
;; write the new termios struct to the fd of the tty now.
604
(sb-alien:with-alien ((term sb-posix::alien-termios))
605
(sb-posix::termios-to-alien termios (sb-alien:addr term))
606
(tcsetattr* fd sb-posix:tcsanow (sb-alien:addr term)))))