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

Last change on this file since 14816 was 14816, checked in by svspire, 10 years ago

Ron Garret's patches.
Fix copy-region-to-input to work like FRED did --
"When you're typing at the Listener and you want to grab another sexpr
in the listener by putting the cursor on it and pressing RETURN, the
base Hemlock behavior is to replace what you're typing with the new
input. FRED (and this patch) appends the new input to what you've
already typed, and also leaves the cursor in the same relative
position as where you placed it."

Fix "Editor Execute Expression" and "Editor Execute Defun" to make the
result visible in listener. New variable *echo-expression-to-listener*
if true will also echo evaluated expression itself to listener,
otherwise only its evaluated result is echoed.

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