;;; slime.el --- Superior Lisp Interaction Mode for Emacs ;; ;;;; License ;; Copyright (C) 2003 Eric Marsden, Luke Gorrie, Helmut Eller ;; Copyright (C) 2004,2005,2006 Luke Gorrie, Helmut Eller ;; ;; This program 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 of ;; the License, or (at your option) any later version. ;; ;; This program 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 this program; if not, write to the Free ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, ;; MA 02111-1307, USA. ;;;; Commentary ;; ;; This file contains extensions for programming in Common Lisp. The ;; main features are: ;; ;; A socket-based communication/RPC interface between Emacs and ;; Lisp. ;; ;; The `slime-mode' minor-mode complementing `lisp-mode'. This new ;; mode includes many commands for interacting with the Common Lisp ;; process. ;; ;; Common Lisp REPL (Read-Eval-Print Loop) written in Emacs Lisp, ;; similar to `ielm'. ;; ;; Common Lisp debugger written in Emacs Lisp. The debugger pops up ;; an Emacs buffer similar to the Emacs/Elisp debugger. ;; ;; Trapping compiler messages and creating annotations in the source ;; file on the appropriate forms. ;; ;; SLIME is compatible with GNU Emacs 20 and 21 and XEmacs 21. In ;; order to run SLIME requires a supporting Lisp server called ;; Swank. Swank is distributed with slime.el and will automatically be ;; started in a normal installation. ;;;; Dependencies and setup (eval-and-compile (require 'cl) (unless (fboundp 'define-minor-mode) (require 'easy-mmode) (defalias 'define-minor-mode 'easy-mmode-define-minor-mode)) (when (locate-library "hyperspec") (require 'hyperspec))) (require 'comint) (require 'timer) (require 'pp) (require 'hideshow) (require 'font-lock) (when (featurep 'xemacs) (require 'overlay)) (require 'easymenu) (eval-when (compile) (require 'arc-mode) (require 'apropos) (require 'outline) (require 'etags)) (eval-and-compile (defvar slime-path (let ((path (or (locate-library "slime") load-file-name))) (and path (file-name-directory path))) "Directory containing the Slime package. This is used to load the supporting Common Lisp library, Swank. The default value is automatically computed from the location of the Emacs Lisp package.")) (defvar slime-lisp-modes '(lisp-mode)) (defun slime-setup (&optional contribs) "Setup Emacs so that lisp-mode buffers always use SLIME. CONTRIBS is a list of contrib packages to load." (when (member 'lisp-mode slime-lisp-modes) (add-hook 'lisp-mode-hook 'slime-lisp-mode-hook)) (when contribs (add-to-list 'load-path (expand-file-name "contrib" slime-path)) (dolist (c contribs) (require c) (let ((init (intern (format "%s-init" c)))) (when (fboundp init) (funcall init)))))) (defun slime-lisp-mode-hook () (slime-mode 1) (set (make-local-variable 'lisp-indent-function) 'common-lisp-indent-function)) (eval-and-compile (defun slime-changelog-date () "Return the datestring of the latest entry in the ChangeLog file. Return nil if the ChangeLog file cannot be found." (let ((changelog (concat slime-path "ChangeLog"))) (if (file-exists-p changelog) (with-temp-buffer (insert-file-contents-literally changelog nil 0 100) (goto-char (point-min)) (symbol-name (read (current-buffer)))) nil)))) (defvar slime-protocol-version nil) (setq slime-protocol-version (eval-when-compile (slime-changelog-date))) ;;;; Customize groups ;; ;;;;; slime (defgroup slime nil "Interaction with the Superior Lisp Environment." :prefix "slime-" :group 'applications) ;;;;; slime-ui (defgroup slime-ui nil "Interaction with the Superior Lisp Environment." :prefix "slime-" :group 'slime) (defcustom slime-truncate-lines t "Set `truncate-lines' in popup buffers. This applies to buffers that present lines as rows of data, such as debugger backtraces and apropos listings." :type 'boolean :group 'slime-ui) (defcustom slime-extended-modeline t "If non-nil, display various information in the mode line of a Lisp buffer. The information includes the current connection of that buffer, the buffer package, and some state indication." :type 'boolean :group 'slime-ui) (defcustom slime-kill-without-query-p nil "If non-nil, kill SLIME processes without query when quitting Emacs. This applies to the *inferior-lisp* buffer and the network connections." :type 'boolean :group 'slime-ui) ;;;;; slime-lisp (defgroup slime-lisp nil "Lisp server configuration." :prefix "slime-" :group 'slime) (defcustom slime-backend "swank-loader.lisp" "The name of the Lisp file that loads the Swank server. This name is interpreted relative to the directory containing slime.el, but could also be set to an absolute filename." :type 'string :group 'slime-lisp) (defcustom slime-connected-hook nil "List of functions to call when SLIME connects to Lisp." :type 'hook :group 'slime-lisp) (defcustom slime-enable-evaluate-in-emacs nil "*If non-nil, the inferior Lisp can evaluate arbitrary forms in Emacs. The default is nil, as this feature can be a security risk." :type '(boolean) :group 'slime-lisp) (defcustom slime-lisp-host "127.0.0.1" "The default hostname (or IP address) to connect to." :type 'string :group 'slime-lisp) (defcustom slime-port 4005 "Port to use as the default for `slime-connect'." :type 'integer :group 'slime-lisp) ;;;;; slime-mode (defgroup slime-mode nil "Settings for slime-mode Lisp source buffers." :prefix "slime-" :group 'slime) (defcustom slime-find-definitions-function 'slime-find-definitions-rpc "Function to find definitions for a name. The function is called with the definition name, a string, as its argument." :type 'function :group 'slime-mode :options '(slime-find-definitions-rpc slime-etags-definitions (lambda (name) (append (slime-find-definitions-rpc name) (slime-etags-definitions name))) (lambda (name) (or (slime-find-definitions-rpc name) (and tags-table-list (slime-etags-definitions name)))))) (defcustom slime-complete-symbol-function 'slime-simple-complete-symbol "*Function to perform symbol completion." :group 'slime-mode :type '(choice (const :tag "Simple" slime-simple-complete-symbol) (const :tag "Compound" slime-complete-symbol*) (const :tag "Fuzzy" slime-fuzzy-complete-symbol))) (defcustom slime-when-complete-filename-expand nil "Use comint-replace-by-expanded-filename instead of comint-dynamic-complete-as-filename to complete file names" :group 'slime-mode :type 'boolean) (defcustom slime-space-information-p t "Have the SPC key offer arglist information." :type 'boolean :group 'slime-mode) ;;;;; slime-mode-faces (defgroup slime-mode-faces nil "Faces in slime-mode source code buffers." :prefix "slime-" :group 'slime-mode) (defun slime-underline-color (color) "Return a legal value for the :underline face attribute based on COLOR." ;; In XEmacs the :underline attribute can only be a boolean. ;; In GNU it can be the name of a colour. (if (featurep 'xemacs) (if color t nil) color)) (defface slime-error-face `((((class color) (background light)) (:underline ,(slime-underline-color "red"))) (((class color) (background dark)) (:underline ,(slime-underline-color "red"))) (t (:underline t))) "Face for errors from the compiler." :group 'slime-mode-faces) (defface slime-warning-face `((((class color) (background light)) (:underline ,(slime-underline-color "orange"))) (((class color) (background dark)) (:underline ,(slime-underline-color "coral"))) (t (:underline t))) "Face for warnings from the compiler." :group 'slime-mode-faces) (defface slime-style-warning-face `((((class color) (background light)) (:underline ,(slime-underline-color "brown"))) (((class color) (background dark)) (:underline ,(slime-underline-color "gold"))) (t (:underline t))) "Face for style-warnings from the compiler." :group 'slime-mode-faces) (defface slime-note-face `((((class color) (background light)) (:underline ,(slime-underline-color "brown4"))) (((class color) (background dark)) (:underline ,(slime-underline-color "light goldenrod"))) (t (:underline t))) "Face for notes from the compiler." :group 'slime-mode-faces) (defun slime-face-inheritance-possible-p () "Return true if the :inherit face attribute is supported." (assq :inherit custom-face-attributes)) (defface slime-highlight-face (if (slime-face-inheritance-possible-p) '((t (:inherit highlight :underline nil))) '((((class color) (background light)) (:background "darkseagreen2")) (((class color) (background dark)) (:background "darkolivegreen")) (t (:inverse-video t)))) "Face for compiler notes while selected." :group 'slime-mode-faces) ;;;;; sldb (defgroup slime-debugger nil "Backtrace options and fontification." :prefix "sldb-" :group 'slime) (defmacro define-sldb-faces (&rest faces) "Define the set of SLDB faces. Each face specifiation is (NAME DESCRIPTION &optional PROPERTIES). NAME is a symbol; the face will be called sldb-NAME-face. DESCRIPTION is a one-liner for the customization buffer. PROPERTIES specifies any default face properties." `(progn ,@(loop for face in faces collect `(define-sldb-face ,@face)))) (defmacro define-sldb-face (name description &optional default) (let ((facename (intern (format "sldb-%s-face" (symbol-name name))))) `(defface ,facename (list (list t ,default)) ,(format "Face for %s." description) :group 'slime-debugger))) (define-sldb-faces (topline "the top line describing the error") (condition "the condition class") (section "the labels of major sections in the debugger buffer") (frame-label "backtrace frame numbers") (restart-type "restart names." (if (slime-face-inheritance-possible-p) '(:inherit font-lock-keyword-face))) (restart "restart descriptions") (restart-number "restart numbers (correspond to keystrokes to invoke)" '(:bold t)) (frame-line "function names and arguments in the backtrace") (restartable-frame-line "frames which are surely restartable") (non-restartable-frame-line "frames which are surely not restartable") (detailed-frame-line "function names and arguments in a detailed (expanded) frame") (local-name "local variable names") (local-value "local variable values") (catch-tag "catch tags")) ;;;;; slime-repl (defgroup slime-repl nil "The Read-Eval-Print Loop (*slime-repl* buffer)." :prefix "slime-repl-" :group 'slime) (defcustom slime-repl-shortcut-dispatch-char ?\, "Character used to distinguish repl commands from lisp forms." :type '(character) :group 'slime-repl) (defcustom slime-repl-only-save-lisp-buffers t "When T we only attempt to save lisp-mode file buffers. When NIL slime will attempt to save all buffers (as per save-some-buffers). This applies to all ASDF related repl shortcuts." :type '(boolean) :group 'slime-repl) (defface slime-repl-prompt-face (if (slime-face-inheritance-possible-p) '((t (:inherit font-lock-keyword-face))) '((((class color) (background light)) (:foreground "Purple")) (((class color) (background dark)) (:foreground "Cyan")) (t (:weight bold)))) "Face for the prompt in the SLIME REPL." :group 'slime-repl) (defface slime-repl-output-face (if (slime-face-inheritance-possible-p) '((t (:inherit font-lock-string-face))) '((((class color) (background light)) (:foreground "RosyBrown")) (((class color) (background dark)) (:foreground "LightSalmon")) (t (:slant italic)))) "Face for Lisp output in the SLIME REPL." :group 'slime-repl) (defface slime-repl-input-face '((t (:bold t))) "Face for previous input in the SLIME REPL." :group 'slime-repl) (defface slime-repl-result-face '((t ())) "Face for the result of an evaluation in the SLIME REPL." :group 'slime-repl) (defcustom slime-repl-history-file "~/.slime-history.eld" "File to save the persistent REPL history to." :type 'string :group 'slime-repl) (defcustom slime-repl-history-size 200 "*Maximum number of lines for persistent REPL history." :type 'integer :group 'slime-repl) ;;;; Minor modes ;;;;; slime-mode (define-minor-mode slime-mode "\\\ SLIME: The Superior Lisp Interaction Mode for Emacs (minor-mode). Commands to compile the current buffer's source file and visually highlight any resulting compiler notes and warnings: \\[slime-compile-and-load-file] - Compile and load the current buffer's file. \\[slime-compile-file] - Compile (but not load) the current buffer's file. \\[slime-compile-defun] - Compile the top-level form at point. Commands for visiting compiler notes: \\[slime-next-note] - Goto the next form with a compiler note. \\[slime-previous-note] - Goto the previous form with a compiler note. \\[slime-remove-notes] - Remove compiler-note annotations in buffer. Finding definitions: \\[slime-edit-definition] - Edit the definition of the function called at point. \\[slime-pop-find-definition-stack] - Pop the definition stack to go back from a definition. Documentation commands: \\[slime-describe-symbol] - Describe symbol. \\[slime-apropos] - Apropos search. \\[slime-disassemble-symbol] - Disassemble a function. Evaluation commands: \\[slime-eval-defun] - Evaluate top-level from containing point. \\[slime-eval-last-expression] - Evaluate sexp before point. \\[slime-pprint-eval-last-expression] - Evaluate sexp before point, pretty-print result. Full set of commands: \\{slime-mode-map}" nil nil ;; Fake binding to coax `define-minor-mode' to create the keymap '((" " 'undefined))) (make-variable-buffer-local (defvar slime-modeline-string nil "The string that should be displayed in the modeline if `slime-extended-modeline' is true, and which indicates the current connection, package and state of a Lisp buffer. The string is periodically updated by an idle timer.")) ;;; These are used to keep track of old values, so we can determine ;;; whether the mode line has changed, and should be updated. (make-variable-buffer-local (defvar slime-modeline-package nil)) (make-variable-buffer-local (defvar slime-modeline-connection-name nil)) (make-variable-buffer-local (defvar slime-modeline-connection-state nil)) (defun slime-compute-modeline-package () (when (memq major-mode slime-lisp-modes) ;; WHEN-LET is defined later. (let ((package (slime-current-package))) (when package (slime-pretty-package-name package))))) (defun slime-pretty-package-name (name) "Return a pretty version of a package name NAME." (let ((name (cond ((string-match "^#?:\\(.*\\)$" name) (match-string 1 name)) ((string-match "^\"\\(.*\\)\"$" name) (match-string 1 name)) (t name)))) (format "%s" (read name)))) (defun slime-compute-modeline-connection () (let ((conn (slime-current-connection))) (if (or (null conn) (slime-stale-connection-p conn)) nil (slime-connection-name conn)))) (defun slime-compute-modeline-connection-state () (let* ((conn (slime-current-connection)) (new-state (slime-compute-connection-state conn))) (if (eq new-state :connected) (let ((rex-cs (length (slime-rex-continuations))) (sldb-cs (length (sldb-debugged-continuations conn))) ;; There can be SLDB buffers which have no continuations ;; attached to it, e.g. the one resulting from ;; `slime-interrupt'. (sldbs (length (sldb-buffers conn)))) (cond ((and (= sldbs 0) (zerop rex-cs)) nil) ((= sldbs 0) (format "%s" rex-cs)) (t (format "%s/%s" (if (= rex-cs 0) 0 (- rex-cs sldb-cs)) sldbs)))) (slime-connection-state-as-string new-state)))) (defun slime-compute-modeline-string (conn state pkg) (concat (when (or conn pkg) "[") (when pkg (format "%s" pkg)) (when (and (or conn state) pkg) ", ") (when conn (format "%s" conn)) (when state (format "{%s}" state)) (when (or conn pkg) "]"))) (defun slime-update-modeline-string () (let ((old-pkg slime-modeline-package) (old-conn slime-modeline-connection-name) (old-state slime-modeline-connection-state) (new-pkg (slime-compute-modeline-package)) (new-conn (slime-compute-modeline-connection)) (new-state (slime-compute-modeline-connection-state))) (when (or (not (equal old-pkg new-pkg)) (not (equal old-conn new-conn)) (not (equal old-state new-state))) (setq slime-modeline-package new-pkg) (setq slime-modeline-connection-name new-conn) (setq slime-modeline-connection-state new-state) (setq slime-modeline-string (slime-compute-modeline-string new-conn new-state new-pkg))))) (defun slime-shall-we-update-modeline-p () (and slime-extended-modeline (or slime-mode slime-popup-buffer-mode))) (defun slime-update-all-modelines () (dolist (window (window-list)) (with-current-buffer (window-buffer window) (when (slime-shall-we-update-modeline-p) (slime-update-modeline-string) (force-mode-line-update))))) (defvar slime-modeline-update-timer nil) (defun slime-restart-or-init-modeline-update-timer () (when slime-modeline-update-timer (cancel-timer slime-modeline-update-timer)) (setq slime-modeline-update-timer (run-with-idle-timer 0.5 0.5 'slime-update-all-modelines))) (slime-restart-or-init-modeline-update-timer) (defun slime-recompute-modelines (delay) (cond (delay ;; Minimize flashing of modeline due to short lived ;; requests such as those of autodoc. (slime-restart-or-init-modeline-update-timer)) (t ;; Must do this ourselves since emacs may have ;; been idling long enough that ;; SLIME-MODELINE-UPDATE-TIMER is not going to ;; trigger by itself. (slime-update-all-modelines)))) ;; Setup the mode-line to say when we're in slime-mode, which ;; connection is active, and which CL package we think the current ;; buffer belongs to. (add-to-list 'minor-mode-alist '(slime-mode (" Slime" slime-modeline-string))) ;;;;; Key bindings ;; See `slime-define-key' below for keyword meanings. (defvar slime-keys '(;; Compiler notes ("\M-p" slime-previous-note) ("\M-n" slime-next-note) ("\M-c" slime-remove-notes :prefixed t) ("\C-k" slime-compile-and-load-file :prefixed t) ("\M-k" slime-compile-file :prefixed t) ("\C-c" slime-compile-defun :prefixed t) ("\C-l" slime-load-file :prefixed t) ;; Editing/navigating ("\M-\C-i" slime-complete-symbol :inferior t) ("\C-i" slime-complete-symbol :prefixed t :inferior t) ("\M-." slime-edit-definition :inferior t :sldb t) ("\C-x4." slime-edit-definition-other-window :inferior t :sldb t) ("\C-x5." slime-edit-definition-other-frame :inferior t :sldb t) ("\M-," slime-pop-find-definition-stack :inferior t :sldb t) ;; Evaluating ("\C-x\C-e" slime-eval-last-expression :inferior t) ("\C-x\M-e" slime-eval-last-expression-display-output :inferior t) ("\C-p" slime-pprint-eval-last-expression :prefixed t :inferior t) ("\C-r" slime-eval-region :prefixed t :inferior t) ("\C-\M-x" slime-eval-defun) (":" slime-interactive-eval :prefixed t :sldb t) ("\C-e" slime-interactive-eval :prefixed t :sldb t :inferior t) ("\C-y" slime-call-defun :prefixed t) ("E" slime-edit-value :prefixed t :sldb t :inferior t) ("\C-z" slime-switch-to-output-buffer :prefixed t :sldb t) ("\C-b" slime-interrupt :prefixed t :inferior t :sldb t) ("\M-g" slime-quit :prefixed t :inferior t :sldb t) ;; Documentation (" " slime-space :inferior t) ("\C-f" slime-describe-function :prefixed t :inferior t :sldb t) ("\M-d" slime-disassemble-symbol :prefixed t :inferior t :sldb t) ("\C-t" slime-toggle-trace-fdefinition :prefixed t :sldb t) ("\C-u" slime-undefine-function :prefixed t) ("\C-m" slime-macroexpand-1 :prefixed t :inferior t) ("\M-m" slime-macroexpand-all :prefixed t :inferior t) ("\M-0" slime-restore-window-configuration :prefixed t :inferior t) ([(control meta ?\.)] slime-next-location :inferior t) ("~" slime-sync-package-and-default-directory :prefixed t :inferior t) ("\M-p" slime-repl-set-package :prefixed t :inferior t) ;; Cross reference ("<" slime-list-callers :prefixed t :inferior t :sldb t) (">" slime-list-callees :prefixed t :inferior t :sldb t) ;; "Other" ("\I" slime-inspect :prefixed t :inferior t :sldb t) ("\C-]" slime-close-all-parens-in-sexp :prefixed t :inferior t :sldb t) ("\C-xt" slime-list-threads :prefixed t :inferior t :sldb t) ("\C-xc" slime-list-connections :prefixed t :inferior t :sldb t) ;; ;; Shadow unwanted bindings from inf-lisp ;; ("\C-a" slime-nop :prefixed t :inferior t :sldb t) ;; ("\C-v" slime-nop :prefixed t :inferior t :sldb t) )) (defun slime-nop () "The null command. Used to shadow currently-unused keybindings." (interactive) (call-interactively 'undefined)) (defvar slime-doc-map (make-sparse-keymap) "Keymap for documentation commands. Bound to a prefix key.") (defvar slime-doc-bindings '((?a slime-apropos) (?z slime-apropos-all) (?p slime-apropos-package) (?d slime-describe-symbol) (?f slime-describe-function) (?h slime-hyperspec-lookup) (?~ common-lisp-hyperspec-format))) (defvar slime-who-map (make-sparse-keymap) "Keymap for who-xref commands. Bound to a prefix key.") (defvar slime-who-bindings '((?c slime-who-calls) (?w slime-calls-who) (?r slime-who-references) (?b slime-who-binds) (?s slime-who-sets) (?m slime-who-macroexpands) (?a slime-who-specializes))) ;; Maybe a good idea, maybe not.. (defvar slime-prefix-key "\C-c" "The prefix key to use in SLIME keybinding sequences.") (defvar slime-prefix-map (make-sparse-keymap) "Keymap for commands prefixed with `slime-prefix-key'.") (defun* slime-define-key (key command &key prefixed) "Define a keybinding of KEY for COMMAND. If PREFIXED is non-nil, `slime-prefix-key' is prepended to KEY." (cond (prefixed (define-key slime-prefix-map key command)) (t (define-key slime-mode-map key command)))) (defun slime-init-keymaps () "(Re)initialize the keymaps for `slime-mode'." (interactive) (setq slime-prefix-map (make-sparse-keymap)) (define-key slime-mode-map slime-prefix-key slime-prefix-map) (loop for (key command . keys) in slime-keys do (apply #'slime-define-key key command :allow-other-keys t keys)) ;; Documentation (setq slime-doc-map (make-sparse-keymap)) (slime-define-both-key-bindings slime-doc-map slime-doc-bindings) ;; C-c C-d is the prefix for the doc map. (slime-define-key "\C-d" slime-doc-map :prefixed t) ;; Who-xref (setq slime-who-map (make-sparse-keymap)) (slime-define-both-key-bindings slime-who-map slime-who-bindings) ;; C-c C-w is the prefix for the who-xref map. (slime-define-key "\C-w" slime-who-map :prefixed t)) (defun slime-define-both-key-bindings (keymap bindings) (loop for (char command) in bindings do ;; We bind both unmodified and with control. (define-key keymap `[,char] command) (unless (equal char ?h) ; But don't bind C-h (define-key keymap `[(control ,char)] command)))) (slime-init-keymaps) ;;;; Setup initial `slime-mode' hooks (make-variable-buffer-local (defvar slime-pre-command-actions nil "List of functions to execute before the next Emacs command. This list of flushed between commands.")) (defun slime-pre-command-hook () "Execute all functions in `slime-pre-command-actions', then NIL it." (dolist (undo-fn slime-pre-command-actions) (ignore-errors (funcall undo-fn))) (setq slime-pre-command-actions nil)) (defun slime-post-command-hook () (when (null pre-command-hook) ; sometimes this is lost (add-hook 'pre-command-hook 'slime-pre-command-hook))) (defun slime-setup-command-hooks () "Setup a buffer-local `pre-command-hook' to call `slime-pre-command-hook'." (slime-add-local-hook 'pre-command-hook 'slime-pre-command-hook) (slime-add-local-hook 'post-command-hook 'slime-post-command-hook)) ;;;; Framework'ey bits ;;; ;;; This section contains some standard SLIME idioms: basic macros, ;;; ways of showing messages to the user, etc. All the code in this ;;; file should use these functions when applicable. ;;; ;;;;; Syntactic sugar (defmacro* when-let ((var value) &rest body) "Evaluate VALUE, and if the result is non-nil bind it to VAR and evaluate BODY. \(fn (VAR VALUE) &rest BODY)" `(let ((,var ,value)) (when ,var ,@body))) (put 'when-let 'lisp-indent-function 1) (defmacro destructure-case (value &rest patterns) "Dispatch VALUE to one of PATTERNS. A cross between `case' and `destructuring-bind'. The pattern syntax is: ((HEAD . ARGS) . BODY) The list of patterns is searched for a HEAD `eq' to the car of VALUE. If one is found, the BODY is executed with ARGS bound to the corresponding values in the CDR of VALUE." (let ((operator (gensym "op-")) (operands (gensym "rand-")) (tmp (gensym "tmp-"))) `(let* ((,tmp ,value) (,operator (car ,tmp)) (,operands (cdr ,tmp))) (case ,operator ,@(mapcar (lambda (clause) (if (eq (car clause) t) `(t ,@(cdr clause)) (destructuring-bind ((op &rest rands) &rest body) clause `(,op (destructuring-bind ,rands ,operands . ,body))))) patterns) ,@(if (eq (caar (last patterns)) t) '() `((t (error "Elisp destructure-case failed: %S" ,tmp)))))))) (put 'destructure-case 'lisp-indent-function 1) (defmacro slime-define-keys (keymap &rest key-command) "Define keys in KEYMAP. Each KEY-COMMAND is a list of (KEY COMMAND)." `(progn . ,(mapcar (lambda (k-c) `(define-key ,keymap . ,k-c)) key-command))) (put 'slime-define-keys 'lisp-indent-function 1) (defmacro* with-struct ((conc-name &rest slots) struct &body body) "Like with-slots but works only for structs. \(fn (CONC-NAME &rest SLOTS) STRUCT &body BODY)" (flet ((reader (slot) (intern (concat (symbol-name conc-name) (symbol-name slot))))) (let ((struct-var (gensym "struct"))) `(let ((,struct-var ,struct)) (symbol-macrolet ,(mapcar (lambda (slot) (etypecase slot (symbol `(,slot (,(reader slot) ,struct-var))) (cons `(,(first slot) (,(reader (second slot)) ,struct-var))))) slots) . ,body))))) (put 'with-struct 'lisp-indent-function 2) ;;;;; Very-commonly-used functions (defvar slime-message-function 'message) ;; Interface (defun slime-message (format &rest args) "Like `message' but with special support for multi-line messages. Single-line messages use the echo area." (apply slime-message-function format args)) (when (or (featurep 'xemacs) (= emacs-major-version 20)) (setq slime-message-function 'slime-format-display-message)) (defun slime-format-display-message (format &rest args) (slime-display-message (apply #'format format args) "*SLIME Note*")) (defun slime-display-message (message buffer-name) "Display MESSAGE in the echo area or in BUFFER-NAME. Use the echo area if MESSAGE needs only a single line. If the MESSAGE requires more than one line display it in BUFFER-NAME and add a hook to `slime-pre-command-actions' to remove the window before the next command." (when (get-buffer-window buffer-name) (delete-windows-on buffer-name)) (cond ((or (string-match "\n" message) (> (length message) (1- (frame-width)))) (lexical-let ((buffer (get-buffer-create buffer-name))) (with-current-buffer buffer (erase-buffer) (insert message) (goto-char (point-min)) (let ((win (slime-create-message-window))) (set-window-buffer win (current-buffer)) (shrink-window-if-larger-than-buffer (display-buffer (current-buffer))))) (push (lambda () (delete-windows-on buffer) (bury-buffer buffer)) slime-pre-command-actions))) (t (message "%s" message)))) (defun slime-create-message-window () "Create a window at the bottom of the frame, above the minibuffer." (let ((previous (previous-window (minibuffer-window)))) (when (<= (window-height previous) (* 2 window-min-height)) (save-selected-window (select-window previous) (enlarge-window (- (1+ (* 2 window-min-height)) (window-height previous))))) (split-window previous))) (defvar slime-background-message-function 'slime-display-oneliner) ;; Interface (defun slime-background-message (format-string &rest format-args) "Display a message in passing. This is like `slime-message', but less distracting because it will never pop up a buffer or display multi-line messages. It should be used for \"background\" messages such as argument lists." (apply slime-background-message-function format-string format-args)) (defun slime-display-oneliner (format-string &rest format-args) (let* ((msg (apply #'format format-string format-args))) (unless (minibuffer-window-active-p (minibuffer-window)) (message "%s" (slime-oneliner msg))))) (defun slime-oneliner (string) "Return STRING truncated to fit in a single echo-area line." (substring string 0 (min (length string) (or (position ?\n string) most-positive-fixnum) (1- (frame-width))))) ;; Interface (defun slime-set-truncate-lines () "Apply `slime-truncate-lines' to the current buffer." (when slime-truncate-lines (set (make-local-variable 'truncate-lines) t))) ;; Interface (defun slime-read-package-name (prompt &optional initial-value) "Read a package name from the minibuffer, prompting with PROMPT." (let ((completion-ignore-case t)) (completing-read prompt (slime-bogus-completion-alist (slime-eval `(swank:list-all-package-names t))) nil t initial-value))) ;; Interface (defun slime-read-symbol-name (prompt &optional query) "Either read a symbol name or choose the one at point. The user is prompted if a prefix argument is in effect, if there is no symbol at point, or if QUERY is non-nil. This function avoids mistaking the REPL prompt for a symbol." (cond ((or current-prefix-arg query (not (slime-symbol-name-at-point))) (slime-read-from-minibuffer prompt (slime-symbol-name-at-point))) (t (slime-symbol-name-at-point)))) ;; Interface (defmacro slime-propertize-region (props &rest body) "Execute BODY and add PROPS to all the text it inserts. More precisely, PROPS are added to the region between the point's positions before and after executing BODY." (let ((start (gensym))) `(let ((,start (point))) (prog1 (progn ,@body) (add-text-properties ,start (point) ,props))))) (put 'slime-propertize-region 'lisp-indent-function 1) ;; Interface (defsubst slime-insert-propertized (props &rest args) "Insert all ARGS and then add text-PROPS to the inserted text." (slime-propertize-region props (apply #'insert args))) (defmacro slime-with-rigid-indentation (level &rest body) "Execute BODY and then rigidly indent its text insertions. Assumes all insertions are made at point." (let ((start (gensym)) (l (gensym))) `(let ((,start (point)) (,l ,(or level '(current-column)))) (prog1 (progn ,@body) (slime-indent-rigidly ,start (point) ,l))))) (put 'slime-with-rigid-indentation 'lisp-indent-function 1) (defun slime-indent-rigidly (start end column) ;; Similar to `indent-rigidly' but doesn't inherit text props. (save-excursion (goto-char end) (beginning-of-line) (while (and (<= start (point)) (progn (save-excursion (insert-char ?\ column)) (zerop (forward-line -1))))))) (defun slime-insert-indented (&rest strings) "Insert all arguments rigidly indented." (slime-with-rigid-indentation nil (apply #'insert strings))) (defun slime-curry (fun &rest args) `(lambda (&rest more) (apply ',fun (append ',args more)))) (defun slime-rcurry (fun &rest args) `(lambda (&rest more) (apply ',fun (append more ',args)))) ;;;;; Snapshots of current Emacs state ;;; Window configurations do not save (and hence not restore) ;;; any narrowing that could be applied to a buffer. ;;; ;;; For this purpose, we introduce a superset of a window ;;; configuration that does include the necessary information to ;;; properly restore narrowing. ;;; ;;; We call this superset an Emacs Snapshot. (defstruct (slime-narrowing-configuration (:conc-name slime-narrowing-configuration.)) narrowedp beg end) (defstruct (slime-emacs-snapshot (:conc-name slime-emacs-snapshot.)) ;; We explicitly store the value of point even though it's implicitly ;; stored in the window-configuration because Emacs provides no ;; way to access the things stored in a window configuration. window-configuration narrowing-configuration point-marker) (defun slime-current-narrowing-configuration (&optional buffer) (with-current-buffer (or buffer (current-buffer)) (make-slime-narrowing-configuration :narrowedp (slime-buffer-narrowed-p) :beg (point-min-marker) :end (point-max-marker)))) (defun slime-set-narrowing-configuration (narrowing-cfg) (when (slime-narrowing-configuration.narrowedp narrowing-cfg) (narrow-to-region (slime-narrowing-configuration.beg narrowing-cfg) (slime-narrowing-configuration.end narrowing-cfg)))) (defun slime-current-emacs-snapshot (&optional frame) "Returns a snapshot of the current state of FRAME, or the currently active frame if FRAME is not given respectively." (with-current-buffer (if frame (window-buffer (frame-selected-window (selected-frame))) (current-buffer)) (make-slime-emacs-snapshot :window-configuration (current-window-configuration frame) :narrowing-configuration (slime-current-narrowing-configuration) :point-marker (point-marker)))) (defun slime-set-emacs-snapshot (snapshot) "Restores the state of Emacs according to the information saved in SNAPSHOT." (let ((window-cfg (slime-emacs-snapshot.window-configuration snapshot)) (narrowing-cfg (slime-emacs-snapshot.narrowing-configuration snapshot)) (marker (slime-emacs-snapshot.point-marker snapshot))) (set-window-configuration window-cfg) ; restores previously current buffer. (slime-set-narrowing-configuration narrowing-cfg) (goto-char (marker-position marker)))) (defun slime-current-emacs-snapshot-fingerprint (&optional frame) "Return a fingerprint of the current emacs snapshot. Fingerprints are `equalp' if and only if they represent window configurations that are very similar (same windows and buffers.) Unlike real window-configuration objects, fingerprints are not sensitive to the point moving and they can't be restored." (mapcar (lambda (window) (list window (window-buffer window))) (slime-frame-windows frame))) (defun slime-frame-windows (&optional frame) "Return the list of windows in FRAME." (loop with last-window = (previous-window (frame-first-window frame)) for window = (frame-first-window frame) then (next-window window) collect window until (eq window last-window))) ;;;;; Temporary popup buffers (make-variable-buffer-local (defvar slime-popup-buffer-saved-emacs-snapshot nil "The snapshot of the current state in Emacs before the popup-buffer was displayed, so that this state can be restored later on. Buffer local in popup-buffers.")) (make-variable-buffer-local (defvar slime-popup-buffer-saved-fingerprint nil "The emacs snapshot \"fingerprint\" after displaying the buffer.")) ;; Interface (defmacro* slime-with-popup-buffer ((name &optional package connection emacs-snapshot) &body body) "Similar to `with-output-to-temp-buffer'. Bind standard-output and initialize some buffer-local variables. Restore window configuration when closed. NAME is the name of the buffer to be created. PACKAGE is the value `slime-buffer-package'. CONNECTION is the value for `slime-buffer-connection'. If nil, no explicit connection is associated with the buffer. If t, the current connection is taken. If EMACS-SNAPSHOT is non-NIL, it's used to restore the previous state of Emacs after closing the temporary buffer. Otherwise, the current state will be saved and later restored." `(let* ((vars% (list ,(if (eq package t) '(slime-current-package) package) ,(if (eq connection t) '(slime-connection) connection) ;; Defer the decision for NILness until runtime. (or ,emacs-snapshot (slime-current-emacs-snapshot)))) (standard-output (slime-make-popup-buffer ,name vars%))) (with-current-buffer standard-output (prog1 (progn ,@body) (assert (eq (current-buffer) standard-output)) (setq buffer-read-only t) (slime-init-popup-buffer vars%) (slime-display-popup-buffer))))) (put 'slime-with-popup-buffer 'lisp-indent-function 1) (defun slime-make-popup-buffer (name buffer-vars) "Return a temporary buffer called NAME. The buffer also uses the minor-mode `slime-popup-buffer-mode'." (when (and (get-buffer name) (kill-buffer (get-buffer name)))) (with-current-buffer (get-buffer-create name) (set-syntax-table lisp-mode-syntax-table) (slime-init-popup-buffer buffer-vars) (current-buffer))) (defun slime-init-popup-buffer (buffer-vars) (slime-popup-buffer-mode 1) (setq slime-popup-buffer-saved-fingerprint (slime-current-emacs-snapshot-fingerprint)) (multiple-value-setq (slime-buffer-package slime-buffer-connection slime-popup-buffer-saved-emacs-snapshot) buffer-vars)) (defun slime-display-popup-buffer () "Display the current buffer. Save the selected-window in a buffer-local variable, so that we can restore it later." (let ((selected-window (selected-window)) (windows)) (walk-windows (lambda (w) (push w windows)) nil t) (prog1 (pop-to-buffer (current-buffer)) (set (make-local-variable 'slime-popup-buffer-restore-info) (list (unless (memq (selected-window) windows) (selected-window)) selected-window))))) (define-minor-mode slime-popup-buffer-mode "Mode for displaying read only stuff" nil (" Slime-Tmp" slime-modeline-string) '(("q" . slime-popup-buffer-quit-function) ("\C-c\C-z" . slime-switch-to-output-buffer) ("\M-." . slime-edit-definition))) (make-variable-buffer-local (defvar slime-popup-buffer-quit-function 'slime-popup-buffer-quit "The function that is used to quit a temporary popup buffer.")) (defun slime-popup-buffer-quit-function (&optional kill-buffer-p) "Wrapper to invoke the value of `slime-popup-buffer-quit-function'." (interactive) (funcall slime-popup-buffer-quit-function kill-buffer-p)) ;; Interface (defun slime-popup-buffer-quit (&optional kill-buffer-p) "Get rid of the current (temp) buffer without asking. Restore the window configuration unless it was changed since we last activated the buffer." (interactive) (let ((buffer (current-buffer))) ;;(when (slime-popup-buffer-snapshot-unchanged-p) ;; (slime-popup-buffer-restore-snapshot)) (setq slime-popup-buffer-saved-emacs-snapshot nil) ; buffer-local var! (destructuring-bind (created-window selected-window) slime-popup-buffer-restore-info (bury-buffer) (when (eq created-window (selected-window)) (delete-window created-window)) (when (window-live-p selected-window) (select-window selected-window))) (when kill-buffer-p (kill-buffer buffer)))) (defun slime-popup-buffer-snapshot-unchanged-p () (equalp (slime-current-emacs-snapshot-fingerprint) slime-popup-buffer-saved-fingerprint)) (defun slime-popup-buffer-restore-snapshot () (let ((snapshot slime-popup-buffer-saved-emacs-snapshot)) (assert snapshot) (slime-set-emacs-snapshot snapshot))) ;;;;; Filename translation ;;; ;;; Filenames passed between Emacs and Lisp should be translated using ;;; these functions. This way users who run Emacs and Lisp on separate ;;; machines have a chance to integrate file operations somehow. (defvar slime-to-lisp-filename-function #'convert-standard-filename) (defvar slime-from-lisp-filename-function #'identity) (defun slime-to-lisp-filename (filename) "Translate the string FILENAME to a Lisp filename." (funcall slime-to-lisp-filename-function filename)) (defun slime-from-lisp-filename (filename) "Translate the Lisp filename FILENAME to an Emacs filename." (funcall slime-from-lisp-filename-function filename)) ;;;; Starting SLIME ;;; ;;; This section covers starting an inferior-lisp, compiling and ;;; starting the server, initiating a network connection. ;;;;; Entry points ;; We no longer load inf-lisp, but we use this variable for backward ;; compatibility. (defvar inferior-lisp-program "lisp" "*Program name for invoking an inferior Lisp with for Inferior Lisp mode.") (defvar slime-lisp-implementations nil "*A list of known Lisp implementations. The list should have the form: ((NAME (PROGRAM PROGRAM-ARGS...) &key INIT CODING-SYSTEM ENV) ...) NAME is a symbol for the implementation. PROGRAM and PROGRAM-ARGS are strings used to start the Lisp process. INIT is a function that should return a string to load and start Swank. The function will be called with the PORT-FILENAME and ENCODING as arguments. INIT defaults to `slime-init-command'. CODING-SYSTEM a symbol for the coding system. The default is slime-net-coding-system ENV environment variables for the subprocess (see `process-environment'). Here's an example: ((cmucl (\"/opt/cmucl/bin/lisp\" \"-quiet\") :init slime-init-command) (acl (\"acl7\") :coding-system emacs-mule))") (defvar slime-default-lisp nil "*The name of the default Lisp implementation. See `slime-lisp-implementations'") ;; dummy definitions for the compiler (defvar slime-net-coding-system) (defvar slime-net-processes) (defvar slime-default-connection) (defun slime (&optional command coding-system) "Start an inferior^_superior Lisp and connect to its Swank server." (interactive) (let ((inferior-lisp-program (or command inferior-lisp-program)) (slime-net-coding-system (or coding-system slime-net-coding-system))) (slime-start* (cond ((and command (symbolp command)) (slime-lisp-options command)) (t (slime-read-interactive-args)))))) (defvar slime-inferior-lisp-program-history '() "History list of command strings. Used by `slime'.") (defun slime-read-interactive-args () "Return the list of args which should be passed to `slime-start'. The rules for selecting the arguments are rather complicated: - In the most common case, i.e. if there's no prefix-arg in effect and if `slime-lisp-implementations' is nil, use `inferior-lisp-program' as fallback. - If the table `slime-lisp-implementations' is non-nil use the implementation with name `slime-default-lisp' or if that's nil the first entry in the table. - If the prefix-arg is `-', prompt for one of the registered lisps. - If the prefix-arg is positive, read the command to start the process." (let ((table slime-lisp-implementations)) (cond ((not current-prefix-arg) (slime-lisp-options)) ((eq current-prefix-arg '-) (let ((key (completing-read "Lisp name: " (mapcar (lambda (x) (list (symbol-name (car x)))) table) nil t))) (slime-lookup-lisp-implementation table (intern key)))) (t (destructuring-bind (program &rest program-args) (split-string (read-string "Run lisp: " inferior-lisp-program 'slime-inferior-lisp-program-history)) (let ((coding-system (if (eq 16 (prefix-numeric-value current-prefix-arg)) (read-coding-system "set slime-coding-system: " slime-net-coding-system) slime-net-coding-system))) (list :program program :program-args program-args :coding-system coding-system))))))) (defun slime-lisp-options (&optional name) (let ((table slime-lisp-implementations)) (assert (or (not name) table)) (cond (table (slime-lookup-lisp-implementation slime-lisp-implementations (or name slime-default-lisp (car (car table))))) (t (destructuring-bind (program &rest args) (split-string inferior-lisp-program) (list :program program :program-args args)))))) (defun slime-lookup-lisp-implementation (table name) (destructuring-bind (name (prog &rest args) &rest keys) (assoc name table) (list* :name name :program prog :program-args args keys))) (defun* slime-start (&key (program inferior-lisp-program) program-args directory (coding-system slime-net-coding-system) (init 'slime-init-command) name (buffer "*inferior-lisp*") init-function env) (let ((args (list :program program :program-args program-args :buffer buffer :coding-system coding-system :init init :name name :init-function init-function :env env))) (slime-check-coding-system coding-system) (when (slime-bytecode-stale-p) (slime-urge-bytecode-recompile)) (let ((proc (slime-maybe-start-lisp program program-args env directory buffer))) (slime-inferior-connect proc args) (pop-to-buffer (process-buffer proc))))) (defun slime-start* (options) (apply #'slime-start options)) (defun slime-connect (host port &optional coding-system) "Connect to a running Swank server." (interactive (list (read-from-minibuffer "Host: " slime-lisp-host) (read-from-minibuffer "Port: " (format "%d" slime-port) nil t))) (when (and (interactive-p) slime-net-processes (y-or-n-p "Close old connections first? ")) (slime-disconnect)) (message "Connecting to Swank on port %S.." port) (let ((coding-system (or coding-system slime-net-coding-system))) (slime-check-coding-system coding-system) (message "Connecting to Swank on port %S.." port) (let* ((process (slime-net-connect host port coding-system)) (slime-dispatching-connection process)) (slime-setup-connection process)))) (defun slime-start-and-load (filename &optional package) "Start Slime, if needed, load the current file and set the package." (interactive (list (expand-file-name (buffer-file-name)) (slime-find-buffer-package))) (cond ((slime-connected-p) (slime-load-file-set-package filename package)) (t (slime-start-and-init (slime-lisp-options) (slime-curry #'slime-start-and-load filename package))))) (defun slime-start-and-init (options fun) (let* ((rest (plist-get options :init-function)) (init (cond (rest `(lambda () (funcall ',rest) (funcall ',fun))) (t fun)))) (slime-start* (plist-put (copy-list options) :init-function init)))) (defun slime-load-file-set-package (filename package) (let ((filename (slime-to-lisp-filename filename))) (slime-eval-async `(swank:load-file ,filename) (lexical-let ((package package)) (lambda (ignored) (slime-repl-set-package package)))))) ;;;;; Start inferior lisp ;;; ;;; Here is the protocol for starting SLIME: ;;; ;;; 0. Emacs recompiles/reloads slime.elc if it exists and is stale. ;;; 1. Emacs starts an inferior Lisp process. ;;; 2. Emacs tells Lisp (via stdio) to load and start Swank. ;;; 3. Lisp recompiles the Swank if needed. ;;; 4. Lisp starts the Swank server and writes its TCP port to a temp file. ;;; 5. Emacs reads the temp file to get the port and then connects. ;;; 6. Emacs prints a message of warm encouragement for the hacking ahead. ;;; ;;; Between steps 2-5 Emacs polls for the creation of the temp file so ;;; that it can make the connection. This polling may continue for a ;;; fair while if Swank needs recompilation. (defvar slime-connect-retry-timer nil "Timer object while waiting for an inferior-lisp to start.") ;;; Recompiling bytecode: (defun slime-bytecode-stale-p () "Return true if slime.elc is older than slime.el." (when-let (libfile (locate-library "slime")) (let* ((basename (file-name-sans-extension libfile)) (sourcefile (concat basename ".el")) (bytefile (concat basename ".elc"))) (and (file-exists-p bytefile) (file-newer-than-file-p sourcefile bytefile))))) (defun slime-recompile-bytecode () "Recompile and reload slime. Warning: don't use this in XEmacs, it seems to crash it!" (interactive) (let ((sourcefile (concat (file-name-sans-extension (locate-library "slime")) ".el"))) (byte-compile-file sourcefile t))) (defun slime-urge-bytecode-recompile () "Urge the user to recompile slime.elc. Return true if we have been given permission to continue." (cond ((featurep 'xemacs) ;; My XEmacs crashes and burns if I recompile/reload an elisp ;; file from itself. So they have to do it themself. (or (y-or-n-p "slime.elc is older than source. Continue? ") (signal 'quit nil))) ((y-or-n-p "slime.elc is older than source. Recompile first? ") (slime-recompile-bytecode)) (t))) (defun slime-abort-connection () "Abort connection the current connection attempt." (interactive) (cond (slime-connect-retry-timer (slime-cancel-connect-retry-timer) (message "Cancelled connection attempt.")) (t (error "Not connecting")))) ;;; Starting the inferior Lisp and loading Swank: (defun slime-maybe-start-lisp (program program-args env directory buffer) "Return a new or existing inferior lisp process." (cond ((not (comint-check-proc buffer)) (slime-start-lisp program program-args env directory buffer)) ((slime-reinitialize-inferior-lisp-p program program-args env buffer) (when-let (conn (find (get-buffer-process buffer) slime-net-processes :key #'slime-inferior-process)) (slime-net-close conn)) (get-buffer-process buffer)) (t (slime-start-lisp program program-args env directory (generate-new-buffer-name buffer))))) (defun slime-reinitialize-inferior-lisp-p (program program-args env buffer) (let ((args (slime-inferior-lisp-args (get-buffer-process buffer)))) (and (equal (plist-get args :program) program) (equal (plist-get args :program-args) program-args) (equal (plist-get args :env) env) (not (y-or-n-p "Create an additional *inferior-lisp*? "))))) (defun slime-start-lisp (program program-args env directory buffer) "Does the same as `inferior-lisp' but less ugly. Return the created process." (with-current-buffer (get-buffer-create buffer) (when directory (cd (expand-file-name directory))) (comint-mode) (let ((process-environment (append env process-environment))) (comint-exec (current-buffer) "inferior-lisp" program nil program-args)) (lisp-mode-variables t) (let ((proc (get-buffer-process (current-buffer)))) (slime-set-query-on-exit-flag proc) proc))) (defun slime-inferior-connect (process args) "Start a Swank server in the inferior Lisp and connect." (slime-delete-swank-port-file 'quiet) (slime-start-swank-server process args) (slime-read-port-and-connect process nil)) (defvar slime-inferior-lisp-args nil "A buffer local variable in the inferior proccess.") (defun slime-start-swank-server (process args) "Start a Swank server on the inferior lisp." (destructuring-bind (&key coding-system init &allow-other-keys) args (with-current-buffer (process-buffer process) (make-local-variable 'slime-inferior-lisp-args) (setq slime-inferior-lisp-args args) (let ((str (funcall init (slime-swank-port-file) coding-system))) (goto-char (process-mark process)) (insert-before-markers str) (process-send-string process str))))) (defun slime-inferior-lisp-args (process) (with-current-buffer (process-buffer process) slime-inferior-lisp-args)) ;; XXX load-server & start-server used to be separated. maybe that was better. (defun slime-init-command (port-filename coding-system) "Return a string to initialize Lisp." (let ((loader (if (file-name-absolute-p slime-backend) slime-backend (concat slime-path slime-backend))) (encoding (slime-coding-system-cl-name coding-system))) ;; Return a single form to avoid problems with buffered input. (format "%S\n\n" `(progn (load ,(expand-file-name loader) :verbose t) (funcall (read-from-string "swank-loader:init")) (funcall (read-from-string "swank:start-server") ,port-filename :coding-system ,encoding))))) (defun slime-swank-port-file () "Filename where the SWANK server writes its TCP port number." (concat (file-name-as-directory (slime-temp-directory)) (format "slime.%S" (emacs-pid)))) (defun slime-temp-directory () (cond ((fboundp 'temp-directory) (temp-directory)) ((boundp 'temporary-file-directory) temporary-file-directory) (t "/tmp/"))) (defun slime-delete-swank-port-file (&optional quiet) (condition-case data (delete-file (slime-swank-port-file)) (error (ecase quiet ((nil) (signal (car data) (cdr data))) (quiet) (message (message "Unable to delete swank port file %S" (slime-swank-port-file))))))) (defun slime-read-port-and-connect (inferior-process retries) (slime-cancel-connect-retry-timer) (slime-attempt-connection inferior-process retries 1)) (defun slime-attempt-connection (process retries attempt) ;; A small one-state machine to attempt a connection with ;; timer-based retries. (let ((file (slime-swank-port-file))) (unless (active-minibuffer-window) (message "Polling %S.. (Abort with `M-x slime-abort-connection'.)" file)) (cond ((and (file-exists-p file) (> (nth 7 (file-attributes file)) 0)) ; file size (slime-cancel-connect-retry-timer) (let ((port (slime-read-swank-port)) (args (slime-inferior-lisp-args process))) (slime-delete-swank-port-file 'message) (let ((c (slime-connect slime-lisp-host port (plist-get args :coding-system)))) (slime-set-inferior-process c process)))) ((and retries (zerop retries)) (slime-cancel-connect-retry-timer) (message "Failed to connect to Swank.")) (t (when (and (file-exists-p file) (zerop (nth 7 (file-attributes file)))) (message "(Zero length port file)") ;; the file may be in the filesystem but not yet written (unless retries (setq retries 3))) (unless slime-connect-retry-timer (setq slime-connect-retry-timer (run-with-timer 0.3 0.3 #'slime-timer-call #'slime-attempt-connection process (and retries (1- retries)) (1+ attempt)))))))) (defun slime-timer-call (fun &rest args) "Call function FUN with ARGS, reporting all errors. The default condition handler for timer functions (see `timer-event-handler') ignores errors." (condition-case data (apply fun args) (error (debug nil (list "Error in timer" fun args data))))) (defun slime-cancel-connect-retry-timer () (when slime-connect-retry-timer (cancel-timer slime-connect-retry-timer) (setq slime-connect-retry-timer nil))) (defun slime-read-swank-port () "Read the Swank server port number from the `slime-swank-port-file'." (save-excursion (with-temp-buffer (insert-file-contents (slime-swank-port-file)) (goto-char (point-min)) (let ((port (read (current-buffer)))) (assert (integerp port)) port)))) (defun slime-hide-inferior-lisp-buffer () "Display the REPL buffer instead of the *inferior-lisp* buffer." (let* ((buffer (if (slime-process) (process-buffer (slime-process)))) (window (if buffer (get-buffer-window buffer t))) (repl-buffer (slime-output-buffer t)) (repl-window (get-buffer-window repl-buffer))) (when buffer (bury-buffer buffer)) (cond (repl-window (when window (delete-window window))) (window (set-window-buffer window repl-buffer)) (t (pop-to-buffer repl-buffer) (goto-char (point-max)))))) ;;; Words of encouragement (defun slime-user-first-name () (let ((name (if (string= (user-full-name) "") (user-login-name) (user-full-name)))) (string-match "^[^ ]*" name) (capitalize (match-string 0 name)))) (defvar slime-words-of-encouragement `("Let the hacking commence!" "Hacks and glory await!" "Hack and be merry!" "Your hacking starts... NOW!" "May the source be with you!" "Take this REPL, brother, and may it serve you well." "Lemonodor-fame is but a hack away!" ,(format "%s, this could be the start of a beautiful program." (slime-user-first-name))) "Scientifically-proven optimal words of hackerish encouragement.") (defun slime-random-words-of-encouragement () "Return a string of hackerish encouragement." (eval (nth (random (length slime-words-of-encouragement)) slime-words-of-encouragement))) ;;;; Networking ;;; ;;; This section covers the low-level networking: establishing ;;; connections and encoding/decoding protocol messages. ;;; ;;; Each SLIME protocol message beings with a 3-byte length header ;;; followed by an S-expression as text. The sexp must be readable ;;; both by Emacs and by Common Lisp, so if it contains any embedded ;;; code fragments they should be sent as strings. ;;; ;;; The set of meaningful protocol messages are not specified ;;; here. They are defined elsewhere by the event-dispatching ;;; functions in this file and in swank.lisp. (defvar slime-net-processes nil "List of processes (sockets) connected to Lisps.") (defvar slime-net-process-close-hooks '() "List of functions called when a slime network connection closes. The functions are called with the process as their argument.") (defun slime-secret () "Finds the magic secret from the user's home directory. Returns nil if the file doesn't exist or is empty; otherwise the first line of the file." (condition-case err (with-temp-buffer (insert-file-contents "~/.slime-secret") (goto-char (point-min)) (buffer-substring (point-min) (line-end-position))) (file-error nil))) ;;; Interface (defun slime-net-connect (host port coding-system) "Establish a connection with a CL." (let* ((inhibit-quit nil) (proc (open-network-stream "SLIME Lisp" nil host port)) (buffer (slime-make-net-buffer " *cl-connection*"))) (push proc slime-net-processes) (set-process-buffer proc buffer) (set-process-filter proc 'slime-net-filter) (set-process-sentinel proc 'slime-net-sentinel) (slime-set-query-on-exit-flag proc) (when (fboundp 'set-process-coding-system) (slime-check-coding-system coding-system) (set-process-coding-system proc coding-system coding-system)) (when-let (secret (slime-secret)) (slime-net-send secret proc)) proc)) (defun slime-make-net-buffer (name) "Make a buffer suitable for a network process." (let ((buffer (generate-new-buffer name))) (with-current-buffer buffer (buffer-disable-undo)) buffer)) (defun slime-set-query-on-exit-flag (process) "Set PROCESS's query-on-exit-flag to `slime-kill-without-query-p'." (when slime-kill-without-query-p ;; avoid byte-compiler warnings (let ((fun (if (fboundp 'set-process-query-on-exit-flag) 'set-process-query-on-exit-flag 'process-kill-without-query))) (funcall fun process nil)))) ;;;;; Coding system madness (defvar slime-net-valid-coding-systems '((iso-latin-1-unix nil "iso-latin-1-unix") (iso-8859-1-unix nil "iso-latin-1-unix") (binary nil "iso-latin-1-unix") (utf-8-unix t "utf-8-unix") (emacs-mule-unix t "emacs-mule-unix") (euc-jp-unix t "euc-jp-unix")) "A list of valid coding systems. Each element is of the form: (NAME MULTIBYTEP CL-NAME)") (defun slime-find-coding-system (name) "Return the coding system for the symbol NAME. The result is either an element in `slime-net-valid-coding-systems' of nil." (let* ((probe (assq name slime-net-valid-coding-systems))) (if (and probe (if (fboundp 'check-coding-system) (ignore-errors (check-coding-system (car probe))) (eq (car probe) 'binary))) probe))) (defvar slime-net-coding-system (find-if 'slime-find-coding-system '(iso-latin-1-unix iso-8859-1-unix binary)) "*Coding system used for network connections. See also `slime-net-valid-coding-systems'.") (defun slime-check-coding-system (coding-system) "Signal an error if CODING-SYSTEM isn't a valid coding system." (interactive) (let ((props (slime-find-coding-system coding-system))) (unless props (error "Invalid slime-net-coding-system: %s. %s" coding-system (mapcar #'car slime-net-valid-coding-systems))) (when (and (second props) (boundp 'default-enable-multibyte-characters)) (assert default-enable-multibyte-characters)) t)) (defcustom slime-repl-history-file-coding-system (cond ((slime-find-coding-system 'utf-8-unix) 'utf-8-unix) (t slime-net-coding-system)) "*The coding system for the history file." :type 'symbol :group 'slime-repl) (defun slime-coding-system-mulibyte-p (coding-system) (second (slime-find-coding-system coding-system))) (defun slime-coding-system-cl-name (coding-system) (third (slime-find-coding-system coding-system))) ;;; Interface (defun slime-net-send (sexp proc) "Send a SEXP to Lisp over the socket PROC. This is the lowest level of communication. The sexp will be READ and EVAL'd by Lisp." (let* ((msg (concat (slime-prin1-to-string sexp) "\n")) (string (concat (slime-net-encode-length (length msg)) msg)) (coding-system (cdr (process-coding-system proc)))) (slime-log-event sexp) (cond ((slime-safe-encoding-p coding-system string) (process-send-string proc string)) (t (error "Coding system %s not suitable for %S" coding-system string))))) (defun slime-safe-encoding-p (coding-system string) "Return true iff CODING-SYSTEM can safely encode STRING." (if (featurep 'xemacs) ;; FIXME: XEmacs encodes non-encodeable chars as ?~ automatically t (or (let ((candidates (find-coding-systems-string string)) (base (coding-system-base coding-system))) (or (equal candidates '(undecided)) (memq base candidates))) (and (not (multibyte-string-p string)) (not (slime-coding-system-mulibyte-p coding-system)))))) (defun slime-net-close (process &optional debug) (setq slime-net-processes (remove process slime-net-processes)) (when (eq process slime-default-connection) (setq slime-default-connection nil)) (cond (debug (set-process-sentinel process 'ignore) (set-process-filter process 'ignore) (delete-process process)) (t (run-hook-with-args 'slime-net-process-close-hooks process) ;; killing the buffer also closes the socket (kill-buffer (process-buffer process))))) (defun slime-net-sentinel (process message) (message "Lisp connection closed unexpectedly: %s" message) (slime-net-close process)) ;;; Socket input is handled by `slime-net-filter', which decodes any ;;; complete messages and hands them off to the event dispatcher. (defun slime-net-filter (process string) "Accept output from the socket and process all complete messages." (with-current-buffer (process-buffer process) (goto-char (point-max)) (insert string)) (slime-process-available-input process)) (defun slime-process-available-input (process) "Process all complete messages that have arrived from Lisp." (with-current-buffer (process-buffer process) (while (slime-net-have-input-p) (let ((event (slime-net-read-or-lose process)) (ok nil)) (slime-log-event event) (unwind-protect (save-current-buffer (slime-dispatch-event event process) (setq ok t)) (unless ok (slime-run-when-idle 'slime-process-available-input process))))))) (defun slime-net-have-input-p () "Return true if a complete message is available." (goto-char (point-min)) (and (>= (buffer-size) 6) (>= (- (buffer-size) 6) (slime-net-decode-length)))) (defun slime-run-when-idle (function &rest args) "Call FUNCTION as soon as Emacs is idle." (apply #'run-at-time (if (featurep 'xemacs) itimer-short-interval 0) nil function args)) (defun slime-net-read-or-lose (process) (condition-case error (slime-net-read) (error (debug) (slime-net-close process t) (error "net-read error: %S" error)))) (defun slime-net-read () "Read a message from the network buffer." (goto-char (point-min)) (let* ((length (slime-net-decode-length)) (start (+ 6 (point))) (end (+ start length))) (assert (plusp length)) (prog1 (save-restriction (narrow-to-region start end) (read (current-buffer))) (delete-region (point-min) end)))) (defun slime-net-decode-length () "Read a 24-bit hex-encoded integer from buffer." (string-to-number (buffer-substring-no-properties (point) (+ (point) 6)) 16)) (defun slime-net-encode-length (n) "Encode an integer into a 24-bit hex string." (format "%06x" n)) (defun slime-prin1-to-string (sexp) "Like `prin1-to-string' but don't octal-escape non-ascii characters. This is more compatible with the CL reader." (with-temp-buffer (let (print-escape-nonascii print-escape-newlines print-length print-level) (prin1 sexp (current-buffer)) (buffer-string)))) ;;;; Connections ;;; ;;; "Connections" are the high-level Emacs<->Lisp networking concept. ;;; ;;; Emacs has a connection to each Lisp process that it's interacting ;;; with. Typically there would only be one, but a user can choose to ;;; connect to many Lisps simultaneously. ;;; ;;; A connection consists of a control socket, optionally an extra ;;; socket dedicated to receiving Lisp output (an optimization), and a ;;; set of connection-local state variables. ;;; ;;; The state variables are stored as buffer-local variables in the ;;; control socket's process-buffer and are used via accessor ;;; functions. These variables include things like the *FEATURES* list ;;; and Unix Pid of the Lisp process. ;;; ;;; One connection is "current" at any given time. This is: ;;; `slime-dispatching-connection' if dynamically bound, or ;;; `slime-buffer-connection' if this is set buffer-local, or ;;; `slime-default-connection' otherwise. ;;; ;;; When you're invoking commands in your source files you'll be using ;;; `slime-default-connection'. This connection can be interactively ;;; reassigned via the connection-list buffer. ;;; ;;; When a command creates a new buffer it will set ;;; `slime-buffer-connection' so that commands in the new buffer will ;;; use the connection that the buffer originated from. For example, ;;; the apropos command creates the *Apropos* buffer and any command ;;; in that buffer (e.g. `M-.') will go to the same Lisp that did the ;;; apropos search. REPL buffers are similarly tied to their ;;; respective connections. ;;; ;;; When Emacs is dispatching some network message that arrived from a ;;; connection it will dynamically bind `slime-dispatching-connection' ;;; so that the event will be processed in the context of that ;;; connection. ;;; ;;; This is mostly transparent. The user should be aware that he can ;;; set the default connection to pick which Lisp handles commands in ;;; Lisp-mode source buffers, and slime hackers should be aware that ;;; they can tie a buffer to a specific connection. The rest takes ;;; care of itself. (defvar slime-dispatching-connection nil "Network process currently executing. This is dynamically bound while handling messages from Lisp; it overrides `slime-buffer-connection' and `slime-default-connection'.") (make-variable-buffer-local (defvar slime-buffer-connection nil "Network connection to use in the current buffer. This overrides `slime-default-connection'.")) (defvar slime-default-connection nil "Network connection to use by default. Used for all Lisp communication, except when overridden by `slime-dispatching-connection' or `slime-buffer-connection'.") (defun slime-current-connection () "Return the connection to use for Lisp interaction. Return nil if there's no connection." (or slime-dispatching-connection slime-buffer-connection slime-default-connection)) (defun slime-connection () "Return the connection to use for Lisp interaction. Signal an error if there's no connection." (let ((conn (slime-current-connection))) (cond ((and (not conn) slime-net-processes) (or (slime-auto-select-connection) (error "No default connection selected."))) ((not conn) (or (slime-auto-connect) (error "Not connected."))) ((not (eq (process-status conn) 'open)) (error "Connection closed.")) (t conn)))) (defvar slime-auto-connect 'never) (defun slime-auto-connect () (cond ((or (eq slime-auto-connect 'always) (and (eq slime-auto-connect 'ask) (y-or-n-p "No connection. Start Slime? "))) (save-window-excursion (slime) (while (not (slime-current-connection)) (sleep-for 1)) (slime-connection))) (t nil))) (defvar slime-auto-select-connection 'ask) (defun slime-auto-select-connection () (let* ((c0 (car slime-net-processes)) (c (cond ((eq slime-auto-select-connection 'always) c0) ((and (eq slime-auto-select-connection 'ask) (y-or-n-p (format "No default connection selected. %s %s? " "Switch to" (slime-connection-name c0)))) c0)))) (when c (slime-select-connection c) (message "Switching to connection: %s" (slime-connection-name c)) c))) (defun slime-select-connection (process) "Make PROCESS the default connection." (setq slime-default-connection process)) (defun slime-cycle-connections () "Change current slime connection, and make it buffer local." (interactive) (let* ((tail (or (cdr (member (slime-current-connection) slime-net-processes)) slime-net-processes)) (p (car tail))) (slime-select-connection p) (unless (eq major-mode 'slime-repl-mode) (setq slime-buffer-connection p)) (message "Lisp: %s %s" (slime-connection-name p) (process-contact p)))) (defmacro* slime-with-connection-buffer ((&optional process) &rest body) "Execute BODY in the process-buffer of PROCESS. If PROCESS is not specified, `slime-connection' is used. \(fn (&optional PROCESS) &body BODY))" `(with-current-buffer (process-buffer (or ,process (slime-connection) (error "No connection"))) ,@body)) (put 'slime-with-connection-buffer 'lisp-indent-function 1) (defun slime-compute-connection-state (conn) (cond ((null conn) :disconnected) ((slime-stale-connection-p conn) :stale) ((and (slime-use-sigint-for-interrupt conn) (slime-busy-p conn)) :busy) ((eq slime-buffer-connection conn) :local) (t :connected))) (defun slime-connection-state-as-string (state) (case state (:disconnected "not connected") (:busy "busy..") (:stale "stale") (:local "local"))) ;;; Connection-local variables: (defmacro slime-def-connection-var (varname &rest initial-value-and-doc) "Define a connection-local variable. The value of the variable can be read by calling the function of the same name (it must not be accessed directly). The accessor function is setf-able. The actual variable bindings are stored buffer-local in the process-buffers of connections. The accessor function refers to the binding for `slime-connection'." (let ((real-var (intern (format "%s:connlocal" varname)))) `(progn ;; Variable (make-variable-buffer-local (defvar ,real-var ,@initial-value-and-doc)) ;; Accessor (defun ,varname (&optional process) (slime-with-connection-buffer (process) ,real-var)) ;; Setf (defsetf ,varname (&optional process) (store) `(slime-with-connection-buffer (,process) (setq (\, (quote (\, real-var))) (\, store)) (\, store))) '(\, varname)))) (put 'slime-def-connection-var 'lisp-indent-function 2) ;; Let's indulge in some pretty colours. (unless (featurep 'xemacs) (font-lock-add-keywords 'emacs-lisp-mode '(("(\\(slime-def-connection-var\\)\\s +\\(\\(\\w\\|\\s_\\)+\\)" (1 font-lock-keyword-face) (2 font-lock-variable-name-face))))) (slime-def-connection-var slime-connection-number nil "Serial number of a connection. Bound in the connection's process-buffer.") (slime-def-connection-var slime-lisp-features '() "The symbol-names of Lisp's *FEATURES*. This is automatically synchronized from Lisp.") (slime-def-connection-var slime-lisp-modules '() "The strings of Lisp's *MODULES*.") (slime-def-connection-var slime-lisp-package "COMMON-LISP-USER" "The current package name of the Superior lisp. This is automatically synchronized from Lisp.") (slime-def-connection-var slime-lisp-package-prompt-string "CL-USER" "The current package name of the Superior lisp. This is automatically synchronized from Lisp.") (slime-def-connection-var slime-pid nil "The process id of the Lisp process.") (slime-def-connection-var slime-lisp-implementation-type nil "The implementation type of the Lisp process.") (slime-def-connection-var slime-lisp-implementation-version nil "The implementation type of the Lisp process.") (slime-def-connection-var slime-lisp-implementation-name nil "The short name for the Lisp implementation.") (slime-def-connection-var slime-connection-name nil "The short name for connection.") (slime-def-connection-var slime-inferior-process nil "The inferior process for the connection if any.") (slime-def-connection-var slime-communication-style nil "The communication style.") (slime-def-connection-var slime-machine-instance nil "The name of the (remote) machine running the Lisp process.") ;;;;; Connection setup (defvar slime-connection-counter 0 "The number of SLIME connections made. For generating serial numbers.") ;;; Interface (defun slime-setup-connection (process) "Make a connection out of PROCESS." (let ((slime-dispatching-connection process)) (slime-init-connection-state process) (slime-select-connection process) process)) (defun slime-init-connection-state (proc) "Initialize connection state in the process-buffer of PROC." ;; To make life simpler for the user: if this is the only open ;; connection then reset the connection counter. (when (equal slime-net-processes (list proc)) (setq slime-connection-counter 0)) (slime-with-connection-buffer () (setq slime-buffer-connection proc)) (setf (slime-connection-number proc) (incf slime-connection-counter)) ;; We do the rest of our initialization asynchronously. The current ;; function may be called from a timer, and if we setup the REPL ;; from a timer then it mysteriously uses the wrong keymap for the ;; first command. (slime-eval-async '(swank:connection-info) (slime-curry #'slime-set-connection-info proc))) (defun slime-set-connection-info (connection info) "Initialize CONNECTION with INFO received from Lisp." (let ((slime-dispatching-connection connection)) (destructuring-bind (&key pid style lisp-implementation machine features package version modules &allow-other-keys) info (slime-check-version version connection) (setf (slime-pid) pid (slime-communication-style) style (slime-lisp-features) features (slime-lisp-modules) modules) (destructuring-bind (&key name prompt) package (setf (slime-lisp-package) name (slime-lisp-package-prompt-string) prompt)) (destructuring-bind (&key type name version) lisp-implementation (setf (slime-lisp-implementation-type) type (slime-lisp-implementation-version) version (slime-lisp-implementation-name) name (slime-connection-name) (slime-generate-connection-name name))) (destructuring-bind (&key instance type version) machine (setf (slime-machine-instance) instance))) (let ((args (when-let (p (slime-inferior-process)) (slime-inferior-lisp-args p)))) (when-let (name (plist-get args ':name)) (unless (string= (slime-lisp-implementation-name) name) (setf (slime-connection-name) (slime-generate-connection-name (symbol-name name))))) (slime-hide-inferior-lisp-buffer) (slime-init-output-buffer connection) (slime-load-contribs) (run-hooks 'slime-connected-hook) (when-let (fun (plist-get args ':init-function)) (funcall fun))) (message "Connected. %s" (slime-random-words-of-encouragement)))) (defun slime-check-version (version conn) (or (equal version slime-protocol-version) (equal slime-protocol-version 'ignore) (y-or-n-p (format "Versions differ: %s (slime) vs. %s (swank). Continue? " slime-protocol-version version)) (slime-net-close conn) (top-level))) (defun slime-generate-connection-name (lisp-name) (loop for i from 1 for name = lisp-name then (format "%s<%d>" lisp-name i) while (find name slime-net-processes :key #'slime-connection-name :test #'equal) finally (return name))) (defun slime-connection-close-hook (process) (when (eq process slime-default-connection) (when slime-net-processes (slime-select-connection (car slime-net-processes)) (message "Default connection closed; switched to #%S (%S)" (slime-connection-number) (slime-connection-name))))) (add-hook 'slime-net-process-close-hooks 'slime-connection-close-hook) ;;;;; Commands on connections (defun slime-disconnect () "Disconnect all connections." (interactive) (mapc #'slime-net-close slime-net-processes)) (defun slime-connection-port (connection) "Return the remote port number of CONNECTION." (if (featurep 'xemacs) (car (process-id connection)) (cadr (process-contact connection)))) (defun slime-process (&optional connection) "Return the Lisp process for CONNECTION (default `slime-connection'). Can return nil if there's no process object for the connection." (let ((proc (slime-inferior-process connection))) (if (and proc (memq (process-status proc) '(run stop))) proc))) ;; Non-macro version to keep the file byte-compilable. (defun slime-set-inferior-process (connection process) (setf (slime-inferior-process connection) process)) (defun slime-use-sigint-for-interrupt (&optional connection) (let ((c (or connection (slime-connection)))) (ecase (slime-communication-style c) ((:fd-handler nil) t) ((:spawn :sigio) nil)))) (defvar slime-inhibit-pipelining t "*If true, don't send background requests if Lisp is already busy.") (defun slime-background-activities-enabled-p () (and (or slime-mode (eq major-mode 'sldb-mode) (eq major-mode 'slime-repl-mode)) (let ((con (slime-current-connection))) (and con (eq (process-status con) 'open))) (or (not (slime-busy-p)) (not slime-inhibit-pipelining)))) ;;;; Communication protocol ;;;;; Emacs Lisp programming interface ;;; ;;; The programming interface for writing Emacs commands is based on ;;; remote procedure calls (RPCs). The basic operation is to ask Lisp ;;; to apply a named Lisp function to some arguments, then to do ;;; something with the result. ;;; ;;; Requests can be either synchronous (blocking) or asynchronous ;;; (with the result passed to a callback/continuation function). If ;;; an error occurs during the request then the debugger is entered ;;; before the result arrives -- for synchronous evaluations this ;;; requires a recursive edit. ;;; ;;; You should use asynchronous evaluations (`slime-eval-async') for ;;; most things. Reserve synchronous evaluations (`slime-eval') for ;;; the cases where blocking Emacs is really appropriate (like ;;; completion) and that shouldn't trigger errors (e.g. not evaluate ;;; user-entered code). ;;; ;;; We have the concept of the "current Lisp package". RPC requests ;;; always say what package the user is making them from and the Lisp ;;; side binds that package to *BUFFER-PACKAGE* to use as it sees ;;; fit. The current package is defined as the buffer-local value of ;;; `slime-buffer-package' if set, and otherwise the package named by ;;; the nearest IN-PACKAGE as found by text search (first backwards, ;;; then forwards). ;;; ;;; Similarly we have the concept of the current thread, i.e. which ;;; thread in the Lisp process should handle the request. The current ;;; thread is determined solely by the buffer-local value of ;;; `slime-current-thread'. This is usually bound to t meaning "no ;;; particular thread", but can also be used to nominate a specific ;;; thread. The REPL and the debugger both use this feature to deal ;;; with specific threads. (make-variable-buffer-local (defvar slime-current-thread t "The id of the current thread on the Lisp side. t means the \"current\" thread; :repl-thread the thread that executes REPL requests; fixnum a specific thread.")) (make-variable-buffer-local (defvar slime-buffer-package nil "The Lisp package associated with the current buffer. This is set only in buffers bound to specific packages.")) ;;; `slime-rex' is the RPC primitive which is used to implement both ;;; `slime-eval' and `slime-eval-async'. You can use it directly if ;;; you need to, but the others are usually more convenient. (defmacro* slime-rex ((&rest saved-vars) (sexp &optional (package '(slime-current-package)) (thread 'slime-current-thread)) &rest continuations) "(slime-rex (VAR ...) (SEXP &optional PACKAGE THREAD) CLAUSES ...) Remote EXecute SEXP. VARs are a list of saved variables visible in the other forms. Each VAR is either a symbol or a list (VAR INIT-VALUE). SEXP is evaluated and the princed version is sent to Lisp. PACKAGE is evaluated and Lisp binds *BUFFER-PACKAGE* to this package. The default value is (slime-current-package). CLAUSES is a list of patterns with same syntax as `destructure-case'. The result of the evaluation of SEXP is dispatched on CLAUSES. The result is either a sexp of the form (:ok VALUE) or (:abort). CLAUSES is executed asynchronously. Note: don't use backquote syntax for SEXP, because Emacs20 cannot deal with that." (let ((result (gensym))) `(lexical-let ,(loop for var in saved-vars collect (etypecase var (symbol (list var var)) (cons var))) (slime-dispatch-event (list :emacs-rex ,sexp ,package ,thread (lambda (,result) (destructure-case ,result ,@continuations))))))) (put 'slime-rex 'lisp-indent-function 2) ;;; Interface (defun slime-current-package () "Return the Common Lisp package in the current context. If `slime-buffer-package' has a value then return that, otherwise search for and read an `in-package' form. The REPL buffer is a special case: its package is `slime-lisp-package'." (cond ((eq major-mode 'slime-repl-mode) (slime-lisp-package)) (slime-buffer-package) (t (save-restriction (widen) (slime-find-buffer-package))))) (defvar slime-find-buffer-package-function 'slime-search-buffer-package "*Function to use for `slime-find-buffer-package'. The result should be the package-name (a string) or nil if nothing suitable can be found.") (defun slime-find-buffer-package () "Figure out which Lisp package the current buffer is associated with." (funcall slime-find-buffer-package-function)) ;; When modifing this code consider cases like: ;; (in-package #.*foo*) ;; (in-package #:cl) ;; (in-package :cl) ;; (in-package "CL") ;; (in-package |CL|) ;; (in-package #+ansi-cl :cl #-ansi-cl 'lisp) (defun slime-search-buffer-package () (let ((case-fold-search t) (regexp (concat "^(\\(cl:\\|common-lisp:\\)?in-package\\>[ \t']*" "\\([^)]+\\)[ \t]*)"))) (save-excursion (when (or (re-search-backward regexp nil t) (re-search-forward regexp nil t)) (match-string-no-properties 2))))) ;;; Synchronous requests are implemented in terms of asynchronous ;;; ones. We make an asynchronous request with a continuation function ;;; that `throw's its result up to a `catch' and then enter a loop of ;;; handling I/O until that happens. (defvar slime-stack-eval-tags nil "List of stack-tags of continuations waiting on the stack.") (defun slime-eval (sexp &optional package) "Evaluate EXPR on the superior Lisp and return the result." (when (null package) (setq package (slime-current-package))) (let* ((tag (gensym (format "slime-result-%d-" (1+ (slime-continuation-counter))))) (slime-stack-eval-tags (cons tag slime-stack-eval-tags))) (apply #'funcall (catch tag (slime-rex (tag sexp) (sexp package) ((:ok value) (unless (member tag slime-stack-eval-tags) (error "Reply to canceled synchronous eval request tag=%S sexp=%S" tag sexp)) (throw tag (list #'identity value))) ((:abort) (throw tag (list #'error "Synchronous Lisp Evaluation aborted")))) (let ((debug-on-quit t) (inhibit-quit nil) (conn (slime-connection))) (while t (unless (eq (process-status conn) 'open) (error "Lisp connection closed unexpectedly")) (slime-accept-process-output nil 0.01))))))) (defun slime-eval-async (sexp &optional cont package) "Evaluate EXPR on the superior Lisp and call CONT with the result." (slime-rex (cont (buffer (current-buffer))) (sexp (or package (slime-current-package))) ((:ok result) (when cont (set-buffer buffer) (funcall cont result))) ((:abort) (message "Evaluation aborted.")))) ;;; These functions can be handy too: (defun slime-connected-p () "Return true if the Swank connection is open." (not (null slime-net-processes))) (defun slime-check-connected () "Signal an error if we are not connected to Lisp." (unless (slime-connected-p) (error "Not connected. Use `%s' to start a Lisp." (substitute-command-keys "\\[slime]")))) (defun slime-stale-connection-p (conn) (not (memq conn slime-net-processes))) ;; UNUSED (defun slime-debugged-connection-p (conn) ;; This previously was (AND (SLDB-DEBUGGED-CONTINUATIONS CONN) T), ;; but an SLDB buffer may exist without having continuations ;; attached to it, e.g. the one resulting from `slime-interrupt'. (loop for b in (sldb-buffers) thereis (with-current-buffer b (eq slime-buffer-connection conn)))) (defun slime-busy-p (&optional conn) "True if Lisp has outstanding requests. Debugged requests are ignored." (let ((debugged (sldb-debugged-continuations (or conn (slime-connection))))) (remove-if (lambda (id) (memq id debugged)) (slime-rex-continuations) :key #'car))) ;; dummy defvar for compiler (defvar slime-repl-read-mode) (defun slime-reading-p () "True if Lisp is currently reading input from the REPL." (with-current-buffer (slime-output-buffer) slime-repl-read-mode)) (defun slime-sync () "Block until the most recent request has finished." (when (slime-rex-continuations) (let ((tag (caar (slime-rex-continuations)))) (while (find tag (slime-rex-continuations) :key #'car) (slime-accept-process-output nil 0.1))))) (defun slime-ping () "Check that communication works." (interactive) (message "%s" (slime-eval "PONG"))) ;;;;; Protocol event handler (the guts) ;;; ;;; This is the protocol in all its glory. The input to this function ;;; is a protocol event that either originates within Emacs or arrived ;;; over the network from Lisp. ;;; ;;; Each event is a list beginning with a keyword and followed by ;;; arguments. The keyword identifies the type of event. Events ;;; originating from Emacs have names starting with :emacs- and events ;;; from Lisp don't. (slime-def-connection-var slime-rex-continuations '() "List of (ID . FUNCTION) continuations waiting for RPC results.") (slime-def-connection-var slime-continuation-counter 0 "Continuation serial number counter.") (defvar slime-event-hooks) (defun slime-dispatch-event (event &optional process) (let ((slime-dispatching-connection (or process (slime-connection)))) (or (run-hook-with-args-until-success 'slime-event-hooks event) (destructure-case event ((:write-string output &optional target) (slime-write-string output target)) ((:emacs-rex form package thread continuation) (when (and (slime-use-sigint-for-interrupt) (slime-busy-p)) (slime-display-oneliner "; pipelined request... %S" form)) (let ((id (incf (slime-continuation-counter)))) (push (cons id continuation) (slime-rex-continuations)) (slime-send `(:emacs-rex ,form ,package ,thread ,id)) (slime-recompute-modelines t))) ((:return value id) (let ((rec (assq id (slime-rex-continuations)))) (cond (rec (setf (slime-rex-continuations) (remove rec (slime-rex-continuations))) (slime-recompute-modelines nil) (funcall (cdr rec) value)) (t (error "Unexpected reply: %S %S" id value))))) ((:debug-activate thread level &optional select) (assert thread) (sldb-activate thread level select)) ((:debug thread level condition restarts frames conts) (assert thread) (sldb-setup thread level condition restarts frames conts)) ((:debug-return thread level stepping) (assert thread) (sldb-exit thread level stepping)) ((:emacs-interrupt thread) (slime-send `(:emacs-interrupt ,thread))) ((:read-string thread tag) (assert thread) (slime-repl-read-string thread tag)) ((:y-or-n-p thread tag question) (slime-y-or-n-p thread tag question)) ((:read-aborted thread tag) (assert thread) (slime-repl-abort-read thread tag)) ((:emacs-return-string thread tag string) (slime-send `(:emacs-return-string ,thread ,tag ,string))) ;; ((:new-package package prompt-string) (setf (slime-lisp-package) package) (setf (slime-lisp-package-prompt-string) prompt-string)) ((:new-features features) (setf (slime-lisp-features) features)) ((:indentation-update info) (slime-handle-indentation-update info)) ((:open-dedicated-output-stream port) (slime-open-stream-to-lisp port)) ((:eval-no-wait fun args) (apply (intern fun) args)) ((:eval thread tag form-string) (slime-check-eval-in-emacs-enabled) (slime-eval-for-lisp thread tag form-string)) ((:emacs-return thread tag value) (slime-send `(:emacs-return ,thread ,tag ,value))) ((:ed what) (slime-ed what)) ((:inspect what) (slime-open-inspector what)) ((:background-message message) (slime-background-message "%s" message)) ((:debug-condition thread message) (assert thread) (message "%s" message)) ((:ping thread tag) (slime-send `(:emacs-pong ,thread ,tag))) ((:reader-error packet condition) (slime-with-popup-buffer ("*Slime Error*") (princ (format "Invalid protocol message:\n%s\n\n%S" condition packet)) (goto-char (point-min))) (error "Invalid protocol message")))))) (defun slime-send (sexp) "Send SEXP directly over the wire on the current connection." (slime-net-send sexp (slime-connection))) (defun slime-reset () "Clear all pending continuations." (interactive) (setf (slime-rex-continuations) '()) (mapc #'kill-buffer (sldb-buffers))) (defun slime-send-sigint () (interactive) (signal-process (slime-pid) 'SIGINT)) ;;;;; Event logging to *slime-events* ;;; ;;; The *slime-events* buffer logs all protocol messages for debugging ;;; purposes. Optionally you can enable outline-mode in that buffer, ;;; which is convenient but slows things down significantly. (defvar slime-log-events t "*Log protocol events to the *slime-events* buffer.") (defvar slime-outline-mode-in-events-buffer nil "*Non-nil means use outline-mode in *slime-events*.") (defvar slime-event-buffer-name "*slime-events*" "The name of the slime event buffer.") (defun slime-log-event (event) "Record the fact that EVENT occurred." (when slime-log-events (with-current-buffer (slime-events-buffer) ;; trim? (when (> (buffer-size) 100000) (goto-char (/ (buffer-size) 2)) (re-search-forward "^(" nil t) (delete-region (point-min) (point))) (goto-char (point-max)) (save-excursion (slime-pprint-event event (current-buffer))) (when (and (boundp 'outline-minor-mode) outline-minor-mode) (hide-entry)) (goto-char (point-max))))) (defun slime-pprint-event (event buffer) "Pretty print EVENT in BUFFER with limited depth and width." (let ((print-length 20) (print-level 6) (pp-escape-newlines t)) (pp event buffer))) (defun slime-events-buffer () (or (get-buffer slime-event-buffer-name) (let ((buffer (get-buffer-create slime-event-buffer-name))) (with-current-buffer buffer (buffer-disable-undo) (set (make-local-variable 'outline-regexp) "^(") (set (make-local-variable 'comment-start) ";") (set (make-local-variable 'comment-end) "") (when slime-outline-mode-in-events-buffer (outline-minor-mode))) buffer))) ;;;; Stream output (slime-def-connection-var slime-connection-output-buffer nil "The buffer for the REPL. May be nil or a dead buffer.") (make-variable-buffer-local (defvar slime-output-start nil "Marker for the start of the output for the evaluation.")) (make-variable-buffer-local (defvar slime-output-end nil "Marker for end of output. New output is inserted at this mark.")) ;; dummy definitions for the compiler (defvar slime-repl-package-stack) (defvar slime-repl-directory-stack) (defvar slime-repl-input-start-mark) (defvar slime-repl-prompt-start-mark) (defun slime-output-buffer (&optional noprompt) "Return the output buffer, create it if necessary." (let ((buffer (slime-connection-output-buffer))) (or (if (buffer-live-p buffer) buffer) (setf (slime-connection-output-buffer) (let ((connection (slime-connection))) (with-current-buffer (slime-repl-buffer t connection) (unless (eq major-mode 'slime-repl-mode) (slime-repl-mode)) (setq slime-buffer-connection connection) (slime-reset-repl-markers) (unless noprompt (slime-repl-insert-prompt)) (current-buffer))))))) (defvar slime-repl-banner-function 'slime-repl-insert-banner) (defun slime-repl-update-banner () (funcall slime-repl-banner-function) (goto-char (point-max)) (slime-mark-output-start) (slime-mark-input-start) (slime-repl-insert-prompt)) (defun slime-repl-insert-banner () (when (zerop (buffer-size)) (let ((welcome (concat "; SLIME " (or (slime-changelog-date) "- ChangeLog file not found")))) (insert welcome)))) (defun slime-init-output-buffer (connection) (with-current-buffer (slime-output-buffer t) (setq slime-buffer-connection connection slime-repl-directory-stack '() slime-repl-package-stack '()) (slime-repl-update-banner))) (defun slime-display-output-buffer () "Display the output buffer and scroll to bottom." (with-current-buffer (slime-output-buffer) (goto-char (point-max)) (unless (get-buffer-window (current-buffer) t) (display-buffer (current-buffer) t)) (slime-repl-show-maximum-output))) (defmacro slime-with-output-end-mark (&rest body) "Execute BODY at `slime-output-end'. If point is initially at `slime-output-end' and the buffer is visible update window-point afterwards. If point is initially not at `slime-output-end, execute body inside a `save-excursion' block." `(let ((body.. (lambda () ,@body)) (updatep.. (and (eobp) (pos-visible-in-window-p)))) (cond ((= (point) slime-output-end) (let ((start.. (point))) (funcall body..) (set-marker slime-output-end (point)) (when (= start.. slime-repl-input-start-mark) (set-marker slime-repl-input-start-mark (point))))) (t (save-excursion (goto-char slime-output-end) (funcall body..)))) (when updatep.. (slime-repl-show-maximum-output)))) (defun slime-output-filter (process string) (with-current-buffer (process-buffer process) (when (and (plusp (length string)) (eq (process-status slime-buffer-connection) 'open)) (slime-write-string string)))) (defvar slime-open-stream-hooks) (defun slime-open-stream-to-lisp (port) (let ((stream (open-network-stream "*lisp-output-stream*" (slime-with-connection-buffer () (current-buffer)) slime-lisp-host port))) (slime-set-query-on-exit-flag stream) (set-process-filter stream 'slime-output-filter) (let ((pcs (process-coding-system (slime-current-connection)))) (set-process-coding-system stream (car pcs) (cdr pcs))) (when-let (secret (slime-secret)) (slime-net-send secret stream)) (run-hook-with-args 'slime-open-stream-hooks stream) stream)) (defun slime-io-speed-test (&optional profile) "A simple minded benchmark for stream performance. If a prefix argument is given, instrument the slime package for profiling before running the benchmark." (interactive "P") (eval-and-compile (require 'elp)) (elp-reset-all) (elp-restore-all) (load "slime.el") ;;(byte-compile-file "slime-net.el" t) ;;(setq slime-log-events nil) (setq slime-enable-evaluate-in-emacs t) ;;(setq slime-repl-enable-presentations nil) (when profile (elp-instrument-package "slime-")) (kill-buffer (slime-output-buffer)) (switch-to-buffer (slime-output-buffer)) (delete-other-windows) (sit-for 0) (slime-repl-send-string "(swank:io-speed-test 4000 1)") (let ((proc (slime-inferior-process))) (when proc (display-buffer (process-buffer proc) t) (goto-char (point-max))))) (defvar slime-write-string-function 'slime-repl-write-string) (defun slime-write-string (string &optional target) "Insert STRING in the REPL buffer or some other TARGET. If TARGET is nil, insert STRING as regular process output. If TARGET is :repl-result, insert STRING as the result of the evaluation. Other values of TARGET map to an Emacs marker via the hashtable `slime-output-target-to-marker'; output is inserted at this marker." (funcall slime-write-string-function string target)) (defun slime-repl-write-string (string &optional target) (case target ((nil) (slime-repl-emit string)) (:repl-result (slime-repl-emit-result string)) (t (slime-emit-string string target)))) (defvar slime-repl-popup-on-output nil "Display the output buffer when some output is written. This is set to nil after displaying the buffer.") (defmacro slime-save-marker (marker &rest body) (let ((pos (gensym "pos"))) `(let ((,pos (marker-position ,marker))) (prog1 (progn . ,body) (set-marker ,marker ,pos))))) (put 'slime-save-marker 'lisp-indent-function 1) (defun slime-repl-emit (string) ;; insert the string STRING in the output buffer (with-current-buffer (slime-output-buffer) (save-excursion (goto-char slime-output-end) (slime-save-marker slime-output-start (slime-propertize-region '(face slime-repl-output-face rear-nonsticky (face)) (insert-before-markers string) (when (and (= (point) slime-repl-prompt-start-mark) (not (bolp))) (insert-before-markers "\n") (set-marker slime-output-end (1- (point))))))) (when slime-repl-popup-on-output (setq slime-repl-popup-on-output nil) (display-buffer (current-buffer))) (slime-repl-show-maximum-output))) (defun slime-repl-emit-result (string &optional bol) ;; insert STRING and mark it as evaluation result (with-current-buffer (slime-output-buffer) (save-excursion (slime-save-marker slime-output-start (slime-save-marker slime-output-end (goto-char slime-repl-input-start-mark) (when (and bol (not (bolp))) (insert-before-markers "\n")) (slime-propertize-region `(face slime-repl-result-face rear-nonsticky (face)) (insert-before-markers string))))) (slime-repl-show-maximum-output))) (defvar slime-last-output-target-id 0 "The last integer we used as a TARGET id.") (defvar slime-output-target-to-marker (make-hash-table) "Map from TARGET ids to Emacs markers. The markers indicate where output should be inserted.") (defun slime-output-target-marker (target) "Return the marker where output for TARGET should be inserted." (case target ((nil) (with-current-buffer (slime-output-buffer) slime-output-end)) (:repl-result (with-current-buffer (slime-output-buffer) slime-repl-input-start-mark)) (t (gethash target slime-output-target-to-marker)))) (defun slime-emit-string (string target) "Insert STRING at target TARGET. See `slime-output-target-to-marker'." (let* ((marker (slime-output-target-marker target)) (buffer (and marker (marker-buffer marker)))) (when buffer (with-current-buffer buffer (save-excursion ;; Insert STRING at MARKER, then move MARKER behind ;; the insertion. (goto-char marker) (insert-before-markers string) (set-marker marker (point))))))) (defun slime-switch-to-output-buffer () "Select the output buffer, when possible in an existing window. Hint: You can use `display-buffer-reuse-frames' and `special-display-buffer-names' to customize the frame in which the buffer should appear." (interactive) (slime-pop-to-buffer (slime-output-buffer)) (goto-char (point-max))) ;;;; REPL ;; ;; The REPL uses some markers to separate input from output. The ;; usual configuration is as follows: ;; ;; ... output ... ... result ... prompt> ... input ... ;; ^ ^ ^ ^ ^ ;; output-start output-end prompt-start input-start point-max ;; ;; input-start is a right inserting marker, because ;; we want it to stay behind when the user inserts text. ;; ;; We maintain the following invariant: ;; ;; output-start <= output-end <= input-start. ;; ;; This invariant is important, because we must be prepared for ;; asynchronous output and asynchronous reads. ("Asynchronous" means, ;; triggered by Lisp and not by Emacs.) ;; ;; All output is inserted at the output-end marker. Some care must be ;; taken when output-end and input-start are at the same position: if ;; we insert at that point, we must move the right markers. We should ;; also not leave (window-)point in the middle of the new output. The ;; idiom we use is a combination to slime-save-marker, ;; insert-before-markers, and manually updating window-point ;; afterwards. ;; ;; A "synchronous" evaluation request proceeds as follows: the user ;; inserts some text between input-start and point-max and then hits ;; return. We send that region to Lisp, move the output and input ;; makers to the line after the input and wait. When we receive the ;; result, we insert it together with a prompt between the output-end ;; and input-start mark. See `slime-repl-insert-prompt'. ;; ;; It is possible that some output for such an evaluation request ;; arrives after the result. This output is inserted before the ;; result (and before the prompt). ;; ;; If we are in "reading" state, e.g., during a call to Y-OR-N-P, ;; there is no prompt between output-end and input-start. ;; ;; Small helper. (defun slime-make-variables-buffer-local (&rest variables) (mapcar #'make-variable-buffer-local variables)) (slime-make-variables-buffer-local (defvar slime-repl-package-stack nil "The stack of packages visited in this repl.") (defvar slime-repl-directory-stack nil "The stack of default directories associated with this repl.") (defvar slime-repl-prompt-start-mark) (defvar slime-repl-input-start-mark) (defvar slime-repl-old-input-counter 0 "Counter used to generate unique `slime-repl-old-input' properties. This property value must be unique to avoid having adjacent inputs be joined together.")) (defun slime-reset-repl-markers () (dolist (markname '(slime-output-start slime-output-end slime-repl-prompt-start-mark slime-repl-input-start-mark)) (set markname (make-marker)) (set-marker (symbol-value markname) (point)))) ;;;;; REPL mode setup (defvar slime-repl-mode-map) (setq slime-repl-mode-map (make-sparse-keymap)) (set-keymap-parent slime-repl-mode-map lisp-mode-map) (dolist (spec slime-keys) (destructuring-bind (key command &key inferior prefixed &allow-other-keys) spec (when inferior (let ((key (if prefixed (concat slime-prefix-key key) key))) (define-key slime-repl-mode-map key command))))) (slime-define-keys slime-repl-mode-map ("\C-m" 'slime-repl-return) ([return] 'slime-repl-return) ("\C-j" 'slime-repl-newline-and-indent) ("\C-\M-m" 'slime-repl-closing-return) ([(control return)] 'slime-repl-closing-return) ("\C-a" 'slime-repl-bol) ([home] 'slime-repl-bol) ("\M-p" 'slime-repl-previous-input) ((kbd "C-") 'slime-repl-backward-input) ("\M-n" 'slime-repl-next-input) ((kbd "C-") 'slime-repl-forward-input) ("\M-r" 'slime-repl-previous-matching-input) ("\M-s" 'slime-repl-next-matching-input) ("\C-c\C-c" 'slime-interrupt) ("\C-c\C-b" 'slime-interrupt) ("\C-c:" 'slime-interactive-eval) ("\C-c\C-e" 'slime-interactive-eval) ("\C-cE" 'slime-edit-value) ;("\t" 'slime-complete-symbol) ("\t" 'slime-indent-and-complete-symbol) (" " 'slime-space) ("\C-c\C-d" slime-doc-map) ("\C-c\C-w" slime-who-map) ("\C-\M-x" 'slime-eval-defun) ("\C-c\C-o" 'slime-repl-clear-output) ("\C-c\M-o" 'slime-repl-clear-buffer) ("\C-c\C-t" 'slime-toggle-trace-fdefinition) ("\C-c\C-u" 'slime-repl-kill-input) ("\C-c\C-n" 'slime-repl-next-prompt) ("\C-c\C-p" 'slime-repl-previous-prompt) ("\C-c\C-l" 'slime-load-file) ("\C-c\C-k" 'slime-compile-and-load-file) ("\C-c\C-z" 'slime-nop)) (defun slime-repl-mode () "Major mode for interacting with a superior Lisp. \\{slime-repl-mode-map}" (interactive) (kill-all-local-variables) (setq major-mode 'slime-repl-mode) (use-local-map slime-repl-mode-map) (lisp-mode-variables t) (set (make-local-variable 'lisp-indent-function) 'common-lisp-indent-function) (setq font-lock-defaults nil) (setq mode-name "REPL") (setq slime-current-thread :repl-thread) (set (make-local-variable 'scroll-conservatively) 20) (set (make-local-variable 'scroll-margin) 0) (when slime-repl-history-file (slime-repl-safe-load-history) (slime-add-local-hook 'kill-buffer-hook 'slime-repl-safe-save-merged-history)) (add-hook 'kill-emacs-hook 'slime-repl-save-all-histories) (slime-setup-command-hooks) ;; At the REPL, we define beginning-of-defun and end-of-defun to be ;; the start of the previous prompt or next prompt respectively. ;; Notice the interplay with SLIME-REPL-BEGINNING-OF-DEFUN. (set (make-local-variable 'beginning-of-defun-function) 'slime-repl-mode-beginning-of-defun) (set (make-local-variable 'end-of-defun-function) 'slime-repl-mode-end-of-defun) (run-mode-hooks 'slime-repl-mode-hook)) (defun slime-repl-buffer (&optional create connection) "Get the REPL buffer for the current connection; optionally create." (funcall (if create #'get-buffer-create #'get-buffer) (format "*slime-repl %s*" (slime-connection-name connection)))) (defun slime-repl () (interactive) (slime-switch-to-output-buffer)) (defun slime-repl-mode-beginning-of-defun () (slime-repl-previous-prompt) t) (defun slime-repl-mode-end-of-defun () (slime-repl-next-prompt) t) (defun slime-repl-send-string (string &optional command-string) (cond (slime-repl-read-mode (slime-repl-return-string string)) (t (slime-repl-eval-string string)))) (defun slime-repl-eval-string (string) (slime-rex () ((list 'swank:listener-eval string) (slime-lisp-package)) ((:ok result) (slime-repl-insert-result result)) ((:abort) (slime-repl-show-abort)))) (defun slime-repl-insert-result (result) (with-current-buffer (slime-output-buffer) (save-excursion (when result (destructure-case result ((:values &rest strings) (cond ((null strings) (slime-repl-emit-result "; No value\n" t)) (t (dolist (s strings) (slime-repl-emit-result s t))))))) (slime-repl-insert-prompt)) (slime-repl-show-maximum-output))) (defun slime-repl-show-abort () (with-current-buffer (slime-output-buffer) (save-excursion (slime-save-marker slime-output-start (slime-save-marker slime-output-end (goto-char slime-output-end) (insert-before-markers "; Evaluation aborted.\n") (slime-repl-insert-prompt)))) (slime-repl-show-maximum-output))) (defun slime-repl-insert-prompt () "Insert the prompt (before markers!). Set point after the prompt. Return the position of the prompt beginning." (goto-char slime-repl-input-start-mark) (slime-save-marker slime-output-start (slime-save-marker slime-output-end (unless (bolp) (insert-before-markers "\n")) (let ((prompt-start (point)) (prompt (format "%s> " (slime-lisp-package-prompt-string)))) (slime-propertize-region '(face slime-repl-prompt-face read-only t intangible t slime-repl-prompt t ;; emacs stuff rear-nonsticky (slime-repl-prompt read-only face intangible) ;; xemacs stuff start-open t end-open t) (insert-before-markers prompt)) (set-marker slime-repl-prompt-start-mark prompt-start) prompt-start)))) (defun slime-repl-show-maximum-output () "Put the end of the buffer at the bottom of the window." (when (eobp) (let ((win (get-buffer-window (current-buffer)))) (when win (with-selected-window win (set-window-point win (point-max)) (recenter -1)))))) (defvar slime-repl-current-input-hooks) (defun slime-repl-current-input (&optional until-point-p) "Return the current input as string. The input is the region from after the last prompt to the end of buffer." (or (run-hook-with-args-until-success 'slime-repl-current-input-hooks until-point-p) (buffer-substring-no-properties slime-repl-input-start-mark (if until-point-p (point) (point-max))))) (defun slime-property-position (text-property &optional object) "Return the first position of TEXT-PROPERTY, or nil." (if (get-text-property 0 text-property object) 0 (next-single-property-change 0 text-property object))) (defun slime-mark-input-start () (set-marker slime-repl-input-start-mark (point) (current-buffer))) (defun slime-mark-output-start () (set-marker slime-output-start (point)) (set-marker slime-output-end (point))) (defun slime-mark-output-end () ;; Don't put slime-repl-output-face again; it would remove the ;; special presentation face, for instance in the SBCL inspector. (add-text-properties slime-output-start slime-output-end '(;;face slime-repl-output-face rear-nonsticky (face)))) (defun slime-repl-bol () "Go to the beginning of line or the prompt." (interactive) (cond ((and (>= (point) slime-repl-input-start-mark) (slime-same-line-p (point) slime-repl-input-start-mark)) (goto-char slime-repl-input-start-mark)) (t (beginning-of-line 1))) (slime-preserve-zmacs-region)) (defun slime-preserve-zmacs-region () "In XEmacs, ensure that the zmacs-region stays active after this command." (when (boundp 'zmacs-region-stays) (set 'zmacs-region-stays t))) (defun slime-repl-in-input-area-p () (<= slime-repl-input-start-mark (point))) (defun slime-repl-at-prompt-start-p () ;; This will not work on non-current prompts. (= (point) slime-repl-input-start-mark)) (defun slime-repl-beginning-of-defun () "Move to beginning of defun." (interactive) ;; We call BEGINNING-OF-DEFUN if we're at the start of a prompt ;; already, to trigger SLIME-REPL-MODE-BEGINNING-OF-DEFUN by means ;; of the locally bound BEGINNING-OF-DEFUN-FUNCTION, in order to ;; jump to the start of the previous prompt. (if (and (not (slime-repl-at-prompt-start-p)) (slime-repl-in-input-area-p)) (goto-char slime-repl-input-start-mark) (beginning-of-defun)) t) ;; FIXME: this looks very strange (defun slime-repl-end-of-defun () "Move to next of defun." (interactive) ;; C.f. SLIME-REPL-BEGINNING-OF-DEFUN. (if (and (not (= (point) (point-max))) (slime-repl-in-input-area-p)) (goto-char (point-max)) (end-of-defun)) t) (defun slime-repl-previous-prompt () "Move backward to the previous prompt." (interactive) (slime-repl-find-prompt t)) (defun slime-repl-next-prompt () "Move forward to the next prompt." (interactive) (slime-repl-find-prompt)) (defun slime-repl-find-prompt (&optional backward) (let ((origin (point)) (prop 'slime-repl-prompt)) (while (progn (slime-search-property-change prop backward) (not (or (slime-end-of-proprange-p prop) (bobp) (eobp))))) (unless (slime-end-of-proprange-p prop) (goto-char origin)))) (defun slime-search-property-change (prop &optional backward) (cond (backward (goto-char (previous-single-char-property-change (point) prop))) (t (goto-char (next-single-char-property-change (point) prop))))) (defun slime-end-of-proprange-p (property) (and (get-char-property (max 1 (1- (point))) property) (not (get-char-property (point) property)))) (defvar slime-repl-return-hooks) (defun slime-repl-return (&optional end-of-input) "Evaluate the current input string, or insert a newline. Send the current input ony if a whole expression has been entered, i.e. the parenthesis are matched. With prefix argument send the input even if the parenthesis are not balanced." (interactive "P") (slime-check-connected) (cond (end-of-input (slime-repl-send-input)) (slime-repl-read-mode ; bad style? (slime-repl-send-input t)) ((and (get-text-property (point) 'slime-repl-old-input) (< (point) slime-repl-input-start-mark)) (slime-repl-grab-old-input end-of-input) (slime-repl-recenter-if-needed)) ((run-hook-with-args-until-success 'slime-repl-return-hooks)) ((slime-input-complete-p slime-repl-input-start-mark (point-max)) (slime-repl-send-input t)) (t (slime-repl-newline-and-indent) (message "[input not complete]")))) (defun slime-repl-recenter-if-needed () "Make sure that (point) is visible." (unless (pos-visible-in-window-p (point-max)) (save-excursion (goto-char (point-max)) (recenter -1)))) (defun slime-repl-send-input (&optional newline) "Goto to the end of the input and send the current input. If NEWLINE is true then add a newline at the end of the input." (unless (slime-repl-in-input-area-p) (error "No input at point.")) (goto-char (point-max)) (let ((end (point))) ; end of input, without the newline (slime-repl-add-to-input-history (buffer-substring slime-repl-input-start-mark end)) (when newline (insert "\n") (slime-repl-show-maximum-output)) (let ((inhibit-modification-hooks t)) (add-text-properties slime-repl-input-start-mark (point) `(slime-repl-old-input ,(incf slime-repl-old-input-counter)))) (let ((overlay (make-overlay slime-repl-input-start-mark end))) ;; These properties are on an overlay so that they won't be taken ;; by kill/yank. (overlay-put overlay 'read-only t) (overlay-put overlay 'face 'slime-repl-input-face))) (let ((input (slime-repl-current-input))) (goto-char (point-max)) (slime-mark-input-start) (slime-mark-output-start) (slime-repl-send-string input))) (defun slime-repl-grab-old-input (replace) "Resend the old REPL input at point. If replace is non-nil the current input is replaced with the old input; otherwise the new input is appended. The old input has the text property `slime-repl-old-input'." (multiple-value-bind (beg end) (slime-property-bounds 'slime-repl-old-input) (let ((old-input (buffer-substring beg end)) ;;preserve ;;properties, they will be removed later (offset (- (point) beg))) ;; Append the old input or replace the current input (cond (replace (goto-char slime-repl-input-start-mark)) (t (goto-char (point-max)) (unless (eq (char-before) ?\ ) (insert " ")))) (delete-region (point) (point-max)) (save-excursion (insert old-input) (when (equal (char-before) ?\n) (delete-char -1))) (forward-char offset)))) (defun slime-property-bounds (prop) "Return two the positions of the previous and next changes to PROP. PROP is the name of a text property." (assert (get-text-property (point) prop)) (let ((end (next-single-char-property-change (point) prop))) (list (previous-single-char-property-change end prop) end))) (defun slime-repl-closing-return () "Evaluate the current input string after closing all open lists." (interactive) (goto-char (point-max)) (save-restriction (narrow-to-region slime-repl-input-start-mark (point)) (while (ignore-errors (save-excursion (backward-up-list 1)) t) (insert ")"))) (slime-repl-return)) (defun slime-repl-newline-and-indent () "Insert a newline, then indent the next line. Restrict the buffer from the prompt for indentation, to avoid being confused by strange characters (like unmatched quotes) appearing earlier in the buffer." (interactive) (save-restriction (narrow-to-region slime-repl-prompt-start-mark (point-max)) (insert "\n") (lisp-indent-line))) (defun slime-input-complete-p (start end) "Return t if the region from START to END contains a complete sexp." (save-excursion (goto-char start) (cond ((looking-at "\\s *['`#]?[(\"]") (ignore-errors (save-restriction (narrow-to-region start end) ;; Keep stepping over blanks and sexps until the end of ;; buffer is reached or an error occurs. Tolerate extra ;; close parens. (loop do (skip-chars-forward " \t\r\n)") until (eobp) do (forward-sexp)) t))) (t t)))) (defun slime-repl-delete-current-input () (delete-region slime-repl-input-start-mark (point-max))) (defun slime-repl-kill-input () "Kill all text from the prompt to point." (interactive) (cond ((< (marker-position slime-repl-input-start-mark) (point)) (kill-region slime-repl-input-start-mark (point))) ((= (point) (marker-position slime-repl-input-start-mark)) (slime-repl-delete-current-input)))) (defun slime-repl-replace-input (string) (slime-repl-delete-current-input) (insert-and-inherit string)) (defun slime-repl-input-line-beginning-position () (save-excursion (goto-char slime-repl-input-start-mark) (line-beginning-position))) (defvar slime-repl-clear-buffer-hook) (defun slime-repl-clear-buffer () "Delete the output generated by the Lisp process." (interactive) (let ((inhibit-read-only t)) (delete-region (point-min) slime-repl-prompt-start-mark) (delete-region slime-output-start slime-output-end) (goto-char slime-repl-input-start-mark) (recenter)) (run-hooks 'slime-repl-clear-buffer-hook)) (defun slime-repl-clear-output () "Delete the output inserted since the last input." (interactive) (let ((start (save-excursion (slime-repl-previous-prompt) (ignore-errors (forward-sexp)) (forward-line) (point))) (end (1- (slime-repl-input-line-beginning-position)))) (when (< start end) (let ((inhibit-read-only t)) (delete-region start end) (save-excursion (goto-char start) (insert ";;; output flushed")))))) (defun slime-indent-and-complete-symbol () "Indent the current line and perform symbol completion. First indent the line. If indenting doesn't move point, complete the symbol. If there's no symbol at the point, show the arglist for the most recently enclosed macro or function." (interactive) (let ((pos (point))) (unless (get-text-property (line-beginning-position) 'slime-repl-prompt) (lisp-indent-line)) (when (= pos (point)) (cond ((save-excursion (re-search-backward "[^() \n\t\r]+\\=" nil t)) (slime-complete-symbol)) ((memq (char-before) '(?\t ?\ )) (slime-echo-arglist)))))) (defun slime-repl-set-package (package) "Set the package of the REPL buffer to PACKAGE." (interactive (list (let* ((p (slime-current-package)) (p (and p (slime-pretty-package-name p))) (p (and (not (equal p (slime-lisp-package))) p))) (slime-read-package-name "Package: " p)))) (with-current-buffer (slime-output-buffer) (let ((unfinished-input (slime-repl-current-input))) (destructuring-bind (name prompt-string) (slime-repl-shortcut-eval `(swank:set-package ,package)) (setf (slime-lisp-package) name) (setf (slime-lisp-package-prompt-string) prompt-string) (slime-repl-insert-prompt) (insert unfinished-input))))) ;;;;; History (defcustom slime-repl-wrap-history nil "*T to wrap history around when the end is reached." :type 'boolean :group 'slime-repl) (make-variable-buffer-local (defvar slime-repl-input-history '() "History list of strings read from the REPL buffer.")) (defun slime-repl-add-to-input-history (string) "Add STRING to the input history. Empty strings and duplicates are ignored." (unless (or (equal string "") (equal string (car slime-repl-input-history))) (push string slime-repl-input-history))) ;; These two vars contain the state of the last history search. We ;; only use them if `last-command' was 'slime-repl-history-replace, ;; otherwise we reinitialize them. (defvar slime-repl-input-history-position -1 "Newer items have smaller indices.") (defvar slime-repl-history-pattern nil "The regexp most recently used for finding input history.") (defun slime-repl-history-replace (direction &optional regexp) "Replace the current input with the next line in DIRECTION. DIRECTION is 'forward' or 'backward' (in the history list). If REGEXP is non-nil, only lines matching REGEXP are considered." (setq slime-repl-history-pattern regexp) (let* ((min-pos -1) (max-pos (length slime-repl-input-history)) (pos0 (cond ((slime-repl-history-search-in-progress-p) slime-repl-input-history-position) (t min-pos))) (pos (slime-repl-position-in-history pos0 direction (or regexp ""))) (msg nil)) (cond ((and (< min-pos pos) (< pos max-pos)) (slime-repl-replace-input (nth pos slime-repl-input-history)) (setq msg (format "History item: %d" pos))) ((not slime-repl-wrap-history) (setq msg (cond ((= pos min-pos) "End of history") ((= pos max-pos) "Beginning of history")))) (slime-repl-wrap-history (setq pos (if (= pos min-pos) max-pos min-pos)) (setq msg "Wrapped history"))) (when (or (<= pos min-pos) (<= max-pos pos)) (when regexp (setq msg (concat msg "; no matching item")))) ;;(message "%s [%d %d %s]" msg start-pos pos regexp) (message "%s%s" msg (cond ((not regexp) "") (t (format "; current regexp: %s" regexp)))) (setq slime-repl-input-history-position pos) (setq this-command 'slime-repl-history-replace))) (defun slime-repl-history-search-in-progress-p () (eq last-command 'slime-repl-history-replace)) (defun slime-repl-terminate-history-search () (setq last-command this-command)) (defun slime-repl-position-in-history (start-pos direction regexp) "Return the position of the history item matching regexp. Return -1 resp.