;;; ielm.el --- interaction mode for Emacs Lisp;; Copyright (C) 1994 Free Software Foundation, Inc.;; Author: David Smith <maa036@lancaster.ac.uk>;; Created: 25 Feb 1994;; Keywords: lisp;; This file is part of GNU Emacs.;; GNU Emacs is free software; you can redistribute it and/or modify;; it under the terms of the GNU General Public License as published by;; the Free Software Foundation; either version 2, or (at your option);; any later version.;; GNU Emacs is distributed in the hope that it will be useful,;; but WITHOUT ANY WARRANTY; without even the implied warranty of;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the;; GNU General Public License for more details.;; You should have received a copy of the GNU General Public License;; along with GNU Emacs; see the file COPYING. If not, write to the;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,;; Boston, MA 02111-1307, USA.;;; Commentary:;; Provides a nice interface to evaluating Emacs Lisp expressions.;; Input is handled by the comint package, and output is passed;; through the pretty-printer.;; To install: copy this file to a directory in your load-path, and;; add the following line to your .emacs file:;;;; (autoload 'ielm "ielm" "Start an inferior Emacs Lisp session" t);;;; For completion to work, the comint.el from FSF Emacs 19.23 is;; required. If you do not have it, or if you are running Lemacs,;; also add the following code to your .emacs:;;;; (setq ielm-mode-hook;; '(lambda nil;; (define-key ielm-map "\t";; '(lambda nil (interactive) (or (ielm-tab) ;; (lisp-complete-symbol))))));; To start: M-x ielm. Type C-h m in the *ielm* buffer for more info.;; The latest version is available by WWW from ;; http://mathssun5.lancs.ac.uk:2080/~maa036/elisp/dir.html;; or by anonymous FTP from;; /anonymous@wingra.stat.wisc.edu:pub/src/emacs-lisp/ielm.el.gz;; or from the author: David M. Smith <maa036@lancaster.ac.uk>;;; Code:(require'comint)(require'pp);;; User variables(defvarielm-noisyt"*If non-nil, IELM will beep on error.")(defvarielm-prompt"ELISP> ""Prompt used in IELM.")(defvarielm-dynamic-returnt"*Controls whether \\<ielm-map>\\[ielm-return] has intelligent behaviour in IELM.If non-nil, \\[ielm-return] evaluates input for complete sexps, or inserts a newlineand indents for incomplete sexps. If nil, always inserts newlines.")(defvarielm-dynamic-multiline-inputst"*Force multiline inputs to start from column zero?If non-nil, after entering the first line of an incomplete sexp, a newlinewill be inserted after the prompt, moving the input to the next line.This gives more frame width for large indented sexps, and allows functionssuch as `edebug-defun' to work with such inputs.")(defvarielm-mode-hooknil"*Hooks to be run when IELM (`inferior-emacs-lisp-mode') is started.");;; System variables(defvarielm-working-buffernil"Buffer in which IELM sexps will be evaluated.This variable is buffer-local.")(defvarielm-header(concat"*** Welcome to IELM version "(substring"$Revision: 1.7 $"11-2)" *** Type (describe-mode) for help.\n""IELM has ABSOLUTELY NO WARRANTY; type (describe-no-warranty) for details.\n")"Message to display when IELM is started.")(defvarielm-mapnil)(ifielm-mapnil(if(string-match"Lucid"emacs-version);; Lemacs(progn(setqielm-map(make-sparse-keymap))(set-keymap-parentielm-mapcomint-mode-map));; FSF(setqielm-map(cons'keymapcomint-mode-map)))(define-keyielm-map"\t"'comint-dynamic-complete)(define-keyielm-map"\C-m"'ielm-return)(define-keyielm-map"\C-j"'ielm-send-input)(define-keyielm-map"\e\C-x"'eval-defun); for consistency with(define-keyielm-map"\e\t"'lisp-complete-symbol); lisp-interaction-mode;; These bindings are from shared-lisp-mode-map -- can you inherit;; from more than one keymap??(define-keyielm-map"\e\C-q"'indent-sexp)(define-keyielm-map"\177"'backward-delete-char-untabify);; Some convenience bindings for setting the working buffer(define-keyielm-map"\C-c\C-b"'ielm-change-working-buffer)(define-keyielm-map"\C-c\C-f"'ielm-display-working-buffer)(define-keyielm-map"\C-c\C-v"'ielm-print-working-buffer))(defvarielm-font-lock-keywords(list(cons(concat"^"(regexp-quoteielm-prompt))'font-lock-keyword-face)'("\\(^\\*\\*\\*[^*]+\\*\\*\\*\\)\\(.*$\\)"(1font-lock-comment-face)(2font-lock-reference-face)))"Additional expressions to highlight in ielm buffers.");;; Completion stuff(defunielm-tabnil"Possibly indent the current line as lisp code."(interactive)(if(or(eq(preceding-char)?\n)(eq(char-syntax(preceding-char))?))(progn(ielm-indent-line)t)))(defunielm-complete-symbolnil"Complete the lisp symbol before point.";; A wrapper for lisp-complete symbol that returns non-nil if;; completion has occurred(let*((btick(buffer-modified-tick))(cbuffer(get-buffer"*Completions*"))(ctick(andcbuffer(buffer-modified-tickcbuffer))))(lisp-complete-symbol);; completion has occurred if:(or;; the buffer has been modified(not(=btick(buffer-modified-tick)));; a completions buffer has been modified or created(ifcbuffer(not(=ctick(buffer-modified-tickcbuffer)))(get-buffer"*Completions*")))))(defunielm-complete-filenamenil"Dynamically complete filename before point, if in a string."(if(nth3(parse-partial-sexpcomint-last-input-start(point)))(comint-dynamic-complete-filename)))(defunielm-indent-linenil"Indent the current line as Lisp code if it is not a prompt line."(if(save-excursion(beginning-of-line)(looking-atcomint-prompt-regexp))nil(lisp-indent-line)));;; Working buffer manipulation(defunielm-print-working-buffernil"Print the current IELM working buffer's name in the echo area."(interactive)(message"The current working buffer is: %s"(buffer-nameielm-working-buffer)))(defunielm-display-working-buffernil"Display the current IELM working buffer.Don't forget that selecting that buffer will change its value of `point'to its value of `window-point'!"(interactive)(display-bufferielm-working-buffer)(ielm-print-working-buffer))(defunielm-change-working-buffer(buf)"Change the current IELM working buffer to BUF.This is the buffer in which all sexps entered at the IELM prompt areevaluated. You can achieve the same effect with a call to`set-buffer' at the IELM prompt."(interactive"bSet working buffer to: ")(setqielm-working-buffer(or(get-bufferbuf)(error"No such buffer")))(ielm-print-working-buffer));;; Other bindings(defunielm-returnnil"Newline and indent, or evaluate the sexp before the prompt.Complete sexps are evaluated; for incomplete sexps inserts a newlineand indents. If however `ielm-dynamic-return' is nil, this alwayssimply inserts a newline."(interactive)(ifielm-dynamic-return(let((state(save-excursion(end-of-line)(parse-partial-sexp(ielm-pm)(point)))))(if(and(<(carstate)1)(not(nth3state)))(ielm-send-input)(if(andielm-dynamic-multiline-inputs(save-excursion(beginning-of-line)(looking-atcomint-prompt-regexp)))(save-excursion(goto-char(ielm-pm))(newline1)))(newline-and-indent)))(newline)))(defunielm-input-sender(procinput);; Just sets the variable ielm-input, which is in the scope of ;; `ielm-send-input's call.(setqielm-inputinput))(defunielm-send-inputnil"Evaluate the Emacs Lisp expression after the prompt."(interactive)(let((buf(current-buffer))ielm-input); set by ielm-input-sender(comint-send-input); update history, markers etc.(ielm-eval-inputielm-input)));;; Utility functions(defunielm-is-whitespace(string)"Return non-nil if STRING is all whitespace."(or(string=string"")(string-match"\\`[ \t\n]+\\'"string)))(defunielm-format-errors(errlist)(let((result""))(whileerrlist(setqresult(concatresult(prin1-to-string(carerrlist))", "))(setqerrlist(cdrerrlist)))(substringresult0-2)))(defunielm-format-error(err);; Return a string form of the error ERR.(format"%s%s"(or(get(carerr)'error-message)"Peculiar error")(if(cdrerr)(format": %s"(ielm-format-errors(cdrerr)))"")));;; Evaluation(defunielm-eval-input(ielm-string)"Evaluate the Lisp expression IELM-STRING, and pretty-print the result.";; This is the function that actually `sends' the input to the;; `inferior Lisp process'. All comint-send-input does is works out;; what that input is. What this function does is evaluates that;; input and produces `output' which gets inserted into the buffer,;; along with a new prompt. A better way of doing this might have;; been to actually send the output to the `cat' process, and write;; this as in output filter that converted sexps in the output;; stream to their evaluated value. But that would have involved;; more process coordination than I was happy to deal with.;;;; NOTE: all temporary variables in this function will be in scope;; during the eval, and so need to have non-clashing names.(let(ielm-form; form to evaluateielm-pos; End posn of parse in stringielm-result; Result, or error messageielm-error-type; string, nil if no error(ielm-output""); result to display(ielm-wbufielm-working-buffer); current buffer after evaluation(ielm-pmark(ielm-pm)))(if(not(ielm-is-whitespaceielm-string))(progn(condition-caseerr(let(rout)(setqrout(read-from-stringielm-string))(setqielm-form(carrout))(setqielm-pos(cdrrout)))(error(setqielm-result(ielm-format-errorerr))(setqielm-error-type"Read error")))(ifielm-error-typenil;; Make sure working buffer has not been killed(if(not(buffer-nameielm-working-buffer))(setqielm-result"Working buffer has been killed"ielm-error-type"IELM Error"ielm-wbuf(current-buffer))(if(ielm-is-whitespace(substringielm-stringielm-pos));; need this awful let convolution to work around;; an Emacs bug involving local vbls and let binding(let((:save:)(::save::)(:::save:::))(save-excursion(set-bufferielm-working-buffer)(condition-caseerr(let((::save)(::::save)(::::::save)(ielm-obuf(current-buffer)))(setqielm-result(evalielm-form))(setqielm-wbuf(current-buffer));; The eval may have changed current-buffer;;; need to set it back here to avoid a bug;; in let. Don't want to use save-excursion;; because we want to allow changes in point.(set-bufferielm-obuf))(error(setqielm-result(ielm-format-errorerr))(setqielm-error-type"Eval error"))(quit(setqielm-result"Quit during evaluation")(setqielm-error-type"Eval error")))))(setqielm-error-type"IELM error")(setqielm-result"More than one sexp in input"))));; If the eval changed the current buffer, mention it here(if(eqielm-wbufielm-working-buffer)nil(message"current buffer is now: %s"ielm-wbuf)(setqielm-working-bufferielm-wbuf))(goto-charielm-pmark)(if(notielm-error-type)(condition-caseerr;; Self-referential objects cause loops in the printer, so;; trap quits here. May as well do errors, too(setqielm-output(concatielm-output(pp-to-stringielm-result)))(error(setqielm-error-type"IELM Error")(setqielm-result"Error during pretty-printing (bug in pp)"))(quit(setqielm-error-type"IELM Error")(setqielm-result"Quit during pretty-printing"))))(ifielm-error-type(progn(ifielm-noisy(ding))(setqielm-output(concatielm-output"*** "ielm-error-type" *** "))(setqielm-output(concatielm-outputielm-result)));; There was no error, so shift the ::: values(setq:::::)(setq:::)(setq:ielm-result))(setqielm-output(concatielm-output"\n"))))(setqielm-output(concatielm-outputielm-prompt))(comint-output-filter(ielm-process)ielm-output)));;; Process and marker utilities(defunielm-processnil;; Return the current buffer's process.(get-buffer-process(current-buffer)))(defunielm-pmnil;; Return the process mark of the current buffer.(process-mark(get-buffer-process(current-buffer))))(defunielm-set-pm(pos);; Set the process mark in the current buffer to POS.(set-marker(process-mark(get-buffer-process(current-buffer)))pos));;; Major mode(defuninferior-emacs-lisp-modenil"Major mode for interactively evaluating Emacs Lisp expressions.Uses the interface provided by `comint-mode' (which see).* \\<ielm-map>\\[ielm-send-input] evaluates the sexp following the prompt. There must be at most one top-level sexp per prompt.* \\[ielm-return] inserts a newline and indents, or evaluates a complete expression (but see variable `ielm-dynamic-return'). Inputs longer than one line are moved to the line following the prompt (but see variable `ielm-dynamic-multiline-inputs').* \\[comint-dynamic-complete] completes Lisp symbols (or filenames, within strings), or indents the line if there is nothing to complete.During evaluations, the values of the variables `:', `::', and `:::'are the results of the previous, second previous and third previousevaluations respectively.The current working buffer may be changed (with a call to`set-buffer', or with \\[ielm-change-working-buffer]), and its valueis preserved between successive evaluations. In this way, expressionsmay be evaluated in a different buffer than the *ielm* buffer.Display the name of the working buffer with \\[ielm-print-working-buffer],or the buffer itself with \\[ielm-display-working-buffer].Expressions evaluated by IELM are not subject to `debug-on-quit' or`debug-on-error'.The behaviour of IELM may be customised with the following variables:* To stop beeping on error, set `ielm-noisy' to nil* If you don't like the prompt, you can change it by setting `ielm-prompt'.* Set `ielm-dynamic-return' to nil for bindings like `lisp-interaction-mode'* Entry to this mode runs `comint-mode-hook' and `ielm-mode-hook' (in that order).Customised bindings may be defined in `ielm-map', which currently contains:\\{ielm-map}"(interactive)(comint-mode)(setqcomint-prompt-regexp(concat"^"(regexp-quoteielm-prompt)))(make-local-variable'paragraph-start)(setqparagraph-startcomint-prompt-regexp)(setqcomint-input-sender'ielm-input-sender)(setqcomint-process-echoesnil)(setqcomint-dynamic-complete-functions'(ielm-tabcomint-replace-by-expanded-historyielm-complete-filenameielm-complete-symbol))(setqcomint-get-old-input'ielm-get-old-input)(setqmajor-mode'inferior-emacs-lisp-mode)(setqmode-name"IELM")(use-local-mapielm-map)(set-syntax-tableemacs-lisp-mode-syntax-table)(make-local-variable'indent-line-function)(make-local-variable'ielm-working-buffer)(setqielm-working-buffer(current-buffer))(setqindent-line-function'ielm-indent-line)(make-local-variable'fill-paragraph-function)(setqfill-paragraph-function'lisp-fill-paragraph);; Value holders(setq:nil)(make-local-variable':)(setq::nil)(make-local-variable'::)(setq:::nil)(make-local-variable':::);; font-lock support(make-local-variable'font-lock-defaults)(setqfont-lock-defaults'(ielm-font-lock-keywordsnilnil((?:."w")(?-."w")(?*."w"))));; A dummy process to keep comint happy. It will never get any input(if(comint-check-proc(current-buffer))nil(start-process"ielm"(current-buffer)"cat")(process-kill-without-query(ielm-process))(goto-char(point-max));; Add a silly header(insertielm-header)(ielm-set-pm(point-max))(comint-output-filter(ielm-process)ielm-prompt)(set-markercomint-last-input-start(ielm-pm))(set-process-filter(get-buffer-process(current-buffer))'comint-output-filter))(run-hooks'ielm-mode-hook))(defunielm-get-old-inputnil;; Return the previous input surrounding point(save-excursion(beginning-of-line)(if(looking-atcomint-prompt-regexp)nil(re-search-backwardcomint-prompt-regexp))(comint-skip-prompt)(buffer-substring(point)(progn(forward-sexp1)(point)))));;; User command;;;###autoload (add-hook 'same-window-buffer-names "*ielm*");;;###autoload(defunielmnil"Interactively evaluate Emacs Lisp expressions.Switches to the buffer `*ielm*', or creates it if it does not exist."(interactive)(if(comint-check-proc"*ielm*")nil(save-excursion(set-buffer(get-buffer-create"*ielm*"))(inferior-emacs-lisp-mode)))(pop-to-buffer"*ielm*"));;; ielm.el ends here