source: trunk/ccl/cocoa-ide/hemlock/src/lispmode.lisp @ 7583

Last change on this file since 7583 was 7583, checked in by rme, 13 years ago

Indentation tweak for rlet

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 68.2 KB
Line 
1;;; -*- Log: hemlock.log; Package: Hemlock -*-
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;;; Hemlock LISP Mode commands
13;;;
14;;; Written by Ivan Vazquez and Bill Maddox.
15;;;
16
17(in-package :hemlock)
18
19;; (declaim (optimize (speed 2))); turn off byte compilation.
20
21
22;;;; Variables and lisp-info structure.
23
24;;; These routines are used to define, for standard LISP mode, the start and end
25;;; of a block to parse.  If these need to be changed for a minor mode that sits
26;;; on top of LISP mode, simply do a DEFHVAR with the minor mode and give the
27;;; name of the function to use instead of START-OF-PARSE-BLOCK and
28;;; END-OF-PARSE-BLOCK.
29;;;
30
31(defhvar "Parse Start Function"
32  "Take a mark and move it to the top of a block for paren parsing."
33  :value 'start-of-parse-block)
34
35(defhvar "Parse End Function"
36  "Take a mark and move it to the bottom of a block for paren parsing."
37  :value 'end-of-parse-block)
38
39           
40;;; LISP-INFO is the structure used to store the data about the line in its
41;;; Plist.
42;;;
43;;;     -> BEGINS-QUOTED, ENDING-QUOTED are both Boolean slots that tell whether
44;;;        or not a line's begining and/or ending are quoted.
45;;;
46;;;     -> RANGES-TO-IGNORE is a list of cons cells, each having the form
47;;;        ( [begining-charpos] [end-charpos] ) each of these cells indicating
48;;;        a range to ignore.  End is exclusive.
49;;;
50;;;     -> NET-OPEN-PARENS, NET-CLOSE-PARENS integers that are the number of
51;;;        unmatched opening and closing parens that there are on a line.
52;;;
53;;;     -> SIGNATURE-SLOT ...
54;;;
55
56(defstruct (lisp-info (:constructor make-lisp-info ()))
57  (begins-quoted nil)           ; (or t nil)
58  (ending-quoted nil)           ; (or t nil)
59  (ranges-to-ignore nil)        ; (or t nil)
60  (net-open-parens 0 :type fixnum)
61  (net-close-parens 0 :type fixnum)
62  (signature-slot))
63
64
65
66;;;; Macros.
67
68;;; The following Macros exist to make it easy to acces the Syntax primitives
69;;; without uglifying the code.  They were originally written by Maddox.
70;;;
71
72(defmacro scan-char (mark attribute values)
73  `(find-attribute ,mark ',attribute ,(attr-predicate values)))
74
75(defmacro rev-scan-char (mark attribute values)
76  `(reverse-find-attribute ,mark ',attribute ,(attr-predicate values)))
77
78(defmacro test-char (char attribute values)
79  `(let ((x (character-attribute ',attribute ,char)))
80     ,(attr-predicate-aux values)))
81
82(eval-when (:compile-toplevel :execute :load-toplevel)
83(defun attr-predicate (values)
84  (cond ((eq values 't)
85         '#'plusp)
86        ((eq values 'nil)
87         '#'zerop)
88        (t `#'(lambda (x) ,(attr-predicate-aux values)))))
89
90(defun attr-predicate-aux (values)
91  (cond ((eq values t)
92         '(plusp x))
93        ((eq values nil)
94         '(zerop x))
95        ((symbolp values)
96         `(eq x ',values))
97        ((and (listp values) (member (car values) '(and or not)))
98         (cons (car values) (mapcar #'attr-predicate-aux (cdr values))))
99        (t (error "Illegal form in attribute pattern - ~S" values))))
100
101); Eval-When
102
103;;;
104;;; FIND-LISP-CHAR
105
106(defmacro find-lisp-char (mark)
107  "Move MARK to next :LISP-SYNTAX character, if one isn't found, return NIL."
108  `(find-attribute ,mark :lisp-syntax
109                   #'(lambda (x)
110                       (member x '(:open-paren :close-paren :newline :comment
111                                               :char-quote :string-quote))))) 
112;;;
113;;; PUSH-RANGE
114
115(defmacro push-range (new-range info-struct)
116  "Insert NEW-RANGE into the LISP-INFO-RANGES-TO-IGNORE slot of the INFO-STRUCT."
117  `(when ,new-range
118     (setf (lisp-info-ranges-to-ignore ,info-struct) 
119           (cons ,new-range (lisp-info-ranges-to-ignore ,info-struct)))))
120;;;
121;;; SCAN-DIRECTION
122
123(defmacro scan-direction (mark forwardp &rest forms)
124  "Expand to a form that scans either backward or forward according to Forwardp."
125  (if forwardp
126      `(scan-char ,mark ,@forms)
127      `(rev-scan-char ,mark ,@forms)))
128;;;
129;;; DIRECTION-CHAR
130
131(defmacro direction-char (mark forwardp)
132  "Expand to a form that returns either the previous or next character according
133  to Forwardp."
134  (if forwardp
135      `(next-character ,mark)
136      `(previous-character ,mark)))
137
138;;;
139;;; NEIGHBOR-MARK
140
141(defmacro neighbor-mark (mark forwardp)
142  "Expand to a form that moves MARK either backward or forward one character,
143  depending on FORWARDP."
144  (if forwardp
145      `(mark-after ,mark)
146      `(mark-before ,mark)))
147
148;;;
149;;; NEIGHBOR-LINE
150
151(defmacro neighbor-line (line forwardp)
152  "Expand to return the next or previous line, according to Forwardp."
153  (if forwardp
154      `(line-next ,line)
155      `(line-previous ,line)))
156
157
158;;;; Parsing functions.
159
160;;; PRE-COMMAND-PARSE-CHECK -- Public.
161;;;
162(defun pre-command-parse-check (mark &optional (fer-sure-parse nil))
163  "Parse the area before the command is actually executed."
164  (with-mark ((top mark)
165              (bottom mark))
166    (funcall (value parse-start-function) top)
167    (funcall (value parse-end-function) bottom)
168    (parse-over-block (mark-line top) (mark-line bottom) fer-sure-parse)))
169
170;;; PARSE-OVER-BLOCK
171;;;
172(defun parse-over-block (start-line end-line &optional (fer-sure-parse nil))
173  "Parse over an area indicated from END-LINE to START-LINE."
174  (let ((test-line start-line)
175        prev-line-info)
176   
177    (with-mark ((mark (mark test-line 0)))
178     
179      ; Set the pre-begining and post-ending lines to delimit the range
180      ; of action any command will take.  This means set the lisp-info of the
181      ; lines immediately before and after the block to Nil.
182     
183      (when (line-previous start-line)
184        (setf (getf (line-plist (line-previous start-line)) 'lisp-info) nil))
185      (when (line-next end-line)
186        (setf (getf (line-plist (line-next end-line)) 'lisp-info) nil))
187     
188      (loop
189       (let ((line-info (getf (line-plist test-line) 'lisp-info)))
190         
191         ;;    Reparse the line when any of the following are true:
192         ;;
193         ;;      FER-SURE-PARSE is T
194         ;;
195         ;;      LINE-INFO or PREV-LINE-INFO are Nil.
196         ;;
197         ;;      If the line begins quoted and the previous one wasn't
198         ;;      ended quoted.
199         ;;
200         ;;      The Line's signature slot is invalid (the line has changed).
201         ;;
202         
203         (when (or fer-sure-parse     
204                   (not line-info)     
205                   (not prev-line-info)
206                   
207                   (not (eq (lisp-info-begins-quoted line-info) 
208                            (lisp-info-ending-quoted prev-line-info)))
209                   
210                   (not (eql (line-signature test-line)     
211                             (lisp-info-signature-slot line-info))))
212           
213           (move-to-position mark 0 test-line)
214           
215           (unless line-info
216             (setf line-info (make-lisp-info))
217             (setf (getf (line-plist test-line) 'lisp-info) line-info))
218           
219           (parse-lisp-line-info mark line-info prev-line-info))
220         
221         (when (eq end-line test-line)
222           (return nil))
223         
224         (setq prev-line-info line-info)
225         
226         (setq test-line (line-next test-line)))))))
227
228
229;;;; Parse block finders.
230
231(defhvar "Minimum Lines Parsed"
232  "The minimum number of lines before and after the point parsed by Lisp mode."
233  :value 50)
234(defhvar "Maximum Lines Parsed"
235  "The maximum number of lines before and after the point parsed by Lisp mode."
236  :value 500)
237(defhvar "Defun Parse Goal"
238  "Lisp mode parses the region obtained by skipping this many defuns forward
239   and backward from the point unless this falls outside of the range specified
240   by \"Minimum Lines Parsed\" and \"Maximum Lines Parsed\"."
241  :value 2)
242
243
244(macrolet ((frob (step end)
245             `(let ((min (value minimum-lines-parsed))
246                    (max (value maximum-lines-parsed))
247                    (goal (value defun-parse-goal))
248                    (last-defun nil))
249                (declare (fixnum min max goal))
250                (do ((line (mark-line mark) (,step line))
251                     (count 0 (1+ count)))
252                    ((null line)
253                     (,end mark))
254                  (declare (fixnum count))
255                  (when (char= (line-character line 0) #\()
256                    (setq last-defun line)
257                    (decf goal)
258                    (when (and (<= goal 0) (>= count min))
259                      (line-start mark line)
260                      (return)))
261                  (when (> count max)
262                    (line-start mark (or last-defun line))
263                    (return))))))
264
265  (defun start-of-parse-block (mark)
266    (frob line-previous buffer-start))
267
268  (defun end-of-parse-block (mark)
269    (frob line-next buffer-end)))
270
271;;;
272;;; START-OF-SEARCH-LINE
273
274(defun start-of-search-line (line)
275  "Set LINE to the begining line of the block of text to parse."
276  (with-mark ((mark (mark line 0)))
277    (funcall (value 'Parse-Start-Function) mark)
278    (setq line (mark-line mark))))
279
280;;;
281;;; END-OF-SEACH-LINE
282
283(defun end-of-search-line (line)
284  "Set LINE to the ending line of the block of text to parse."
285  (with-mark ((mark (mark line 0)))
286    (funcall (value 'Parse-End-Function) mark)
287    (setq line (mark-line mark))))
288
289
290;;;; PARSE-LISP-LINE-INFO.
291
292;;; PARSE-LISP-LINE-INFO -- Internal.
293;;;
294;;; This parses through the line doing the following things:
295;;;
296;;;      Counting/Setting the NET-OPEN-PARENS & NET-CLOSE-PARENS.
297;;;
298;;;      Making all areas of the line that should be invalid (comments,
299;;;      char-quotes, and the inside of strings) and such be in
300;;;      RANGES-TO-IGNORE.
301;;;
302;;;      Set BEGINS-QUOTED and ENDING-QUOTED
303;;;
304(defun parse-lisp-line-info (mark line-info prev-line-info)
305  "Parse line and set line information like NET-OPEN-PARENS, NET-CLOSE-PARENS,
306   RANGES-TO-INGORE, and ENDING-QUOTED."
307  (let ((net-open-parens 0)
308        (net-close-parens 0))
309    (declare (fixnum net-open-parens net-close-parens))
310   
311    ;; Re-set the slots necessary
312   
313    (setf (lisp-info-ranges-to-ignore line-info) nil)
314   
315    ;; The only way the current line begins quoted is when there
316    ;; is a previous line and it's ending was quoted.
317   
318    (setf (lisp-info-begins-quoted line-info)
319          (and prev-line-info 
320               (lisp-info-ending-quoted prev-line-info)))
321   
322    (if (lisp-info-begins-quoted line-info)
323        (deal-with-string-quote mark line-info)
324        (setf (lisp-info-ending-quoted line-info) nil))
325   
326    (unless (lisp-info-ending-quoted line-info)
327      (loop 
328        (find-lisp-char mark)
329        (ecase (character-attribute :lisp-syntax (next-character mark))
330         
331          (:open-paren
332           (setq net-open-parens (1+ net-open-parens))
333           (mark-after mark))
334         
335          (:close-paren
336           (if (zerop net-open-parens)
337               (setq net-close-parens (1+ net-close-parens))
338               (setq net-open-parens (1- net-open-parens)))
339           (mark-after mark))
340         
341          (:newline
342           (setf (lisp-info-ending-quoted line-info) nil)
343           (return t))
344         
345          (:comment
346           (push-range (cons (mark-charpos mark) (line-length (mark-line mark)))
347                       line-info)
348           (setf (lisp-info-ending-quoted line-info) nil)
349           (return t))
350         
351          (:char-quote
352           (mark-after mark)
353           (push-range (cons (mark-charpos mark) (1+ (mark-charpos mark)))
354                       line-info)
355           (mark-after mark))
356         
357          (:string-quote
358           (mark-after mark)
359           (unless (deal-with-string-quote mark line-info)
360             (setf (lisp-info-ending-quoted line-info) t)
361             (return t))))))
362   
363    (setf (lisp-info-net-open-parens line-info) net-open-parens)
364    (setf (lisp-info-net-close-parens line-info) net-close-parens)
365    (setf (lisp-info-signature-slot line-info) 
366          (line-signature (mark-line mark)))))
367
368
369
370;;;; String quote utilities.
371
372;;; VALID-STRING-QUOTE-P
373;;;
374(defmacro valid-string-quote-p (mark forwardp)
375  "Return T if the string-quote indicated by MARK is valid."
376  (let ((test-mark (gensym)))
377    `(with-mark ((,test-mark ,mark))
378       ,(unless forwardp
379          ;; TEST-MARK should always be right before the String-quote to be
380          ;; checked.
381          `(mark-before ,test-mark))
382       (when (test-char (next-character ,test-mark) :lisp-syntax :string-quote)
383         (let ((slash-count 0))
384           (loop
385             (mark-before ,test-mark)
386             (if (test-char (next-character ,test-mark) :lisp-syntax :char-quote)
387                 (incf slash-count)
388                 (return t)))
389           (not (oddp slash-count)))))))
390
391;;;
392;;; FIND-VALID-STRING-QUOTE
393
394(defmacro find-valid-string-quote (mark &key forwardp (cease-at-eol nil))
395  "Expand to a form that will leave MARK before a valid string-quote character,
396  in either a forward or backward direction, according to FORWARDP.  If
397  CEASE-AT-EOL is T then it will return nil if encountering the EOL before a
398  valid string-quote."
399  (let ((e-mark (gensym)))
400    `(with-mark ((,e-mark ,mark))
401       
402       (loop
403        (unless (scan-direction ,e-mark ,forwardp :lisp-syntax 
404                                ,(if cease-at-eol 
405                                     `(or :newline :string-quote)
406                                     `:string-quote))
407          (return nil))
408       
409        ,@(if cease-at-eol
410              `((when (test-char (direction-char ,e-mark ,forwardp) :lisp-syntax
411                                 :newline)
412                  (return nil))))
413       
414        (when (valid-string-quote-p ,e-mark ,forwardp)
415          (move-mark ,mark ,e-mark)
416          (return t))
417       
418        (neighbor-mark ,e-mark ,forwardp)))))
419
420;;;; DEAL-WITH-STRING-QUOTE.
421
422;;; DEAL-WITH-STRING-QUOTE
423;;;
424;;; Called when a string is begun (i.e. parse hits a #\").  It checks for a
425;;; matching quote on the line that MARK points to, and puts the appropriate
426;;; area in the RANGES-TO-IGNORE slot and leaves MARK pointing after this area.
427;;; The "appropriate area" is from MARK to the end of the line or the matching
428;;; string-quote, whichever comes first.
429;;;
430(defun deal-with-string-quote (mark info-struct)
431  "Alter the current line's info struct as necessary as due to encountering a
432   string quote character."
433  (with-mark ((e-mark mark))
434    (cond ((find-valid-string-quote e-mark :forwardp t :cease-at-eol t)
435           ;; If matching quote is on this line then mark the area between the
436           ;; first quote (MARK) and the matching quote as invalid by pushing
437           ;; its begining and ending into the IGNORE-RANGE.
438           (push-range (cons (mark-charpos mark) (mark-charpos e-mark))
439                       info-struct)
440           (setf (lisp-info-ending-quoted info-struct) nil)
441           (mark-after e-mark)
442           (move-mark mark e-mark))
443          ;; If the EOL has been hit before the matching quote then mark the
444          ;; area from MARK to the EOL as invalid.
445          (t
446           (push-range (cons (mark-charpos mark)
447                             (1+ (line-length (mark-line mark))))
448                       info-struct)
449           ;; The Ending is marked as still being quoted.
450           (setf (lisp-info-ending-quoted info-struct) t)
451           (line-end mark)
452           nil))))
453
454
455
456;;;; Character validity checking:
457
458;;; Find-Ignore-Region  --  Internal
459;;;
460;;;    If the character in the specified direction from Mark is in an ignore
461;;; region, then return the region and the line that the region is in as
462;;; values.  If there is no ignore region, then return NIL and the Mark-Line.
463;;; If the line is not parsed, or there is no character (because of being at
464;;; the buffer beginning or end), then return both values NIL.
465;;;
466(defun find-ignore-region (mark forwardp)
467  (flet ((scan (line pos)
468           (declare (fixnum pos))
469           (let ((info (getf (line-plist line) 'lisp-info)))
470             (if info
471                 (dolist (range (lisp-info-ranges-to-ignore info)
472                                (values nil line))
473                   (let ((start (car range))
474                         (end (cdr range)))
475                     (declare (fixnum start end))
476                     (when (and (>= pos start) (< pos end))
477                       (return (values range line)))))
478                 (values nil nil)))))
479    (let ((pos (mark-charpos mark))
480          (line (mark-line mark)))
481      (declare (fixnum pos))
482      (cond (forwardp (scan line pos))
483            ((> pos 0) (scan line (1- pos)))
484            (t
485             (let ((prev (line-previous line)))
486               (if prev
487                   (scan prev (line-length prev))
488                   (values nil nil))))))))
489
490
491;;; Valid-Spot  --  Public
492;;;
493(defun valid-spot (mark forwardp)
494  "Return true if the character pointed to by Mark is not in a quoted context,
495  false otherwise.  If Forwardp is true, we use the next character, otherwise
496  we use the previous."
497  (multiple-value-bind (region line)
498                       (find-ignore-region mark forwardp)
499    (and line (not region))))
500
501
502;;; Scan-Direction-Valid  --  Internal
503;;;
504;;;    Like scan-direction, but only stop on valid characters.
505;;;
506(defmacro scan-direction-valid (mark forwardp &rest forms)
507  (let ((n-mark (gensym))
508        (n-line (gensym))
509        (n-region (gensym))
510        (n-won (gensym)))
511    `(let ((,n-mark ,mark) (,n-won nil))
512       (loop
513         (multiple-value-bind (,n-region ,n-line)
514                              (find-ignore-region ,n-mark ,forwardp)
515           (unless ,n-line (return nil))
516           (if ,n-region
517               (move-to-position ,n-mark
518                                 ,(if forwardp
519                                      `(cdr ,n-region) 
520                                      `(car ,n-region))
521                                 ,n-line)
522               (when ,n-won (return t)))
523           ;;
524           ;; Peculiar condition when a quoting character terminates a line.
525           ;; The ignore region is off the end of the line causing %FORM-OFFSET
526           ;; to infinitely loop.
527           (when (> (mark-charpos ,n-mark) (line-length ,n-line))
528             (line-offset ,n-mark 1 0))
529           (unless (scan-direction ,n-mark ,forwardp ,@forms)
530             (return nil))
531           (setq ,n-won t))))))
532
533
534;;;; List offseting.
535
536;;; %LIST-OFFSET allows for BACKWARD-LIST and FORWARD-LIST to be built
537;;; with the same existing structure, with the altering of one variable.
538;;; This one variable being FORWARDP.
539;;;
540(defmacro %list-offset (actual-mark forwardp &key (extra-parens 0) )
541  "Expand to code that will go forward one list either backward or forward,
542   according to the FORWARDP flag."
543  (let ((mark (gensym)))
544    `(let ((paren-count ,extra-parens))
545       (declare (fixnum paren-count))
546       (with-mark ((,mark ,actual-mark))
547         (loop
548           (scan-direction ,mark ,forwardp :lisp-syntax
549                           (or :close-paren :open-paren :newline))
550           (let ((ch (direction-char ,mark ,forwardp)))
551             (unless ch (return nil))
552             (when (valid-spot ,mark ,forwardp)
553               (case (character-attribute :lisp-syntax ch)
554                 (:close-paren
555                  (decf paren-count)
556                  ,(when forwardp
557                     ;; When going forward, an unmatching close-paren means the
558                     ;; end of list.
559                     `(when (<= paren-count 0)
560                        (neighbor-mark ,mark ,forwardp)
561                        (move-mark ,actual-mark ,mark)
562                        (return t))))
563                 (:open-paren
564                  (incf paren-count)
565                  ,(unless forwardp             ; Same as above only end of list
566                     `(when (>= paren-count 0)  ; is opening parens.
567                        (neighbor-mark ,mark ,forwardp)
568                        (move-mark ,actual-mark ,mark)
569                        (return t))))
570                 
571                 (:newline 
572                  ;; When a #\Newline is hit, then the matching paren must lie
573                  ;; on some other line so drop down into the multiple line
574                  ;; balancing function: QUEST-FOR-BALANCING-PAREN If no paren
575                  ;; seen yet, keep going.
576                  (cond ((zerop paren-count))
577                        ((quest-for-balancing-paren ,mark paren-count ,forwardp)
578                         (move-mark ,actual-mark ,mark)
579                         (return t))
580                        (t
581                         (return nil)))))))
582           
583           (neighbor-mark ,mark ,forwardp))))))
584
585;;;
586;;; QUEST-FOR-BALANCING-PAREN
587
588(defmacro quest-for-balancing-paren (mark paren-count forwardp)
589  "Expand to a form that finds the the balancing paren for however many opens or
590  closes are registered by Paren-Count."
591  `(let* ((line (mark-line ,mark)))
592     (loop
593       (setq line (neighbor-line line ,forwardp))
594       (unless line (return nil))
595       (let ((line-info (getf (line-plist line) 'lisp-info))
596             (unbal-paren ,paren-count))
597         (unless line-info (return nil))
598         
599         ,(if forwardp
600              `(decf ,paren-count (lisp-info-net-close-parens line-info))
601              `(incf ,paren-count (lisp-info-net-open-parens line-info)))
602         
603         (when ,(if forwardp
604                    `(<= ,paren-count 0)
605                    `(>= ,paren-count 0))
606           ,(if forwardp
607                `(line-start ,mark line)
608                `(line-end ,mark line))
609           (return (goto-correct-paren-char ,mark unbal-paren ,forwardp)))
610
611         ,(if forwardp
612              `(incf ,paren-count (lisp-info-net-open-parens line-info))
613              `(decf ,paren-count (lisp-info-net-close-parens line-info)))))))
614                   
615
616;;;
617;;; GOTO-CORRECT-PAREN-CHAR
618
619(defmacro goto-correct-paren-char (mark paren-count forwardp)
620  "Expand to a form that will leave MARK on the correct balancing paren matching
621   however many are indicated by COUNT." 
622  `(with-mark ((m ,mark))
623     (let ((count ,paren-count))
624       (loop
625         (scan-direction m ,forwardp :lisp-syntax 
626                         (or :close-paren :open-paren :newline))
627         (when (valid-spot m ,forwardp)
628           (ecase (character-attribute :lisp-syntax (direction-char m ,forwardp))
629             (:close-paren 
630              (decf count)
631              ,(when forwardp
632                 `(when (zerop count)
633                    (neighbor-mark m ,forwardp)
634                    (move-mark ,mark m)
635                    (return t))))
636             
637             (:open-paren 
638              (incf count)
639              ,(unless forwardp
640                 `(when (zerop count)
641                    (neighbor-mark m ,forwardp)
642                    (move-mark ,mark m)
643                    (return t))))))
644         (neighbor-mark m ,forwardp)))))
645
646
647(defun list-offset (mark offset)
648  (if (plusp offset)
649      (dotimes (i offset t)
650        (unless (%list-offset mark t) (return nil)))
651      (dotimes (i (- offset) t)
652        (unless (%list-offset mark nil) (return nil)))))
653
654(defun forward-up-list (mark)
655  "Moves mark just past the closing paren of the immediately containing list."
656  (%list-offset mark t :extra-parens 1))
657
658(defun backward-up-list (mark)
659  "Moves mark just before the opening paren of the immediately containing list."
660  (%list-offset mark nil :extra-parens -1))
661
662
663
664;;;; Top level form location hacks (open parens beginning lines).
665
666;;; NEIGHBOR-TOP-LEVEL is used only in TOP-LEVEL-OFFSET.
667;;;
668(eval-when (:compile-toplevel :execute)
669(defmacro neighbor-top-level (line forwardp)
670  `(loop
671     (when (test-char (line-character ,line 0) :lisp-syntax :open-paren)
672       (return t))
673     (setf ,line ,(if forwardp `(line-next ,line) `(line-previous ,line)))
674     (unless ,line (return nil))))
675) ;eval-when
676
677(defun top-level-offset (mark offset)
678  "Go forward or backward offset number of top level forms.  Mark is
679   returned if offset forms exists, otherwise nil."
680  (declare (fixnum offset))
681  (let* ((line (mark-line mark))
682         (at-start (test-char (line-character line 0) :lisp-syntax :open-paren)))
683    (cond ((zerop offset) mark)
684          ((plusp offset)
685           (do ((offset (if at-start offset (1- offset))
686                        (1- offset)))
687               (nil)
688             (declare (fixnum offset))
689             (unless (neighbor-top-level line t) (return nil))
690             (when (zerop offset) (return (line-start mark line)))
691             (unless (setf line (line-next line)) (return nil))))
692          (t
693           (do ((offset (if (and at-start (start-line-p mark))
694                            offset
695                            (1+ offset))
696                        (1+ offset)))
697                (nil)
698             (declare (fixnum offset))
699             (unless (neighbor-top-level line nil) (return nil))
700             (when (zerop offset) (return (line-start mark line)))
701             (unless (setf line (line-previous line)) (return nil)))))))
702
703
704(defun mark-top-level-form (mark1 mark2)
705  "Moves mark1 and mark2 to the beginning and end of the current or next defun.
706   Mark1 one is used as a reference.  The marks may be altered even if
707   unsuccessful.  if successful, return mark2, else nil."
708  (let ((winp (cond ((inside-defun-p mark1)
709                     (cond ((not (top-level-offset mark1 -1)) nil)
710                           ((not (form-offset (move-mark mark2 mark1) 1)) nil)
711                           (t mark2)))
712                    ((start-defun-p mark1)
713                     (form-offset (move-mark mark2 mark1) 1))
714                    ((and (top-level-offset (move-mark mark2 mark1) -1)
715                          (start-defun-p mark2)
716                          (form-offset mark2 1)
717                          (same-line-p mark1 mark2))
718                     (form-offset (move-mark mark1 mark2) -1)
719                     mark2)
720                    ((top-level-offset mark1 1)
721                     (form-offset (move-mark mark2 mark1) 1)))))
722    (when winp
723      (when (blank-after-p mark2) (line-offset mark2 1 0))
724      mark2)))
725
726(defun inside-defun-p (mark)
727  "T if the current point is (supposedly) in a top level form."
728  (with-mark ((m mark))
729    (when (top-level-offset m -1)
730      (form-offset m 1)
731      (mark> m mark))))
732
733(defun start-defun-p (mark)
734  "Returns t if mark is sitting before an :open-paren at the beginning of a
735   line."
736  (and (start-line-p mark)
737       (test-char (next-character mark) :lisp-syntax :open-paren)))
738
739
740
741;;;; Form offseting.
742
743(defmacro %form-offset (mark forwardp)
744  `(with-mark ((m ,mark))
745     (when (scan-direction-valid m ,forwardp :lisp-syntax
746                                 (or :open-paren :close-paren
747                                     :char-quote :string-quote
748                                     :constituent))
749       (ecase (character-attribute :lisp-syntax (direction-char m ,forwardp))
750         (:open-paren
751          (when ,(if forwardp `(list-offset m 1) `(mark-before m))
752            ,(unless forwardp
753               '(scan-direction m nil :lisp-syntax (not :prefix)))
754            (move-mark ,mark m)
755            t))
756         (:close-paren
757          (when ,(if forwardp `(mark-after m) `(list-offset m -1))
758            ,(unless forwardp
759               '(scan-direction m nil :lisp-syntax (not :prefix)))
760            (move-mark ,mark m)
761            t))
762         ((:constituent :char-quote)
763          (scan-direction-valid m ,forwardp :lisp-syntax
764                                (not (or :constituent :char-quote)))
765          ,(if forwardp
766               `(scan-direction-valid m t :lisp-syntax
767                                      (not (or :constituent :char-quote)))
768               `(scan-direction-valid m nil :lisp-syntax
769                                      (not (or :constituent :char-quote
770                                               :prefix))))
771          (move-mark ,mark m)
772          t)
773         (:string-quote
774          (cond ((valid-spot m ,(not forwardp))
775                 (neighbor-mark m ,forwardp)
776                 (when (scan-direction-valid m ,forwardp :lisp-syntax
777                                             :string-quote)
778                   (neighbor-mark m ,forwardp)
779                   (move-mark ,mark m)
780                   t))
781                (t (neighbor-mark m ,forwardp)
782                   (move-mark ,mark m)
783                   t)))))))
784
785
786(defun form-offset (mark offset)
787  "Move mark offset number of forms, after if positive, before if negative.
788   Mark is always moved.  If there weren't enough forms, returns nil instead of
789   mark."
790  (if (plusp offset)
791      (dotimes (i offset t)
792        (unless (%form-offset mark t) (return nil)))
793      (dotimes (i (- offset) t)
794        (unless (%form-offset mark nil) (return nil)))))
795
796
797
798;;;; Table of special forms with special indenting requirements.
799
800(defhvar "Indent Defanything"
801  "This is the number of special arguments implicitly assumed to be supplied
802   in calls to functions whose names begin with \"DEF\".  If set to NIL, this
803   feature is disabled."
804  :value 2)
805
806(defvar *special-forms* (make-hash-table :test #'equal))
807
808(defun defindent (fname args)
809  "Define Fname to have Args special arguments.  If args is null then remove
810   any special arguments information."
811  (check-type fname string)
812  (let ((fname (string-upcase fname)))
813    (cond ((null args) (remhash fname *special-forms*))
814          (t
815           (check-type args integer)
816           (setf (gethash fname *special-forms*) args)))))
817
818
819;;; Hemlock forms.
820;;;
821(defindent "with-mark" 1)
822(defindent "with-random-typeout" 1)
823(defindent "with-pop-up-display" 1)
824(defindent "defhvar" 1)
825(defindent "hlet" 1)
826(defindent "defcommand" 2)
827(defindent "defattribute" 1)
828(defindent "command-case" 1)
829(defindent "with-input-from-region" 1)
830(defindent "with-output-to-mark" 1)
831(defindent "with-output-to-window" 1)
832(defindent "do-strings" 1)
833(defindent "save-for-undo" 1)
834(defindent "do-alpha-chars" 1)
835(defindent "do-headers-buffers" 1)
836(defindent "do-headers-lines" 1)
837(defindent "with-headers-mark" 1)
838(defindent "frob" 1) ;cover silly FLET and MACROLET names for Rob and Bill.
839(defindent "with-writable-buffer" 1)
840
841;;; Common Lisp forms.
842;;;
843(defindent "block" 1)
844(defindent "case" 1)
845(defindent "catch" 1)
846(defindent "ccase" 1)                     
847(defindent "compiler-let" 1)
848(defindent "ctypecase" 1)
849(defindent "defconstant" 1)
850(defindent "define-compiler-macro" 2)
851(defindent "define-setf-method" 2)
852(defindent "destructuring-bind" 2)
853(defindent "defmacro" 2)
854(defindent "defpackage" 1)
855(defindent "defparameter" 1)
856(defindent "defstruct" 1)
857(defindent "deftype" 2)
858(defindent "defun" 2)
859(defindent "defvar" 1)
860(defindent "do" 2)
861(defindent "do*" 2)
862(defindent "do-all-symbols" 1)
863(defindent "do-external-symbols" 1)
864(defindent "do-symbols" 1)
865(defindent "dolist" 1)
866(defindent "dotimes" 1)
867(defindent "ecase" 1)
868(defindent "etypecase" 1)
869(defindent "eval-when" 1)
870(defindent "flet" 1)
871(defindent "if" 1)
872(defindent "labels" 1)
873(defindent "lambda" 1)
874(defindent "let" 1)
875(defindent "let*" 1)
876(defindent "locally" 0)
877(defindent "loop" 0)
878(defindent "macrolet" 1)
879(defindent "multiple-value-bind" 2)
880(defindent "multiple-value-call" 1)
881(defindent "multiple-value-prog1" 1)
882(defindent "multiple-value-setq" 1)
883(defindent "prog1" 1)
884(defindent "progv" 2)
885(defindent "progn" 0)
886(defindent "typecase" 1)
887(defindent "unless" 1)
888(defindent "unwind-protect" 1)
889(defindent "when" 1)
890(defindent "with-input-from-string" 1)
891(defindent "with-open-file" 1)
892(defindent "with-open-stream" 1)
893(defindent "with-output-to-string" 1)
894(defindent "with-package-iterator" 1)
895
896;;; Error/condition system forms.
897;;;
898(defindent "define-condition" 2)
899(defindent "handler-bind" 1)
900(defindent "handler-case" 1)
901(defindent "restart-bind" 1)
902(defindent "restart-case" 1)
903(defindent "with-simple-restart" 1)
904;;; These are for RESTART-CASE branch formatting.
905(defindent "store-value" 1)
906(defindent "use-value" 1)
907(defindent "muffle-warning" 1)
908(defindent "abort" 1)
909(defindent "continue" 1)
910
911;;; Debug-internals forms.
912;;;
913(defindent "do-debug-function-blocks" 1)
914(defindent "di:do-debug-function-blocks" 1)
915(defindent "do-debug-function-variables" 1)
916(defindent "di:do-debug-function-variables" 1)
917(defindent "do-debug-block-locations" 1)
918(defindent "di:do-debug-block-locations" 1)
919;;;
920;;; Debug-internals conditions
921;;; (define these to make uses of HANDLER-CASE indent branches correctly.)
922;;;
923(defindent "debug-condition" 1)
924(defindent "di:debug-condition" 1)
925(defindent "no-debug-info" 1)
926(defindent "di:no-debug-info" 1)
927(defindent "no-debug-function-returns" 1)
928(defindent "di:no-debug-function-returns" 1)
929(defindent "no-debug-blocks" 1)
930(defindent "di:no-debug-blocks" 1)
931(defindent "lambda-list-unavailable" 1)
932(defindent "di:lambda-list-unavailable" 1)
933(defindent "no-debug-variables" 1)
934(defindent "di:no-debug-variables" 1)
935(defindent "invalid-value" 1)
936(defindent "di:invalid-value" 1)
937(defindent "ambiguous-variable-name" 1)
938(defindent "di:ambiguous-variable-name" 1)
939(defindent "debug-error" 1)
940(defindent "di:debug-error" 1)
941(defindent "unhandled-condition" 1)
942(defindent "di:unhandled-condition" 1)
943(defindent "unknown-code-location" 1)
944(defindent "di:unknown-code-location" 1)
945(defindent "unknown-debug-variable" 1)
946(defindent "di:unknown-debug-variable" 1)
947(defindent "invalid-control-stack-pointer" 1)
948(defindent "di:invalid-control-stack-pointer" 1)
949(defindent "frame-function-mismatch" 1)
950(defindent "di:frame-function-mismatch" 1)
951
952
953;;; CLOS forms.
954;;;
955(defindent "with-slots" 1)
956(defindent "with-accessors" 2)
957(defindent "defclass" 2)
958(defindent "print-unreadable-object" 1)
959(defindent "defmethod" 2)
960(defindent "make-instance" 1)
961
962;;; System forms.
963;;;
964(defindent "rlet" 1)
965
966;;; Multiprocessing forms.
967(defindent "with-lock-grabbed" 1)
968(defindent "process-wait" 1)
969
970
971
972;;;; Indentation.
973
974;;; LISP-INDENTATION -- Internal Interface.
975
976(defun strip-package-prefix (string)
977  (let* ((p (position #\: string :from-end t)))
978    (if p
979      (subseq string (1+ p))
980      string)))
981;;;
982(defun lisp-indentation (mark)
983  "Compute number of spaces which mark should be indented according to
984   local context and lisp grinding conventions.  This assumes mark is at the
985   beginning of the line to be indented."
986  (with-mark ((m mark)
987              (temp mark))
988    ;; See if we are in a quoted context.
989    (unless (valid-spot m nil)
990      (return-from lisp-indentation (lisp-generic-indentation m)))
991    ;; Look for the paren that opens the containing form.
992    (unless (backward-up-list m)
993      (return-from lisp-indentation 0))
994    ;; Move after the paren, save the start, and find the form name.
995    (mark-after m)
996    (with-mark ((start m))
997      (unless (and (scan-char m :lisp-syntax
998                              (not (or :space :prefix :char-quote)))
999                   (test-char (next-character m) :lisp-syntax :constituent))
1000        (return-from lisp-indentation (mark-column start)))
1001      (with-mark ((fstart m))
1002        (scan-char m :lisp-syntax (not :constituent))
1003        (let* ((fname (nstring-upcase
1004                       (strip-package-prefix (region-to-string (region fstart m)))))
1005               (special-args (or (gethash fname *special-forms*)
1006                                 (and (> (length fname) 2)
1007                                      (string= fname "DEF" :end1 3)
1008                                      (value indent-defanything)))))
1009          (declare (simple-string fname))
1010          ;; Now that we have the form name, did it have special syntax?
1011          (cond (special-args
1012                 (with-mark ((spec m))
1013                   (cond ((and (form-offset spec special-args)
1014                               (mark<= spec mark))
1015                          (1+ (mark-column start)))
1016                         ((skip-valid-space m)
1017                          (mark-column m))
1018                         (t
1019                          (+ (mark-column start) 3)))))
1020                ;; See if the user seems to have altered the editor's
1021                ;; indentation, and if so, try to adhere to it.  This usually
1022                ;; happens when you type in a quoted list constant that line
1023                ;; wraps.  You want all the items on successive lines to fall
1024                ;; under the first character after the opening paren, not as if
1025                ;; you are calling a function.
1026                ((and (form-offset temp -1)
1027                      (or (blank-before-p temp) (not (same-line-p temp fstart)))
1028                      (not (same-line-p temp mark)))
1029                 (unless (blank-before-p temp)
1030                   (line-start temp)
1031                   (find-attribute temp :space #'zerop))
1032                 (mark-column temp))
1033                ;; Appears to be a normal form.  Is the first arg on the same
1034                ;; line as the form name?
1035                ((skip-valid-space m)
1036                 (or (lisp-indentation-check-for-local-def
1037                      mark temp fstart start t)
1038                     (mark-column m)))
1039                ;; Okay, fall under the first character after the opening paren.
1040                (t
1041                 (or (lisp-indentation-check-for-local-def
1042                      mark temp fstart start nil)
1043                     (mark-column start)))))))))
1044
1045(defhvar "Lisp Indentation Local Definers"
1046  "Forms with syntax like LABELS, MACROLET, etc."
1047  :value '("LABELS" "MACROLET" "FLET"))
1048
1049;;; LISP-INDENTATION-CHECK-FOR-LOCAL-DEF -- Internal.
1050;;;
1051;;; This is a temporary hack to see how it performs.  When we are indenting
1052;;; what appears to be a function call, let's look for FLET or MACROLET to see
1053;;; if we really are indenting a local definition.  If we are, return the
1054;;; indentation for a DEFUN; otherwise, nil
1055;;;
1056;;; Mark is the argument to LISP-INDENTATION.  Start is just inside the paren
1057;;; of what looks like a function call.  If we are in an FLET, arg-list
1058;;; indicates whether the local function's arg-list has been entered, that is,
1059;;; whether we need to normally indent for a DEFUN body or indent specially for
1060;;; the arg-list.
1061;;;
1062(defun lisp-indentation-check-for-local-def (mark temp1 temp2 start arg-list)
1063  ;; We know this succeeds from LISP-INDENTATION.
1064  (backward-up-list (move-mark temp1 mark)) ;Paren for local definition.
1065  (cond ((and (backward-up-list temp1)      ;Paren opening the list of defs
1066              (form-offset (move-mark temp2 temp1) -1)
1067              (mark-before temp2)
1068              (backward-up-list temp1)      ;Paren for FLET or MACROLET.
1069              (mark= temp1 temp2))          ;Must be in first arg form.
1070         ;; See if the containing form is named FLET or MACROLET.
1071         (mark-after temp1)
1072         (unless (and (scan-char temp1 :lisp-syntax
1073                                 (not (or :space :prefix :char-quote)))
1074                      (test-char (next-character temp1) :lisp-syntax
1075                                 :constituent))
1076           (return-from lisp-indentation-check-for-local-def nil))
1077         (move-mark temp2 temp1)
1078         (scan-char temp2 :lisp-syntax (not :constituent))
1079         (let ((fname (nstring-upcase (region-to-string (region temp1 temp2)))))
1080           (cond ((not (member fname (value lisp-indentation-local-definers)
1081                               :test #'string=))
1082                  nil)
1083                 (arg-list
1084                  (1+ (mark-column start)))
1085                 (t
1086                  (+ (mark-column start) 3)))))))
1087
1088;;; LISP-GENERIC-INDENTATION -- Internal.
1089;;;
1090;;; LISP-INDENTATION calls this when mark is in a invalid spot, or quoted
1091;;; context.  If we are inside a string, we return the column one greater
1092;;; than the opening double quote.  Otherwise, we just use the indentation
1093;;; of the first preceding non-blank line.
1094;;;
1095(defun lisp-generic-indentation (mark)
1096  (with-mark ((m mark))
1097    (form-offset m -1)
1098    (cond ((eq (character-attribute :lisp-syntax (next-character m))
1099               :string-quote)
1100           (1+ (mark-column m)))
1101          (t
1102           (let* ((line (mark-line mark))
1103                  (prev (do ((line (line-previous line) (line-previous line)))
1104                            ((not (and line (blank-line-p line))) line))))
1105             (cond (prev
1106                    (line-start mark prev)
1107                    (find-attribute mark :space #'zerop)
1108                    (mark-column mark))
1109                   (t 0)))))))
1110
1111;;; Skip-Valid-Space  --  Internal
1112;;;
1113;;;    Skip over any space on the line Mark is on, stopping at the first valid
1114;;; non-space character.  If there is none on the line, return nil.
1115;;;
1116(defun skip-valid-space (mark)
1117  (loop
1118    (scan-char mark :lisp-syntax (not :space))
1119    (let ((val (character-attribute :lisp-syntax
1120                                    (next-character mark))))
1121      (cond ((eq val :newline) (return nil))
1122            ((valid-spot mark t) (return mark))))
1123    (mark-after mark)))
1124
1125;; (declaim (optimize (speed 0))); byte compile again
1126
1127
1128;;;; Indentation commands and hook functions.
1129
1130(defcommand "Defindent" (p)
1131  "Define the Lisp indentation for the current function.
1132  The indentation is a non-negative integer which is the number
1133  of special arguments for the form.  Examples: 2 for Do, 1 for Dolist.
1134  If a prefix argument is supplied, then delete the indentation information."
1135  "Do a defindent, man!"
1136  (with-mark ((m (current-point)))
1137    (pre-command-parse-check m)
1138    (unless (backward-up-list m) (editor-error))
1139    (mark-after m)
1140    (with-mark ((n m))
1141      (scan-char n :lisp-syntax (not :constituent))
1142      (let ((s (region-to-string (region m n))))
1143        (declare (simple-string s))
1144        (when (zerop (length s)) (editor-error))
1145        (if p
1146            (defindent s nil)
1147            (let ((i (prompt-for-integer
1148                      :prompt (format nil "Indentation for ~A: " s)
1149                      :help "Number of special arguments.")))
1150              (when (minusp i)
1151                (editor-error "Indentation must be non-negative."))
1152              (defindent s i))))))
1153  (indent-command nil))
1154
1155(defcommand "Indent Form" (p)
1156  "Indent Lisp code in the next form."
1157  "Indent Lisp code in the next form."
1158  (declare (ignore p))
1159  (let ((point (current-point)))
1160    (pre-command-parse-check point)
1161    (with-mark ((m point))
1162      (unless (form-offset m 1) (editor-error))
1163      (lisp-indent-region (region point m) "Indent Form"))))
1164
1165;;; LISP-INDENT-REGION -- Internal.
1166;;;
1167;;; This indents a region of Lisp code without doing excessive redundant
1168;;; computation.  We parse the entire region once, then scan through doing
1169;;; indentation on each line.  We forcibly reparse each line that we indent so
1170;;; that the list operations done to determine indentation of subsequent lines
1171;;; will work.  This is done undoably with save1, save2, buf-region, and
1172;;; undo-region.
1173;;;
1174(defun lisp-indent-region (region &optional (undo-text "Lisp region indenting"))  (let* ((start (region-start region))
1175         (end (region-end region))
1176         (buffer (hi::line-%buffer (mark-line start))))
1177    (with-mark ((m1 start)
1178                (m2 end))
1179      (funcall (value parse-start-function) m1)
1180      (funcall (value parse-end-function) m2)
1181      (parse-over-block (mark-line m1) (mark-line m2)))
1182    (hi::check-buffer-modification buffer start)
1183    (hi::check-buffer-modification buffer end)
1184    (let* ((first-line (mark-line start))
1185              (last-line (mark-line end))
1186              (prev (line-previous first-line))
1187              (prev-line-info
1188               (and prev (getf (line-plist prev) 'lisp-info)))
1189              (save1 (line-start (copy-mark start :right-inserting)))
1190              (save2 (line-end (copy-mark end :left-inserting)))
1191              (buf-region (region save1 save2))
1192              (undo-region (copy-region buf-region)))
1193         (with-mark ((bol start :left-inserting))
1194           (do ((line first-line (line-next line)))
1195               (nil)
1196             (line-start bol line)
1197             (ensure-lisp-indentation bol)
1198             (let ((line-info (getf (line-plist line) 'lisp-info)))
1199               (parse-lisp-line-info bol line-info prev-line-info)
1200               (setq prev-line-info line-info))
1201             (when (eq line last-line) (return nil))))
1202         (make-region-undo :twiddle undo-text buf-region undo-region))))
1203
1204;;; INDENT-FOR-LISP -- Internal.
1205;;;
1206;;; This is the value of "Indent Function" for "Lisp" mode.
1207;;;
1208(defun indent-for-lisp (mark)
1209  (line-start mark)
1210  (pre-command-parse-check mark)
1211  (ensure-lisp-indentation mark))
1212
1213(defun count-leading-whitespace (mark)
1214  (with-mark ((m mark))
1215    (line-start m)
1216    (do* ((p 0)
1217          (q 0 (1+ q))
1218          (tab-width (value spaces-per-tab)))
1219         ()
1220      (case (next-character m)
1221        (#\space (incf p))
1222        (#\tab (setq p (* tab-width (ceiling (1+ p) tab-width))))
1223        (t (return (values p q))))
1224      (character-offset m 1))))
1225
1226;;; Don't do anything if M's line is already correctly indented.
1227(defun ensure-lisp-indentation (m)
1228  (let* ((col (lisp-indentation m)))
1229    (multiple-value-bind (curcol curpos) (count-leading-whitespace m)
1230      (cond ((= curcol col) (setf (mark-charpos m) curpos))
1231            (t
1232             (delete-horizontal-space m)
1233             (funcall (value indent-with-tabs) m col))))))
1234
1235
1236
1237
1238;;;; Most "Lisp" mode commands.
1239
1240(defcommand "Beginning of Defun" (p)
1241  "Move the point to the beginning of a top-level form, collapsing the selection.
1242  with an argument, skips the previous p top-level forms."
1243  "Move the point to the beginning of a top-level form, collapsing the selection."
1244  (let ((point (current-point-collapsing-selection))
1245        (count (or p 1)))
1246    (pre-command-parse-check point)
1247    (if (minusp count)
1248        (end-of-defun-command (- count))
1249        (unless (top-level-offset point (- count))
1250          (editor-error)))))
1251
1252(defcommand "Select to Beginning of Defun" (p)
1253  "Move the point to the beginning of a top-level form, extending the selection.
1254  with an argument, skips the previous p top-level forms."
1255  "Move the point to the beginning of a top-level form, extending the selection."
1256  (let ((point (current-point-extending-selection))
1257        (count (or p 1)))
1258    (pre-command-parse-check point)
1259    (if (minusp count)
1260        (end-of-defun-command (- count))
1261        (unless (top-level-offset point (- count))
1262          (editor-error)))))
1263
1264;;; "End of Defun", with a positive p (the normal case), does something weird.
1265;;; Get a mark at the beginning of the defun, and then offset it forward one
1266;;; less top level form than we want.  This sets us up to use FORM-OFFSET which
1267;;; allows us to leave the point immediately after the defun.  If we used
1268;;; TOP-LEVEL-OFFSET one less than p on the mark at the end of the current
1269;;; defun, point would be left at the beginning of the p+1'st form instead of
1270;;; at the end of the p'th form.
1271;;;
1272(defcommand "End of Defun" (p)
1273  "Move the point to the end of a top-level form, collapsing the selection.
1274   With an argument, skips the next p top-level forms."
1275  "Move the point to the end of a top-level form, collapsing the selection."
1276  (let ((point (current-point-collapsing-selection))
1277        (count (or p 1)))
1278    (pre-command-parse-check point)
1279    (if (minusp count)
1280        (beginning-of-defun-command (- count))
1281        (with-mark ((m point)
1282                    (dummy point))
1283          (cond ((not (mark-top-level-form m dummy))
1284                 (editor-error "No current or next top level form."))
1285                (t 
1286                 (unless (top-level-offset m (1- count))
1287                   (editor-error "Not enough top level forms."))
1288                 ;; We might be one unparsed for away.
1289                 (pre-command-parse-check m)
1290                 (unless (form-offset m 1)
1291                   (editor-error "Not enough top level forms."))
1292                 (when (blank-after-p m) (line-offset m 1 0))
1293                 (move-mark point m)))))))
1294
1295(defcommand "Select to End of Defun" (p)
1296  "Move the point to the end of a top-level form, extending the selection.
1297   With an argument, skips the next p top-level forms."
1298  "Move the point to the end of a top-level form, extending the selection."
1299  (let ((point (current-point-extending-selection))
1300        (count (or p 1)))
1301    (pre-command-parse-check point)
1302    (if (minusp count)
1303        (beginning-of-defun-command (- count))
1304        (with-mark ((m point)
1305                    (dummy point))
1306          (cond ((not (mark-top-level-form m dummy))
1307                 (editor-error "No current or next top level form."))
1308                (t 
1309                 (unless (top-level-offset m (1- count))
1310                   (editor-error "Not enough top level forms."))
1311                 ;; We might be one unparsed for away.
1312                 (pre-command-parse-check m)
1313                 (unless (form-offset m 1)
1314                   (editor-error "Not enough top level forms."))
1315                 (when (blank-after-p m) (line-offset m 1 0))
1316                 (move-mark point m)))))))
1317
1318(defcommand "Forward List" (p)
1319  "Skip over the next Lisp list, collapsing the selection.
1320  With argument, skips the next p lists."
1321  "Skip over the next Lisp list, collapsing the selection."
1322  (let ((point (current-point-collapsing-selection))
1323        (count (or p 1)))
1324    (pre-command-parse-check point)
1325    (unless (list-offset point count) (editor-error))))
1326
1327(defcommand "Select Forward List" (p)
1328  "Skip over the next Lisp list, extending the selection.
1329  With argument, skips the next p lists."
1330  "Skip over the next Lisp list, extending the selection."
1331  (let ((point (current-point-extending-selection))
1332        (count (or p 1)))
1333    (pre-command-parse-check point)
1334    (unless (list-offset point count) (editor-error))))
1335
1336(defcommand "Backward List" (p)
1337  "Skip over the previous Lisp list, collapsing the selection.
1338  With argument, skips the previous p lists."
1339  "Skip over the previous Lisp list, collapsing the selection."
1340  (let ((point (current-point-collapsing-selection))
1341        (count (- (or p 1))))
1342    (pre-command-parse-check point)
1343    (unless (list-offset point count) (editor-error))))
1344
1345(defcommand "Select Backward List" (p)
1346  "Skip over the previous Lisp list, extending the selection.
1347  With argument, skips the previous p lists."
1348  "Skip over the previous Lisp list, extending the selection."
1349  (let ((point (current-point-extending-selection))
1350        (count (- (or p 1))))
1351    (pre-command-parse-check point)
1352    (unless (list-offset point count) (editor-error))))
1353
1354(defcommand "Forward Form" (p)
1355  "Skip over the next Form, collapsing the selection.
1356  With argument, skips the next p Forms."
1357  "Skip over the next Form, collapsing the selection."
1358  (let ((point (current-point-collapsing-selection))
1359        (count (or p 1)))
1360    (pre-command-parse-check point)
1361    (unless (form-offset point count) (editor-error))))
1362
1363(defcommand "Select Forward Form" (p)
1364  "Skip over the next Form, extending the selection.
1365  With argument, skips the next p Forms."
1366  "Skip over the next Form, extending the selection."
1367  (let ((point (current-point-extending-selection))
1368        (count (or p 1)))
1369    (pre-command-parse-check point)
1370    (unless (form-offset point count) (editor-error))))
1371
1372(defcommand "Backward Form" (p)
1373  "Skip over the previous Form, collapsing the selection.
1374  With argument, skips the previous p Forms."
1375  "Skip over the previous Form, collaspsing the selection."
1376  (let ((point (current-point-collapsing-selection))
1377        (count (- (or p 1))))
1378    (pre-command-parse-check point)
1379    (unless (form-offset point count) (editor-error))))
1380
1381(defcommand "Select Backward Form" (p)
1382  "Skip over the previous Form, extending the selection.
1383  With argument, skips the previous p Forms."
1384  "Skip over the previous Form, extending the selection."
1385  (let ((point (current-point-extending-selection))
1386        (count (- (or p 1))))
1387    (pre-command-parse-check point)
1388    (unless (form-offset point count) (editor-error))))
1389
1390(defcommand "Mark Form" (p)
1391  "Set the mark at the end of the next Form.
1392   With a positive argument, set the mark after the following p
1393   Forms. With a negative argument, set the mark before
1394   the preceding -p Forms."
1395  "Set the mark at the end of the next Form."
1396  (with-mark ((m (current-point)))
1397    (pre-command-parse-check m)
1398    (let ((count (or p 1))
1399          (mark (push-buffer-mark (copy-mark m) t)))
1400      (if (form-offset m count)
1401          (move-mark mark m)
1402          (editor-error)))))
1403
1404(defcommand "Mark Defun" (p)
1405  "Puts the region around the next or containing top-level form.
1406   The point is left before the form and the mark is placed immediately
1407   after it."
1408  "Puts the region around the next or containing top-level form."
1409  (declare (ignore p))
1410  (let ((point (current-point)))
1411    (pre-command-parse-check point)
1412    (with-mark ((start point)
1413                (end point))
1414      (cond ((not (mark-top-level-form start end))
1415             (editor-error "No current or next top level form."))
1416            (t
1417             (move-mark point start)
1418             (move-mark (push-buffer-mark (copy-mark point) t) end))))))
1419
1420(defcommand "Forward Kill Form" (p)
1421  "Kill the next Form.
1422   With a positive argument, kills the next p Forms.
1423   Kills backward with a negative argument."
1424  "Kill the next Form."
1425  (with-mark ((m1 (current-point))
1426              (m2 (current-point)))
1427    (pre-command-parse-check m1)
1428    (let ((count (or p 1)))
1429      (unless (form-offset m1 count) (editor-error))
1430      (if (minusp count)
1431          (kill-region (region m1 m2) :kill-backward)
1432          (kill-region (region m2 m1) :kill-forward)))))
1433
1434(defcommand "Backward Kill Form" (p)
1435  "Kill the previous Form.
1436  With a positive argument, kills the previous p Forms.
1437  Kills forward with a negative argument."
1438  "Kill the previous Form."
1439  (forward-kill-form-command (- (or p 1))))
1440
1441(defcommand "Extract Form" (p)
1442  "Replace the current containing list with the next form.  The entire affected
1443   area is pushed onto the kill ring.  If an argument is supplied, that many
1444   upward levels of list nesting is replaced by the next form."
1445  "Replace the current containing list with the next form.  The entire affected
1446   area is pushed onto the kill ring.  If an argument is supplied, that many
1447   upward levels of list nesting is replaced by the next form."
1448  (let ((point (current-point)))
1449    (pre-command-parse-check point)
1450    (with-mark ((form-start point :right-inserting)
1451                (form-end point))
1452      (unless (form-offset form-end 1) (editor-error))
1453      (form-offset (move-mark form-start form-end) -1)
1454      (with-mark ((containing-start form-start :left-inserting)
1455                  (containing-end form-end :left-inserting))
1456        (dotimes (i (or p 1))
1457          (unless (and (forward-up-list containing-end)
1458                       (backward-up-list containing-start))
1459            (editor-error)))
1460        (let ((r (copy-region (region form-start form-end))))
1461          (ring-push (delete-and-save-region
1462                      (region containing-start containing-end))
1463                     *kill-ring*)
1464          (ninsert-region point r)
1465          (move-mark point form-start))))))
1466
1467(defcommand "Extract List" (p)
1468  "Extract the current list.
1469  The current list replaces the surrounding list.  The entire affected
1470  area is pushed on the kill-ring.  With prefix argument, remove that
1471  many surrounding lists."
1472  "Replace the P containing lists with the current one."
1473  (let ((point (current-point)))
1474    (pre-command-parse-check point)
1475    (with-mark ((lstart point :right-inserting)
1476                (lend point))
1477      (if (eq (character-attribute :lisp-syntax (next-character lstart))
1478              :open-paren)
1479          (mark-after lend)
1480          (unless (backward-up-list lstart) (editor-error)))
1481      (unless (forward-up-list lend) (editor-error))
1482      (with-mark ((rstart lstart)
1483                  (rend lend))
1484        (dotimes (i (or p 1))
1485          (unless (and (forward-up-list rend) (backward-up-list rstart))
1486            (editor-error)))
1487        (let ((r (copy-region (region lstart lend))))
1488          (ring-push (delete-and-save-region (region rstart rend))
1489                     *kill-ring*)
1490          (ninsert-region point r)
1491          (move-mark point lstart))))))
1492
1493(defcommand "Transpose Forms" (p)
1494  "Transpose Forms immediately preceding and following the point.
1495  With a zero argument, tranposes the Forms at the point and the mark.
1496  With a positive argument, transposes the Form preceding the point
1497  with the p-th one following it.  With a negative argument, transposes the
1498  Form following the point with the p-th one preceding it."
1499  "Transpose Forms immediately preceding and following the point."
1500  (let ((point (current-point))
1501        (count (or p 1)))
1502    (pre-command-parse-check point)
1503    (if (zerop count)
1504        (let ((mark (current-mark)))
1505          (with-mark ((s1 mark :left-inserting)
1506                      (s2 point :left-inserting))
1507            (scan-char s1 :whitespace nil)
1508            (scan-char s2 :whitespace nil)
1509            (with-mark ((e1 s1 :right-inserting)
1510                        (e2 s2 :right-inserting))
1511              (unless (form-offset e1 1) (editor-error))
1512              (unless (form-offset e2 1) (editor-error))
1513              (ninsert-region s1 (delete-and-save-region (region s2 e2)))
1514              (ninsert-region s2 (delete-and-save-region (region s1 e1))))))
1515        (let ((fcount (if (plusp count) count 1))
1516              (bcount (if (plusp count) 1 count)))
1517          (with-mark ((s1 point :left-inserting)
1518                      (e2 point :right-inserting))
1519            (dotimes (i bcount)
1520              (unless (form-offset s1 -1) (editor-error)))
1521            (dotimes (i fcount)
1522              (unless (form-offset e2 1) (editor-error)))
1523            (with-mark ((e1 s1 :right-inserting)
1524                        (s2 e2 :left-inserting))
1525              (unless (form-offset e1 1) (editor-error))
1526              (unless (form-offset s2 -1) (editor-error))
1527              (ninsert-region s1 (delete-and-save-region (region s2 e2)))
1528              (ninsert-region s2 (delete-and-save-region (region s1 e1)))
1529              (move-mark point s2)))))))
1530
1531
1532(defcommand "Insert ()" (count)
1533  "Insert a pair of parentheses ().  With positive argument, puts
1534   parentheses around the next COUNT Forms, or previous COUNT forms, if
1535   COUNT is negative.  The point is positioned after the open parenthesis."
1536  "Insert a pair of parentheses ()."
1537  ;; TODO Form navigation is broken, so this is broken too -- it is
1538  ;; possible to put parens around more forms than there are in current
1539  ;; expression.  It works by moving past as many forms as there is, and
1540  ;; then each delimiting paren also counts as a form.
1541  (let ((point (current-point)))
1542    (pre-command-parse-check point)
1543    (cond (count
1544           (when (minusp count)
1545             (form-offset point count)
1546             (setq count (- count)))
1547           (insert-character point #\()
1548           (with-mark ((m point))
1549             (unless (form-offset m count)
1550               (editor-error "Could not find that many forms."))
1551             (insert-character m #\))))
1552          ;; The simple case with no prefix argument
1553          (t
1554           (insert-character point #\()
1555           (insert-character point #\))
1556           (mark-before point)))))
1557
1558
1559(defcommand "Move Over )" (p)
1560  "Move past the next close parenthesis, and start a new line.  Any
1561   indentation preceding the preceding the parenthesis is deleted, and the
1562   new line is indented.  If there is only whitespace preceding the close
1563   paren, the paren is moved to the end of the previous line. With prefix
1564   argument, this command moves past next closing paren and inserts space."
1565  "Move past the next close parenthesis, and start a new line."
1566  ;; TODO This is still not complete, because SCAN-CHAR finds the next
1567  ;; close-paren, but we need to find the next paren that closes current
1568  ;; expression.  This will have to be updated when form navigation is
1569  ;; fixed.
1570  (let ((point (current-point)))
1571    (pre-command-parse-check point)
1572    (with-mark ((m point :right-inserting))
1573      (cond ((scan-char m :lisp-syntax :close-paren)
1574             (cond ((same-line-p point m)
1575                    (delete-horizontal-space m))
1576                   (t
1577                    (move-mark point m)
1578                    (reverse-find-attribute point :whitespace #'zerop)
1579                    (delete-region (region point m))))
1580             (cond ((not p)
1581                    ;; Move to the previous line if current is empty
1582                    (when (zerop (mark-charpos m))
1583                      (delete-characters m -1))
1584                    (mark-after m)
1585                    (move-mark point m)
1586                    (indent-new-line-command 1))
1587                   (t
1588                    (mark-after m)
1589                    (move-mark point m)
1590                    (insert-character m #\space))))
1591            (t 
1592             (editor-error "Could not find closing paren."))))))
1593
1594
1595(defcommand "Forward Up List" (p)
1596  "Move forward past a one containing )."
1597  "Move forward past a one containing )."
1598  (let ((point (current-point-collapsing-selection))
1599        (count (or p 1)))
1600    (pre-command-parse-check point)
1601    (if (minusp count)
1602        (backward-up-list-command (- count))
1603        (with-mark ((m point))
1604          (dotimes (i count (move-mark point m))
1605            (unless (forward-up-list m) (editor-error)))))))
1606
1607
1608(defcommand "Backward Up List" (p)
1609  "Move backward past a one containing (."
1610  "Move backward past a one containing (."
1611  (let ((point (current-point-collapsing-selection))
1612        (count (or p 1)))
1613    (pre-command-parse-check point)
1614    (if (minusp count)
1615        (forward-up-list-command (- count))
1616        (with-mark ((m point))
1617          (dotimes (i count (move-mark point m))
1618            (unless (backward-up-list m) (editor-error)))))))
1619
1620
1621(defcommand "Down List" (p)
1622  "Move down a level in list structure.  With positive argument, moves down
1623   p levels.  With negative argument, moves down backward, but only one
1624   level."
1625  "Move down a level in list structure."
1626  (let ((point (current-point-collapsing-selection))
1627        (count (or p 1)))
1628    (pre-command-parse-check point)
1629    (with-mark ((m point))
1630      (cond ((plusp count)
1631             (loop repeat count
1632                   do (unless (and (scan-char m :lisp-syntax :open-paren)
1633                                   (mark-after m))
1634                        (editor-error))))
1635            (t
1636             (unless (and (rev-scan-char m :lisp-syntax :close-paren)
1637                          (mark-before m))
1638               (editor-error))))
1639      (move-mark point m))))
1640
1641
1642
1643;;;; Filling Lisp comments, strings, and indented text.
1644
1645(defhvar "Fill Lisp Comment Paragraph Confirm"
1646  "This determines whether \"Fill Lisp Comment Paragraph\" will prompt for
1647   confirmation to fill contiguous lines with the same initial whitespace when
1648   it is invoked outside of a comment or string."
1649  :value t)
1650
1651(defcommand "Fill Lisp Comment Paragraph" (p)
1652  "This fills a flushleft or indented Lisp comment.
1653   This also fills Lisp string literals using the proper indentation as a
1654   filling prefix.  When invoked outside of a comment or string, this tries
1655   to fill all contiguous lines beginning with the same initial, non-empty
1656   blankspace.  When filling a comment, the current line is used to determine a
1657   fill prefix by taking all the initial whitespace on the line, the semicolons,
1658   and any whitespace following the semicolons."
1659  "Fills a flushleft or indented Lisp comment."
1660  (declare (ignore p))
1661  (let ((point (current-point)))
1662    (pre-command-parse-check point)
1663    (with-mark ((start point)
1664                (end point)
1665                (m point))
1666      (let ((commentp (fill-lisp-comment-paragraph-prefix start end)))
1667        (cond (commentp
1668               (fill-lisp-comment-or-indented-text start end))
1669              ((and (not (valid-spot m nil))
1670                    (form-offset m -1)
1671                    (eq (character-attribute :lisp-syntax (next-character m))
1672                        :string-quote))
1673               (fill-lisp-string m))
1674              ((or (not (value fill-lisp-comment-paragraph-confirm))
1675                   (prompt-for-y-or-n
1676                    :prompt '("Not in a comment or string.  Fill contiguous ~
1677                               lines with the same initial whitespace? ")))
1678               (fill-lisp-comment-or-indented-text start end)))))))
1679
1680;;; FILL-LISP-STRING -- Internal.
1681;;;
1682;;; This fills the Lisp string containing mark as if it had been entered using
1683;;; Hemlock's Lisp string indentation, "Indent Function" for "Lisp" mode.  This
1684;;; assumes the area around mark has already been PRE-COMMAND-PARSE-CHECK'ed,
1685;;; and it ensures the string ends before doing any filling.  This function
1686;;; is undo'able.
1687;;;
1688(defun fill-lisp-string (mark)
1689  (with-mark ((end mark))
1690    (unless (form-offset end 1)
1691      (editor-error "Attempted to fill Lisp string, but it doesn't end?"))
1692    (let* ((mark (copy-mark mark :left-inserting))
1693           (end (copy-mark end :left-inserting))
1694           (string-region (region mark end))
1695           (undo-region (copy-region string-region))
1696           (hack (make-empty-region)))
1697      ;; Generate prefix.
1698      (funcall (value indent-with-tabs)
1699               (region-end hack) (1+ (mark-column mark)))
1700      ;; Skip opening double quote and fill string starting on its own line.
1701      (mark-after mark)
1702      (insert-character mark #\newline)
1703      (line-start mark)
1704      (setf (mark-kind mark) :right-inserting)
1705      (fill-region string-region (region-to-string hack))
1706      ;; Clean up inserted prefix on first line, delete inserted newline, and
1707      ;; move before the double quote for undo.
1708      (with-mark ((text mark :left-inserting))
1709        (find-attribute text :whitespace #'zerop)
1710        (delete-region (region mark text)))
1711      (delete-characters mark -1)
1712      (mark-before mark)
1713      ;; Save undo.
1714      (make-region-undo :twiddle "Fill Lisp Comment Paragraph"
1715                        string-region undo-region))))
1716
1717;;; FILL-LISP-COMMENT-OR-INDENTED-TEXT -- Internal.
1718;;;
1719;;; This fills all contiguous lines around start and end containing fill prefix
1720;;; designated by the region between start and end.  These marks can only be
1721;;; equal when there is no comment and no initial whitespace.  This is a bad
1722;;; situation since this function in that situation would fill the entire
1723;;; buffer into one paragraph.  This function is undo'able.
1724;;;
1725(defun fill-lisp-comment-or-indented-text (start end)
1726  (when (mark= start end)
1727    (editor-error "This command only fills Lisp comments, strings, or ~
1728                   indented text, but this line is flushleft."))
1729  ;;
1730  ;; Find comment block.
1731  (let* ((prefix (region-to-string (region start end)))
1732         (length (length prefix)))
1733    (declare (simple-string prefix))
1734    (flet ((frob (mark direction)
1735             (loop
1736               (let* ((line (line-string (mark-line mark)))
1737                      (line-len (length line)))
1738                 (declare (simple-string line))
1739                 (unless (string= line prefix :end1 (min line-len length))
1740                   (when (= direction -1)
1741                     (unless (same-line-p mark end) (line-offset mark 1 0)))
1742                   (return)))
1743               (unless (line-offset mark direction 0)
1744                 (when (= direction 1) (line-end mark))
1745                 (return)))))
1746      (frob start -1)
1747      (frob end 1))
1748    ;;
1749    ;; Do it undoable.
1750    (let* ((start1 (copy-mark start :right-inserting))
1751           (end2 (copy-mark end :left-inserting))
1752           (region (region start1 end2))
1753           (undo-region (copy-region region)))
1754      (fill-region region prefix)
1755      (make-region-undo :twiddle "Fill Lisp Comment Paragraph"
1756                        region undo-region))))
1757
1758;;; FILL-LISP-COMMENT-PARAGRAPH-PREFIX -- Internal.
1759;;;
1760;;; This sets start and end around the prefix to be used for filling.  We
1761;;; assume we are dealing with a comment.  If there is no ";", then we try to
1762;;; find some initial whitespace.  If there is a ";", we make sure the line is
1763;;; blank before it to eliminate ";"'s in the middle of a line of text.
1764;;; Finally, if we really have a comment instead of some indented text, we skip
1765;;; the ";"'s and any immediately following whitespace.  We allow initial
1766;;; whitespace, so we can fill strings with the same command.
1767;;;
1768(defun fill-lisp-comment-paragraph-prefix (start end)
1769  (line-start start)
1770  (let ((commentp t)) ; Assumes there's a comment.
1771    (unless (to-line-comment (line-start end) ";")
1772      (find-attribute end :whitespace #'zerop)
1773      #|(when (start-line-p end)
1774        (editor-error "No comment on line, and no initial whitespace."))|#
1775      (setf commentp nil))
1776    (when commentp
1777      (unless (blank-before-p end)
1778        (find-attribute (line-start end) :whitespace #'zerop)
1779        #|(when (start-line-p end)
1780          (editor-error "Semicolon preceded by unindented text."))|#
1781        (setf commentp nil)))
1782    (when commentp
1783      (find-attribute end :lisp-syntax #'(lambda (x) (not (eq x :comment))))
1784      (find-attribute end :whitespace #'zerop))
1785    commentp))
1786
1787
1788
1789;;;; "Lisp" mode.
1790
1791(defcommand "LISP Mode" (p)
1792  "Put current buffer in LISP mode." 
1793  "Put current buffer in LISP mode." 
1794  (declare (ignore p))
1795  (setf (buffer-major-mode (current-buffer)) "LISP"))
1796
1797
1798(defmode "Lisp" :major-p t :setup-function 'setup-lisp-mode)
1799
1800
1801(defun buffer-first-in-package-form (buffer)
1802  "Returns the package name referenced in the first apparent IN-PACKAGE
1803   form in buffer, or NIL if it can't find an IN-PACKAGE."
1804  (let* ((pattern (new-search-pattern :string-insensitive :forward "in-package" nil))
1805         (mark (copy-mark (buffer-start-mark buffer))))
1806    (with-mark ((start mark)
1807                (end mark))
1808      (loop
1809        (unless (find-pattern mark pattern)
1810          (return))
1811        (pre-command-parse-check mark)
1812        (when (valid-spot mark t)
1813          (move-mark end mark)
1814          (when (form-offset end 1)
1815            (move-mark start end)
1816            (when (backward-up-list start)
1817              (when (scan-char start :lisp-syntax :constituent)
1818                (let* ((s (nstring-upcase (region-to-string (region start end))))
1819                       (*package* (find-package "CL-USER")))
1820                  (unless (eq (ignore-errors (values (read-from-string s)))
1821                              'in-package)
1822                    (return)))
1823                (unless (form-offset end 1) (return))
1824                (move-mark start end)
1825                (form-offset start -1)
1826                (let* ((pkgname (ignore-errors (values (read-from-string (region-to-string (region start end)))))))
1827                  (return
1828                    (if pkgname
1829                      (values (ignore-errors (string pkgname))))))))))))))
1830
1831(defparameter *previous-in-package-search-pattern*
1832    (new-search-pattern :string-insensitive :backward "in-package" nil))
1833
1834(defun package-at-mark (start-mark)
1835  (let* ((pattern *previous-in-package-search-pattern*)
1836         (mark (copy-mark start-mark :temporary)))
1837    (with-mark ((start mark)
1838                (end mark)
1839                (list-end mark))
1840      (loop
1841        (unless (find-pattern mark pattern)
1842          (return))
1843        (pre-command-parse-check mark)
1844        (when (valid-spot mark t)
1845          (move-mark end mark)
1846          (when (form-offset end 1)
1847            (move-mark start end)
1848            (when (backward-up-list start)
1849              (move-mark list-end start)
1850              (unless (and (list-offset list-end 1)
1851                           (mark<= list-end start-mark))
1852                (return))
1853              (when (scan-char start :lisp-syntax :constituent)
1854                (unless (or (mark= mark start)
1855                            (let* ((s (nstring-upcase (region-to-string (region start end))))
1856                                   (*package* (find-package "CL-USER")))
1857                              (eq (ignore-errors (values (read-from-string s)))
1858                                  'in-package)))
1859                  (return))
1860                (unless (form-offset end 1) (format t "~& worse") (return 4))
1861                (move-mark start end)
1862                (form-offset start -1)
1863                (return
1864                  (if (eql (next-character start) #\")
1865                    (progn
1866                      (character-offset start 1)
1867                      (character-offset end -1)
1868                      (region-to-string (region start end)))
1869                    (let* ((pkgname (ignore-errors (values (read-from-string (region-to-string (region start end)))))))
1870                      (if pkgname
1871                        (values (ignore-errors (string pkgname)))))))))))))))
1872
1873(defun ensure-buffer-package (buffer)
1874  (or (variable-value 'current-package :buffer buffer)
1875      (setf (variable-value 'current-package :buffer buffer)
1876            (buffer-first-in-package-form buffer))))
1877
1878(defun buffer-package (buffer)
1879  (when (hemlock-bound-p 'current-package :buffer buffer)
1880    (let ((package-name (variable-value 'current-package :buffer buffer)))
1881      (find-package package-name))))
1882
1883(defun setup-lisp-mode (buffer)
1884  (unless (hemlock-bound-p 'current-package :buffer buffer)
1885    (defhvar "Current Package"
1886      "The package used for evaluation of Lisp in this buffer."
1887      :buffer buffer
1888      :value "CL-USER"
1889      :hooks (list 'package-name-change-hook))))
1890
1891
1892
1893
1894
1895;;;; Some mode variables to coordinate with other stuff.
1896
1897(defhvar "Auto Fill Space Indent"
1898  "When non-nil, uses \"Indent New Comment Line\" to break lines instead of
1899   \"New Line\"."
1900  :mode "Lisp" :value t)
1901
1902(defhvar "Comment Start"
1903  "String that indicates the start of a comment."
1904  :mode "Lisp" :value ";")
1905
1906(defhvar "Comment Begin"
1907  "String that is inserted to begin a comment."
1908  :mode "Lisp" :value "; ")
1909
1910(defhvar "Indent Function"
1911  "Indentation function which is invoked by \"Indent\" command.
1912   It must take one argument that is the prefix argument."
1913  :value 'indent-for-lisp
1914  :mode "Lisp")
1915
1916(defun string-to-arglist (string buffer &optional quiet-if-unknown)
1917  (multiple-value-bind (name error)
1918      (let* ((*package* (or
1919                         (find-package
1920                          (variable-value 'current-package :buffer buffer))
1921                         *package*)))
1922        (ignore-errors (values (read-from-string string))))
1923    (unless error
1924      (when (typep name 'symbol)
1925        (multiple-value-bind (arglist win)
1926            (ccl::arglist-string name)
1927          (if (or win (not quiet-if-unknown))
1928            (format nil "~S : ~A" name (if win (or arglist "()") "(unknown)"))))))))
1929
1930(defcommand "Current Function Arglist" (p)
1931  "Show arglist of function whose name precedes point."
1932  "Show arglist of function whose name precedes point."
1933  (declare (ignore p))
1934  (let ((point (current-point)))
1935    (pre-command-parse-check point)
1936    (with-mark ((mark1 point)
1937                (mark2 point))
1938      (when (backward-up-list mark1)
1939        (when (form-offset (move-mark mark2 (mark-after mark1)) 1)
1940          (let* ((fun-name (region-to-string (region mark1 mark2)))
1941                 (arglist-string (string-to-arglist fun-name (current-buffer))))
1942            (when arglist-string
1943              (message arglist-string))))))))
1944
1945(defcommand "Arglist On Space" (p)
1946  "Insert a space, then show the current function's arglist."
1947  "Insert a space, then show the current function's arglist."
1948  (declare (ignore p))
1949  (let ((point (current-point)))
1950    (insert-character point #\Space)
1951    (pre-command-parse-check point)
1952    (with-mark ((mark1 point)
1953                (mark2 point))
1954      (when (backward-up-list mark1)
1955        (when (form-offset (move-mark mark2 (mark-after mark1)) 1)
1956          (with-mark ((mark3 mark2))
1957            (do* ()
1958                 ((mark= mark3 point)
1959                  (let* ((fun-name (region-to-string (region mark1 mark2)))
1960                         (arglist-string
1961                          (string-to-arglist fun-name (current-buffer) t)))
1962                    (when arglist-string
1963                      (message arglist-string))))
1964              (if (ccl::whitespacep (next-character mark3))
1965                (mark-after mark3)
1966                (return nil)))))))))
1967
1968#||
1969(defcommand "Set Package Name" (p)
1970  (variable-value 'current-package :buffer buffer)
1971||#               
Note: See TracBrowser for help on using the repository browser.