source: release/1.4/source/cocoa-ide/hemlock/src/macros.lisp

Last change on this file was 13161, checked in by R. Matthew Emerson, 15 years ago

merge r13134 through r13135 from trunk

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.8 KB
RevLine 
[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))
Note: See TracBrowser for help on using the repository browser.