(ielm-map): Don't bind M-q.
[bpt/emacs.git] / lisp / ielm.el
CommitLineData
813f532d 1;;; ielm.el --- interaction mode for Emacs Lisp
b578f267 2
813f532d
RS
3;; Copyright (C) 1994 Free Software Foundation, Inc.
4
5;; Author: David Smith <maa036@lancaster.ac.uk>
6;; Created: 25 Feb 1994
7;; Keywords: lisp
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
b578f267
EN
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
813f532d
RS
25
26;;; Commentary:
27
e848854a 28;; Provides a nice interface to evaluating Emacs Lisp expressions.
813f532d
RS
29;; Input is handled by the comint package, and output is passed
30;; through the pretty-printer.
31
32;; To install: copy this file to a directory in your load-path, and
e848854a 33;; add the following line to your .emacs file:
813f532d 34;;
e848854a 35;; (autoload 'ielm "ielm" "Start an inferior Emacs Lisp session" t)
813f532d
RS
36;;
37;; For completion to work, the comint.el from FSF Emacs 19.23 is
38;; required. If you do not have it, or if you are running Lemacs,
39;; also add the following code to your .emacs:
40;;
41;; (setq ielm-mode-hook
42;; '(lambda nil
43;; (define-key ielm-map "\t"
44;; '(lambda nil (interactive) (or (ielm-tab)
45;; (lisp-complete-symbol))))))
46
47;; To start: M-x ielm. Type C-h m in the *ielm* buffer for more info.
48
49;; The latest version is available by WWW from
50;; http://mathssun5.lancs.ac.uk:2080/~maa036/elisp/dir.html
51;; or by anonymous FTP from
52;; /anonymous@wingra.stat.wisc.edu:pub/src/emacs-lisp/ielm.el.gz
53;; or from the author: David M. Smith <maa036@lancaster.ac.uk>
54
55;;; Code:
56
57(require 'comint)
58(require 'pp)
59
60;;; User variables
61
62(defvar ielm-noisy t
e848854a 63 "*If non-nil, IELM will beep on error.")
813f532d 64
e848854a
RS
65(defvar ielm-prompt "ELISP> "
66 "Prompt used in IELM.")
813f532d
RS
67
68(defvar ielm-dynamic-return t
e848854a
RS
69 "*Controls whether \\<ielm-map>\\[ielm-return] has intelligent behaviour in IELM.
70If non-nil, \\[ielm-return] evaluates input for complete sexps, or inserts a newline
71and indents for incomplete sexps. If nil, always inserts newlines.")
72
73(defvar ielm-dynamic-multiline-inputs t
74 "*Force multiline inputs to start from column zero?
75If non-nil, after entering the first line of an incomplete sexp, a newline
76will be inserted after the prompt, moving the input to the next line.
77This gives more frame width for large indented sexps, and allows functions
78such as `edebug-defun' to work with such inputs.")
813f532d
RS
79
80(defvar ielm-mode-hook nil
e848854a 81 "*Hooks to be run when IELM (`inferior-emacs-lisp-mode') is started.")
813f532d
RS
82
83;;; System variables
84
85(defvar ielm-working-buffer nil
e848854a
RS
86 "Buffer in which IELM sexps will be evaluated.
87This variable is buffer-local.")
813f532d
RS
88
89(defvar ielm-header
90 (concat
e848854a 91 "*** Welcome to IELM version "
950086e6 92 (substring "$Revision: 1.7 $" 11 -2)
813f532d 93 " *** Type (describe-mode) for help.\n"
e848854a
RS
94 "IELM has ABSOLUTELY NO WARRANTY; type (describe-no-warranty) for details.\n")
95 "Message to display when IELM is started.")
813f532d
RS
96
97(defvar ielm-map nil)
98(if ielm-map nil
99 (if (string-match "Lucid" emacs-version)
100 ;; Lemacs
101 (progn
102 (setq ielm-map (make-sparse-keymap))
103 (set-keymap-parent ielm-map comint-mode-map))
104 ;; FSF
105 (setq ielm-map (cons 'keymap comint-mode-map)))
106 (define-key ielm-map "\t" 'comint-dynamic-complete)
107 (define-key ielm-map "\C-m" 'ielm-return)
e848854a
RS
108 (define-key ielm-map "\C-j" 'ielm-send-input)
109 (define-key ielm-map "\e\C-x" 'eval-defun) ; for consistency with
110 (define-key ielm-map "\e\t" 'lisp-complete-symbol) ; lisp-interaction-mode
111 ;; These bindings are from shared-lisp-mode-map -- can you inherit
112 ;; from more than one keymap??
113 (define-key ielm-map "\e\C-q" 'indent-sexp)
e848854a
RS
114 (define-key ielm-map "\177" 'backward-delete-char-untabify)
115 ;; Some convenience bindings for setting the working buffer
116 (define-key ielm-map "\C-c\C-b" 'ielm-change-working-buffer)
117 (define-key ielm-map "\C-c\C-f" 'ielm-display-working-buffer)
118 (define-key ielm-map "\C-c\C-v" 'ielm-print-working-buffer))
813f532d 119
9d42eea3
RS
120(defvar ielm-font-lock-keywords
121 (list
122 (cons (concat "^" (regexp-quote ielm-prompt)) 'font-lock-keyword-face)
123 '("\\(^\\*\\*\\*[^*]+\\*\\*\\*\\)\\(.*$\\)" (1 font-lock-comment-face) (2 font-lock-reference-face)))
124 "Additional expressions to highlight in ielm buffers.")
125
813f532d
RS
126;;; Completion stuff
127
128(defun ielm-tab nil
e848854a 129 "Possibly indent the current line as lisp code."
813f532d
RS
130 (interactive)
131 (if (or (eq (preceding-char) ?\n)
132 (eq (char-syntax (preceding-char)) ? ))
133 (progn
134 (ielm-indent-line)
135 t)))
136
137(defun ielm-complete-symbol nil
e848854a
RS
138 "Complete the lisp symbol before point."
139 ;; A wrapper for lisp-complete symbol that returns non-nil if
140 ;; completion has occurred
813f532d 141 (let* ((btick (buffer-modified-tick))
e58df0dc 142 (cbuffer (get-buffer "*Completions*"))
813f532d
RS
143 (ctick (and cbuffer (buffer-modified-tick cbuffer))))
144 (lisp-complete-symbol)
145 ;; completion has occurred if:
146 (or
147 ;; the buffer has been modified
148 (not (= btick (buffer-modified-tick)))
a7acbbe4 149 ;; a completions buffer has been modified or created
813f532d
RS
150 (if cbuffer
151 (not (= ctick (buffer-modified-tick cbuffer)))
e58df0dc 152 (get-buffer "*Completions*")))))
813f532d
RS
153
154(defun ielm-complete-filename nil
e848854a 155 "Dynamically complete filename before point, if in a string."
813f532d
RS
156 (if (nth 3 (parse-partial-sexp comint-last-input-start (point)))
157 (comint-dynamic-complete-filename)))
158
159(defun ielm-indent-line nil
e848854a 160 "Indent the current line as Lisp code if it is not a prompt line."
813f532d
RS
161 (if (save-excursion
162 (beginning-of-line)
163 (looking-at comint-prompt-regexp)) nil
164 (lisp-indent-line)))
165
e848854a
RS
166;;; Working buffer manipulation
167
168(defun ielm-print-working-buffer nil
169 "Print the current IELM working buffer's name in the echo area."
170 (interactive)
171 (message "The current working buffer is: %s" (buffer-name ielm-working-buffer)))
172
173(defun ielm-display-working-buffer nil
174 "Display the current IELM working buffer.
175Don't forget that selecting that buffer will change its value of `point'
176to its value of `window-point'!"
177 (interactive)
178 (display-buffer ielm-working-buffer)
179 (ielm-print-working-buffer))
180
181(defun ielm-change-working-buffer (buf)
182 "Change the current IELM working buffer to BUF.
183This is the buffer in which all sexps entered at the IELM prompt are
184evaluated. You can achieve the same effect with a call to
185`set-buffer' at the IELM prompt."
186 (interactive "bSet working buffer to: ")
187 (setq ielm-working-buffer (or (get-buffer buf) (error "No such buffer")))
188 (ielm-print-working-buffer))
189
813f532d
RS
190;;; Other bindings
191
192(defun ielm-return nil
e848854a
RS
193 "Newline and indent, or evaluate the sexp before the prompt.
194Complete sexps are evaluated; for incomplete sexps inserts a newline
195and indents. If however `ielm-dynamic-return' is nil, this always
196simply inserts a newline."
813f532d
RS
197 (interactive)
198 (if ielm-dynamic-return
199 (let ((state
200 (save-excursion
201 (end-of-line)
202 (parse-partial-sexp (ielm-pm)
203 (point)))))
204 (if (and (< (car state) 1) (not (nth 3 state)))
205 (ielm-send-input)
e848854a
RS
206 (if (and ielm-dynamic-multiline-inputs
207 (save-excursion
208 (beginning-of-line)
209 (looking-at comint-prompt-regexp)))
210 (save-excursion
211 (goto-char (ielm-pm))
212 (newline 1)))
813f532d
RS
213 (newline-and-indent)))
214 (newline)))
215
216(defun ielm-input-sender (proc input)
e848854a
RS
217 ;; Just sets the variable ielm-input, which is in the scope of
218 ;; `ielm-send-input's call.
813f532d
RS
219 (setq ielm-input input))
220
221(defun ielm-send-input nil
e848854a 222 "Evaluate the Emacs Lisp expression after the prompt."
813f532d
RS
223 (interactive)
224 (let ((buf (current-buffer))
225 ielm-input) ; set by ielm-input-sender
226 (comint-send-input) ; update history, markers etc.
227 (ielm-eval-input ielm-input)))
228
229;;; Utility functions
230
231(defun ielm-is-whitespace (string)
e848854a 232 "Return non-nil if STRING is all whitespace."
813f532d
RS
233 (or (string= string "") (string-match "\\`[ \t\n]+\\'" string)))
234
235(defun ielm-format-errors (errlist)
236 (let ((result ""))
237 (while errlist
238 (setq result (concat result (prin1-to-string (car errlist)) ", "))
239 (setq errlist (cdr errlist)))
240 (substring result 0 -2)))
241
242
243(defun ielm-format-error (err)
e848854a 244 ;; Return a string form of the error ERR.
813f532d
RS
245 (format "%s%s"
246 (or (get (car err) 'error-message) "Peculiar error")
247 (if (cdr err)
248 (format ": %s" (ielm-format-errors (cdr err)))
249 "")))
250
251;;; Evaluation
252
e848854a
RS
253(defun ielm-eval-input (ielm-string)
254 "Evaluate the Lisp expression IELM-STRING, and pretty-print the result."
813f532d 255 ;; This is the function that actually `sends' the input to the
e848854a 256 ;; `inferior Lisp process'. All comint-send-input does is works out
813f532d
RS
257 ;; what that input is. What this function does is evaluates that
258 ;; input and produces `output' which gets inserted into the buffer,
259 ;; along with a new prompt. A better way of doing this might have
260 ;; been to actually send the output to the `cat' process, and write
261 ;; this as in output filter that converted sexps in the output
262 ;; stream to their evaluated value. But that would have involved
263 ;; more process coordination than I was happy to deal with.
e848854a
RS
264 ;;
265 ;; NOTE: all temporary variables in this function will be in scope
266 ;; during the eval, and so need to have non-clashing names.
267 (let (ielm-form ; form to evaluate
268 ielm-pos ; End posn of parse in string
269 ielm-result ; Result, or error message
270 ielm-error-type ; string, nil if no error
271 (ielm-output "") ; result to display
272 (ielm-wbuf ielm-working-buffer) ; current buffer after evaluation
273 (ielm-pmark (ielm-pm)))
274 (if (not (ielm-is-whitespace ielm-string))
813f532d
RS
275 (progn
276 (condition-case err
277 (let (rout)
e848854a
RS
278 (setq rout (read-from-string ielm-string))
279 (setq ielm-form (car rout))
280 (setq ielm-pos (cdr rout)))
281 (error (setq ielm-result (ielm-format-error err))
282 (setq ielm-error-type "Read error")))
283 (if ielm-error-type nil
284 ;; Make sure working buffer has not been killed
285 (if (not (buffer-name ielm-working-buffer))
286 (setq ielm-result "Working buffer has been killed"
287 ielm-error-type "IELM Error"
288 ielm-wbuf (current-buffer))
289 (if (ielm-is-whitespace (substring ielm-string ielm-pos))
290 ;; need this awful let convolution to work around
291 ;; an Emacs bug involving local vbls and let binding
292 (let ((:save :)
293 (::save ::)
294 (:::save :::))
295 (save-excursion
296 (set-buffer ielm-working-buffer)
297 (condition-case err
298 (let ((: :save)
299 (:: ::save)
300 (::: :::save)
301 (ielm-obuf (current-buffer)))
302 (setq ielm-result (eval ielm-form))
303 (setq ielm-wbuf (current-buffer))
304 ;; The eval may have changed current-buffer;
305 ;; need to set it back here to avoid a bug
306 ;; in let. Don't want to use save-excursion
307 ;; because we want to allow changes in point.
308 (set-buffer ielm-obuf))
309 (error (setq ielm-result (ielm-format-error err))
310 (setq ielm-error-type "Eval error"))
311 (quit (setq ielm-result "Quit during evaluation")
312 (setq ielm-error-type "Eval error")))))
313 (setq ielm-error-type "IELM error")
314 (setq ielm-result "More than one sexp in input"))))
813f532d
RS
315
316 ;; If the eval changed the current buffer, mention it here
e848854a
RS
317 (if (eq ielm-wbuf ielm-working-buffer) nil
318 (message "current buffer is now: %s" ielm-wbuf)
319 (setq ielm-working-buffer ielm-wbuf))
813f532d 320
e848854a
RS
321 (goto-char ielm-pmark)
322 (if (not ielm-error-type)
813f532d
RS
323 (condition-case err
324 ;; Self-referential objects cause loops in the printer, so
325 ;; trap quits here. May as well do errors, too
e848854a
RS
326 (setq ielm-output (concat ielm-output (pp-to-string ielm-result)))
327 (error (setq ielm-error-type "IELM Error")
328 (setq ielm-result "Error during pretty-printing (bug in pp)"))
329 (quit (setq ielm-error-type "IELM Error")
330 (setq ielm-result "Quit during pretty-printing"))))
331 (if ielm-error-type
813f532d
RS
332 (progn
333 (if ielm-noisy (ding))
e848854a
RS
334 (setq ielm-output (concat ielm-output "*** " ielm-error-type " *** "))
335 (setq ielm-output (concat ielm-output ielm-result)))
813f532d
RS
336 ;; There was no error, so shift the ::: values
337 (setq ::: ::)
338 (setq :: :)
e848854a
RS
339 (setq : ielm-result))
340 (setq ielm-output (concat ielm-output "\n"))))
341 (setq ielm-output (concat ielm-output ielm-prompt))
342 (comint-output-filter (ielm-process) ielm-output)))
813f532d
RS
343
344;;; Process and marker utilities
345
346(defun ielm-process nil
e848854a 347 ;; Return the current buffer's process.
813f532d
RS
348 (get-buffer-process (current-buffer)))
349
350(defun ielm-pm nil
e848854a 351 ;; Return the process mark of the current buffer.
813f532d
RS
352 (process-mark (get-buffer-process (current-buffer))))
353
354(defun ielm-set-pm (pos)
e848854a 355 ;; Set the process mark in the current buffer to POS.
813f532d
RS
356 (set-marker (process-mark (get-buffer-process (current-buffer))) pos))
357
358;;; Major mode
359
360(defun inferior-emacs-lisp-mode nil
e848854a
RS
361 "Major mode for interactively evaluating Emacs Lisp expressions.
362Uses the interface provided by `comint-mode' (which see).
363
364* \\<ielm-map>\\[ielm-send-input] evaluates the sexp following the prompt. There must be at most
365 one top-level sexp per prompt.
813f532d 366
e848854a
RS
367* \\[ielm-return] inserts a newline and indents, or evaluates a
368 complete expression (but see variable `ielm-dynamic-return').
369 Inputs longer than one line are moved to the line following the
370 prompt (but see variable `ielm-dynamic-multiline-inputs').
371
372* \\[comint-dynamic-complete] completes Lisp symbols (or filenames, within strings),
373 or indents the line if there is nothing to complete.
813f532d
RS
374
375During evaluations, the values of the variables `:', `::', and `:::'
376are the results of the previous, second previous and third previous
377evaluations respectively.
378
e848854a
RS
379The current working buffer may be changed (with a call to
380`set-buffer', or with \\[ielm-change-working-buffer]), and its value
381is preserved between successive evaluations. In this way, expressions
382may be evaluated in a different buffer than the *ielm* buffer.
383Display the name of the working buffer with \\[ielm-print-working-buffer],
384or the buffer itself with \\[ielm-display-working-buffer].
813f532d 385
e848854a
RS
386Expressions evaluated by IELM are not subject to `debug-on-quit' or
387`debug-on-error'.
813f532d
RS
388
389The behaviour of IELM may be customised with the following variables:
390* To stop beeping on error, set `ielm-noisy' to nil
391* If you don't like the prompt, you can change it by setting `ielm-prompt'.
392* Set `ielm-dynamic-return' to nil for bindings like `lisp-interaction-mode'
393* Entry to this mode runs `comint-mode-hook' and `ielm-mode-hook'
394 (in that order).
395
396Customised bindings may be defined in `ielm-map', which currently contains:
397\\{ielm-map}"
398 (interactive)
399 (comint-mode)
400 (setq comint-prompt-regexp (concat "^" (regexp-quote ielm-prompt)))
401 (make-local-variable 'paragraph-start)
402 (setq paragraph-start comint-prompt-regexp)
403 (setq comint-input-sender 'ielm-input-sender)
404 (setq comint-process-echoes nil)
405 (setq comint-dynamic-complete-functions
406 '(ielm-tab comint-replace-by-expanded-history ielm-complete-filename ielm-complete-symbol))
e848854a 407 (setq comint-get-old-input 'ielm-get-old-input)
813f532d
RS
408
409 (setq major-mode 'inferior-emacs-lisp-mode)
410 (setq mode-name "IELM")
411 (use-local-map ielm-map)
412 (set-syntax-table emacs-lisp-mode-syntax-table)
413
414 (make-local-variable 'indent-line-function)
415 (make-local-variable 'ielm-working-buffer)
416 (setq ielm-working-buffer (current-buffer))
417 (setq indent-line-function 'ielm-indent-line)
950086e6
KH
418 (make-local-variable 'fill-paragraph-function)
419 (setq fill-paragraph-function 'lisp-fill-paragraph)
813f532d 420
e848854a 421 ;; Value holders
813f532d
RS
422 (setq : nil)
423 (make-local-variable ':)
424 (setq :: nil)
425 (make-local-variable '::)
426 (setq ::: nil)
427 (make-local-variable ':::)
428
9d42eea3
RS
429 ;; font-lock support
430 (make-local-variable 'font-lock-defaults)
431 (setq font-lock-defaults
432 '(ielm-font-lock-keywords nil nil ((?: . "w") (?- . "w") (?* . "w"))))
433
813f532d
RS
434 ;; A dummy process to keep comint happy. It will never get any input
435 (if (comint-check-proc (current-buffer)) nil
436 (start-process "ielm" (current-buffer) "cat")
437 (process-kill-without-query (ielm-process))
438 (goto-char (point-max))
439 ;; Add a silly header
440 (insert ielm-header)
441 (ielm-set-pm (point-max))
442 (comint-output-filter (ielm-process) ielm-prompt)
443 (set-marker comint-last-input-start (ielm-pm))
444 (set-process-filter (get-buffer-process (current-buffer)) 'comint-output-filter))
445 (run-hooks 'ielm-mode-hook))
446
e848854a
RS
447(defun ielm-get-old-input nil
448 ;; Return the previous input surrounding point
449 (save-excursion
450 (beginning-of-line)
451 (if (looking-at comint-prompt-regexp) nil
452 (re-search-backward comint-prompt-regexp))
453 (comint-skip-prompt)
454 (buffer-substring (point) (progn (forward-sexp 1) (point)))))
455
813f532d
RS
456;;; User command
457
9e983c78
RS
458;;;###autoload (add-hook 'same-window-buffer-names "*ielm*")
459
e848854a 460;;;###autoload
813f532d 461(defun ielm nil
e848854a 462 "Interactively evaluate Emacs Lisp expressions.
9e983c78 463Switches to the buffer `*ielm*', or creates it if it does not exist."
813f532d 464 (interactive)
9e983c78
RS
465 (if (comint-check-proc "*ielm*")
466 nil
467 (save-excursion
813f532d
RS
468 (set-buffer (get-buffer-create "*ielm*"))
469 (inferior-emacs-lisp-mode)))
9e983c78 470 (pop-to-buffer "*ielm*"))
813f532d 471
e848854a 472;;; ielm.el ends here