|
| 1 | +(in-package :software-evolution-library/terminal) |
| 2 | +(enable-interpol-syntax) |
| 3 | + |
| 4 | +(defun make-terminal-raw () |
| 5 | + "Place the terminal into 'raw' mode, no echo or delete. |
| 6 | +This allows characters to be read directly without waiting for a newline. |
| 7 | +See 'man 3 termios' for more information." |
| 8 | + #+win32 (error "`make-terminal-raw' not implemented for windows.") |
| 9 | + #-sbcl (error "`make-terminal-raw' not implemented for non-SBCL.") |
| 10 | + #+sbcl |
| 11 | + (let ((options (sb-posix:tcgetattr 0))) |
| 12 | + (setf (sb-posix:termios-lflag options) |
| 13 | + (logand (sb-posix:termios-lflag options) |
| 14 | + (lognot (logior sb-posix:icanon |
| 15 | + sb-posix:echo |
| 16 | + sb-posix:echoe |
| 17 | + sb-posix:echok |
| 18 | + sb-posix:echonl)))) |
| 19 | + (sb-posix:tcsetattr 0 sb-posix:TCSANOW options))) |
| 20 | + |
| 21 | +(defun make-terminal-unraw () |
| 22 | + "Place the terminal out of 'raw' mode, with echo and delete. |
| 23 | +This allows characters to be read directly without waiting for a newline. |
| 24 | +See 'man 3 termios' for more information." |
| 25 | + #+win32 (error "`make-terminal-raw' not implemented for windows.") |
| 26 | + #-sbcl (error "`make-terminal-raw' not implemented for non-SBCL.") |
| 27 | + #+sbcl |
| 28 | + (let ((options (sb-posix:tcgetattr 0))) |
| 29 | + (setf (sb-posix:termios-lflag options) |
| 30 | + (logior (sb-posix:termios-lflag options) |
| 31 | + sb-posix:icanon |
| 32 | + sb-posix:echo |
| 33 | + sb-posix:echoe |
| 34 | + sb-posix:echok |
| 35 | + sb-posix:echonl)) |
| 36 | + (sb-posix:tcsetattr 0 sb-posix:TCSANOW options))) |
| 37 | + |
| 38 | + |
| 39 | +;;; Terminal size with CFFI and ioctl. |
| 40 | +;;; Adapted from: |
| 41 | +;;; https://github.com/cffi/cffi/blob/master/examples/gettimeofday.lisp |
| 42 | +(define-foreign-type ioctl-result-type () |
| 43 | + () |
| 44 | + (:actual-type :int) |
| 45 | + (:simple-parser ioctl-result)) |
| 46 | + |
| 47 | +(define-condition ioctl (error) |
| 48 | + ((ret :initarg :ret :initform nil :reader ret)) |
| 49 | + (:report (lambda (condition stream) |
| 50 | + (format stream "IOCTL call failed with return value ~d~%~ |
| 51 | +(NOTE: IOCTL fails when called from slime.)" |
| 52 | + (ret condition))))) |
| 53 | + |
| 54 | +(defmethod translate-from-foreign (value (type ioctl-result-type)) |
| 55 | + (if (minusp value) |
| 56 | + (error (make-condition 'ioctl :ret value)) |
| 57 | + value)) |
| 58 | + |
| 59 | +(defcfun ("ioctl" %ioctl) ioctl-result |
| 60 | + (fd :int) |
| 61 | + (request :unsigned-long) |
| 62 | + (winsz (:pointer (:struct winsize)))) |
| 63 | + |
| 64 | +(defun term-size () |
| 65 | + "Return terminal size information. |
| 66 | +The following are returned in a property list row, col, xpixels, |
| 67 | +ypixels. See `man 2 ioctl` for more inforamtion. Note ioctl and thus |
| 68 | +`term-size' will throw an error of type IOCTL when called from SLIME." |
| 69 | + (restart-case |
| 70 | + (with-foreign-object (wnsz '(:struct winsize)) |
| 71 | + (%ioctl STDOUT-FILENO TIOCGWINSZ wnsz) |
| 72 | + (with-foreign-slots ((row col xpixel ypixel) wnsz (:struct winsize)) |
| 73 | + `(:row ,row |
| 74 | + :col ,col |
| 75 | + :xpixel ,xpixel |
| 76 | + :ypixel ,ypixel))) |
| 77 | + (return-no-extent-term () |
| 78 | + :report "Return info for a terminal with no extent." |
| 79 | + '(:row 0 |
| 80 | + :col 0 |
| 81 | + :xpixel 0 |
| 82 | + :ypixel 0)))) |
| 83 | + |
| 84 | + |
| 85 | +;;; Color and control sequences |
| 86 | +(eval-when (:compile-toplevel :load-toplevel :execute) |
| 87 | + (defvar *view-stream* t |
| 88 | + "Dynamically bind to use modify.")) |
| 89 | + |
| 90 | +(mapc (lambda (triple) |
| 91 | + (destructuring-bind (name value documentation) triple |
| 92 | + (eval `(define-constant ,name ,value :test 'equalp |
| 93 | + :documentation ,documentation)))) |
| 94 | + '((+set-G1+ #?"\x1b)0" "Set G1 for box drawing") |
| 95 | + (+reset-G1+ #?"\x1b)B" "Reset G1 to ASCII") |
| 96 | + (+b-start+ #?"\x0e" "Enter G1 drawing mode") |
| 97 | + (+b-stop+ #?"\x0f" "Leave G1 drawing mode") |
| 98 | + (+b-h+ #\q "Horizontal line") |
| 99 | + (+b-v+ #\x "Vertical line") |
| 100 | + (+b-lt+ #\l "Left top corner") |
| 101 | + (+b-rt+ #\k "Right top corner") |
| 102 | + (+b-lb+ #\m "Left bottom corner") |
| 103 | + (+b-rb+ #\j "Right bottom corner") |
| 104 | + (+b-x+ #\n "Cross") |
| 105 | + (+b-vr+ #\t "Vertical, branch right") |
| 106 | + (+b-vl+ #\u "Vertical, branch left") |
| 107 | + (+b-ht+ #\v "Horizontal, branch top") |
| 108 | + (+b-hb+ #\w "Horizontal, branch bottom") |
| 109 | + (+term-home+ #?"\x1b[H" "Set terminal back to home (top left).") |
| 110 | + (+term-clear+ #?"\x1b[H[2J" "Clear terminal.") |
| 111 | + (+ceol+ #?"\x1b[0K" "Clear to end of line.") |
| 112 | + (+cursor-hide+ #?"\x1b[?25l" "Hide the cursor.") |
| 113 | + (+cursor-show+ #?"\x1b[?25h" "Show the cursor.") |
| 114 | + ;; Colors |
| 115 | + (+color-BLK+ #?"\x1b[0;30m" "Color BLK.") |
| 116 | + (+color-RED+ #?"\x1b[0;31m" "Color RED.") |
| 117 | + (+color-GRN+ #?"\x1b[0;32m" "Color GRN.") |
| 118 | + (+color-BRN+ #?"\x1b[0;33m" "Color BRN.") |
| 119 | + (+color-BLU+ #?"\x1b[0;34m" "Color BLU.") |
| 120 | + (+color-MGN+ #?"\x1b[0;35m" "Color MGN.") |
| 121 | + (+color-CYA+ #?"\x1b[0;36m" "Color CYA.") |
| 122 | + (+color-NOR+ #?"\x1b[0;37m" "Color NOR.") |
| 123 | + (+color-GRA+ #?"\x1b[1;30m" "Color GRA.") |
| 124 | + (+color-LRD+ #?"\x1b[1;31m" "Color LRD.") |
| 125 | + (+color-LGN+ #?"\x1b[1;32m" "Color LGN.") |
| 126 | + (+color-YEL+ #?"\x1b[1;33m" "Color YEL.") |
| 127 | + (+color-LBL+ #?"\x1b[1;34m" "Color LBL.") |
| 128 | + (+color-PIN+ #?"\x1b[1;35m" "Color PIN.") |
| 129 | + (+color-LCY+ #?"\x1b[1;36m" "Color LCY.") |
| 130 | + (+color-BRI+ #?"\x1b[1;37m" "Color BRI.") |
| 131 | + (+color-RST+ #?"\x1b[0m" "Color RST."))) |
| 132 | + |
| 133 | + |
| 134 | +;;; Utility functions |
| 135 | + |
| 136 | +(defun clear-terminal () |
| 137 | + (format *view-stream* "~a" +term-clear+)) |
| 138 | + |
| 139 | +(defun hide-cursor () |
| 140 | + (format *view-stream* "~a" +cursor-hide+)) |
| 141 | + |
| 142 | +(defun show-cursor () |
| 143 | + (format *view-stream* "~a" +cursor-show+)) |
| 144 | + |
| 145 | +(defmacro with-line-printing (&rest body) |
| 146 | + `(unwind-protect |
| 147 | + (progn (format ,*view-stream* "~a" +set-G1+) |
| 148 | + (format ,*view-stream* "~a" +b-start+) |
| 149 | + ,@body) |
| 150 | + (format ,*view-stream* "~a" +b-stop+) |
| 151 | + (format ,*view-stream* "~a" +reset-G1+))) |
| 152 | + |
| 153 | +(defmacro with-color-printing (color &rest body) |
| 154 | + `(unwind-protect |
| 155 | + (progn (format ,*view-stream* "~a" ,color) ,@body) |
| 156 | + (format ,*view-stream* "~a" +color-RST+))) |
| 157 | + |
| 158 | +(defun string-output-stream-p (stream) |
| 159 | + (typep stream |
| 160 | + #+sbcl 'sb-impl::string-output-stream |
| 161 | + #+ccl 'ccl:string-output-stream |
| 162 | + #- (or sbcl ccl) |
| 163 | + (error "`string-output-stream-p' only supported for SBCL and CCL"))) |
0 commit comments