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 3 ;; based on https://github.com/McParen/croatoan/tree/master 5 ;; the tests for the original source are at the bottom of the file 11 ;;; Basic terminal control functions based on 7bit escape sequences 12 ;;; according to ANSI X3.64 / ECMA 48 / ISO/IEC 6429 / VT10X / XTerm 14 ;; ECMA-6: 7bit character set 0-127 15 ;; ECMA-35: Bit notation 01/07 16 ;; ECMA-48: ANSI escape sequences 18 ;; 1-char 7bit controls C0 19 ;; 1-char 8bit controls C1 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 29 ;; code x/y = column/line 30 ;; 7bit code table = x-column 0-7 / y-line 0-15 34 ;; Weight: 4 2 1 8 4 2 1 36 ;; 200530 add a stream argument to every function 37 ;; add windows as gray streams 39 ;;(defmacro define-control-function ()) 40 ;;(defmacro define-control-sequence (name args)) 48 ;; TODO 200530 write csi in terms of esc? 49 ;; no because CSI params are separated with ; while esc params arent separated 51 ;; See 5.4 for the overall format of control sequences 55 ;; Name: Control Sequence Introducer 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.") 63 (defun esc (&rest params) 64 "Write an ESC control sequence. The parameters are not separated." 65 (format t "~A~{~A~}" #\esc params)) 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)) 74 ;; C A single character 75 ;; Ps A single numeric parameter 76 ;; Pm Several numeric parameters Ps separated by a semicolon ; 78 ;;; ESC sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 80 ;; Name: Reset to initial state 87 ;; Reference: ANSI 5.72, ECMA 8.3.105 88 (defun reset-to-initial-state () 89 "Reset the terminal to its initial state. 91 In particular, turn on cooked and echo modes and newline translation, 92 turn off raw and cbreak modes, reset any unset special characters. 94 A reset is useful after a program crashes and leaves the terminal in 95 an undefined, unusable state." 98 (setf (fdefinition '.ris) #'reset-to-initial-state) 100 ;;; CSI sequences ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 102 ;;; Cursor control functions 108 ;; Sequence: CSI Pn A 109 ;; Parameters: Pn = m 111 ;; Reference: ANSI 5.17, ECMA 8.3.22 112 (defun cursor-up (&optional (m 1)) 113 "Move the cursor m lines up." 116 (setf (fdefinition '.cuu) #'cursor-up) 122 ;; Sequence: CSI Pn B 123 ;; Parameters: Pn = m 125 ;; Reference: ANSI 5.14, ECMA 8.3.19 126 (defun cursor-down (&optional (m 1)) 127 "Move the cursor m lines down." 130 (setf (fdefinition '.cud) #'cursor-down) 132 ;; Name: Cursor forward 136 ;; Sequence: CSI Pn C 137 ;; Parameters: Pn = n 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)." 145 (setf (fdefinition '.cuf) #'cursor-forward) 147 ;; Name: Cursor backward 151 ;; Sequence: CSI Pn D 152 ;; Parameters: Pn = n 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)." 160 (setf (fdefinition '.cub) #'cursor-backward) 162 ;; Name: Cursor next line 166 ;; Sequence: CSI Pn E 167 ;; Parameters: Pn = m 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." 174 (setf (fdefinition '.cnl) #'cursor-next-line) 176 ;; Name: Cursor preceding line 180 ;; Sequence: CSI Pn F 181 ;; Parameters: Pn = m 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." 188 (setf (fdefinition '.cpl) #'cursor-preceding-line) 190 ;; Name: Cursor horizontal absolute 194 ;; Sequence: CSI Pn G 195 ;; Parameters: Pn = n 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." 203 (setf (fdefinition '.cha) #'cursor-horizontal-absolute) 205 ;; Name: Cursor position 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. 216 The line and column numbering is one-based. 218 Without arguments, the cursor is placed in the home position (1 1), 219 the top left corner." 220 (csi "H" line column)) 222 (setf (fdefinition '.cup) #'cursor-position) 224 ;; Name: Vertical position absolute 228 ;; Sequence: CSI Pn d 229 ;; Parameters: Pn = m 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." 237 (setf (fdefinition '.vpa) #'vertical-position-absolute) 239 ;; Name: Vertical position relative 243 ;; Sequence: CSI Pn e 244 ;; Parameters: Pn = m 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. 251 This has the same effect as cursor-down (cud)." 254 (setf (fdefinition '.vpr) #'vertical-position-relative) 256 ;; Name: Vertical position backward 260 ;; Sequence: CSI Pn k 261 ;; Parameters: Pn = m 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. 268 This has the same effect as cursor-up (cuu)." 271 (setf (fdefinition '.vpb) #'vertical-position-backward) 273 (defun save-cursor-position () 274 "Save cursor position. Move cursor to the saved position using restore-cursor-position." 277 (setf (fdefinition '.scosc) #'save-cursor-position) 279 (defun restore-cursor-position () 280 "Move cursor to the position saved using save-cursor-position." 283 (setf (fdefinition '.scorc) #'restore-cursor-position) 285 ;; Name: Erase in display 289 ;; Sequence: CSI Ps J 290 ;; Parameters: Ps = mode 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. 297 Mode 0 (erase-below, default) erases all characters from the cursor to 298 the end of the screen. 300 Mode 1 (erase-above) erases all characters from the beginning of the 301 screen to the cursor. 303 Mode 2 (erase) erases all characters on the screen. 305 Mode 3 (erase-saved-lines, xterm) erases all characters on the screen 306 including the scrollback buffer." 309 (setf (fdefinition '.ed) #'erase-in-display) 311 (defun erase-below () 312 "Erases all characters from the cursor to the end of the screen." 313 (erase-in-display 0)) 315 (defun erase-above () 316 "Erases all characters from the beginning of the screen to the cursor." 317 (erase-in-display 1)) 320 "Erase all characters on the screen." 321 (erase-in-display 2))) 323 (defun erase-saved-lines () 324 "Erase all characters on the screen including the scrollback buffer." 325 (erase-in-display 3)) 327 ;; Name: Erase in line 331 ;; Sequence: CSI Ps K 332 ;; Parameters: Ps = mode 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. 338 Mode 0 (erase-right, default) erases all characters from the cursor to 341 Mode 1 (erase-left) erases all characters from the beginning of the 344 Mode 2 (erase-line) erases all characters on the line." 347 (setf (fdefinition '.el) #'erase-in-line) 349 (defun erase-right () 350 "Erases all characters from the cursor to the end of the line." 354 "Erases all characters from the beginning of the line to the cursor." 358 "Erases all characters on the current line." 361 ;; Name: Select Graphic Rendition 365 ;; Sequence: CSI Pm m 366 ;; Parameters: See documentation string. 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. 372 0 turn off all previous attributes, set normal, default rendition 374 1 bold, increased intensity 375 2 faint, dim, decreased intensity 380 7 negative, reverse image 381 8 invisible, hidden, concealed 385 22 turn off bold and faint/dim, set normal intensity 386 23 turn off italic, standout 387 24 turn off single, double underline 389 27 turn off negative, reverse image 390 28 turn off hidden, invisible 391 29 turn off crossed-out 403 39 default foreground color 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 418 49 default background color 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)) 424 (setf (fdefinition '.sgr) #'select-graphic-rendition) 426 ;; Name: Device Status Report 430 ;; Sequence: CSI Ps n 431 ;; Parameters: Ps = status command to send to the terminal 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." 439 (setf (fdefinition '.dsr) #'device-status-report) 441 ;; Name: Cursor Position Report 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. 454 ;; Set (enable, turn on) 456 (defun dec-private-mode-set (mode) 457 "Set (turn on, enable) a DEC private mode. 461 25 show or hide the cursor 462 1047 alternate or normal screen buffer" 465 (setf (fdefinition '.decset) #'dec-private-mode-set) 467 (defun show-cursor () 468 (dec-private-mode-set 25)) 470 (defun use-alternate-screen-buffer () 471 (dec-private-mode-set 1047)) 473 ;; Reset (disable, turn off) 475 (defun dec-private-mode-reset (mode) 476 "Reset (turn off, disable) a DEC private mode." 479 (setf (fdefinition '.decrst) #'dec-private-mode-reset) 481 (defun hide-cursor () 482 (dec-private-mode-reset 25)) 484 (defun use-normal-screen-buffer () 485 (dec-private-mode-reset 1047)) 489 "Move the cursor to the home position, the top left corner." 493 "Erase the whole screen, then move the cursor to the home position." 500 From /usr/include/x86_64-linux-gnu/bits/termios.h 502 typedef unsigned char cc_t; 503 typedef unsigned int speed_t; 504 typedef unsigned int tcflag_t; 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 */ 522 (defun mode-type (mode) 523 "Return the keyword designating the type of the terminal mode: 525 :input, :output, :control, :local, :character, :combination." 527 '(:IGNBRK :BRKINT :IGNPAR :PARMRK :INPCK :ISTRIP :INLCR :IGNCR 528 :ICRNL :IUCLC :IXON :IXANY :IXOFF :IMAXBEL :IUTF8)) 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)) 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)) 541 '(:ISIG :ICANON :XCASE :ECHO :ECHOE :ECHOK :ECHONL :NOFLSH :TOSTOP 542 :ECHOCTL :ECHOPRT :ECHOKE :FLUSHO :PENDIN :IEXTEN :EXTPROC)) 544 '(:VINTR :VQUIT :VERASE :VKILL :VEOF :VTIME :VMIN :VSWTC :VSTART 545 :VSTOP :VSUSP :VEOL :VREPRINT :VDISCARD :VWERASE :VLNEXT :VEOL2)) 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) 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) 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))) 577 ;; cooked: ixon brkint parmrk 578 ;; raw = -cooked -icanon -isig -iexten 579 ;; noraw = cooked icanon isig iexten 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 585 (defparameter *combinations* 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 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) 594 :brkint t :ignpar t :istrip t :icrnl t :ixon t :icanon t :opost t 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))) 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) 618 ;; the value for a flag can be t or nil 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))) 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 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))) 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) 640 (update-termios termios (mode-combination mode value))) 641 ((:iflag :oflag :cflag :lflag) 642 (set-termios-flag termios mode value)) 644 (set-termios-param termios mode value))))) 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)))