home *** CD-ROM | disk | FTP | other *** search
/ Il CD di internet / CD.iso / SOURCE / D / CLISP / CLISPSRC.TAR / clisp-1995-01-01 / src / screen.lsp < prev    next >
Encoding:
Lisp/Scheme  |  1994-12-19  |  12.6 KB  |  395 lines

  1. (in-package "SCREEN" :use '("LISP"))
  2.  
  3. #|
  4. ; Re-Export von importierten Symbolen kann man nicht mit der
  5. ; P I S E R U I - Regel erreichen. Diese Symbole mu▀ man zuerst importieren.
  6. (in-package "SYSTEM")
  7. (import '(make-window window-size
  8.           window-cursor-position set-window-cursor-position
  9.           clear-window clear-window-to-eot clear-window-to-eol
  10.           delete-window-line insert-window-line
  11.           highlight-on highlight-off window-cursor-on window-cursor-off
  12.          )
  13.         "SCREEN"
  14. )
  15. (in-package "SCREEN")
  16. |#
  17.  
  18. (export '(; exported functions and macros:
  19.           make-window window-size
  20.           window-cursor-position set-window-cursor-position
  21.           clear-window clear-window-to-eot clear-window-to-eol
  22.           delete-window-line insert-window-line
  23.           highlight-on highlight-off window-cursor-on window-cursor-off
  24.           with-window *window*
  25.           read-keyboard-char
  26.           ; user-settable things:
  27.           *new-window*
  28. )        )
  29.  
  30. (proclaim '(special *window*))
  31.  
  32. #-AMIGA
  33. (defun read-keyboard-char (stream)
  34.   (declare (ignore stream))
  35.   (read-char *keyboard-input*)
  36. )
  37.  
  38. #-AMIGA
  39. (defconstant *new-window* nil)
  40.  
  41.  
  42. ;;;; SCREEN-Package for Amiga
  43. ;;;; J÷rg H÷hle, 20.11.1994
  44.  
  45. #+AMIGA (use-package "CLOS")
  46. #+AMIGA
  47. (progn
  48.  
  49. ; Determines the "new window" policy.
  50. (defvar *new-window* "RAW:0/11/581/231/CLISP Window"
  51.   "This variables determines the behaviour of SCREEN:MAKE-WINDOW.
  52. If NIL, it uses *TERMINAL-IO*. If non-NIL, it should be the specification
  53. string of a special file to be OPENed, e.g. \"RAW:0/11/581/231/Window Title\"."
  54. )
  55.  
  56. ;;; Why is this so complex? Because applications (Punimax) need to use
  57. ;;; the raw mode functions but nevertheless expect to read input in
  58. ;;; cooked mode. Cooked mode is also nicer if you happen to fall into
  59. ;;; the debugger. Thus I provide special streams that switch modes
  60. ;;; automatically.
  61.  
  62.  
  63. ;; The class of all data present in SCREEN's generic streams.
  64. (defclass screen-controller (generic-stream-controller) ())
  65.  
  66. ; Most stream functions are aliased to the following stream:
  67. (defgeneric controller-stream (controller))
  68.  
  69. ; The screen's mode: either T (raw) or NIL (line editing enabled)
  70. (defgeneric controller-mode (controller))
  71. (defgeneric (setf controller-mode) (mode controller))
  72.  
  73.  
  74. ;; Two subclasses:
  75.  
  76. ; terminal-controller generic streams refer to *terminal-io*.
  77. (defclass terminal-controller (screen-controller) ())
  78. (defmethod controller-stream ((controller terminal-controller))
  79.   *terminal-io*
  80. )
  81. (defvar *terminal-mode* 'nil) ; initially, line editing is enabled
  82. (defmethod controller-mode ((controller terminal-controller))
  83.   *terminal-mode*
  84. )
  85. (defmethod (setf controller-mode) (mode (controller terminal-controller))
  86.   (setf *terminal-mode* mode)
  87. )
  88.  
  89. ; window-controller generic streams refer to a special device stream.
  90. (defclass window-controller (screen-controller)
  91.   (stream :reader controller-stream
  92.           :type stream
  93.           :initarg :stream
  94.   )
  95.   (mode :accessor controller-mode
  96.         :initform 'unknown ; the initial mode is unknown
  97.   )
  98. )
  99.  
  100.  
  101. ;; (raw-mode stream mode) puts the stream into the given mode (T or NIL).
  102. (defun raw-mode (stream mode)
  103.   (if (generic-stream-p stream)
  104.     (generic-raw-mode (generic-stream-controller stream) mode)
  105.     ; handle low-level streams here
  106.     (if (eq stream *terminal-io*)
  107.       ; compare against the cached current mode
  108.       (unless (eq mode *terminal-raw*)
  109.         (sys::terminal-raw stream mode)
  110.         (setq *terminal-raw* mode)
  111.       )
  112.       ; other low-level streams
  113.       (sys::terminal-raw stream mode)
  114. ) ) )
  115. (defgeneric generic-raw-mode (controller mode))
  116. (defmethod generic-raw-mode ((controller screen-controller) mode)
  117.   (raw-mode (controller-stream controller) mode)
  118. )
  119. (defmethod generic-raw-mode ((controller window-controller) mode)
  120.   ; compare against the cached current mode
  121.   (unless (eq mode (controller-mode controller))
  122.     (raw-mode (controller-stream controller) mode)
  123.     (setf (controller-mode controller) mode)
  124. ) )
  125.  
  126.  
  127. ;; Return a new window stream.
  128. (defun make-window (&optional (*new-window* *new-window*))
  129.   (let ((stream
  130.           (make-generic-stream
  131.             (if *new-window*
  132.               (make-instance 'window-controller
  133.                 :stream (etypecase *new-window*
  134.                           (STREAM *new-window*)
  135.                           ((OR PATHNAME STRING) (open *new-window* :direction :io))
  136.               )         )
  137.               (make-instance 'terminal-controller)
  138.        )) ) )
  139.     ; (raw-mode stream t) ; need this??
  140.     stream
  141. ) )
  142.  
  143.  
  144. ;; Operations on SCREEN streams.
  145.  
  146. (defmethod generic-stream-read-char ((controller screen-controller))
  147.   (generic-raw-mode controller nil) ;; need to switch to cooked mode
  148.   (read-char (controller-stream controller))
  149. )
  150.  
  151. (defmethod generic-stream-listen ((controller screen-controller))
  152.   (generic-raw-mode controller nil) ;; need to switch to cooked mode
  153.   (if (listen (controller-stream controller))
  154.     0 ; something available
  155.     +1 ; nothing available, not EOF
  156. ) )
  157.  
  158. (defmethod generic-stream-clear-input ((controller screen-controller))
  159.   (generic-raw-mode controller nil) ;; need to switch to cooked mode
  160.   (clear-input (controller-stream controller))
  161.   t
  162. )
  163.  
  164. (defmethod generic-stream-write-char ((controller screen-controller) ch)
  165.   (write-char ch (controller-stream controller))
  166. )
  167.  
  168. ;; for speed only
  169. (defmethod generic-stream-write-string ((controller screen-controller) string start len)
  170.   (write-string (substring string start (+ start len))
  171.                 (controller-stream controller)
  172. ) )
  173.  
  174. (defmethod generic-stream-finish-output ((controller screen-controller))
  175.   (finish-output (controller-stream controller))
  176. )
  177.  
  178. (defmethod generic-stream-force-output ((controller screen-controller))
  179.   (force-output (controller-stream controller))
  180. )
  181.  
  182. (defmethod generic-stream-clear-output ((controller screen-controller))
  183.   (clear-output (controller-stream controller))
  184. )
  185.  
  186. (defmethod generic-stream-close ((controller screen-controller))
  187.   (raw-mode (controller-stream controller) nil)
  188.   T
  189. )
  190. (defmethod generic-stream-close ((controller window-controller))
  191.   ; Don't need to call raw-mode on this window since it will go away anyway.
  192.   (close (controller-stream controller))
  193. )
  194.  
  195. ; returns a list of all characters immediately available on stream
  196. (defun stream-chars (stream)
  197.   (let ((res '()))
  198.     (loop
  199.       (let ((c (read-char-no-hang stream)))
  200.         (unless c (return))
  201.         (push c res)
  202.     ) )
  203.     (nreverse res)
  204. ) )
  205.  
  206. ; Parse an ANSI Control String:
  207. ; { #\CSI | #\ESC #\[ } { digits #\; }* [ digits [ #\; ] ] { rest }
  208. ; Return (rest . ... num2 num1)
  209. (defun parse-csi (string)
  210.   (let ((res '())
  211.         num
  212.         (start (cond ((eq (aref string 0) #\CSI)      1)
  213.                      ((and (eq (aref string 0) #\ESC)
  214.                            (> (length string) 1)
  215.                            (eq (aref string 1) #\[))  2)
  216.                      (t (error "Not a CSI sequence: ~S" string))
  217.        ))      )
  218.     (loop
  219.       (multiple-value-setq (num start) (parse-integer string :start start :junk-allowed t))
  220.       (when (null num) (return))
  221.       (push num res)
  222.       (when (and (< start (length string)) (eq (aref string start) #\;))
  223.         (incf start)                 ; skip ANSI separator
  224.     ) )
  225.     (cons (subseq string start) res) ; push rest
  226. ) )
  227.  
  228. ; Send a CSI sequence to the terminal and read the response, an ANSI sequence.
  229. ; Return a reversed list of numbers.
  230. ; (Note: As a side effect, a (clear-input stream) is done, which throws away
  231. ; characters.)
  232. (defun read-csi-response (stream send expected)
  233.   (clear-input stream)
  234.   (write-string send stream)
  235.   (let* ((chars
  236.            (or (stream-chars stream)
  237.                (error "Got no response from ~S." stream)
  238.          ) )
  239.          (response (parse-csi (coerce chars 'string))))
  240.     (unless (string= expected (first response))
  241.       (error (DEUTSCH "Von ~S schlechte Antwort erhalten: ~S"
  242.               ENGLISH "Got bad response from ~S: ~S"
  243.               FRANCAIS "Mauvaise rΘponse par ~S : ~S")
  244.              stream chars
  245.     ) )
  246.     (cdr response)
  247. ) )
  248.  
  249. (defun window-size (stream)
  250.   "Reports window size.
  251. Will flush pending characters!"
  252.   ;; (window-checks stream)
  253.   (when (and (generic-stream-p stream)
  254.              (typep (generic-stream-controller stream) 'screen-controller))
  255.     (raw-mode stream t)
  256.     (setq stream (controller-stream (generic-stream-controller stream)))
  257.   )
  258.   (let ((response
  259.           (read-csi-response
  260.             stream
  261.             (load-time-value (coerce '(#\CSI #\0 #\Space #\q) 'string))
  262.             "r"               ; parse-integer ate the space
  263.        )) )
  264.     (values (second response) (first response)) ; height;width
  265. ) )
  266.  
  267. (defun window-cursor-position (stream)
  268.   "Reports cursor position, report origin as 0;0.
  269. Will flush pending characters!"
  270.   ;; (window-checks stream)
  271.   (when (and (generic-stream-p stream)
  272.              (typep (generic-stream-controller stream) 'screen-controller))
  273.     (raw-mode stream t)
  274.     (setq stream (controller-stream (generic-stream-controller stream)))
  275.   )
  276.   (let ((response
  277.           (read-csi-response
  278.             stream
  279.             (load-time-value (coerce '(#\CSI #\6 #\n) 'string))
  280.             "R"
  281.        )) )
  282.     (values (1- (second response)) (1- (first response))) ; line;column
  283. ) )
  284.  
  285. (defun set-window-cursor-position (stream line column)
  286.   ;; ANSI position origin is 1;1, but SCREEN uses 0;0
  287.   (format stream "~a~d;~dH" #\CSI (1+ line) (1+ column))
  288. )
  289.  
  290. (defun clear-window (stream)
  291.   (write-char '#\FF stream)
  292. )
  293.  
  294. (defun clear-window-to-eot (stream)
  295.   (write-string (load-time-value (coerce '(#\CSI #\J) 'string)) stream)
  296. )
  297.  
  298. (defun clear-window-to-eol (stream)
  299.   (write-string (load-time-value (coerce '(#\CSI #\K) 'string)) stream)
  300. )
  301.  
  302. (defun delete-window-line (stream)
  303.   (write-string (load-time-value (coerce '(#\CSI #\M) 'string)) stream)
  304. )
  305.  
  306. (defun insert-window-line (stream)
  307.   (write-string (load-time-value (coerce '(#\CSI #\L) 'string)) stream)
  308. )
  309.  
  310. (defun highlight-on (stream)
  311.   (write-string (load-time-value (coerce '(#\CSI #\1 #\m) 'string)) stream)
  312. )
  313.  
  314. (defun highlight-off (stream)
  315.   (write-string (load-time-value (coerce '(#\CSI #\m) 'string)) stream)
  316. )
  317.  
  318. (defun window-cursor-on (stream)
  319.   (write-string (load-time-value (coerce '(#\CSI #\Space #\p) 'string)) stream)
  320. )
  321.  
  322. (defun window-cursor-off (stream)
  323.   (write-string (load-time-value (coerce '(#\CSI #\0 #\Space #\p) 'string)) stream)
  324. )
  325.  
  326.  
  327. ;; Read characters in raw mode
  328. (defun read-raw-char (stream)
  329.   (raw-mode stream t)
  330.   (setq stream (controller-stream (generic-stream-controller stream)))
  331.   (read-char stream)
  332. )
  333.  
  334. ;; This function does a simple mapping from CSI-sequences as reported
  335. ;; by the Amiga keyboard to characters with HYPER (even SUPER) bit set.
  336. ;; key   codes  shift   character
  337. ;; f1    CSI0~  CSI10~  #\f1, #\s-f1
  338. ;; f10   CSI9~  CSI19~  #\f10, #\s-f10
  339. ;; Help  CSI?~  CSI?~   #\Help
  340. ;; Up    CSIA   CSIT    #\Up,    #\S-Up
  341. ;; Down  CSIB   CSIS    #\Down,  #\S-Down
  342. ;; Left  CSID   CSI A   #\Left,  #\S-Left
  343. ;; Right CSIC   CSI @   #\Right, #\S-Right
  344. (defun read-keyboard-char (stream)
  345.   (let ((c (read-raw-char stream)))
  346.     (if (char= c '#\CSI)
  347.       (let ((chars '()) c)
  348.         (loop
  349.           (setq c (read-raw-char stream))
  350.           (unless (char<= #\Space c #\?) (return))
  351.           (push c chars)
  352.         )
  353.         (cond ((char/= c '#\~) ; arrow keys
  354.                (or (cdr (assoc c (if chars
  355.                                    '((#\A . #\S-Left)
  356.                                      (#\@ . #\S-Right)
  357.                                     )
  358.                                    '((#\A . #\Up)
  359.                                      (#\B . #\Down)
  360.                                      (#\C . #\Right)
  361.                                      (#\D . #\Left)
  362.                                      (#\S . #\S-Down)
  363.                                      (#\T . #\S-Up)
  364.                                     )
  365.                    )    )        )
  366.                    '#\CSI
  367.               ))
  368.               ((null chars) '#\CSI) ; don't parse this...
  369.               ((eq (first chars) '#\?) '#\Help) ; Help key
  370.               ((not (digit-char-p (first chars))) '#\CSI) ; don't parse this...
  371.               ((null (rest chars)) ; f1 ... f10
  372.                (int-char (+ (char-int '#\f1) (digit-char-p (first chars))))
  373.               )
  374.               ((eq '#\1 (second chars)) ; F1 ... F10
  375.                (int-char (+ (char-int '#\s-f1) (digit-char-p (first chars))))
  376.               )
  377.               (t '#\CSI) ; don't parse this...
  378.       ) )
  379.       (if (and (<= 1 (char-int c) 26) ; Ctrl-A ... Ctrl-Z
  380.                (not (or (eql c #\Newline) (eql c #\Backspace) (eql c #\Tab)))
  381.           )
  382.         (set-char-bit (int-char (+ 64 (char-int c))) :CONTROL t)
  383.         c
  384. ) ) ) )
  385.  
  386. ) ; #+AMIGA
  387.  
  388.  
  389. (defmacro with-window (&body body)
  390.   `(LET ((*WINDOW* (MAKE-WINDOW)))
  391.      (UNWIND-PROTECT (PROGN ,@body) (CLOSE *WINDOW*))
  392.    )
  393. )
  394.  
  395.