7267
|
1 ;;; ielm.el --- interaction mode for Emacs Lisp
|
|
2 ;; Copyright (C) 1994 Free Software Foundation, Inc.
|
|
3
|
|
4 ;; Author: David Smith <maa036@lancaster.ac.uk>
|
|
5 ;; Created: 25 Feb 1994
|
|
6 ;; Keywords: lisp
|
|
7
|
|
8 ;; This file is part of GNU Emacs.
|
|
9
|
|
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
11 ;; it under the terms of the GNU General Public License as published by
|
|
12 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
13 ;; any later version.
|
|
14
|
|
15 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18 ;; GNU General Public License for more details.
|
|
19
|
|
20 ;; You should have received a copy of the GNU General Public License
|
|
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
|
|
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
23
|
|
24 ;;; Commentary:
|
|
25
|
|
26 ;; Provides a nice interface to evaluating Emacs-Lisp expressions.
|
|
27 ;; Input is handled by the comint package, and output is passed
|
|
28 ;; through the pretty-printer.
|
|
29
|
|
30 ;; To install: copy this file to a directory in your load-path, and
|
|
31 ;; add the line
|
|
32 ;;
|
|
33 ;; (autoload 'ielm "ielm" "Start an inferior emacs-lisp session" t)
|
|
34 ;;
|
|
35 ;; For completion to work, the comint.el from FSF Emacs 19.23 is
|
|
36 ;; required. If you do not have it, or if you are running Lemacs,
|
|
37 ;; also add the following code to your .emacs:
|
|
38 ;;
|
|
39 ;; (setq ielm-mode-hook
|
|
40 ;; '(lambda nil
|
|
41 ;; (define-key ielm-map "\t"
|
|
42 ;; '(lambda nil (interactive) (or (ielm-tab)
|
|
43 ;; (lisp-complete-symbol))))))
|
|
44
|
|
45 ;; To start: M-x ielm. Type C-h m in the *ielm* buffer for more info.
|
|
46
|
|
47 ;; The latest version is available by WWW from
|
|
48 ;; http://mathssun5.lancs.ac.uk:2080/~maa036/elisp/dir.html
|
|
49 ;; or by anonymous FTP from
|
|
50 ;; /anonymous@wingra.stat.wisc.edu:pub/src/emacs-lisp/ielm.el.gz
|
|
51 ;; or from the author: David M. Smith <maa036@lancaster.ac.uk>
|
|
52
|
|
53 ;;; Code:
|
|
54
|
|
55 (require 'comint)
|
|
56 (require 'pp)
|
|
57
|
|
58 ;;; User variables
|
|
59
|
|
60 (defvar ielm-noisy t
|
|
61 "*If non-nil, beep on error")
|
|
62
|
|
63 (defvar ielm-prompt "ELISP> ")
|
|
64
|
|
65 (defvar ielm-dynamic-return t
|
|
66 "*If non-nil, RET either evaluates input or inserts a newline,
|
|
67 depending on context")
|
|
68
|
|
69 (defvar ielm-mode-hook nil
|
|
70 "*Hooks to be run when the inferior-emacs-lisp-mode is started")
|
|
71
|
|
72 ;;; System variables
|
|
73
|
|
74 (defvar ielm-working-buffer nil
|
|
75 "Buffer, if any, to use in ielm. Usually buffer-local")
|
|
76
|
|
77 (defvar ielm-header
|
|
78 (concat
|
|
79 "*** Welcome to IELM mode version "
|
|
80 (substring "$Revision: 1.15 $" 11 -2)
|
|
81 " *** Type (describe-mode) for help.\n"
|
|
82 "IELM has ABSOLUTELY NO WARRANTY; type (describe-no-warranty) for details\n"))
|
|
83
|
|
84 (defvar ielm-map nil)
|
|
85 (if ielm-map nil
|
|
86 (if (string-match "Lucid" emacs-version)
|
|
87 ;; Lemacs
|
|
88 (progn
|
|
89 (setq ielm-map (make-sparse-keymap))
|
|
90 (set-keymap-parent ielm-map comint-mode-map))
|
|
91 ;; FSF
|
|
92 (setq ielm-map (cons 'keymap comint-mode-map)))
|
|
93 (define-key ielm-map "\t" 'comint-dynamic-complete)
|
|
94 (define-key ielm-map "\C-m" 'ielm-return)
|
|
95 (define-key ielm-map "\C-j" 'ielm-send-input))
|
|
96
|
|
97 ;;; Completion stuff
|
|
98
|
|
99 (defun ielm-tab nil
|
|
100 "Possibly indent the current line as lisp code"
|
|
101 (interactive)
|
|
102 (if (or (eq (preceding-char) ?\n)
|
|
103 (eq (char-syntax (preceding-char)) ? ))
|
|
104 (progn
|
|
105 (ielm-indent-line)
|
|
106 t)))
|
|
107
|
|
108 (defun ielm-complete-symbol nil
|
|
109 "Just like lisp-complete-symbol"
|
|
110 ;; except that it returns non-nil if completion has occurred
|
|
111 (let* ((btick (buffer-modified-tick))
|
|
112 (cbuffer (get-buffer " *Completions*"))
|
|
113 (ctick (and cbuffer (buffer-modified-tick cbuffer))))
|
|
114 (lisp-complete-symbol)
|
|
115 ;; completion has occurred if:
|
|
116 (or
|
|
117 ;; the buffer has been modified
|
|
118 (not (= btick (buffer-modified-tick)))
|
|
119 ;; a completions buffer has been modifed or created
|
|
120 (if cbuffer
|
|
121 (not (= ctick (buffer-modified-tick cbuffer)))
|
|
122 (get-buffer " *Completions*")))))
|
|
123
|
|
124 (defun ielm-complete-filename nil
|
|
125 ;; Completes filenames if in a string
|
|
126 (if (nth 3 (parse-partial-sexp comint-last-input-start (point)))
|
|
127 (comint-dynamic-complete-filename)))
|
|
128
|
|
129 (defun ielm-indent-line nil
|
|
130 "Indent the current line as lisp code if it is not a prompt line"
|
|
131 (if (save-excursion
|
|
132 (beginning-of-line)
|
|
133 (looking-at comint-prompt-regexp)) nil
|
|
134 (lisp-indent-line)))
|
|
135
|
|
136 ;;; Other bindings
|
|
137
|
|
138 (defun ielm-return nil
|
|
139 "Evaluate the sexp at the prompt if it is complete, otherwise newline
|
|
140 and indent. If ielm-dynamic-return is nil, just insert a newline."
|
|
141 (interactive)
|
|
142 (if ielm-dynamic-return
|
|
143 (let ((state
|
|
144 (save-excursion
|
|
145 (end-of-line)
|
|
146 (parse-partial-sexp (ielm-pm)
|
|
147 (point)))))
|
|
148 (if (and (< (car state) 1) (not (nth 3 state)))
|
|
149 (ielm-send-input)
|
|
150 (newline-and-indent)))
|
|
151 (newline)))
|
|
152
|
|
153 (defun ielm-input-sender (proc input)
|
|
154 (setq ielm-input input))
|
|
155
|
|
156 (defun ielm-send-input nil
|
|
157 "Evaluate the Emacs Lisp expression after the prompt"
|
|
158 (interactive)
|
|
159 (let ((buf (current-buffer))
|
|
160 ielm-input) ; set by ielm-input-sender
|
|
161 (comint-send-input) ; update history, markers etc.
|
|
162 (ielm-eval-input ielm-input)))
|
|
163
|
|
164 ;;; Utility functions
|
|
165
|
|
166 (defun ielm-is-whitespace (string)
|
|
167 "Return non-nil if STRING is all whitespace"
|
|
168 (or (string= string "") (string-match "\\`[ \t\n]+\\'" string)))
|
|
169
|
|
170 (defun ielm-format-errors (errlist)
|
|
171 (let ((result ""))
|
|
172 (while errlist
|
|
173 (setq result (concat result (prin1-to-string (car errlist)) ", "))
|
|
174 (setq errlist (cdr errlist)))
|
|
175 (substring result 0 -2)))
|
|
176
|
|
177
|
|
178 (defun ielm-format-error (err)
|
|
179 "Return a string form of the error ERR"
|
|
180 (format "%s%s"
|
|
181 (or (get (car err) 'error-message) "Peculiar error")
|
|
182 (if (cdr err)
|
|
183 (format ": %s" (ielm-format-errors (cdr err)))
|
|
184 "")))
|
|
185
|
|
186 ;;; Evaluation
|
|
187
|
|
188 (defun ielm-eval-input (string)
|
|
189 "Evaluate the lisp expression STRING, and pretty-print the result"
|
|
190 ;; This is the function that actually `sends' the input to the
|
|
191 ;; `inferior lisp process'. All comint-send-input does is works out
|
|
192 ;; what that input is. What this function does is evaluates that
|
|
193 ;; input and produces `output' which gets inserted into the buffer,
|
|
194 ;; along with a new prompt. A better way of doing this might have
|
|
195 ;; been to actually send the output to the `cat' process, and write
|
|
196 ;; this as in output filter that converted sexps in the output
|
|
197 ;; stream to their evaluated value. But that would have involved
|
|
198 ;; more process coordination than I was happy to deal with.
|
|
199 (let (form ; form to evaluate
|
|
200 pos ; End posn of parse in string
|
|
201 result ; Result, or error message
|
|
202 error-type ; string, nil if no error
|
|
203 (output "") ; result to display
|
|
204 (wbuf ielm-working-buffer) ; current buffer after evaluation
|
|
205 (pmark (ielm-pm)))
|
|
206 (if (not (ielm-is-whitespace string))
|
|
207 (progn
|
|
208 (condition-case err
|
|
209 (let (rout)
|
|
210 (setq rout (read-from-string string))
|
|
211 (setq form (car rout))
|
|
212 (setq pos (cdr rout)))
|
|
213 (error (setq result (ielm-format-error err))
|
|
214 (setq error-type "Read error")))
|
|
215 (if error-type nil
|
|
216 (if (ielm-is-whitespace (substring string pos))
|
|
217 ;; need this awful let convolution to work around
|
|
218 ;; an Emacs bug involving local vbls and let binding
|
|
219 (let ((:save :)
|
|
220 (::save ::)
|
|
221 (:::save :::))
|
|
222 (save-excursion
|
|
223 (set-buffer ielm-working-buffer)
|
|
224 (condition-case err
|
|
225 (let ((: :save)
|
|
226 (:: ::save)
|
|
227 (::: :::save))
|
|
228 (save-excursion
|
|
229 (setq result (eval form))
|
|
230 (setq wbuf (current-buffer))))
|
|
231 (error (setq result (ielm-format-error err))
|
|
232 (setq error-type "Eval error"))
|
|
233 (quit (setq result "Quit during evaluation")
|
|
234 (setq error-type "Eval error")))))
|
|
235 (setq error-type "IELM error")
|
|
236 (setq result "More than one sexp in input")))
|
|
237
|
|
238 ;; If the eval changed the current buffer, mention it here
|
|
239 (if (eq wbuf ielm-working-buffer) nil
|
|
240 (message "current buffer is now: %s" wbuf)
|
|
241 (setq ielm-working-buffer wbuf))
|
|
242
|
|
243 (goto-char pmark)
|
|
244 (if (not error-type)
|
|
245 (condition-case err
|
|
246 ;; Self-referential objects cause loops in the printer, so
|
|
247 ;; trap quits here. May as well do errors, too
|
|
248 (setq output (concat output (pp-to-string result)))
|
|
249 (error (setq error-type "IELM Error")
|
|
250 (setq result "Error during pretty-printing (bug in pp)"))
|
|
251 (quit (setq error-type "IELM Error")
|
|
252 (setq result "Quit during pretty-printing"))))
|
|
253 (if error-type
|
|
254 (progn
|
|
255 (if ielm-noisy (ding))
|
|
256 (setq output (concat output "*** " error-type " *** "))
|
|
257 (setq output (concat output result)))
|
|
258 ;; There was no error, so shift the ::: values
|
|
259 (setq ::: ::)
|
|
260 (setq :: :)
|
|
261 (setq : result))
|
|
262 (setq output (concat output "\n"))))
|
|
263 (setq output (concat output ielm-prompt))
|
|
264 (comint-output-filter (ielm-process) output)))
|
|
265
|
|
266 ;;; Process and marker utilities
|
|
267
|
|
268 (defun ielm-process nil
|
|
269 "Return the current buffer's process"
|
|
270 (get-buffer-process (current-buffer)))
|
|
271
|
|
272 (defun ielm-pm nil
|
|
273 "Return the process mark of the current buffer"
|
|
274 (process-mark (get-buffer-process (current-buffer))))
|
|
275
|
|
276 (defun ielm-set-pm (pos)
|
|
277 "Set the process mark in the current buffer to POS"
|
|
278 (set-marker (process-mark (get-buffer-process (current-buffer))) pos))
|
|
279
|
|
280 ;;; Major mode
|
|
281
|
|
282 (defun inferior-emacs-lisp-mode nil
|
|
283 "Major mode for interactively evaluating Emacs-Lisp expressions
|
|
284 Uses the interface provided by `comint-mode' (q.v.)
|
|
285
|
|
286 \\[ielm-send-input] evaluates the sexp following the prompt. There must be at most
|
|
287 one top-level sexp per prompt.
|
|
288 \\[ielm-return] inserts a newline and indents. However, if the variable
|
|
289 ielm-dynamic-return is non-nil (the default) then it will also evaluate
|
|
290 a complete expression.
|
|
291 \\[comint-dynamic-complete] completes lisp symbols (or filenames, within strings),
|
|
292 or indents the line if there is nothing to complete.
|
|
293
|
|
294 During evaluations, the values of the variables `:', `::', and `:::'
|
|
295 are the results of the previous, second previous and third previous
|
|
296 evaluations respectively.
|
|
297
|
|
298 The current buffer may be changed, and its value is preserved between
|
|
299 successive evaluations. In this way, expressions may be evaluated in
|
|
300 a different buffer than the *ielm* buffer.
|
|
301
|
|
302 Expressions evaluated by IELM are not subject to debug-on-quit or
|
|
303 debug-on-error.
|
|
304
|
|
305 The behaviour of IELM may be customised with the following variables:
|
|
306 * To stop beeping on error, set `ielm-noisy' to nil
|
|
307 * If you don't like the prompt, you can change it by setting `ielm-prompt'.
|
|
308 * Set `ielm-dynamic-return' to nil for bindings like `lisp-interaction-mode'
|
|
309 * Entry to this mode runs `comint-mode-hook' and `ielm-mode-hook'
|
|
310 (in that order).
|
|
311
|
|
312 Customised bindings may be defined in `ielm-map', which currently contains:
|
|
313 \\{ielm-map}"
|
|
314 (interactive)
|
|
315 (comint-mode)
|
|
316 (setq comint-prompt-regexp (concat "^" (regexp-quote ielm-prompt)))
|
|
317 (make-local-variable 'paragraph-start)
|
|
318 (setq paragraph-start comint-prompt-regexp)
|
|
319 (setq comint-input-sender 'ielm-input-sender)
|
|
320 (setq comint-process-echoes nil)
|
|
321 (setq comint-dynamic-complete-functions
|
|
322 '(ielm-tab comint-replace-by-expanded-history ielm-complete-filename ielm-complete-symbol))
|
|
323
|
|
324 (setq major-mode 'inferior-emacs-lisp-mode)
|
|
325 (setq mode-name "IELM")
|
|
326 (use-local-map ielm-map)
|
|
327 (set-syntax-table emacs-lisp-mode-syntax-table)
|
|
328
|
|
329 (make-local-variable 'indent-line-function)
|
|
330 (make-local-variable 'ielm-working-buffer)
|
|
331 (setq ielm-working-buffer (current-buffer))
|
|
332 (setq indent-line-function 'ielm-indent-line)
|
|
333
|
|
334 ;;; Value holders
|
|
335 (setq : nil)
|
|
336 (make-local-variable ':)
|
|
337 (setq :: nil)
|
|
338 (make-local-variable '::)
|
|
339 (setq ::: nil)
|
|
340 (make-local-variable ':::)
|
|
341
|
|
342 ;; A dummy process to keep comint happy. It will never get any input
|
|
343 (if (comint-check-proc (current-buffer)) nil
|
|
344 (start-process "ielm" (current-buffer) "cat")
|
|
345 (process-kill-without-query (ielm-process))
|
|
346 (goto-char (point-max))
|
|
347 ;; Add a silly header
|
|
348 (insert ielm-header)
|
|
349 (ielm-set-pm (point-max))
|
|
350 (comint-output-filter (ielm-process) ielm-prompt)
|
|
351 (set-marker comint-last-input-start (ielm-pm))
|
|
352 (set-process-filter (get-buffer-process (current-buffer)) 'comint-output-filter))
|
|
353 (run-hooks 'ielm-mode-hook))
|
|
354
|
|
355 ;;; User command
|
|
356
|
|
357 (defun ielm nil
|
|
358 "Switch to or create the buffer *ielm* for evaluating emacs-lisp expressions"
|
|
359 (interactive)
|
|
360 (if (comint-check-proc "*ielm*") nil
|
|
361 (progn
|
|
362 (set-buffer (get-buffer-create "*ielm*"))
|
|
363 (inferior-emacs-lisp-mode)))
|
|
364 (switch-to-buffer "*ielm*"))
|
|
365
|
|
366 ;; ielm.el ends here
|