| [6] | 1 | ;;; -*- Log: hemlock.log; Package: Hemlock-Internals -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+CMU (ext:file-comment
|
|---|
| 8 | "$Header$")
|
|---|
| 9 | ;;;
|
|---|
| 10 | ;;; **********************************************************************
|
|---|
| 11 | ;;;
|
|---|
| 12 | ;;; This file contains most of the junk that needs to be in the compiler
|
|---|
| 13 | ;;; to compile Hemlock commands.
|
|---|
| 14 | ;;;
|
|---|
| 15 | ;;; Written by Rob MacLachlin and Bill Chiles.
|
|---|
| 16 | ;;;
|
|---|
| 17 |
|
|---|
| 18 | (in-package :hemlock-internals)
|
|---|
| 19 |
|
|---|
| 20 | |
|---|
| 21 |
|
|---|
| 22 | ;;;; Macros used for manipulating Hemlock variables.
|
|---|
| 23 |
|
|---|
| 24 | (defmacro invoke-hook (place &rest args)
|
|---|
| 25 | "Call the functions in place with args. If place is a symbol, then this
|
|---|
| 26 | interprets it as a Hemlock variable rather than a Lisp variable, using its
|
|---|
| 27 | current value as the list of functions."
|
|---|
| 28 | (let ((f (gensym)))
|
|---|
| 29 | `(dolist (,f ,(if (symbolp place) `(%value ',place) place))
|
|---|
| 30 | (funcall ,f ,@args))))
|
|---|
| 31 |
|
|---|
| 32 | (defmacro value (name)
|
|---|
| 33 | "Return the current value of the Hemlock variable name."
|
|---|
| 34 | `(%value ',name))
|
|---|
| 35 |
|
|---|
| 36 | (defmacro setv (name new-value)
|
|---|
| 37 | "Set the current value of the Hemlock variable name, calling any hook
|
|---|
| 38 | functions with new-value before setting the value."
|
|---|
| 39 | `(%set-value ',name ,new-value))
|
|---|
| 40 |
|
|---|
| 41 | ;;; WITH-VARIABLE-OBJECT -- Internal
|
|---|
| 42 | ;;;
|
|---|
| 43 | ;;; Look up the variable object for name and bind it to obj, giving error
|
|---|
| 44 | ;;; if there is no such variable.
|
|---|
| 45 | ;;;
|
|---|
| [8428] | 46 | (defmacro with-variable-object (name &body forms)
|
|---|
| [6] | 47 | `(let ((obj (get-variable-object ,name :current)))
|
|---|
| 48 | (unless obj (undefined-variable-error ,name))
|
|---|
| 49 | ,@forms))
|
|---|
| 50 |
|
|---|
| 51 | (defmacro hlet (binds &rest forms)
|
|---|
| 52 | "Hlet ({Var Value}*) {Form}*
|
|---|
| 53 | Similar to Let, only it creates temporary Hemlock variable bindings. Each
|
|---|
| 54 | of the vars have the corresponding value during the evaluation of the
|
|---|
| 55 | forms."
|
|---|
| 56 | (let ((lets ())
|
|---|
| 57 | (sets ())
|
|---|
| 58 | (unsets ()))
|
|---|
| 59 | (dolist (bind binds)
|
|---|
| 60 | (let ((n-obj (gensym))
|
|---|
| 61 | (n-val (gensym))
|
|---|
| 62 | (n-old (gensym)))
|
|---|
| 63 | (push `(,n-val ,(second bind)) lets)
|
|---|
| 64 | (push `(,n-old (variable-object-value ,n-obj)) lets)
|
|---|
| 65 | (push `(,n-obj (with-variable-object ',(first bind) obj)) lets)
|
|---|
| 66 | (push `(setf (variable-object-value ,n-obj) ,n-val) sets)
|
|---|
| 67 | (push `(setf (variable-object-value ,n-obj) ,n-old) unsets)))
|
|---|
| 68 | `(let* ,lets
|
|---|
| 69 | (unwind-protect
|
|---|
| 70 | (progn ,@sets nil ,@forms)
|
|---|
| 71 | ,@unsets))))
|
|---|
| 72 |
|
|---|
| [8428] | 73 |
|
|---|
| 74 | ;; MODIFYING-BUFFER-STORAGE
|
|---|
| 75 | ;;
|
|---|
| 76 | ;; This is kinda Cocoa-specific, but we'll pretend it's not. It gets wrapped around
|
|---|
| 77 | ;; possible multiple modifications of the buffer's text, so that the OS can defer
|
|---|
| 78 | ;; layout and redisplay until the end. It takes care of showing the spin cursor
|
|---|
| 79 | ;; if the command takes too long, and it ensures that the cocoa selection matches
|
|---|
| 80 | ;; hemlock's idea of selection.
|
|---|
| 81 | ;; As a special hack, buffer can be NIL to temporarily turn off the grouping.
|
|---|
| 82 |
|
|---|
| 83 | (defmacro modifying-buffer-storage ((buffer) &body body)
|
|---|
| 84 | (if (eq buffer '*current-buffer*)
|
|---|
| 85 | `(hemlock-ext:invoke-modifying-buffer-storage *current-buffer* #'(lambda () ,@body))
|
|---|
| 86 | `(let ((*current-buffer* ,buffer))
|
|---|
| 87 | (hemlock-ext:invoke-modifying-buffer-storage *current-buffer* #'(lambda () ,@body)))))
|
|---|
| [12233] | 88 |
|
|---|
| 89 | ;; If we've done a (cocoa-specific) "beginEditing" on a buffer, finish that (to allow
|
|---|
| 90 | ;; layout, etc.) Call thunk, and maybe restore the editing state after.
|
|---|
| [12856] | 91 | (defmacro allowing-buffer-display ((buffer) &body body)
|
|---|
| [8428] | 92 | `(hemlock-ext:invoke-allowing-buffer-display ,buffer (lambda () ,@body)))
|
|---|
| [12233] | 93 |
|
|---|
| [6] | 94 |
|
|---|
| 95 | ;;;; A couple funs to hack strings to symbols.
|
|---|
| 96 |
|
|---|
| 97 | (eval-when (:compile-toplevel :execute :load-toplevel)
|
|---|
| 98 |
|
|---|
| 99 | (defun bash-string-to-symbol (name suffix)
|
|---|
| 100 | (intern (nsubstitute #\- #\space
|
|---|
| 101 | #-scl
|
|---|
| 102 | (nstring-upcase
|
|---|
| 103 | (concatenate 'simple-string
|
|---|
| 104 | name (symbol-name suffix)))
|
|---|
| 105 | #+scl
|
|---|
| 106 | (let ((base (concatenate 'simple-string
|
|---|
| 107 | name (symbol-name suffix))))
|
|---|
| 108 | (if (eq ext:*case-mode* :upper)
|
|---|
| 109 | (nstring-upcase base)
|
|---|
| 110 | (nstring-downcase base))))))
|
|---|
| 111 |
|
|---|
| 112 | ;;; string-to-variable -- Exported
|
|---|
| 113 | ;;;
|
|---|
| 114 | ;;; Return the symbol which corresponds to the string name
|
|---|
| 115 | ;;; "string".
|
|---|
| 116 | (defun string-to-variable (string)
|
|---|
| 117 | "Returns the symbol name of a Hemlock variable from the corresponding string
|
|---|
| 118 | name."
|
|---|
| 119 | (intern (nsubstitute #\- #\space
|
|---|
| 120 | #-scl
|
|---|
| 121 | (the simple-string (string-upcase string))
|
|---|
| 122 | #+scl
|
|---|
| 123 | (if (eq ext:*case-mode* :upper)
|
|---|
| 124 | (string-upcase string)
|
|---|
| 125 | (string-downcase string)))
|
|---|
| 126 | (find-package :hemlock)))
|
|---|
| 127 |
|
|---|
| 128 | ); eval-when
|
|---|
| 129 |
|
|---|
| 130 | ;;; string-to-keyword -- Internal
|
|---|
| 131 | ;;;
|
|---|
| 132 | ;;; Mash a string into a Keyword.
|
|---|
| 133 | ;;;
|
|---|
| 134 | (defun string-to-keyword (string)
|
|---|
| 135 | (intern (nsubstitute #\- #\space
|
|---|
| 136 | #-scl
|
|---|
| 137 | (the simple-string (string-upcase string))
|
|---|
| 138 | #+scl
|
|---|
| 139 | (if (eq ext:*case-mode* :upper)
|
|---|
| 140 | (string-upcase string)
|
|---|
| 141 | (string-downcase string)))
|
|---|
| 142 | (find-package :keyword)))
|
|---|
| 143 |
|
|---|
| 144 | |
|---|
| 145 |
|
|---|
| 146 | ;;;; Macros to add and delete hook functions.
|
|---|
| 147 |
|
|---|
| 148 | ;;; add-hook -- Exported
|
|---|
| 149 | ;;;
|
|---|
| 150 | ;;; Add a hook function to a hook, defining a variable if
|
|---|
| 151 | ;;; necessary.
|
|---|
| 152 | ;;;
|
|---|
| 153 | (defmacro add-hook (place hook-fun)
|
|---|
| 154 | "Add-Hook Place Hook-Fun
|
|---|
| 155 | Add Hook-Fun to the list stored in Place. If place is a symbol then it
|
|---|
| 156 | it is interpreted as a Hemlock variable rather than a Lisp variable."
|
|---|
| 157 | (if (symbolp place)
|
|---|
| 158 | `(pushnew ,hook-fun (value ,place))
|
|---|
| 159 | `(pushnew ,hook-fun ,place)))
|
|---|
| 160 |
|
|---|
| 161 | ;;; remove-hook -- Public
|
|---|
| 162 | ;;;
|
|---|
| 163 | ;;; Delete a hook-function from somewhere.
|
|---|
| 164 | ;;;
|
|---|
| 165 | (defmacro remove-hook (place hook-fun)
|
|---|
| 166 | "Remove-Hook Place Hook-Fun
|
|---|
| 167 | Remove Hook-Fun from the list in Place. If place is a symbol then it
|
|---|
| 168 | it is interpreted as a Hemlock variable rather than a Lisp variable."
|
|---|
| 169 | (if (symbolp place)
|
|---|
| 170 | `(setf (value ,place) (delete ,hook-fun (value ,place)))
|
|---|
| 171 | `(setf ,place (delete ,hook-fun ,place))))
|
|---|
| 172 |
|
|---|
| 173 |
|
|---|
| 174 | |
|---|
| 175 |
|
|---|
| 176 | ;;;; DEFCOMMAND.
|
|---|
| 177 |
|
|---|
| 178 | ;;; Defcommand -- Public
|
|---|
| [7510] | 179 | ;;;
|
|---|
| [6] | 180 | (defmacro defcommand (name lambda-list command-doc function-doc
|
|---|
| 181 | &body forms)
|
|---|
| 182 | "Defcommand Name Lambda-List Command-Doc [Function-Doc] {Declaration}* {Form}*
|
|---|
| 183 |
|
|---|
| 184 | Define a new Hemlock command named Name. Lambda-List becomes the
|
|---|
| 185 | lambda-list, Function-Doc the documentation, and the Forms the
|
|---|
| 186 | body of the function which implements the command. The first
|
|---|
| 187 | argument, which must be present, is the prefix argument. The name
|
|---|
| 188 | of this function is derived by replacing all spaces in the name with
|
|---|
| 189 | hyphens and appending \"-COMMAND\". Command-Doc becomes the
|
|---|
| 190 | documentation for the command. See the command implementor's manual
|
|---|
| 191 | for further details.
|
|---|
| 192 |
|
|---|
| 193 | An example:
|
|---|
| 194 | (defcommand \"Forward Character\" (p)
|
|---|
| 195 | \"Move the point forward one character.
|
|---|
| 196 | With prefix argument move that many characters, with negative argument
|
|---|
| 197 | go backwards.\"
|
|---|
| 198 | \"Move the point of the current buffer forward p characters.\"
|
|---|
| 199 | (unless (character-offset (buffer-point (current-buffer)) (or p 1))
|
|---|
| [7510] | 200 | (editor-error)))"
|
|---|
| 201 |
|
|---|
| [6] | 202 | (unless (stringp function-doc)
|
|---|
| 203 | (setq forms (cons function-doc forms))
|
|---|
| [8428] | 204 | (setq function-doc command-doc))
|
|---|
| [6] | 205 | (when (atom lambda-list)
|
|---|
| [8428] | 206 | (error "Command argument list is not a list: ~S." lambda-list))
|
|---|
| [6] | 207 | (let (command-name function-name extra-args)
|
|---|
| [8428] | 208 | (cond ((listp name)
|
|---|
| 209 | (setq command-name (car name) function-name (cadr name))
|
|---|
| 210 | (unless (symbolp function-name)
|
|---|
| 211 | (error "Function name is not a symbol: ~S" function-name))
|
|---|
| [6] | 212 | (if (keywordp function-name)
|
|---|
| [8428] | 213 | (setq function-name nil extra-args (cdr name))
|
|---|
| 214 | (setq extra-args (cddr name))))
|
|---|
| 215 | (t
|
|---|
| [6] | 216 | (setq command-name name)))
|
|---|
| 217 | (when (null function-name)
|
|---|
| 218 | (setq function-name (bash-string-to-symbol command-name '-command)))
|
|---|
| 219 | (unless (stringp command-name)
|
|---|
| 220 | (error "Command name is not a string: ~S." name))
|
|---|
| [8428] | 221 | `(eval-when (:load-toplevel :execute)
|
|---|
| [6] | 222 | (defun ,function-name ,lambda-list ,function-doc
|
|---|
| 223 | ,@forms)
|
|---|
| 224 | (make-command ,command-name ,command-doc ',function-name ,@extra-args)
|
|---|
| 225 | ',function-name)))
|
|---|
| 226 |
|
|---|
| 227 |
|
|---|
| 228 | |
|---|
| 229 |
|
|---|
| 230 | ;;;; PARSE-FORMS
|
|---|
| 231 |
|
|---|
| 232 | ;;; Parse-Forms -- Internal
|
|---|
| 233 | ;;;
|
|---|
| 234 | ;;; Used for various macros to get the declarations out of a list of
|
|---|
| 235 | ;;; forms.
|
|---|
| 236 | ;;;
|
|---|
| 237 | (eval-when (:compile-toplevel :execute :load-toplevel)
|
|---|
| 238 | (defmacro parse-forms ((decls-var forms-var forms) &body gorms)
|
|---|
| 239 | "Parse-Forms (Decls-Var Forms-Var Forms) {Form}*
|
|---|
| 240 | Binds Decls-Var to leading declarations off of Forms and Forms-Var
|
|---|
| 241 | to what is left."
|
|---|
| 242 | `(do ((,forms-var ,forms (cdr ,forms-var))
|
|---|
| 243 | (,decls-var ()))
|
|---|
| 244 | ((or (atom ,forms-var) (atom (car ,forms-var))
|
|---|
| 245 | (not (eq (caar ,forms-var) 'declare)))
|
|---|
| 246 | ,@gorms)
|
|---|
| 247 | (push (car ,forms-var) ,decls-var)))
|
|---|
| 248 | )
|
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 | |
|---|
| 252 |
|
|---|
| 253 | ;;;; WITH-MARK and USE-BUFFER.
|
|---|
| 254 |
|
|---|
| 255 | (defmacro with-mark (mark-bindings &rest forms)
|
|---|
| 256 | "With-Mark ({(Mark Pos [Kind])}*) {declaration}* {form}*
|
|---|
| 257 | With-Mark binds a variable named Mark to a mark specified by Pos. This
|
|---|
| 258 | mark is :temporary, or of kind Kind. The forms are then evaluated."
|
|---|
| 259 | (do ((bindings mark-bindings (cdr bindings))
|
|---|
| 260 | (let-slots ())
|
|---|
| 261 | (cleanup ()))
|
|---|
| 262 | ((null bindings)
|
|---|
| 263 | (if cleanup
|
|---|
| 264 | (parse-forms (decls forms forms)
|
|---|
| 265 | `(let ,(nreverse let-slots)
|
|---|
| 266 | ,@decls
|
|---|
| 267 | (unwind-protect
|
|---|
| 268 | (progn ,@forms)
|
|---|
| 269 | ,@cleanup)))
|
|---|
| 270 | `(let ,(nreverse let-slots) ,@forms)))
|
|---|
| 271 | (let ((name (caar bindings))
|
|---|
| 272 | (pos (cadar bindings))
|
|---|
| 273 | (type (or (caddar bindings) :temporary)))
|
|---|
| 274 | (cond ((not (eq type :temporary))
|
|---|
| [6582] | 275 | (push `(,name (copy-mark ,pos ,type)) let-slots)
|
|---|
| [6] | 276 | (push `(delete-mark ,name) cleanup))
|
|---|
| 277 | (t
|
|---|
| 278 | (push `(,name (copy-mark ,pos :temporary)) let-slots))))))
|
|---|
| 279 |
|
|---|
| 280 | #||SAve this shit in case we want WITH-MARKto no longer cons marks.
|
|---|
| 281 | (defconstant with-mark-total 50)
|
|---|
| 282 | (defvar *with-mark-free-marks* (make-array with-mark-total))
|
|---|
| 283 | (defvar *with-mark-next* 0)
|
|---|
| 284 |
|
|---|
| 285 | (defmacro with-mark (mark-bindings &rest forms)
|
|---|
| 286 | "WITH-MARK ({(Mark Pos [Kind])}*) {declaration}* {form}*
|
|---|
| 287 | WITH-MARK evaluates each form with each Mark variable bound to a mark
|
|---|
| 288 | specified by the respective Pos, a mark. The created marks are of kind
|
|---|
| 289 | :temporary, or of kind Kind."
|
|---|
| 290 | (do ((bindings mark-bindings (cdr bindings))
|
|---|
| 291 | (let-slots ())
|
|---|
| 292 | (cleanup ()))
|
|---|
| 293 | ((null bindings)
|
|---|
| 294 | (let ((old-next (gensym)))
|
|---|
| 295 | (parse-forms (decls forms forms)
|
|---|
| 296 | `(let ((*with-mark-next* *with-mark-next*)
|
|---|
| 297 | (,old-next *with-mark-next*))
|
|---|
| 298 | (let ,(nreverse let-slots)
|
|---|
| 299 | ,@decls
|
|---|
| 300 | (unwind-protect
|
|---|
| 301 | (progn ,@forms)
|
|---|
| 302 | ,@cleanup))))))
|
|---|
| 303 | (let ((name (caar bindings))
|
|---|
| 304 | (pos (cadar bindings))
|
|---|
| 305 | (type (or (caddar bindings) :temporary)))
|
|---|
| 306 | (push `(,name (mark-for-with-mark ,pos ,type)) let-slots)
|
|---|
| 307 | (if (eq type :temporary)
|
|---|
| 308 | (push `(delete-mark ,name) cleanup)
|
|---|
| 309 | ;; Assume mark is on free list and drop its hold on data.
|
|---|
| 310 | (push `(setf (mark-line ,name) nil) cleanup)))))
|
|---|
| 311 |
|
|---|
| 312 | ;;; MARK-FOR-WITH-MARK -- Internal.
|
|---|
| 313 | ;;;
|
|---|
| 314 | ;;; At run time of a WITH-MARK form, this returns an appropriate mark at the
|
|---|
| 315 | ;;; position mark of type kind. First it uses one from the vector of free
|
|---|
| 316 | ;;; marks, possibly storing one in the vector if we need more marks than we
|
|---|
| 317 | ;;; have before, and that need is still less than the total free marks we are
|
|---|
| 318 | ;;; willing to hold onto. If we're over the free limit, just make one for
|
|---|
| 319 | ;;; throwing away.
|
|---|
| 320 | ;;;
|
|---|
| 321 | (defun mark-for-with-mark (mark kind)
|
|---|
| 322 | (let* ((line (mark-line mark))
|
|---|
| 323 | (charpos (mark-charpos mark))
|
|---|
| 324 | (mark (cond ((< *with-mark-next* with-mark-total)
|
|---|
| 325 | (let ((m (svref *with-mark-free-marks* *with-mark-next*)))
|
|---|
| 326 | (cond ((markp m)
|
|---|
| 327 | (setf (mark-line m) line)
|
|---|
| 328 | (setf (mark-charpos m) charpos)
|
|---|
| 329 | (setf (mark-%kind m) kind))
|
|---|
| 330 | (t
|
|---|
| 331 | (setf m (internal-make-mark line charpos kind))
|
|---|
| 332 | (setf (svref *with-mark-free-marks*
|
|---|
| 333 | *with-mark-next*)
|
|---|
| 334 | m)))
|
|---|
| 335 | (incf *with-mark-next*)
|
|---|
| [6582] | 336 | m))
|
|---|
| [6] | 337 | (t (internal-make-mark line charpos kind)))))
|
|---|
| 338 | (unless (eq kind :temporary)
|
|---|
| 339 | (push mark (line-marks (mark-line mark))))
|
|---|
| 340 | mark))
|
|---|
| 341 | ||#
|
|---|
| 342 |
|
|---|
| 343 |
|
|---|
| [8428] | 344 | ;;;; EDITOR-ERROR.
|
|---|
| [13161] | 345 |
|
|---|
| 346 | (defun editor-error (&rest args)
|
|---|
| 347 | "This function is called to signal minor errors within Hemlock;
|
|---|
| 348 | these are errors that a normal user could encounter in the course of editing
|
|---|
| [6] | 349 | such as a search failing or an attempt to delete past the end of the buffer."
|
|---|
| 350 | (if (current-view)
|
|---|
| 351 | (let ((message (and args (apply #'format nil args))))
|
|---|
| 352 | (abort-current-command message))
|
|---|
| 353 | (apply #'error args)))
|
|---|
| 354 | |
|---|
| 355 |
|
|---|
| 356 | ;;;; Do-Strings
|
|---|
| 357 |
|
|---|
| 358 | (defmacro do-strings ((string-var value-var table &optional result) &body forms)
|
|---|
| 359 | "Do-Strings (String-Var Value-Var Table [Result]) {declaration}* {form}*
|
|---|
| 360 | Iterate over the strings in a String Table. String-Var and Value-Var
|
|---|
| 361 | are bound to the string and value respectively of each successive entry
|
|---|
| 362 | in the string-table Table in alphabetical order. If supplied, Result is
|
|---|
| 363 | a form to evaluate to get the return value."
|
|---|
| 364 | (let ((value-nodes (gensym))
|
|---|
| 365 | (num-nodes (gensym))
|
|---|
| 366 | (value-node (gensym))
|
|---|
| 367 | (i (gensym)))
|
|---|
| 368 | `(let ((,value-nodes (string-table-value-nodes ,table))
|
|---|
| 369 | (,num-nodes (string-table-num-nodes ,table)))
|
|---|
| 370 | (dotimes (,i ,num-nodes ,result)
|
|---|
| 371 | (declare (fixnum ,i))
|
|---|
| 372 | (let* ((,value-node (svref ,value-nodes ,i))
|
|---|
| 373 | (,value-var (value-node-value ,value-node))
|
|---|
| 374 | (,string-var (value-node-proper ,value-node)))
|
|---|
| 375 | (declare (simple-string ,string-var))
|
|---|
| 376 | ,@forms)))))
|
|---|
| 377 |
|
|---|
| 378 |
|
|---|
| 379 | |
|---|
| 380 |
|
|---|
| 381 | ;;;; COMMAND-CASE
|
|---|
| 382 |
|
|---|
| 383 | ;;; COMMAND-CASE -- Public
|
|---|
| 384 | ;;;
|
|---|
| 385 | ;;; Grovel the awful thing and spit out the corresponding Cond. See Echo
|
|---|
| 386 | ;;; for the definition of COMMAND-CASE-HELP and logical char stuff.
|
|---|
| 387 | ;;;
|
|---|
| 388 | (eval-when (:compile-toplevel :execute :load-toplevel)
|
|---|
| 389 | (defun command-case-tag (tag key-event char)
|
|---|
| 390 | (cond ((and (characterp tag) (standard-char-p tag))
|
|---|
| 391 | `(and ,char (char= ,char ,tag)))
|
|---|
| [8428] | 392 | ((and (symbolp tag) (keywordp tag))
|
|---|
| [6] | 393 | `(logical-key-event-p ,key-event ,tag))
|
|---|
| 394 | (t
|
|---|
| 395 | (error "Tag in COMMAND-CASE is not a standard character or keyword: ~S"
|
|---|
| [8428] | 396 | tag))))
|
|---|
| 397 | ); eval-when
|
|---|
| 398 | ;;;
|
|---|
| [6] | 399 | (defmacro command-case ((&key (prompt "Command character: ")
|
|---|
| 400 | (help "Choose one of the following characters:")
|
|---|
| 401 | (bind (gensym)))
|
|---|
| 402 | &body forms)
|
|---|
| 403 | "This is analogous to the Common Lisp CASE macro. Commands can use this
|
|---|
| [8428] | 404 | to get a key-event, translate it to a character, and then to dispatch on
|
|---|
| 405 | the character to the specified case. The syntax is
|
|---|
| [6] | 406 | as follows:
|
|---|
| [8428] | 407 | (COMMAND-CASE ( {key value}* )
|
|---|
| [6] | 408 | {( {( {tag}* ) | tag} help {form}* )}*
|
|---|
| 409 | )
|
|---|
| 410 | Each tag is either a character or a logical key-event. The user's typed
|
|---|
| 411 | key-event is compared using either LOGICAL-KEY-EVENT-P or CHAR= of
|
|---|
| 412 | KEY-EVENT-CHAR.
|
|---|
| 413 |
|
|---|
| 414 | The legal keys of the key/value pairs are :help, :prompt, and :bind."
|
|---|
| 415 | (do* ((forms forms (cdr forms))
|
|---|
| 416 | (form (car forms) (car forms))
|
|---|
| 417 | (cases ())
|
|---|
| 418 | (bname (gensym))
|
|---|
| 419 | (again (gensym))
|
|---|
| 420 | (n-prompt (gensym))
|
|---|
| [8428] | 421 | (bind-char (gensym))
|
|---|
| 422 | (docs ())
|
|---|
| [6] | 423 | (t-case `(t (beep) (reprompt))))
|
|---|
| 424 | ((atom forms)
|
|---|
| 425 | `(macrolet ((reprompt ()
|
|---|
| [8428] | 426 | `(progn
|
|---|
| 427 | (setf ,',bind
|
|---|
| 428 | (prompt-for-key-event :prompt ,',n-prompt))
|
|---|
| [6] | 429 | (setf ,',bind-char (key-event-char ,',bind))
|
|---|
| 430 | (go ,',again))))
|
|---|
| 431 | (block ,bname
|
|---|
| 432 | (let* ((,n-prompt ,prompt)
|
|---|
| 433 | (,bind (prompt-for-key-event :prompt ,n-prompt))
|
|---|
| 434 | (,bind-char (key-event-char ,bind)))
|
|---|
| 435 | (declare (ignorable,bind ,bind-char))
|
|---|
| 436 | (tagbody
|
|---|
| 437 | ,again
|
|---|
| 438 | (return-from
|
|---|
| 439 | ,bname
|
|---|
| 440 | (cond ,@(nreverse cases)
|
|---|
| 441 | ((logical-key-event-p ,bind :abort)
|
|---|
| 442 | (editor-error))
|
|---|
| 443 | ((logical-key-event-p ,bind :help)
|
|---|
| 444 | (command-case-help ,help ',(nreverse docs))
|
|---|
| 445 | (reprompt))
|
|---|
| 446 | ,t-case)))))))
|
|---|
| 447 |
|
|---|
| 448 | (cond ((atom form)
|
|---|
| 449 | (error "Malformed Command-Case clause: ~S" form))
|
|---|
| 450 | ((eq (car form) t)
|
|---|
| 451 | (setq t-case form))
|
|---|
| 452 | ((or (< (length form) 2)
|
|---|
| 453 | (not (stringp (second form))))
|
|---|
| 454 | (error "Malformed Command-Case clause: ~S" form))
|
|---|
| 455 | (t
|
|---|
| 456 | (let ((tag (car form))
|
|---|
| 457 | (rest (cddr form)))
|
|---|
| 458 | (cond ((atom tag)
|
|---|
| 459 | (push (cons (command-case-tag tag bind bind-char) rest)
|
|---|
| 460 | cases)
|
|---|
| 461 | (setq tag (list tag)))
|
|---|
| 462 | (t
|
|---|
| 463 | (do ((tag tag (cdr tag))
|
|---|
| 464 | (res ()
|
|---|
| 465 | (cons (command-case-tag (car tag) bind bind-char)
|
|---|
| 466 | res)))
|
|---|
| 467 | ((null tag)
|
|---|
| 468 | (push `((or ,@res) . ,rest) cases)))))
|
|---|
| 469 | (push (cons tag (second form)) docs))))))
|
|---|
| 470 |
|
|---|
| 471 |
|
|---|
| 472 | |
|---|
| 473 |
|
|---|
| 474 | ;;;; Some random macros used everywhere.
|
|---|
| [8428] | 475 |
|
|---|
| [6] | 476 | (defmacro strlen (str) `(length (the simple-string ,str)))
|
|---|
| [8428] | 477 | (defmacro neq (a b) `(not (eq ,a ,b)))
|
|---|
| [12233] | 478 |
|
|---|
| 479 |
|
|---|
| [8428] | 480 | |
|---|
| 481 |
|
|---|
| 482 | ;;;; Stuff from here on is implementation dependant.
|
|---|
| [12233] | 483 |
|
|---|
| [8428] | 484 | (defvar *saved-standard-output* nil)
|
|---|
| 485 |
|
|---|
| 486 | (defmacro with-output-to-listener (&body body)
|
|---|
| [6] | 487 | `(let* ((*saved-standard-output* (or *saved-standard-output* *standard-output*))
|
|---|
| 488 | (*standard-output* (hemlock-ext:top-listener-output-stream)))
|
|---|
| 489 | ,@body))
|
|---|
| 490 |
|
|---|
| 491 | (defmacro with-standard-standard-output (&body body)
|
|---|
| 492 | `(let* ((*standard-output* (or *saved-standard-output* *standard-output*)))
|
|---|
| 493 | ,@body))
|
|---|
| 494 |
|
|---|
| 495 |
|
|---|
| 496 | |
|---|
| 497 |
|
|---|
| 498 | ;;;; WITH-INPUT & WITH-OUTPUT macros.
|
|---|
| 499 |
|
|---|
| 500 | (defvar *free-hemlock-output-streams* ()
|
|---|
| 501 | "This variable contains a list of free Hemlock output streams.")
|
|---|
| 502 |
|
|---|
| 503 | (defmacro with-output-to-mark ((var mark &optional (buffered ':line))
|
|---|
| 504 | &body gorms)
|
|---|
| 505 | "With-Output-To-Mark (Var Mark [Buffered]) {Declaration}* {Form}*
|
|---|
| 506 | During the evaluation of Forms, Var is bound to a stream which inserts
|
|---|
| 507 | output at the permanent mark Mark. Buffered is the same as for
|
|---|
| 508 | Make-Hemlock-Output-Stream."
|
|---|
| 509 | (parse-forms (decls forms gorms)
|
|---|
| 510 | `(let ((,var (pop *free-hemlock-output-streams*)))
|
|---|
| 511 | ,@decls
|
|---|
| 512 | (if ,var
|
|---|
| 513 | (modify-hemlock-output-stream ,var ,mark ,buffered)
|
|---|
| 514 | (setq ,var (make-hemlock-output-stream ,mark ,buffered)))
|
|---|
| 515 | (unwind-protect
|
|---|
| 516 | (progn ,@forms)
|
|---|
| 517 | (setf (hemlock-output-stream-mark ,var) nil)
|
|---|
| 518 | (push ,var *free-hemlock-output-streams*)))))
|
|---|
| 519 |
|
|---|
| 520 | (defvar *free-hemlock-region-streams* ()
|
|---|
| 521 | "This variable contains a list of free Hemlock input streams.")
|
|---|
| 522 |
|
|---|
| 523 | (defmacro with-input-from-region ((var region) &body gorms)
|
|---|
| 524 | "With-Input-From-Region (Var Region) {Declaration}* {Form}*
|
|---|
| 525 | During the evaluation of Forms, Var is bound to a stream which
|
|---|
| 526 | returns input from Region."
|
|---|
| 527 | (parse-forms (decls forms gorms)
|
|---|
| 528 | `(let ((,var (pop *free-hemlock-region-streams*)))
|
|---|
| [880] | 529 | ,@decls
|
|---|
| [6790] | 530 | (if ,var
|
|---|
| [6] | 531 | (setq ,var (modify-hemlock-region-stream ,var ,region))
|
|---|
| [880] | 532 | (setq ,var (make-hemlock-region-stream ,region)))
|
|---|
| [6] | 533 | (unwind-protect
|
|---|
| 534 | (progn ,@forms)
|
|---|
| 535 | (delete-mark (hemlock-region-stream-mark ,var))
|
|---|
| 536 | (push ,var *free-hemlock-region-streams*)))))
|
|---|
| 537 |
|
|---|
| 538 |
|
|---|
| [6774] | 539 |
|
|---|
| [880] | 540 | (defmacro with-pop-up-display ((var &key height title)
|
|---|
| [7804] | 541 | &body body)
|
|---|
| [6774] | 542 |
|
|---|
| [6] | 543 | "Execute body in a context with var bound to a stream. Output to the stream
|
|---|
| 544 | appears in the buffer named buffer-name. The pop-up display appears after
|
|---|
| 545 | the body completes, but if you supply :height, the output is line buffered,
|
|---|
| 546 | displaying any current output after each line."
|
|---|
| [6774] | 547 | (when (and (numberp height) (zerop height))
|
|---|
| 548 | (editor-error "I doubt that you really want a window with no height"))
|
|---|
| [6] | 549 | (let ((stream (gensym)))
|
|---|
| [880] | 550 | `(let ()
|
|---|
| [6] | 551 | (let ((,stream (gui::typeout-stream ,title)))
|
|---|
| 552 | (clear-output ,stream)
|
|---|
| 553 | (unwind-protect
|
|---|
| 554 | (progn
|
|---|
| 555 | (catch 'more-punt
|
|---|
| 556 | (let ((,var ,stream))
|
|---|
| 557 | ,@body)))
|
|---|
| 558 | (force-output ,stream))))))
|
|---|
| 559 |
|
|---|
| 560 |
|
|---|
| 561 | (declaim (special *random-typeout-ml-fields* *buffer-names*))
|
|---|
| 562 |
|
|---|
| 563 | |
|---|
| 564 |
|
|---|
| 565 | ;;;; Error handling stuff.
|
|---|
| 566 |
|
|---|
| 567 | (defmacro handle-lisp-errors (&body body)
|
|---|
| 568 | "Handle-Lisp-Errors {Form}*
|
|---|
| 569 | If a Lisp error happens during the evaluation of the body, then it is
|
|---|
| 570 | handled in some fashion. This should be used by commands which may
|
|---|
| 571 | get a Lisp error due to some action of the user."
|
|---|
| 572 | `(handler-bind ((error #'lisp-error-error-handler))
|
|---|
| 573 | ,@body))
|
|---|