source: trunk/source/cocoa-ide/hemlock/src/listener.lisp @ 12859

Last change on this file since 12859 was 12859, checked in by gz, 10 years ago

Make sure that all hemlock functions defined outside of hemlock are in the hemlock-ext package, to make it easier to keep track of them

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.0 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;;;
8;;; **********************************************************************
9;;;
10;;; Listener mode, dervived (sort of) from Hemlock's "Eval" mode.
11;;;
12
13(in-package :hemlock)
14
15
16(defmacro in-lisp (&body body)
17  "Evaluates body inside HANDLE-LISP-ERRORS.  *package* is bound to the package
18   named by \"Current Package\" if it is non-nil."
19  (let ((name (gensym)) (package (gensym)))
20    `(handle-lisp-errors
21      (let* ((,name (variable-value 'current-package :buffer (current-buffer)))
22             (,package (and ,name (find-package ,name))))
23        (progv (if ,package '(*package*)) (if ,package (list ,package))
24          ,@body)))))
25
26
27(defun package-name-change-hook (name kind where new-value)
28  (declare (ignore name new-value))
29  (if (eq kind :buffer)
30    (hi::note-modeline-change where)))
31
32(define-file-option "Package" (buffer value)
33  (let* ((thing (handler-case (read-from-string value t)
34                  (error () (editor-error "Bad package file option value"))))
35         (name
36          (cond
37           ((or (stringp thing) (symbolp thing))
38            (string thing))
39           ((and (consp thing) ;; e.g. Package: (foo :use bar)
40                 (or (stringp (car thing)) (symbolp (car thing))))
41            (string (car thing)))
42           (t
43            (message "Ignoring \"package:\" file option ~a" thing)
44            nil))))
45    (when name
46      (ignore-errors (let* ((*package* *package*))
47                       (apply 'ccl::old-in-package (if (atom thing) (list thing) thing)))))
48    (defhvar "Current Package"
49      "The package used for evaluation of Lisp in this buffer."
50      :buffer buffer
51      :value (or name (package-name *package*))
52      :hooks (list 'package-name-change-hook))
53    (defhvar "Default Package"
54      "The buffer's default package."
55      :buffer buffer
56      :value (or name (package-name *package*)))))
57     
58
59
60;;;; Listener Mode Interaction.
61
62
63
64(defun setup-listener-mode (buffer)
65  (let ((point (buffer-point buffer)))
66    (setf (buffer-minor-mode buffer "Listener") t)
67    (setf (buffer-minor-mode buffer "Editor") t)
68    (setf (buffer-major-mode buffer) "Lisp")
69    (buffer-end point)
70    (defhvar "Current Package"
71      "This variable holds the name of the package currently used for Lisp
72       evaluation and compilation.  If it is Nil, the value of *Package* is used
73       instead."
74      :value nil
75      :buffer buffer)
76    (unless (hemlock-bound-p 'buffer-input-mark :buffer buffer)
77      (defhvar "Buffer Input Mark"
78        "Mark used for Listener Mode input."
79        :buffer buffer
80        :value (copy-mark point :right-inserting))
81      (defhvar "Buffer Output Mark"
82        "Mark used for Listener Mode output."
83        :buffer buffer
84        :value (copy-mark point :left-inserting))
85      (defhvar "Interactive History"
86        "A ring of the regions input to an interactive mode (Eval or Typescript)."
87        :buffer buffer
88        :value (make-ring (value interactive-history-length)))
89      (defhvar "Interactive Pointer"
90        "Pointer into \"Interactive History\"."
91        :buffer buffer
92        :value 0)
93      (defhvar "Searching Interactive Pointer"
94        "Pointer into \"Interactive History\"."
95        :buffer buffer
96        :value 0)
97      (defhvar "Input Regions"
98        "Input region history list."
99        :buffer buffer
100        :value nil)
101      (defhvar "Current Input Font Region"
102          "Current font region, for listener input"
103        :buffer buffer
104        :value nil)
105      (defhvar "Current Output Font Region"
106          "Current font region, for listener output"
107        :buffer buffer
108        :value nil)
109      )
110    (let* ((input-mark (variable-value 'buffer-input-mark :buffer buffer)))
111      (when (hemlock-ext:read-only-listener-p)
112        (setf (hi::buffer-protected-region buffer)
113              (region (buffer-start-mark buffer) input-mark)))
114      (move-mark input-mark point)
115      (append-font-regions buffer))))
116
117(defmode "Listener" :major-p nil :setup-function #'setup-listener-mode)
118
119(declaim (special hi::*listener-input-style* hi::*listener-output-style*))
120
121(defun append-font-regions (buffer)
122  (let* ((end (region-end (buffer-region buffer))))
123    (setf (variable-value 'current-output-font-region :buffer buffer)
124          (hi::new-font-region buffer end end hi::*listener-output-style*))
125    (let* ((input (hi::new-font-region buffer end end hi::*listener-input-style*)))
126      (hi::activate-buffer-font-region buffer input)
127      (setf (variable-value 'current-input-font-region :buffer buffer) input))))
128
129(defun append-buffer-output (buffer string)
130  (let* ((output-region (variable-value 'current-output-font-region
131                                        :buffer buffer))
132         (output-end (region-end output-region)))
133    (hi::with-active-font-region (buffer output-region)
134      (with-mark ((output-mark output-end :left-inserting))
135        ;(setf (mark-charpos output-mark) 0)
136        (insert-string output-mark string))
137      (move-mark (variable-value 'buffer-input-mark :buffer buffer)
138                 output-end))))
139
140
141
142(defparameter *listener-modeline-fields*
143  (list (modeline-field :package)
144        (modeline-field :modes)
145        (modeline-field :process-info)))
146 
147(defun listener-mode-lisp-mode-hook (buffer on)
148  "Turn on Lisp mode when we go into Listener Mode."
149  (when on
150    (setf (buffer-major-mode buffer) "Lisp")))
151;;;
152(add-hook listener-mode-hook 'listener-mode-lisp-mode-hook)
153
154
155
156
157
158(defvar lispbuf-eof '(nil))
159
160(defun skip-line-comment (mark)
161  ;; return t if we skipped a comment, nil otherwise
162  (let ((cstart (to-line-comment mark ";")))
163    (if cstart
164        (progn (to-comment-end mark (string #\newline))
165               t)
166        nil)))
167
168(defun balanced-expressions-in-region (region)
169  "Return true if there's at least one syntactically well-formed S-expression
170between the region's start and end, and if there are no ill-formed expressions in that region."
171  ;; It helps to know that END-MARK immediately follows a #\newline.
172  (let* ((start-mark (region-start region))
173         (end-mark (region-end region))
174         (end-line (mark-line end-mark))
175         (end-charpos (mark-charpos end-mark)))
176    (with-mark ((m start-mark))
177      (pre-command-parse-check m)
178      (when (form-offset m 1)
179        (let* ((skip-whitespace t))
180          (loop
181            (let* ((current-line (mark-line m))
182                   (current-charpos (mark-charpos m)))
183              (when (and (eq current-line end-line)
184                         (eql current-charpos end-charpos))
185                (return t))
186              (if skip-whitespace
187                (progn
188                  (scan-char m :whitespace nil)
189                  (setq skip-whitespace nil))
190                (progn
191                  (pre-command-parse-check m)
192                  (unless (or (form-offset m 1)
193                              (skip-line-comment m))
194                    (return nil))
195                  (setq skip-whitespace t))))))))))
196               
197#| old version
198(defcommand "Confirm Listener Input" (p)
199  "Evaluate Listener Mode input between point and last prompt."
200  "Evaluate Listener Mode input between point and last prompt."
201  (declare (ignore p))
202  (let* ((input-region (get-interactive-input))
203         (r (if input-region
204              (region (copy-mark (region-start input-region))
205                      (copy-mark (region-end input-region) :right-inserting)))))
206
207    (when input-region
208      (insert-character (current-point-for-insertion) #\NewLine)
209      (when (or (input-stream-reading-line
210                 (top-listener-input-stream))
211                (balanced-expressions-in-region input-region))
212        (let* ((string (region-to-string input-region)))
213          (push (cons r nil) (value input-regions))
214          (move-mark (value buffer-input-mark) (current-point))
215          (append-font-regions (current-buffer))
216          (hemlock-ext:send-string-to-listener (current-buffer) string))))))
217|#
218
219(defun point-at-prompt-p ()
220  (with-mark ((input-mark (value buffer-input-mark))
221              (end-mark (value buffer-input-mark)))
222    (buffer-end end-mark)
223    (and (mark>= (current-point) input-mark)
224         (mark>= end-mark (current-point)))))
225
226(defun send-input-region-to-lisp ()
227  (let* ((input-mark (value buffer-input-mark))
228         (end-mark (region-end (buffer-region (current-buffer))))
229         (input-region (region input-mark end-mark))
230         (r (if input-region
231                (region (copy-mark (region-start input-region))
232                        (copy-mark (region-end input-region) :right-inserting)))))
233    (when input-region
234      (if (or (input-stream-reading-line
235               (top-listener-input-stream))
236              (balanced-expressions-in-region input-region))
237        ;; complete expression: send it to lisp
238        (let* ((string (region-to-string input-region))
239               (ring (value interactive-history)))
240          (when (and (or (zerop (ring-length ring))
241                         (string/= string (region-to-string (ring-ref ring 0))))
242                     (> (length string) (value minimum-interactive-input-length)))
243            (ring-push (copy-region input-region) ring))
244          (insert-character (region-end input-region) #\NewLine)
245          (push (cons r nil) (value input-regions))
246          (set-charprop-value (region-start input-region) :font-weight :bold
247                              :end (region-end input-region))
248          (move-mark (value buffer-input-mark) (current-point))
249          (append-font-regions (current-buffer))
250          (hemlock-ext:send-string-to-listener (current-buffer) (concatenate 'string string '(#\Newline)))
251          (buffer-end (current-point)))
252        ;; incomplete expression: enter a newline
253        (progn
254          (insert-character (current-point-for-insertion) #\NewLine))))))
255
256(defun copy-region-to-input (region)
257  (let* ((region-string (when region (region-to-string region)))
258         (input-mark (value buffer-input-mark))
259         (end-mark (region-end (buffer-region (current-buffer))))
260         (input-region (region input-mark end-mark)))
261    (with-mark ((input-mark (value buffer-input-mark)))
262      (move-mark (current-point) input-mark)
263      (delete-region input-region)
264      (insert-string (current-point) region-string)
265      (buffer-end (current-point)))))
266
267(defun find-backward-form (mark)
268  (let ((start (copy-mark mark))
269        (end (copy-mark mark)))
270    (block finding
271      (or (form-offset start -1) (return-from finding nil))
272      (or (form-offset end -1) (return-from finding nil))
273      (or (form-offset end 1) (return-from finding nil))
274      (region start end))))
275
276(defun find-forward-form (mark)
277  (let ((start (copy-mark mark))
278        (end (copy-mark mark)))
279    (block finding
280      (or (form-offset start 1) (return-from finding nil))
281      (or (form-offset end 1) (return-from finding nil))
282      (or (form-offset start -1) (return-from finding nil))
283      (region start end))))
284
285(defun region= (r1 r2)
286  (multiple-value-bind (r1-start r1-end)(region-bounds r1)
287    (multiple-value-bind (r2-start r2-end)(region-bounds r2)
288      (and (mark= r1-start r2-start)
289           (mark= r1-end r2-end)))))
290
291;;; find the start or end of the nearest lisp form. return a region if
292;;; one is found, nil otherwise. try for a commonsense result.
293(defun mark-nearest-form (mark)
294  (let* ((backward-region (find-backward-form mark))
295         (forward-region (find-forward-form mark)))
296    (if backward-region
297        (if forward-region
298            ;; if we're in the middle of a token, then the backward
299            ;; and forward regions will be the same
300            (if (region= backward-region forward-region)
301                backward-region
302                ;; not equal, so figure out which is closer
303                (let* ((mark-pos (mark-absolute-position mark))
304                       (backward-dist (abs (- mark-pos (mark-absolute-position (region-end backward-region)))))
305                       (forward-dist (abs (- (mark-absolute-position (region-start forward-region)) mark-pos))))
306                  (if (< forward-dist backward-dist)
307                      forward-region
308                      backward-region)))
309            backward-region)
310        forward-region)))
311
312(defun copy-expression-at-point-to-input ()
313  (let* ((nearest-form-region (mark-nearest-form (current-point))))
314    (if nearest-form-region
315        (copy-region-to-input nearest-form-region)
316        (beep))))
317
318(defcommand "Confirm Listener Input" (p)
319    "Evaluate Listener Mode input between point and last prompt."
320    "Evaluate Listener Mode input between point and last prompt."
321  (declare (ignore p))
322  (if (point-at-prompt-p)
323    (progn
324      (if (eq (character-attribute :lisp-syntax (previous-character (buffer-end-mark (current-buffer)))) :char-quote)
325        (let* ((point (current-point))) 
326          (buffer-end point)
327          (insert-character point #\newline))
328        (send-input-region-to-lisp)))
329      (if (region-active-p)
330          (let ((selected-region (current-region nil nil)))
331            (copy-region-to-input selected-region))
332          (let ((prior-region (input-region-containing-mark (current-point) (value input-regions))))
333            (if prior-region
334                (copy-region-to-input prior-region)
335                (copy-expression-at-point-to-input))))))
336
337
338(defparameter *pop-string* ":POP
339" "what you have to type to exit a break loop")
340
341(defcommand "POP or Delete Forward" (p)
342  "Send :POP if input-mark is at buffer's end, else delete forward character."
343  "Send :POP if input-mark is at buffer's end, else delete forward character."
344  (let* ((input-mark (value buffer-input-mark))
345         (point (current-point-for-deletion)))
346    (when point
347      (if (and (null (next-character point))
348               (null (next-character input-mark)))
349        (hemlock-ext:send-string-to-listener (current-buffer) *pop-string*)
350        (delete-next-character-command p)))))
351
352
353;;;; General interactive commands used in eval and typescript buffers.
354
355(defhvar "Interactive History Length"
356  "This is the length used for the history ring in interactive buffers.
357   It must be set before turning on the mode."
358  :value 10)
359
360(defun input-region-containing-mark (m history-list)
361  (dolist (pair history-list)
362    (let* ((actual (car pair))
363           (start (region-start actual))
364           (end (region-end actual)))
365      (when (and (mark>= m start)
366                 (mark<= m end))        ; sic: inclusive
367        (return (or (cdr pair) (setf (cdr pair) (copy-region actual))))))))
368
369
370(defun get-interactive-input ()
371  "Tries to return a region.  When the point is not past the input mark, and
372   the user has \"Unwedge Interactive Input Confirm\" set, the buffer is
373   optionally fixed up, and nil is returned.  Otherwise, an editor error is
374   signalled.  When a region is returned, the start is the current buffer's
375   input mark, and the end is the current point moved to the end of the buffer."
376  (let ((point (current-point))
377        (mark (value buffer-input-mark)))
378    (cond
379      ((mark>= point mark)
380       (buffer-end point)
381       (let* ((input-region (region mark point))
382              (string (region-to-string input-region))
383              (ring (value interactive-history)))
384         (when (and (or (zerop (ring-length ring))
385                        (string/= string (region-to-string (ring-ref ring 0))))
386                    (> (length string) (value minimum-interactive-input-length)))
387           (ring-push (copy-region input-region) ring))
388         input-region))
389      (t
390       (let* ((region (input-region-containing-mark point (value input-regions ))))
391         (buffer-end point)
392         (if region
393             (progn
394               (delete-region (region mark point))
395               (insert-region point region))
396             (beep))
397         nil)))))
398
399
400(defhvar "Minimum Interactive Input Length"
401  "When the number of characters in an interactive buffer exceeds this value,
402   it is pushed onto the interactive history, otherwise it is lost forever."
403  :value 2)
404
405
406(defvar *previous-input-search-string* "ignore")
407
408(defvar *previous-input-search-pattern*
409  ;; Give it a bogus string since you can't give it the empty string.
410  (new-search-pattern :string-insensitive :forward "ignore"))
411
412(defun get-previous-input-search-pattern (string)
413  (if (string= *previous-input-search-string* string)
414      *previous-input-search-pattern*
415      (new-search-pattern :string-insensitive :forward 
416                          (setf *previous-input-search-string* string)
417                          *previous-input-search-pattern*)))
418
419(defcommand "Search Previous Interactive Input" (p)
420  "Search backward through the interactive history using the current input as
421   a search string.  Consecutive invocations repeat the previous search."
422  "Search backward through the interactive history using the current input as
423   a search string.  Consecutive invocations repeat the previous search."
424  (declare (ignore p))
425  (let* ((mark (value buffer-input-mark))
426         (ring (value interactive-history))
427         (point (current-point))
428         (just-invoked (eq (last-command-type) :searching-interactive-input)))
429    (when (mark<= point mark)
430      (editor-error "Point not past input mark."))
431    (when (zerop (ring-length ring))
432      (editor-error "No previous input in this buffer."))
433    (unless just-invoked
434      (get-previous-input-search-pattern (region-to-string (region mark point))))
435    (let ((found-it (find-previous-input ring just-invoked)))
436      (unless found-it 
437        (editor-error "Couldn't find ~a." *previous-input-search-string*))
438      (delete-region (region mark point))
439      (insert-region point (ring-ref ring found-it))
440      (setf (value searching-interactive-pointer) found-it))
441  (setf (last-command-type) :searching-interactive-input)))
442
443(defun find-previous-input (ring againp)
444  (let ((ring-length (ring-length ring))
445        (base (if againp
446                  (+ (value searching-interactive-pointer) 1)
447                  0)))
448      (loop
449        (when (= base ring-length)
450          (if againp
451              (setf base 0)
452              (return nil)))
453        (with-mark ((m (region-start (ring-ref ring base))))
454          (when (find-pattern m *previous-input-search-pattern*)
455            (return base)))
456        (incf base))))
457
458(defcommand "Previous Interactive Input" (p)
459  "Insert the previous input in an interactive mode (Listener or Typescript).
460   If repeated, keep rotating the history.  With prefix argument, rotate
461   that many times."
462  "Pop the *interactive-history* at the point."
463  (let* ((point (current-point))
464         (mark (value buffer-input-mark))
465         (ring (value interactive-history))
466         (length (ring-length ring))
467         (p (or p 1)))
468    (when (or (mark< point mark) (zerop length)) (editor-error "Can't get command history"))
469    (cond
470     ((eq (last-command-type) :interactive-history)
471      (let ((base (mod (+ (value interactive-pointer) p) length)))
472        (delete-region (region mark point))
473        (insert-region point (ring-ref ring base))
474        (setf (value interactive-pointer) base)))
475     (t
476      (let ((base (mod (if (minusp p) p (1- p)) length))
477            (region (delete-and-save-region (region mark point))))
478        (insert-region point (ring-ref ring base))
479        (when (mark/= (region-start region) (region-end region))
480          (ring-push region ring)
481          (incf base))
482        (setf (value interactive-pointer) base)))))
483  (setf (last-command-type) :interactive-history))
484
485(defcommand "Next Interactive Input" (p)
486  "Rotate the interactive history backwards.  The region is left around the
487   inserted text.  With prefix argument, rotate that many times."
488  "Call previous-interactive-input-command with negated arg."
489  (previous-interactive-input-command (- (or p 1))))
490
491(defcommand "Kill Interactive Input" (p)
492  "Kill any input to an interactive mode (Listener or Typescript)."
493  "Kill any input to an interactive mode (Listener or Typescript)."
494  (declare (ignore p))
495  (let ((point (buffer-point (current-buffer)))
496        (mark (value buffer-input-mark)))
497    (when (mark< point mark) (editor-error))
498    (kill-region (region mark point) :kill-backward)))
499
500(defcommand "Interactive Beginning of Line" (p)
501  "If on line with current prompt, go to after it, otherwise do what
502  \"Beginning of Line\" always does."
503  "Go to after prompt when on prompt line."
504  (let ((mark (value buffer-input-mark))
505        (point (current-point)))
506    (if (and (same-line-p point mark) (or (not p) (= p 1)))
507        (move-mark point mark)
508        (beginning-of-line-command p))))
509
510(defcommand "Reenter Interactive Input" (p)
511  "Copies the form to the left of point to be after the interactive buffer's
512   input mark.  When the current region is active, it is copied instead."
513  "Copies the form to the left of point to be after the interactive buffer's
514   input mark.  When the current region is active, it is copied instead."
515  (declare (ignore p))
516  (unless (hemlock-bound-p 'buffer-input-mark)
517    (editor-error "Not in an interactive buffer."))
518  (let ((point (current-point)))
519    (let ((region (if (region-active-p)
520                      ;; Copy this, so moving point doesn't affect the region.
521                      (copy-region (current-region))
522                      (with-mark ((start point)
523                                  (end point))
524                        (pre-command-parse-check start)
525                        (unless (form-offset start -1)
526                          (editor-error "Not after complete form."))
527                        (region (copy-mark start) (copy-mark end))))))
528      (buffer-end point)
529      (push-new-buffer-mark point)
530      (insert-region point region)
531      (setf (last-command-type) :ephemerally-active))))
532
533
534
535;;; Other stuff.
536
537(defmode "Editor" :hidden t)
538
539(defcommand "Editor Mode" (p)
540  "Turn on \"Editor\" mode in the current buffer.  If it is already on, turn it
541  off.  When in editor mode, most lisp compilation and evaluation commands
542  manipulate the editor process instead of the current eval server."
543  "Toggle \"Editor\" mode in the current buffer."
544  (declare (ignore p))
545  (setf (buffer-minor-mode (current-buffer) "Editor")
546        (not (buffer-minor-mode (current-buffer) "Editor"))))
547
548(define-file-option "Editor" (buffer value)
549  (declare (ignore value))
550  (setf (buffer-minor-mode buffer "Editor") t))
551
552
553
554(defcommand "Editor Compile Defun" (p)
555  "Compiles the current or next top-level form in the editor Lisp.
556   First the form is evaluated, then the result of this evaluation
557   is passed to compile.  If the current region is active, this
558   compiles the region."
559  "Evaluates the current or next top-level form in the editor Lisp."
560  (declare (ignore p))
561  (if (region-active-p)
562      (editor-compile-region (current-region))
563      (editor-compile-region (defun-region (current-point)) t)))
564
565(defcommand "Editor Compile Region" (p)
566  "Compiles lisp forms between the point and the mark in the editor Lisp."
567  "Compiles lisp forms between the point and the mark in the editor Lisp."
568  (declare (ignore p))
569  (editor-compile-region (current-region)))
570
571(defun defun-region (mark)
572  "This returns a region around the current or next defun with respect to mark.
573   Mark is not used to form the region.  If there is no appropriate top level
574   form, this signals an editor-error.  This calls PRE-COMMAND-PARSE-CHECK."
575  (with-mark ((start mark)
576              (end mark))
577    (pre-command-parse-check start)
578    (cond ((not (mark-top-level-form start end))
579           (editor-error "No current or next top level form."))
580          (t (region start end)))))
581
582(defun eval-region (region
583                    &key
584                    (package (variable-value 'current-package :buffer (current-buffer)))
585                    (path (buffer-pathname (current-buffer))))
586  (ccl::application-ui-operation ccl:*application*
587                                 :eval-selection
588                                 (list package
589                                       path
590                                       (region-to-string region)
591                                       (mark-absolute-position (region-start region)))))
592
593
594(defun editor-compile-region (region &optional quiet)
595  (unless quiet (message "Compiling region ..."))
596  (eval-region region))
597
598
599(defcommand "Editor Evaluate Defun" (p)
600  "Evaluates the current or next top-level form in the editor Lisp.
601   If the current region is active, this evaluates the region."
602  "Evaluates the current or next top-level form in the editor Lisp."
603  (declare (ignore p))
604  (if (region-active-p)
605    (editor-evaluate-region-command nil)
606    (eval-region (defun-region (current-point)))))
607
608(defcommand "Editor Evaluate Region" (p)
609  "Evaluates lisp forms between the point and the mark in the editor Lisp."
610  "Evaluates lisp forms between the point and the mark in the editor Lisp."
611  (declare (ignore p))
612  (if (region-active-p)
613    (eval-region (current-region))
614    (let* ((point (current-point)))
615      (pre-command-parse-check point)
616      (when (valid-spot point nil)      ; not in the middle of a comment
617        (cond ((eql (next-character point) #\()
618               (with-mark ((m point))
619                 (if (form-offset m 1)
620                   (eval-region (region point m)))))
621              ((eql (previous-character point) #\))
622               (with-mark ((m point))
623                 (if (form-offset m -1)
624                   (eval-region (region m point)))))
625              (t
626               (with-mark ((start point)
627                           (end point))
628                 (when (mark-symbol start end)
629                   (eval-region (region start end))))))))))
630
631(defcommand "Editor Re-evaluate Defvar" (p)
632  "Evaluate the current or next top-level form if it is a DEFVAR.  Treat the
633   form as if the variable is not bound.  This occurs in the editor Lisp."
634  "Evaluate the current or next top-level form if it is a DEFVAR.  Treat the
635   form as if the variable is not bound.  This occurs in the editor Lisp."
636  (declare (ignore p))
637  (with-input-from-region (stream (defun-region (current-point)))
638    (clear-echo-area)
639    (in-lisp
640     (let ((form (read stream)))
641       (unless (eq (car form) 'defvar) (editor-error "Not a DEFVAR."))
642       (makunbound (cadr form))
643       (message "Evaluation returned ~S" (eval form))))))
644
645(defun macroexpand-expression (expander)
646  (let* ((point (buffer-point (current-buffer)))
647         (region (if (region-active-p)
648                   (current-region)
649                   (with-mark ((start point))
650                     (pre-command-parse-check start)
651                     (with-mark ((end start))
652                       (unless (form-offset end 1) (editor-error))
653                       (region start end)))))
654         (expr (with-input-from-region (s region)
655                 (read s))))
656    (let* ((*print-pretty* t)
657           (expansion (funcall expander expr)))
658      (format t "~&~s~&" expansion))))
659
660(defcommand "Editor Macroexpand-1 Expression" (p)
661  "Show the macroexpansion of the current expression in the null environment.
662   With an argument, use MACROEXPAND instead of MACROEXPAND-1."
663  "Show the macroexpansion of the current expression in the null environment.
664   With an argument, use MACROEXPAND instead of MACROEXPAND-1."
665  (macroexpand-expression (if p 'macroexpand 'macroexpand-1)))
666
667(defcommand "Editor Macroexpand Expression" (p)
668  "Show the macroexpansion of the current expression in the null environment.
669   With an argument, use MACROEXPAND-1 instead of MACROEXPAND."
670  "Show the macroexpansion of the current expression in the null environment.
671   With an argument, use MACROEXPAND-1 instead of MACROEXPAND."
672  (macroexpand-expression (if p 'macroexpand-1 'macroexpand)))
673
674
675(defcommand "Editor Evaluate Expression" (p)
676  "Prompt for an expression to evaluate in the editor Lisp."
677  "Prompt for an expression to evaluate in the editor Lisp."
678  (declare (ignore p))
679  (in-lisp
680   (multiple-value-call #'message "=> ~@{~#[~;~S~:;~S, ~]~}"
681     (eval (prompt-for-expression
682            :prompt "Editor Eval: "
683            :help "Expression to evaluate")))))
684
685(defcommand "Editor Evaluate Buffer" (p)
686  "Evaluates the text in the current buffer in the editor Lisp."
687  (declare (ignore p))
688  (message "Evaluating buffer in the editor ...")
689  (with-input-from-region (stream (buffer-region (current-buffer)))
690    (in-lisp
691     (do ((object (read stream nil lispbuf-eof) 
692                  (read stream nil lispbuf-eof)))
693         ((eq object lispbuf-eof))
694       (eval object)))
695    (message "Evaluation complete.")))
696
697
698
699(defcommand "Editor Compile File" (p)
700  "Prompts for file to compile in the editor Lisp.  Does not compare source
701   and binary write dates.  Does not check any buffer for that file for
702   whether the buffer needs to be saved."
703  "Prompts for file to compile."
704  (declare (ignore p))
705  (let ((pn (prompt-for-file :default
706                             (buffer-default-pathname (current-buffer))
707                             :prompt "File to compile: ")))
708    (in-lisp (compile-file (namestring pn) #+cmu :error-file #+cmu nil))))
709
710
711(defun older-or-non-existent-fasl-p (pathname &optional definitely)
712  (let ((obj-pn (probe-file (compile-file-pathname pathname))))
713    (or definitely
714        (not obj-pn)
715        (< (file-write-date obj-pn) (file-write-date pathname)))))
716
717
718(defcommand "Editor Compile Buffer File" (p)
719  "Compile the file in the current buffer in the editor Lisp if its associated
720   binary file (of type .fasl) is older than the source or doesn't exist.  When
721   the binary file is up to date, the user is asked if the source should be
722   compiled anyway.  When the prefix argument is supplied, compile the file
723   without checking the binary file.  When \"Compile Buffer File Confirm\" is
724   set, this command will ask for confirmation when it otherwise would not."
725  "Compile the file in the current buffer in the editor Lisp if the fasl file
726   isn't up to date.  When p, always do it."
727  (let* ((buf (current-buffer))
728         (pn (buffer-pathname buf)))
729    (unless pn (editor-error "Buffer has no associated pathname."))
730    (cond ((buffer-modified buf)
731           (when (or (not (value compile-buffer-file-confirm))
732                     (prompt-for-y-or-n
733                      :default t :default-string "Y"
734                      :prompt (list "Save and compile file ~A? "
735                                    (namestring pn))))
736             (write-buffer-file buf pn)
737             (in-lisp (compile-file (namestring pn) #+cmu :error-file #+cmu nil))))
738          ((older-or-non-existent-fasl-p pn p)
739           (when (or (not (value compile-buffer-file-confirm))
740                     (prompt-for-y-or-n
741                      :default t :default-string "Y"
742                      :prompt (list "Compile file ~A? " (namestring pn))))
743             (in-lisp (compile-file (namestring pn) #+cmu :error-file #+cmu nil))))
744          (t (when (or p
745                       (prompt-for-y-or-n
746                        :default t :default-string "Y"
747                        :prompt
748                        "Fasl file up to date, compile source anyway? "))
749               (in-lisp (compile-file (namestring pn) #+cmu :error-file #+cmu nil)))))))
750
751
752
753
754
755
756
757;;;; Lisp documentation stuff.
758
759;;; FUNCTION-TO-DESCRIBE is used in "Editor Describe Function Call" and
760;;; "Describe Function Call".
761;;;
762(defmacro function-to-describe (var error-name)
763  `(cond ((not (symbolp ,var))
764          (,error-name "~S is not a symbol." ,var))
765         ((special-operator-p ,var) ,var)
766         ((macro-function ,var))
767         ((fboundp ,var))
768         (t
769          (,error-name "~S is not a function." ,var))))
770
771(defcommand "Editor Describe Function Call" (p)
772  "Describe the most recently typed function name in the editor Lisp."
773  "Describe the most recently typed function name in the editor Lisp."
774  (declare (ignore p))
775  (with-mark ((mark1 (current-point))
776              (mark2 (current-point)))
777    (pre-command-parse-check mark1)
778    (unless (backward-up-list mark1) (editor-error))
779    (form-offset (move-mark mark2 (mark-after mark1)) 1)
780    (with-input-from-region (s (region mark1 mark2))
781      (in-lisp
782       (let* ((sym (read s))
783              (fun (function-to-describe sym editor-error)))
784         (with-pop-up-display (*standard-output* :title (format nil "~s" sym))
785           (editor-describe-function fun sym)))))))
786
787
788(defcommand "Editor Describe Symbol" (p)
789  "Describe the previous s-expression if it is a symbol in the editor Lisp."
790  "Describe the previous s-expression if it is a symbol in the editor Lisp."
791  (declare (ignore p))
792  (with-mark ((mark1 (current-point))
793              (mark2 (current-point)))
794    (mark-symbol mark1 mark2)
795    (with-input-from-region (s (region mark1 mark2))
796      (let ((thing (read s)))
797        (if (symbolp thing)
798          (with-pop-up-display (*standard-output* :title (format nil "~s" thing))
799            (describe thing))
800          (if (and (consp thing)
801                   (or (eq (car thing) 'quote)
802                       (eq (car thing) 'function))
803                   (symbolp (cadr thing)))
804            (with-pop-up-display (*standard-output* :title (format nil "~s" (cadr thing)))
805              (describe (cadr thing)))
806            (editor-error "~S is not a symbol, or 'symbol, or #'symbol."
807                          thing)))))))
808
809;;; MARK-SYMBOL moves mark1 and mark2 around the previous or current symbol.
810;;; However, if the marks are immediately before the first constituent char
811;;; of the symbol name, we use the next symbol since the marks probably
812;;; correspond to the point, and Hemlock's cursor display makes it look like
813;;; the point is within the symbol name.  This also tries to ignore :prefix
814;;; characters such as quotes, commas, etc.
815;;;
816(defun mark-symbol (mark1 mark2)
817  (pre-command-parse-check mark1)
818  (with-mark ((tmark1 mark1)
819              (tmark2 mark1))
820    (cond ((and (form-offset tmark1 1)
821                (form-offset (move-mark tmark2 tmark1) -1)
822                (or (mark= mark1 tmark2)
823                    (and (find-attribute tmark2 :lisp-syntax
824                                         #'(lambda (x) (not (eq x :prefix))))
825                         (mark= mark1 tmark2))))
826           (form-offset mark2 1))
827          (t
828           (form-offset mark1 -1)
829           (find-attribute mark1 :lisp-syntax
830                           #'(lambda (x) (not (eq x :prefix))))
831           (form-offset (move-mark mark2 mark1) 1)))))
832
833
834(defcommand "Editor Describe" (p)
835  "Call Describe on a Lisp object.
836  Prompt for an expression which is evaluated to yield the object."
837  "Prompt for an object to describe."
838  (declare (ignore p))
839  (in-lisp
840   (let* ((exp (prompt-for-expression
841                :prompt "Object: "
842                :help "Expression to evaluate to get object to describe."))
843          (obj (eval exp)))
844     (with-pop-up-display (*standard-output* :title (format nil "~s" exp))
845       (describe obj)))))
846
847(defcommand "Filter Region" (p)
848  "Apply a Lisp function to each line of the region.
849  An expression is prompted for which should evaluate to a Lisp function
850  from a string to a string.  The function must neither modify its argument
851  nor modify the return value after it is returned."
852  "Call prompt for a function, then call Filter-Region with it and the region."
853  (declare (ignore p))
854  (let* ((exp (prompt-for-expression
855               :prompt "Function: "
856               :help "Expression to evaluate to get function to use as filter."))
857         (fun (in-lisp (eval exp)))
858         (region (current-region)))
859    (let* ((start (copy-mark (region-start region) :left-inserting))
860           (end (copy-mark (region-end region) :left-inserting))
861           (region (region start end))
862           (undo-region (copy-region region)))
863      (filter-region fun region)
864      (make-region-undo :twiddle "Filter Region" region undo-region))))
Note: See TracBrowser for help on using the repository browser.