;;;-*- Mode:LISP; Package:FORMAT; Base:8; Readtable:T -*- ;;; ** (c) Copyright 1980 Massachusetts Institute of Technology ** ;;; (FQUERY OPTIONS FORMAT-STRING &REST FORMAT-ARGS) ;;; OPTIONS is a PLIST. Defined indicators are: ;;; :MAKE-COMPLETE boolean. Send a :MAKE-COMPLETE message to the stream if it understands it. ;;; :TYPE one of :TYI, :READLINE. How typing is gathered and echoed. ;;; :CHOICES a list of choices. ;;; A choice is either the symbol :ANY or a list. ;;; If a list, its car is either a possible return value, ;;; or a list of a possible return value and how to echo it. ;;; The remaining things in the list are input items that select that return value. ;;; For a :READLINE type call, they should be strings. ;;; For a :TYI type call, they should be characters. ;;; Example choice: ((:foo "Foo") #\F #\space) ;;; :FRESH-LINE boolean. Send a FRESH-LINE to the stream initially. ;;; :CONDITION symbol. Signalled before asking. ;;; :LIST-CHOICES boolean. After prompting in parentheses. ;;; :BEEP boolean. Before printing message. ;;; :CLEAR-INPUT boolean. Before printing message. ;;; :HELP-FUNCTION function. Called with STREAM, CHOICES and TYPE-FUNCTION as arguments. ;;; :STREAM stream or expression. Specifies the stream to use. ;;; If it is a symbol (which is not an io-stream) or a list it is evaluated. ;;; Default is to use *QUERY-IO*. ;;; :DEFAULT-VALUE . to return if defaulted or timed out. ;;; :TIMEOUT . ;;; Modified printing of timeout values to use TIME:PRINT-INTERVAL-OR-NEVER, ;;; for more intelligible intervals. -KmC 6/88 (DEFVAR Y-OR-N-P-CHOICES '(((T "Yes.") #/Y #/T #/SP #/HAND-UP) ((NIL "No.") #/N #/RUBOUT #/HAND-DOWN))) (DEFVAR YES-OR-NO-P-CHOICES '((T "Yes") (NIL "No"))) (DEFVAR FQUERY-FORMAT-STRING) (DEFVAR FQUERY-FORMAT-ARGS) (DEFVAR FQUERY-LIST-CHOICES) (DEFVAR FQUERY-CHOICES) (DEFVAR FQUERY-HELP-FUNCTION) (DEFVAR FQUERY-STREAM) (defvar fquery-default-value) (defvar fquery-timeout) (DEFUN FQUERY (OPTIONS FQUERY-FORMAT-STRING &REST FQUERY-FORMAT-ARGS &AUX MAKE-COMPLETE TYPE TYPE-FUNCTION FQUERY-CHOICES STREAM FQUERY-STREAM FRESH-LINE CONDITION FQUERY-LIST-CHOICES FQUERY-HELP-FUNCTION BEEP-P CLEAR-INPUT HANDLED-P VAL FQUERY-DEFAULT-VALUE FQUERY-TIMEOUT) "Ask a multiple-choice question on *QUERY-IO*. FQUERY-FORMAT-STRING and FQUERY-FORMAT-ARGS are used to print the question. Ending the string with /"? /" is often appropriate. OPTIONS is a PLIST. Defined indicators are: :MAKE-COMPLETE boolean. Send a :MAKE-COMPLETE message to the stream if it understands it. :TYPE one of :TYI, :READLINE, :MINI-BUFFER-OR-READLINE. It says how the answer is gathered and echoed. :CHOICES a list of choices. A choice is either the symbol :ANY or a list. If a list, its car is either a possible return value, or a list of a possible return value and how to echo it. The remaining things in the list are input items that select that return value. For a :READLINE type call, they should be strings. For a :TYI type call, they should be characters. Example choice (for :READLINE): ((:foo /"Foo/") #\F #\space) :FRESH-LINE boolean. Send a :FRESH-LINE to the stream initially. :CONDITION symbol. Signalled before asking. :LIST-CHOICES boolean. If T, a list of choices is printed after the question. :BEEP boolean. If T, we beep before printing the message. :CLEAR-INPUT boolean. If T, we discard type-ahead before printing the message. :HELP-FUNCTION specifies a function to be called if the user types Help. It is called with STREAM, CHOICES and TYPE-FUNCTION as arguments. :STREAM stream or expression. Specifies the stream to use. If it is a symbol (which is not an io-stream) or a list it is evaluated. Default is to use *QUERY-IO*. :DEFAULT-VALUE value. Return this if defaulted or timed out. :TIMEOUT ." (SETF (VALUES MAKE-COMPLETE TYPE FQUERY-CHOICES STREAM BEEP-P CLEAR-INPUT FRESH-LINE CONDITION FQUERY-LIST-CHOICES FQUERY-HELP-FUNCTION FQUERY-DEFAULT-VALUE FQUERY-TIMEOUT) (APPLY #'FQUERY-DECODE-OPTIONS OPTIONS)) (SETQ FQUERY-STREAM (IF STREAM (IF (OR (AND (SYMBOLP STREAM) (NOT (GET STREAM 'SI:IO-STREAM-P))) (CONSP STREAM)) (EVAL STREAM) STREAM) *QUERY-IO*)) (SETQ TYPE-FUNCTION (OR (GET TYPE 'FQUERY-FUNCTION) (FERROR NIL "~S is not a valid :TYPE for FQUERY" TYPE))) (AND CONDITION (OR (NEQ CONDITION 'FQUERY) (EH:CONDITION-NAME-HANDLED-P CONDITION)) (MULTIPLE-VALUE (HANDLED-P VAL) (SIGNAL-CONDITION (APPLY #'MAKE-CONDITION CONDITION OPTIONS FQUERY-FORMAT-STRING FQUERY-FORMAT-ARGS) '(:NEW-VALUE)))) (IF HANDLED-P VAL ; (UNWIND-PROTECT (PROGN ; (COND ((AND SELECT ; (MEMQ :SELECT (SEND FQUERY-STREAM :WHICH-OPERATIONS))) ; (SEND FQUERY-STREAM :OUTPUT-HOLD-EXCEPTION) ; (SETQ OLD-SELECTED-WINDOW TV:SELECTED-WINDOW) ; (SEND FQUERY-STREAM :SELECT))) (BLOCK TOP (DO-FOREVER (AND BEEP-P (SEND FQUERY-STREAM :BEEP 'FQUERY)) (AND CLEAR-INPUT (SEND FQUERY-STREAM :CLEAR-INPUT)) (AND FRESH-LINE (SEND FQUERY-STREAM :FRESH-LINE)) (MULTIPLE-VALUE-BIND (TYPEIN TIMEOUT-P) (FUNCALL TYPE-FUNCTION :READ FQUERY-STREAM) (cond (timeout-p (format fquery-stream "~A -- timed out." (find-fquery-default)) (AND MAKE-COMPLETE (SEND FQUERY-STREAM :SEND-IF-HANDLES :MAKE-COMPLETE)) (RETURN-FROM TOP TYPEIN)) (t (DOLIST (CHOICE FQUERY-CHOICES) (COND ((EQ CHOICE :ANY) (FUNCALL TYPE-FUNCTION :ECHO TYPEIN FQUERY-STREAM) (AND MAKE-COMPLETE (SEND FQUERY-STREAM :SEND-IF-HANDLES :MAKE-COMPLETE)) (RETURN-FROM TOP TYPEIN)) ((FUNCALL TYPE-FUNCTION :MEMBER TYPEIN (CDR CHOICE)) (SETQ CHOICE (CAR CHOICE)) (WHEN (CONSP CHOICE) (FUNCALL TYPE-FUNCTION :ECHO (CADR CHOICE) FQUERY-STREAM) (SETQ CHOICE (CAR CHOICE))) (AND MAKE-COMPLETE (SEND FQUERY-STREAM :SEND-IF-HANDLES :MAKE-COMPLETE)) (RETURN-FROM TOP CHOICE))))))) (SETQ BEEP-P T CLEAR-INPUT T FRESH-LINE T ;User spazzed, will need fresh line FQUERY-LIST-CHOICES T)))) ;and should list options ; (AND OLD-SELECTED-WINDOW (SEND OLD-SELECTED-WINDOW :SELECT NIL))) )) (DEFUN FQUERY-DECODE-OPTIONS (&KEY (MAKE-COMPLETE T) (TYPE :TYI) (CHOICES Y-OR-N-P-CHOICES) STREAM BEEP CLEAR-INPUT (FRESH-LINE T) (CONDITION 'FQUERY) SIGNAL-CONDITION (LIST-CHOICES T) SELECT ;no longer used (HELP-FUNCTION 'DEFAULT-FQUERY-HELP) DEFAULT-VALUE TIMEOUT) SIGNAL-CONDITION SELECT (let((choices (if (eq choices :any) '(:any) choices))) (VALUES MAKE-COMPLETE TYPE CHOICES STREAM BEEP CLEAR-INPUT FRESH-LINE CONDITION LIST-CHOICES HELP-FUNCTION DEFAULT-VALUE TIMEOUT))) (defun string-to-handle-any-as-choice(remaining-choices &optional first-p) (cond (remaining-choices "anything") (first-p "anything") (t "anything else"))) (DEFUN FQUERY-PROMPT (STREAM &REST IGNORE) (AND FQUERY-FORMAT-STRING (APPLY #'FORMAT STREAM FQUERY-FORMAT-STRING FQUERY-FORMAT-ARGS)) (AND FQUERY-LIST-CHOICES (DO ((CHOICES FQUERY-CHOICES (CDR CHOICES)) (FIRST-P T NIL) (MANY (> (LENGTH FQUERY-CHOICES) 2)) (CHOICE)) ((NULL CHOICES) (OR FIRST-P (SEND STREAM :STRING-OUT ") "))) (SEND STREAM :STRING-OUT (COND (FIRST-P "(") ((NOT (NULL (CDR CHOICES))) ", ") (MANY ", or ") (T " or "))) (IF (EQ (CAR CHOICES) :ANY) (SEND STREAM :STRING-OUT (string-to-handle-any-as-choice (cdr choices) first-p)) (progn (SETQ CHOICE (CADAR CHOICES)) ; character lossage (COND ((TYPEP CHOICE '(OR NUMBER CHARACTER)) (FORMAT STREAM "~:@C" CHOICE)) ((EQUAL CHOICE "") (PRINC "nothing" STREAM)) (T (SEND STREAM :STRING-OUT CHOICE))))))) (AND FQUERY-TIMEOUT ;attempt to print out what will return default-value. (DO ((CHOICES FQUERY-CHOICES (CDR CHOICES)) (choice)) ((NULL CHOICES) (FORMAT STREAM "(Automatic default returns ~s after ~a) " fquery-default-value (time:print-interval-or-never (// fquery-timeout 60.) nil))) (setq choice (car choices)) (cond ((eq choice :any)) ((equal fquery-default-value (if (consp (car choice)) (caar choice) (car choice))) (return (format stream "(Automatic default after ~a, ~a) " (time:print-interval-or-never (// fquery-timeout 60.) nil) (cadr choice))))))) ) (DEFUN DEFAULT-FQUERY-HELP (STREAM CHOICES TYPE) (declare(ignore TYPE)) ;Not used (DO ((CHOICES CHOICES (CDR CHOICES)) (FIRST-P T NIL) (CHOICE)) ((NULL CHOICES) (OR FIRST-P (SEND STREAM :STRING-OUT ") "))) (SEND STREAM :STRING-OUT (COND (FIRST-P "(Type ") ((NOT (NULL (CDR CHOICES))) ", ") (T " or "))) (SETQ CHOICE (CAR CHOICES)) (COND ((EQ CHOICE :ANY) (PRINC (string-to-handle-any-as-choice (cdr choices) first-p) STREAM)) (T ;;Print the first input which selects this choice. ;;Don't confuse the user by mentioning possible alternative inputs. ; character lossage (COND ((TYPEP (CADR CHOICE) '(OR NUMBER CHARACTER)) (FORMAT STREAM "~:@C" (CADR CHOICE))) ((EQUAL (CADR CHOICE) "") (PRINC "nothing" STREAM)) (T (SEND STREAM :STRING-OUT (CADR CHOICE)))) ;; If that would echo as something else, say so (IF (CONSP (CAR CHOICE)) (FORMAT STREAM " (~A)" (CADAR CHOICE))))))) (defun find-fquery-default () (dolist (element fquery-choices) (let ((choice (car element))) (when (eq fquery-default-value (if (consp choice) (car choice) choice)) ;;We found the default that will be returned. How does it echo? (let ((value (if (consp choice) (cadr choice) (cadr element)))) (return (if (and (stringp value) (char= (char value (1- (string-length value))) #/.)) ;;Kludge -- strip off period at end. (substring value 0 (1- (string-length value))) value))))))) (DEFPROP :TYI TYI-FQUERY-FUNCTION FQUERY-FUNCTION) (DEFSELECT TYI-FQUERY-FUNCTION (:READ (STREAM) (labels ((read-it () (DO ((CH)) (NIL) (FQUERY-PROMPT STREAM) (SETQ CH (READ-CHAR STREAM)) (cond ((AND (CHAR= CH #/HELP) FQUERY-HELP-FUNCTION) (SEND FQUERY-HELP-FUNCTION STREAM FQUERY-CHOICES 'TYI-FQUERY-FUNCTION) (SEND STREAM :FRESH-LINE)) ((char= ch #/clear-screen) (send-if-handles stream :clear-screen)) (t (RETURN CH)))))) (if (null fquery-timeout) (read-it) (with-timeout (fquery-timeout (values fquery-default-value t)) (read-it))))) (:ECHO (ECHO STREAM) (SEND STREAM :STRING-OUT (STRING ECHO))) (:MEMBER (CHAR LIST) ; character lossage (MEM #'(LAMBDA (X Y) (CHAR-EQUAL X (COERCE Y 'CHARACTER))) CHAR LIST))) (DEFPROP :READLINE READLINE-FQUERY-FUNCTION FQUERY-FUNCTION) (DEFSELECT READLINE-FQUERY-FUNCTION (:READ (STREAM &AUX STRING) (labels ((read-it () (if (operation-handled-p stream :rubout-handler) (SETQ STRING (SEND STREAM :RUBOUT-HANDLER `((:EDITING-COMMAND ,(char-int #/Help) #/HELP) ;Just in case (:PROMPT FQUERY-PROMPT) (:DONT-SAVE T)) 'FQUERY-READLINE-WITH-HELP STREAM)) (setq string (send stream :line-in))) (STRING-TRIM '(#/SP) STRING))) (if (null fquery-timeout) (read-it) (with-timeout (fquery-timeout (values fquery-default-value t)) (read-it))))) (:ECHO (ECHO STREAM) ECHO STREAM) (:MEMBER (STRING LIST) (MEM #'STRING-EQUAL STRING LIST))) (DEFUN FQUERY-READLINE-WITH-HELP (STREAM) (DO ((STRING (MAKE-STRING 20. :FILL-POINTER 0)) (CH)) (NIL) (SETQ CH (READ-CHAR STREAM)) (COND ((OR (NULL CH) (CHAR= CH #/CR)) (RETURN STRING)) ((AND (CHAR= CH #/HELP) FQUERY-HELP-FUNCTION) (FRESH-LINE STREAM) (SEND FQUERY-HELP-FUNCTION STREAM FQUERY-CHOICES 'READLINE-FQUERY-FUNCTION) (SEND STREAM :SEND-IF-HANDLES :REFRESH-RUBOUT-HANDLER)) ((NOT (ZEROP (CHAR-BITS CH)))) (T (VECTOR-PUSH-EXTEND CH STRING))))) (PROCLAIM '(SPECIAL ZWEI:*MINI-BUFFER-ARG-DOCUMENTER*)) ;DEFVAR is in ZWEI. (DEFPROP :MINI-BUFFER-OR-READLINE MINI-BUFFER-OR-READLINE-FQUERY-FUNCTION FQUERY-FUNCTION) (DEFUN MINI-BUFFER-OR-READLINE-FQUERY-FUNCTION (&REST ARGS &AUX STRING) (COND ((AND (EQ (CAR ARGS) :READ) (EQ (CADR ARGS) 'ZWEI:*TYPEIN-WINDOW*-SYN-STREAM)) (LET ((ZWEI:*MINI-BUFFER-ARG-DOCUMENTER* 'MINI-BUFFER-OR-READLINE-HELP-FUNCTION)) (FUNCALL (CADR ARGS) :SEND-IF-HANDLES :MAKE-COMPLETE) (SETQ STRING (APPLY #'ZWEI:TYPEIN-LINE-READLINE FQUERY-FORMAT-STRING FQUERY-FORMAT-ARGS))) (STRING-TRIM '(#/SP) STRING)) (T (APPLY #'READLINE-FQUERY-FUNCTION ARGS)))) (DEFUN MINI-BUFFER-OR-READLINE-HELP-FUNCTION () (FORMAT *TERMINAL-IO* "~&~%You are now typing an answer to a query.~&") (FUNCALL FQUERY-HELP-FUNCTION *TERMINAL-IO* FQUERY-CHOICES 'MINI-BUFFER-OR-READLINE-FQUERY-FUNCTION)) (DEFCONST Y-OR-N-P-OPTIONS `(:FRESH-LINE NIL)) (DEFUN Y-OR-N-P (&OPTIONAL FORMAT-STRING &REST FORMAT-ARGS) "Ask the user a question he can answer with Y or N. Passes the arguments to FORMAT. With no args, asks the question without printing anything but the /"(Y or N)/". Returns T if the answer was yes." (FQUERY Y-OR-N-P-OPTIONS (AND FORMAT-STRING (> (length format-string) 0) (IF (CHAR= (CHAR FORMAT-STRING (max 0 (1- (LENGTH FORMAT-STRING)))) #/SP) "~&~?" "~&~? ")) FORMAT-STRING FORMAT-ARGS)) (DEFUN Y-OR-N-P-WITH-TIMEOUT (&OPTIONAL (TIMEOUT (* 15. 60.)) DEFAULT-VALUE FORMAT-STRING &REST FORMAT-ARGS) "Like Y-OR-N-P, but times out and returns DEFAULT-VALUE if the user has not answered in (// TIMEOUT 60.) seconds." (FQUERY `(:timeout ,timeout :default-value ,default-value ,@Y-OR-N-P-OPTIONS) (AND FORMAT-STRING (> (length format-string) 0) (IF (CHAR= (CHAR FORMAT-STRING (max 0 (1- (LENGTH FORMAT-STRING)))) #/SP) "~&~?" "~&~? ")) FORMAT-STRING FORMAT-ARGS)) (DEFCONST YES-OR-NO-P-OPTIONS `(:FRESH-LINE NIL :BEEP T :TYPE :READLINE :CHOICES ,YES-OR-NO-P-CHOICES)) (DEFUN YES-OR-NO-P (&OPTIONAL FORMAT-STRING &REST FORMAT-ARGS) "Ask the user a question he can answer with Yes or No. Beeps and discards type-ahead. Passes the arguments to FORMAT. With no args, asks the question without printing anything but the /"(Yes or No)/". Returns T if the answer was yes." (FQUERY YES-OR-NO-P-OPTIONS (AND FORMAT-STRING (> (length format-string) 0) (IF (CHAR= (CHAR FORMAT-STRING (max 0 (1- (LENGTH FORMAT-STRING)))) #/SP) "~&~?" "~&~? ")) FORMAT-STRING FORMAT-ARGS)) (DEFUN YES-OR-NO-P-WITH-TIMEOUT (&OPTIONAL (TIMEOUT (* 15. 60.)) DEFAULT-VALUE FORMAT-STRING &REST FORMAT-ARGS) "Like YES-OR-NO-P, but times out and returns DEFAULT-VALUE if the user has not answered in (// TIMEOUT 60.) seconds." (FQUERY `(:timeout ,timeout :default-value ,default-value ,@YES-OR-NO-P-OPTIONS) (AND FORMAT-STRING (> (length format-string) 0) (IF (CHAR= (CHAR FORMAT-STRING (max 0 (1- (LENGTH FORMAT-STRING)))) #/SP) "~&~?" "~&~? ")) FORMAT-STRING FORMAT-ARGS)) (DEFCONST YES-OR-NO-QUIETLY-P-OPTIONS `(:TYPE :READLINE :CHOICES ,YES-OR-NO-P-CHOICES))