;;; 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))) (require 'comint) (require 'timer) (require 'pp) (require 'hideshow) (require 'hyperspec) (require 'font-lock) (when (featurep 'xemacs) (require 'overlay)) (require 'easymenu) (defvar slime-use-autodoc-mode nil "When non-nil always enable slime-autodoc-mode in slime-mode.") (defvar slime-use-highlight-edits-mode nil "When non-nil always enable slime-highlight-edits-mode in slime-mode") (defvar slime-highlight-compiler-notes t "When non-nil highlight buffers with compilation notes, warnings and errors." ) (defun* slime-setup (&key autodoc typeout-frame highlight-edits) "Setup Emacs so that lisp-mode buffers always use SLIME." (when (member 'lisp-mode slime-lisp-modes) (add-hook 'lisp-mode-hook 'slime-lisp-mode-hook)) (when (member 'scheme-mode slime-lisp-modes) (add-hook 'scheme-mode-hook 'slime-scheme-mode-hook)) (when typeout-frame (add-hook 'slime-connected-hook 'slime-ensure-typeout-frame)) (setq slime-use-autodoc-mode autodoc) (setq slime-use-highlight-edits-mode highlight-edits)) (defun slime-shared-lisp-mode-hook () (slime-mode 1) (when slime-use-autodoc-mode (slime-autodoc-mode 1)) (when slime-use-highlight-edits-mode (slime-highlight-edits-mode 1))) (defun slime-lisp-mode-hook () (slime-shared-lisp-mode-hook) (set (make-local-variable 'lisp-indent-function) 'common-lisp-indent-function)) (defun slime-scheme-mode-hook () (slime-shared-lisp-mode-hook)) (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.")) (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 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-update-modeline-package t "Automatically update the Lisp package name in the minibuffer. This is done with a text-search that runs on an idle timer." :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) (defcustom slime-startup-animation t "Enable the startup animation." :type '(choice (const :tag "Enable" t) (const :tag "Disable" nil)) :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-filename-translations nil "Alist of mappings between machine names and filename translation functions. Each element is of the form (HOSTNAME-REGEXP TO-LISP FROM-LISP). HOSTNAME-REGEXP is a regexp which is applied to the connection's slime-machine-instance. If HOSTNAME-REGEXP maches then the corresponding TO-LISP and FROM-LISP functions will be used to translate emacs filenames and lisp filenames. TO-LISP will be passed the filename of an emacs buffer and must return a string which the underlying lisp understandas as a pathname. FROM-LISP will be passed a pathname as returned by the underlying lisp and must return something that emacs will understand as a filename (this string will be passed to find-file). This list will be traversed in order, so multiple matching regexps are possible. Example: Assuming you run emacs locally and connect to slime running on the machine 'soren' and you can connect with the username 'animaliter': (push (list \"^soren$\" (lambda (emacs-filename) (subseq emacs-filename (length \"/ssh:animaliter@soren:\"))) (lambda (lisp-filename) (concat \"/ssh:animaliter@soren:\" lisp-filename))) slime-filename-translations) See also `slime-create-filename-translator'." :type 'list :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) ;;;;; slime-mode (defgroup slime-mode nil "Settings for slime-mode Lisp source buffers." :prefix "slime-" :group 'slime) (defcustom slime-edit-definition-fallback-function nil "Function to call when edit-definition fails to find the source itself. The function is called with the definition name, a string, as its argument. If you want to fallback on TAGS you can set this to `find-tag', `slime-find-tag-if-tags-table-visited', or `slime-edit-definition-with-etags'." :type 'symbol :group 'slime-mode-mode :options '(nil slime-edit-definition-with-etags slime-find-tag-if-tags-table-visited find-tag)) (defcustom slime-compilation-finished-hook 'slime-maybe-list-compiler-notes "Hook called with a list of compiler notes after a compilation." :group 'slime-mode :type 'hook :options '(slime-maybe-list-compiler-notes slime-list-compiler-notes slime-maybe-show-xrefs-for-notes)) (defcustom slime-goto-first-note-after-compilation nil "When T next-note will always goto to the first note in a final, no matter where the point is." :group 'slime-mode :type 'boolean) (defcustom slime-complete-symbol-function 'slime-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-complete-symbol*-fancy nil "Use information from argument lists for DWIM'ish symbol completion." :group 'slime-mode :type 'boolean) (defcustom slime-space-information-p t "Have the SPC key offer arglist information." :type 'boolean :group 'slime-mode) (defcustom slime-display-compilation-output t "Display the REPL buffer before compiling files." :type '(choice (const :tag "Enable" t) (const :tag "Disable" nil)) :group 'slime-mode) (defcustom slime-sbcl-manual-root "http://www.sbcl.org/manual/" "*The base URL of the SBCL manual, for documentation lookup." :type 'string :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") (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") (reference "documentation references" '(:underline t))) ;;;;; 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-enable-presentations (cond ((and (not (featurep 'xemacs)) (= emacs-major-version 20)) ;; mouseable text sucks in Emacs 20 nil) (t t)) "*Should we enable presentations" :type '(boolean) :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) (defcustom slime-repl-return-behaviour :send-if-complete "Keyword specifying how slime-repl-return behaves when the point is on a lisp expression (as opposed to being on a previous output). Currently only two values are supported: :send-if-complete - If the current expression is complete, as per slime-input-complete-p, it is sent to the underlying lisp, otherwise a newline is inserted. The current value of (point) has no effect. :send-only-if-after-complete - If the current expression is complete and point is after the expression it is sent, otherwise a newline is inserted." :type '(choice (const :tag "Send if complete" :value :send-if-complete) (const :tag "Send only if after complete" :value :send-only-if-after-complete)) :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-output-mouseover-face (if (featurep 'xemacs) '((t (:bold t))) (if (slime-face-inheritance-possible-p) '((t (:box (:line-width 1 :color "black" :style released-button) :inherit slime-repl-inputed-output-face))) '((t (:box (:line-width 1 :color "black")))))) "Face for Lisp output in the SLIME REPL, when the mouse hovers over it" :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) (defface slime-repl-inputed-output-face '((((class color) (background light)) (:foreground "Red")) (((class color) (background dark)) (:foreground "Red")) (t (:slant italic))) "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-package nil "The Lisp package to show in the modeline. This is automatically updated based on the buffer/point.")) (defun slime-update-modeline-package () (ignore-errors (when (and slime-update-modeline-package (memq major-mode slime-lisp-modes) slime-mode) (let ((package (slime-current-package))) (when package (setq slime-modeline-package (slime-pretty-package-name package))))))) (defun slime-pretty-package-name (name) "Return a pretty version of a package name designator (as a string)." (let ((name (cond ((string-match "^:\\(.*\\)$" name) (match-string 1 name)) ((string-match "^\"\\(.*\\)\"$" name) (match-string 1 name)) (t name)))) (format "%s" (read name)))) (when slime-update-modeline-package (run-with-idle-timer 0.2 0.2 'slime-update-modeline-package)) ;;;;; inferior-slime-mode (define-minor-mode inferior-slime-mode "\\\ Inferior SLIME mode: The Inferior Superior Lisp Mode for Emacs. This mode is intended for use with `inferior-lisp-mode'. It provides a subset of the bindings from `slime-mode'. \\{inferior-slime-mode-map}" nil nil ;; Fake binding to coax `define-minor-mode' to create the keymap '((" " 'undefined))) ;; Setup the mode-line to say when we're in slime-mode, and which CL ;; package we think the current buffer belongs to. (add-to-list 'minor-mode-alist '(slime-mode (" Slime" ((slime-modeline-package (":" slime-modeline-package) "") slime-state-name)))) (add-to-list 'minor-mode-alist '(inferior-slime-mode (" Inf-Slime" slime-state-name))) (defun inferior-slime-return () "Handle the return key in the inferior-lisp buffer. The current input should only be sent if a whole expression has been entered, i.e. the parenthesis are matched. A prefix argument disables this behaviour." (interactive) (if (or current-prefix-arg (inferior-slime-input-complete-p)) (comint-send-input) (insert "\n") (inferior-slime-indent-line))) (defun inferior-slime-indent-line () "Indent the current line, ignoring everything before the prompt." (interactive) (save-restriction (let ((indent-start (save-excursion (goto-char (process-mark (get-buffer-process (current-buffer)))) (let ((inhibit-field-text-motion t)) (beginning-of-line 1)) (point)))) (narrow-to-region indent-start (point-max))) (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 inferior-slime-input-complete-p () "Return true if the input is complete in the inferior lisp buffer." (slime-input-complete-p (process-mark (get-buffer-process (current-buffer))) (point-max))) (defun inferior-slime-closing-return () "Send the current expression to Lisp after closing any open lists." (interactive) (goto-char (point-max)) (save-restriction (narrow-to-region (process-mark (get-buffer-process (current-buffer))) (point-max)) (while (ignore-errors (save-excursion (backward-up-list 1) t)) (insert ")"))) (comint-send-input)) ;;;;; 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-i" slime-fuzzy-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) ("\C-q" slime-close-parens-at-point :prefixed t :inferior t) ("\C-c\M-q" slime-reindent-defun :inferior 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-s" slime-complete-form :prefixed t :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) ;; Emacs20 on LinuxPPC signals a ;; "Invalid character: 400000040, 2147479172, 0xffffffd8" ;; for "\C- ". ;; ("\C- " slime-next-location :prefixed t :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-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.") (defun* slime-define-key (key command &key prefixed inferior) "Define a keybinding of KEY for COMMAND. If PREFIXED is non-nil, `slime-prefix-key' is prepended to KEY. If INFERIOR is non-nil, the key is also bound for `inferior-slime-mode'." (when prefixed (setq key (concat slime-prefix-key key))) (define-key slime-mode-map key command) (when inferior (define-key inferior-slime-mode-map key command))) (defun slime-init-keymaps () "(Re)initialize the keymaps for `slime-mode' and `inferior-slime-mode'." (interactive) (loop for (key command . keys) in slime-keys do (apply #'slime-define-key key command :allow-other-keys t keys)) ;; Extras.. (define-key inferior-slime-mode-map [return] 'inferior-slime-return) (define-key inferior-slime-mode-map [(control return)] 'inferior-slime-closing-return) (define-key inferior-slime-mode-map [(meta control ?m)] 'inferior-slime-closing-return) ;; Documentation (setq slime-doc-map (make-sparse-keymap)) (loop for (key command) in slime-doc-bindings do (progn ;; We bind both unmodified and with control. (define-key slime-doc-map (vector key) command) (unless (equal key ?h) ; But don't bind C-h (let ((modified (slime-control-modified-char key))) (define-key slime-doc-map (vector modified) command))))) ;; C-c C-d is the prefix for the doc map. (slime-define-key "\C-d" slime-doc-map :prefixed t :inferior t) ;; Who-xref (setq slime-who-map (make-sparse-keymap)) (loop for (key command) in slime-who-bindings do (progn ;; We bind both unmodified and with control. (define-key slime-who-map (vector key) command) (let ((modified (slime-control-modified-char key))) (define-key slime-who-map (vector modified) command)))) ;; C-c C-w is the prefix for the who-xref map. (slime-define-key "\C-w" slime-who-map :prefixed t :inferior t)) (defun slime-control-modified-char (char) "Return the control-modified version of CHAR." ;; Maybe better to just bitmask it? (read (format "?\\C-%c" char))) (slime-init-keymaps) ;;;;; Pull-down menu (defvar slime-easy-menu (let ((C '(slime-connected-p))) `("SLIME" [ "Edit Definition..." slime-edit-definition ,C ] [ "Return From Definition" slime-pop-find-definition-stack ,C ] [ "Complete Symbol" slime-complete-symbol ,C ] [ "Fuzzy Complete Symbol" slime-fuzzy-complete-symbol ,C ] [ "Complete Form" slime-complete-form ,C ] [ "Show REPL" slime-switch-to-output-buffer ,C ] "--" ("Evaluation" [ "Eval Defun" slime-eval-defun ,C ] [ "Eval Last Expression" slime-eval-last-expression ,C ] [ "Eval And Pretty-Print" slime-pprint-eval-last-expression ,C ] [ "Eval Region" slime-eval-region ,C ] [ "Scratch Buffer" slime-scratch ,C ] [ "Interactive Eval..." slime-interactive-eval ,C ] [ "Edit Lisp Value..." slime-edit-value ,C ] [ "Call Defun" slime-call-defun ,C ]) ("Debugging" [ "Macroexpand Once..." slime-macroexpand-1 ,C ] [ "Macroexpand All..." slime-macroexpand-all ,C ] [ "Toggle Trace..." slime-toggle-trace-fdefinition ,C ] [ "Untrace All" slime-untrace-all ,C] [ "Disassemble..." slime-disassemble-symbol ,C ] [ "Inspect..." slime-inspect ,C ]) ("Compilation" [ "Compile Defun" slime-compile-defun ,C ] [ "Compile/Load File" slime-compile-and-load-file ,C ] [ "Compile File" slime-compile-file ,C ] [ "Compile Region" slime-compile-region ,C ] [ "Compile System" slime-load-system ,C ] "--" [ "Next Note" slime-next-note t ] [ "Previous Note" slime-previous-note t ] [ "Remove Notes" slime-remove-notes t ] [ "List Notes" slime-list-compiler-notes ,C ]) ("Cross Reference" [ "Who Calls..." slime-who-calls ,C ] [ "Who References... " slime-who-references ,C ] [ "Who Sets..." slime-who-sets ,C ] [ "Who Binds..." slime-who-binds ,C ] [ "Who Macroexpands..." slime-who-macroexpands ,C ] [ "Who Specializes..." slime-who-specializes ,C ] [ "List Callers..." slime-list-callers ,C ] [ "List Callees..." slime-list-callees ,C ] [ "Next Location" slime-next-location t ]) ("Editing" [ "Close All Parens" slime-close-all-sexp t] [ "Check Parens" check-parens t] [ "Update Indentation" slime-update-indentation ,C] [ "Select Buffer" slime-selector t]) ("Profiling" [ "Toggle Profiling..." slime-toggle-profile-fdefinition ,C ] [ "Profile Package" slime-profile-package ,C] [ "Unprofile All" slime-unprofile-all ,C ] [ "Show Profiled" slime-profiled-functions ,C ] "--" [ "Report" slime-profile-report ,C ] [ "Reset Counters" slime-profile-reset ,C ]) ("Documentation" [ "Describe Symbol..." slime-describe-symbol ,C ] [ "Apropos..." slime-apropos ,C ] [ "Apropos all..." slime-apropos-all ,C ] [ "Apropos Package..." slime-apropos-package ,C ] [ "Hyperspec..." slime-hyperspec-lookup t ]) "--" [ "Interrupt Command" slime-interrupt ,C ] [ "Abort Async. Command" slime-quit ,C ] [ "Sync Package & Directory" slime-sync-package-and-default-directory ,C] [ "Set Package in REPL" slime-repl-set-package ,C]))) (defvar slime-repl-easy-menu (let ((C '(slime-connected-p))) `("REPL" [ "Send Input" slime-repl-return ,C ] [ "Close and Send Input " slime-repl-closing-return ,C ] [ "Interrupt Lisp process" slime-interrupt ,C ] "--" [ "Previous Input" slime-repl-previous-input t ] [ "Next Input" slime-repl-next-input t ] [ "Goto Previous Prompt " slime-repl-previous-prompt t ] [ "Goto Next Prompt " slime-repl-next-prompt t ] [ "Clear Last Output" slime-repl-clear-output t ] [ "Clear Buffer " slime-repl-clear-buffer t ] [ "Kill Current Input" slime-repl-kill-input t ]))) (defvar slime-sldb-easy-menu (let ((C '(slime-connected-p))) `("SLDB" [ "Next Frame" sldb-down t ] [ "Previous Frame" sldb-up t ] [ "Toggle Frame Details" sldb-toggle-details t ] [ "Next Frame (Details)" sldb-details-down t ] [ "Previous Frame (Details)" sldb-details-up t ] "--" [ "Eval Expression..." slime-interactive-eval ,C ] [ "Eval in Frame..." sldb-eval-in-frame ,C ] [ "Eval in Frame (pretty print)..." sldb-pprint-eval-in-frame ,C ] [ "Inspect In Frame..." sldb-inspect-in-frame ,C ] [ "Inspect Condition Object" sldb-inspect-condition ,C ] [ "Print Condition to REPL" sldb-print-condition t ] "--" [ "Restart Frame" sldb-restart-frame ,C ] [ "Return from Frame..." sldb-return-from-frame ,C ] ("Invoke Restart" [ "Continue" sldb-continue ,C ] [ "Abort" sldb-abort ,C ] [ "Step" sldb-step ,C ] [ "Step next" sldb-next ,C ] [ "Step out" sldb-out ,C ] ) "--" [ "Quit (throw)" sldb-quit ,C ] [ "Break With Default Debugger" sldb-break-with-default-debugger ,C ]))) (easy-menu-define menubar-slime slime-mode-map "SLIME" slime-easy-menu) (add-hook 'slime-mode-hook (defun slime-add-easy-menu () (easy-menu-add slime-easy-menu 'slime-mode-map))) (add-hook 'slime-repl-mode-hook (defun slime-repl-add-easy-menu () (easy-menu-define menubar-slime-repl slime-repl-mode-map "REPL" slime-repl-easy-menu) (easy-menu-define menubar-slime slime-repl-mode-map "SLIME" slime-easy-menu) (easy-menu-add slime-repl-easy-menu 'slime-repl-mode-map))) (add-hook 'sldb-mode-hook (defun slime-sldb-add-easy-menu () (easy-menu-define menubar-slime-sldb sldb-mode-map "SLDB" slime-sldb-easy-menu) (easy-menu-add slime-sldb-easy-menu 'sldb-mode-map))) ;;;; 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'." (add-local-hook 'pre-command-hook 'slime-pre-command-hook) (add-local-hook 'post-command-hook 'slime-post-command-hook) (when slime-repl-enable-presentations (add-local-hook 'after-change-functions 'slime-after-change-function))) ;;;; 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 with-lexical-bindings (variables &rest body) "Execute BODY with VARIABLES in lexical scope." `(lexical-let ,(mapcar (lambda (variable) (list variable variable)) variables) ,@body)) (put 'with-lexical-bindings '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 ;; Interface (defun slime-message (format &rest args) "Like `message' but with special support for multi-line messages. Single-line messages use the echo area." (if (slime-typeout-active-p) (apply #'slime-typeout-message format args) (if (or (featurep 'xemacs) (= emacs-major-version 20)) (slime-display-message (apply #'format format args) "*SLIME Note*") (apply 'message format args)))) (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)))) (if (slime-typeout-active-p) (slime-typeout-message "%s" message) (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))) ;; 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." (if (slime-typeout-active-p) (slime-typeout-message (apply #'format format-string 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 #'slime-insert-possibly-as-rectangle args))) (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)))))) (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))) `(let ((,start (point))) (prog1 (progn ,@body) (indent-rigidly ,start (point) ,level))))) (put 'slime-with-rigid-indentation 'lisp-indent-function 1) ;;;;; Temporary popup buffers (make-variable-buffer-local (defvar slime-temp-buffer-saved-window-configuration nil "The window configuration before the temp-buffer was displayed. Buffer local in temp-buffers.")) (make-variable-buffer-local (defvar slime-temp-buffer-fingerprint nil "The window config \"fingerprint\" after displaying the buffer.")) ;; Interface (defun* slime-get-temp-buffer-create (name &key mode noselectp reusep window-configuration) "Return a fresh temporary buffer called NAME in MODE. The buffer also uses the minor-mode `slime-temp-buffer-mode'. Pressing `q' in the buffer will restore the window configuration to the way it is when the buffer was created, i.e. when this function was called. If NOSELECTP is true, then the buffer is shown by `display-buffer', otherwise it is shown and selected by `pop-to-buffer'. If REUSEP is true and a buffer does already exist with name NAME, then the buffer will be reused instead of being killed. If WINDOW-CONFIGURATION is non-NIL, it's used to restore the original window configuration after closing the temporary buffer. Otherwise, the current configuration will be saved and that one used for restoration then. " (let ((window-config (or window-configuration (current-window-configuration))) (buffer (get-buffer name))) (when (and buffer (not reusep)) (kill-buffer name) (setq buffer nil)) (with-current-buffer (or buffer (get-buffer-create name)) (when mode (let ((original-configuration slime-temp-buffer-saved-window-configuration) (original-fingerprint slime-temp-buffer-fingerprint)) (funcall mode) (setq slime-temp-buffer-saved-window-configuration original-configuration) (setq slime-temp-buffer-fingerprint original-fingerprint))) (slime-temp-buffer-mode 1) (let ((window (get-buffer-window (current-buffer)))) (if window (unless noselectp (select-window window)) (progn (if noselectp (display-buffer (current-buffer) t) (pop-to-buffer (current-buffer)) (selected-window)) (setq slime-temp-buffer-saved-window-configuration window-config) (setq slime-temp-buffer-fingerprint (slime-window-config-fingerprint))))) (current-buffer)))) ;; Interface (defmacro* slime-with-output-to-temp-buffer ((name &key mode reusep) package &rest body) "Similar to `with-output-to-temp-buffer'. Also saves the window configuration, and inherits the current `slime-connection' in a buffer-local variable." `(let ((connection (slime-connection)) (standard-output (slime-get-temp-buffer-create ,name :mode ',mode :reusep ,reusep))) (prog1 (with-current-buffer standard-output ;; set explicitely to NIL in case the buffer got reused. (REUSEP) (let ((buffer-read-only nil)) ,@body)) (with-current-buffer standard-output (setq slime-buffer-connection connection) (setq slime-buffer-package ,package) (goto-char (point-min)) (slime-mode 1) (set-syntax-table lisp-mode-syntax-table) (setq buffer-read-only t))))) (put 'slime-with-output-to-temp-buffer 'lisp-indent-function 2) (define-minor-mode slime-temp-buffer-mode "Mode for displaying read only stuff" nil " temp" '(("q" . slime-temp-buffer-quit))) ;; Interface (defun slime-temp-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 ((saved-window-config slime-temp-buffer-saved-window-configuration) (temp-buffer (current-buffer))) (setq slime-temp-buffer-saved-window-configuration nil) (if (and saved-window-config (equalp (slime-window-config-fingerprint) slime-temp-buffer-fingerprint)) (set-window-configuration saved-window-config) (bury-buffer)) (when kill-buffer-p (kill-buffer temp-buffer)))) (defun slime-window-config-fingerprint (&optional frame) "Return a fingerprint of the current window configuration. Fingerprints are `equalp' if and only if they represent window configurations that are very similar (same windows and buffers.) Unlike 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))) ;;;;; 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. (defun slime-to-lisp-filename (filename) "Translate the string FILENAME to a Lisp filename. See `slime-filename-translations'." (funcall (first (slime-find-filename-translators (slime-machine-instance))) (expand-file-name filename))) (defun slime-from-lisp-filename (filename) "Translate the Lisp filename FILENAME to an Emacs filename. See `slime-filename-translations'." (funcall (second (slime-find-filename-translators (slime-machine-instance))) filename)) (defun slime-find-filename-translators (hostname) (cond ((and hostname slime-filename-translations) (or (cdr (assoc-if (lambda (regexp) (string-match regexp hostname)) slime-filename-translations)) (error "No filename-translations for hostname: %s" hostname))) (t (list #'identity #'identity)))) (defun slime-make-tramp-file-name (username remote-host lisp-filename) "Old (with multi-hops) tramp compatability function" (require 'tramp) (if (boundp 'tramp-multi-methods) (tramp-make-tramp-file-name nil nil username remote-host lisp-filename) (tramp-make-tramp-file-name nil username remote-host lisp-filename))) (defun* slime-create-filename-translator (&key machine-instance remote-host username) "Creates a three element list suitable for push'ing onto slime-filename-translations which uses Tramp to load files on hostname using username. MACHINE-INSTANCE is a required parameter, REMOTE-HOST defaults to MACHINE-INSTANCE and USERNAME defaults to (user-login-name). MACHINE-INSTANCE is the value returned by slime-machine-instance, which is just the value returned by cl:machine-instance on the remote lisp. REMOTE-HOST is the fully qualified domain name (or just the IP) of the remote machine. USERNAME is the username we should login with. The functions created here expect your tramp-default-method or tramp-default-method-alist to be setup correctly." (lexical-let ((remote-host (or remote-host machine-instance)) (username (or username (user-login-name)))) (list (concat "^" machine-instance "$") (lambda (emacs-filename) (tramp-file-name-localname (tramp-dissect-file-name emacs-filename))) `(lambda (lisp-filename) (slime-make-tramp-file-name ,username ,remote-host lisp-filename))))) ;;;; Starting SLIME ;;; ;;; This section covers starting an inferior-lisp, compiling and ;;; starting the server, initiating a network connection. ;;;;; Entry points (defvar slime-inferior-lisp-program-history '() "History list of command strings. Used by `slime'.") ;; 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.") (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))) (apply #'slime-start (slime-read-interactive-args)))) (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) (cond (table (slime-lookup-lisp-implementation table (or slime-default-lisp (car (first table))))) (t (destructuring-bind (program &rest args) (split-string inferior-lisp-program) (list :program program :program-args args))))) ((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-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) (let ((args (list :program program :program-args program-args :buffer buffer :coding-system coding-system :init init :name name :init-function init-function))) (slime-check-coding-system coding-system) (when (slime-bytecode-stale-p) (slime-urge-bytecode-recompile)) (let ((proc (slime-maybe-start-lisp program program-args directory buffer))) (slime-inferior-connect proc args) (pop-to-buffer (process-buffer proc))))) (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: " "4005" 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 (lexical-let ((hook nil) (package package) (filename filename)) (setq hook (lambda () (remove-hook 'slime-connected-hook hook) (slime-load-file-set-package filename package))) (add-hook 'slime-connected-hook hook) (slime))))) (defun slime-load-file-set-package (filename package) (let ((filename (slime-to-lisp-filename filename))) (slime-eval-async `(swank:load-file-set-package ,filename ,package) (lambda (package) (when package (slime-repl-set-package (second package))))))) (defmacro define-slime-dialect (name &optional program hook) "Define a command slime-dialect-NAME to start a specific Lisp. PROGRAM is the command to start the inferior process. HOOK is function which is run before the process is started." (let ((funsym (intern (format "slime-dialect-%s" name))) (hooksym (intern (format "slime-dialect-%s-hook" name))) (progsym (intern (format "slime-dialect-%s-program" name)))) `(progn (defvar ,progsym ,program) (defvar ,hooksym ,hook) (defun ,funsym () ,(format "Start up slime according to `%s'." progsym) (interactive) (let ((inferior-lisp-program ,progsym)) (run-hooks ',hooksym) (call-interactively 'slime)))))) ;;;;; 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 directory buffer) "Return a new or existing inferior lisp process." (cond ((not (comint-check-proc buffer)) (slime-start-lisp program program-args directory buffer)) ((slime-reinitialize-inferior-lisp-p program program-args 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 directory (generate-new-buffer-name buffer))))) (defun slime-reinitialize-inferior-lisp-p (program program-args 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) (not (y-or-n-p "Create an additional *inferior-lisp*? "))))) (defun slime-start-lisp (program program-args 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) (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: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 (cond ((fboundp 'temp-directory) (temp-directory)) ((boundp 'temporary-file-directory) temporary-file-directory) (t "/tmp/"))) (format "slime.%S" (emacs-pid)))) (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)) (unless (slime-connected-p) (slime-set-state (format "[polling:%S]" attempt))) (slime-cancel-connect-retry-timer) (cond ((and (file-exists-p file) (> (nth 7 (file-attributes file)) 0)) ; file size (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)) (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))) (setq slime-connect-retry-timer (run-with-timer 0.3 nil #'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))) (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-lisp-host "127.0.0.1" "The default hostname (or IP address) to connect to.") (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) (slime-set-state "[not connected]" 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-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-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 (condition-case error (slime-net-read) (error (slime-net-close process t) (error "net-read error: %S" error))))) (slime-log-event event) (let ((ok nil)) (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-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)) (let ((string (buffer-substring-no-properties start end))) (prog1 (read string) (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 nil) (print-escape-newlines nil)) (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) (error "No default connection selected.")) ((not conn) (error "Not connected.")) ((not (eq (process-status conn) 'open)) (error "Connection closed.")) (t conn)))) (defun slime-select-connection (process) "Make PROCESS the default connection." (setq slime-default-connection process)) (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) (defvar slime-state-name "[??]" "Name of the current state of `slime-default-connection'. Just used for informational display in the mode-line.") (defun slime-set-state (name &optional connection) "Set the current connection's informational state name. If this is the default connection then the state will be displayed in the modeline." (when (or (not (slime-connected-p)) (eq (or connection (slime-connection)) slime-default-connection)) (setq slime-state-name name) (force-mode-line-update))) ;;; 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-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) (with-lexical-bindings (proc) (lambda (info) (slime-set-connection-info proc info))))) (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 &allow-other-keys) info (or (equal version slime-protocol-version) (yes-or-no-p "Protocol version mismatch. Continue anyway? ") (slime-net-close connection) (top-level)) (setf (slime-pid) pid (slime-communication-style) style (slime-lisp-features) features) (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))) (setq slime-state-name "") ; FIXME (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) (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-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-make-default-connection () "Make the current connection the default connection." (interactive) (slime-select-connection (slime-connection)) (message "Connection #%S (%s) now default SLIME connection." (slime-connection-number) (slime-connection-name))) (defun slime-choose-connection () "Return an established connection chosen by the user." (let ((default (slime-connection-name))) (slime-find-connection-by-name (completing-read (format "Connection name (default %s): " default) (slime-bogus-completion-alist (mapcar #'slime-connection-name slime-net-processes)) nil t nil nil default)))) (defun slime-find-connection-by-name (name) (find name slime-net-processes :test #'string= :key #'slime-connection-name)) (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 REASON). 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: it's 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\\>[ \n\t\r']*" "\\([^)]+\\)[ \n\t]*)"))) (save-excursion (when (or (re-search-backward regexp nil t) (re-search-forward regexp nil t)) (let ((string (match-string-no-properties 2))) (cond ((string-match "^\"" string) (ignore-errors (read string))) ((string-match "^#?:" string) (substring string (match-end 0))) (t string))))))) ;;; 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 "tag = %S eval-tags = %S sexp = %S" tag slime-stack-eval-tags sexp)) (throw tag (list #'identity value))) ((:abort &optional reason) (throw tag (list #'error (or reason "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) (sexp (or package (slime-current-package))) ((:ok result) (when cont (funcall cont result))) ((:abort &optional reason) (message (or reason "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-busy-p () "True if Lisp has outstanding requests. Debugged requests are ignored." (let ((debugged (sldb-debugged-continuations (slime-connection)))) (remove-if (lambda (id) (memq id debugged)) (slime-rex-continuations) :key #'car))) (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.") (defun slime-dispatch-event (event &optional process) (let ((slime-dispatching-connection (or process (slime-connection)))) (destructure-case event ((:write-string output &optional id target) (slime-write-string output id target)) ((:presentation-start id) (slime-mark-presentation-start id)) ((:presentation-end id) (slime-mark-presentation-end id)) ;; ((:emacs-rex form package thread continuation) (slime-set-state "|eval...") (when (and (slime-use-sigint-for-interrupt) (slime-busy-p)) (message "; 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)))) ((:return value id) (let ((rec (assq id (slime-rex-continuations)))) (cond (rec (setf (slime-rex-continuations) (remove rec (slime-rex-continuations))) (when (null (slime-rex-continuations)) (slime-set-state "")) (funcall (cdr rec) value)) (t (error "Unexpected reply: %S %S" id value))))) ((:debug-activate thread level) (assert thread) (sldb-activate thread level)) ((: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))))) (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 (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.") (defcustom slime-header-line-p t "If non-nil, display a header line in Slime buffers." :type 'boolean :group 'slime-repl) (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.")) (defun slime-reset-repl-markers () (dolist (markname '(slime-output-start slime-output-end slime-repl-prompt-start-mark slime-repl-input-start-mark slime-repl-input-end-mark slime-repl-last-input-start-mark)) (set markname (make-marker)) (set-marker (symbol-value markname) (point))) (set-marker-insertion-type slime-repl-input-end-mark t) (set-marker-insertion-type slime-output-end t) (set-marker-insertion-type slime-repl-prompt-start-mark t)) (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))))))) (defun slime-repl-update-banner () (let* ((banner (format "%s Port: %s Pid: %s" (slime-lisp-implementation-type) (slime-connection-port (slime-connection)) (slime-pid))) ;; Emacs21 has the fancy persistent header-line. (use-header-p (and slime-header-line-p (boundp 'header-line-format))) ;; and dancing text (animantep (and (fboundp 'animate-string) slime-startup-animation))) (when use-header-p (setq header-line-format banner)) (when (zerop (buffer-size)) (let ((hello-message (concat "; SLIME " (or (slime-changelog-date) "- ChangeLog file not found")))) (if animantep (animate-string hello-message 0 0) (insert hello-message)))) (pop-to-buffer (current-buffer)) (slime-repl-insert-prompt))) (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))) (defvar slime-show-last-output-function 'slime-maybe-display-output-buffer "*This function is called when a evaluation request is finished. It is called in the slime-output buffer and receives the region of the output as arguments.") (defun slime-show-last-output-region (start end) (when (< start end) (slime-display-buffer-region (current-buffer) (1- start) slime-repl-input-start-mark))) (defun slime-maybe-display-output-buffer (start end) (when (and (< start end) (not (get-buffer-window (current-buffer) t))) (display-buffer (current-buffer))) (when (eobp) (slime-repl-show-maximum-output t))) (defun slime-show-last-output () "Show the output from the last Lisp evaluation." (with-current-buffer (slime-output-buffer) (let ((start slime-output-start) (end slime-output-end)) (funcall slime-show-last-output-function start end)))) (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..) (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 (> (- slime-output-end slime-output-start) 1000))))) (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)))) ;; FIXME: This conditional is not right - just used because the code ;; here does not work in XEmacs. (when slime-repl-enable-presentations (when (boundp 'text-property-default-nonsticky) (pushnew '(slime-repl-presentation . t) text-property-default-nonsticky :test 'equal) (pushnew '(slime-repl-result-face . t) text-property-default-nonsticky :test 'equal))) (make-variable-buffer-local (defvar slime-presentation-start-to-point (make-hash-table))) (defun slime-mark-presentation-start (id) (setf (gethash id slime-presentation-start-to-point) (with-current-buffer (slime-output-buffer) (marker-position (symbol-value 'slime-output-end))))) (defun slime-mark-presentation-start-handler (process string) (if (and string (string-match "<\\([-0-9]+\\)" string)) (let* ((match (substring string (match-beginning 1) (match-end 1))) (id (car (read-from-string match)))) (slime-mark-presentation-start id)))) (defun slime-mark-presentation-end (id) (let ((start (gethash id slime-presentation-start-to-point))) (remhash id slime-presentation-start-to-point) (when start (with-current-buffer (slime-output-buffer) (slime-add-presentation-properties start (symbol-value 'slime-output-end) id nil))))) (defun slime-mark-presentation-end-handler (process string) (if (and string (string-match ">\\([-0-9]+\\)" string)) (let* ((match (substring string (match-beginning 1) (match-end 1))) (id (car (read-from-string match)))) (slime-mark-presentation-end id)))) (defstruct slime-presentation text id) (defvar slime-presentation-syntax-table (let ((table (copy-syntax-table lisp-mode-syntax-table))) ;; We give < and > parenthesis syntax, so that #< ... > is treated ;; as a balanced expression. This allows to use C-M-k, C-M-SPC, ;; etc. to deal with a whole presentation. (For Lisp mode, this ;; is not desirable, since we do not wish to get a mismatched ;; paren highlighted everytime we type < or >.) (modify-syntax-entry ?< "(>" table) (modify-syntax-entry ?> ")<" table) table) "Syntax table for presentations.") (defun slime-add-presentation-properties (start end id result-p) "Make the text between START and END a presentation with ID. RESULT-P decides whether a face for a return value or output text is used." (let* ((text (buffer-substring-no-properties start end)) (presentation (make-slime-presentation :text text :id id))) (let ((inhibit-modification-hooks t)) (add-text-properties start end `(modification-hooks (slime-after-change-function) insert-in-front-hooks (slime-after-change-function) insert-behind-hooks (slime-after-change-function) syntax-table ,slime-presentation-syntax-table rear-nonsticky t)) ;; Use the presentation as the key of a text property (case (- end start) (0) (1 (add-text-properties start end `(slime-repl-presentation ,presentation ,presentation :start-and-end))) (t (add-text-properties start (1+ start) `(slime-repl-presentation ,presentation ,presentation :start)) (when (> (- end start) 2) (add-text-properties (1+ start) (1- end) `(,presentation :interior))) (add-text-properties (1- end) end `(slime-repl-presentation ,presentation ,presentation :end)))) ;; Also put an overlay for the face and the mouse-face. This enables ;; highlighting of nested presentations. However, overlays get lost ;; when we copy a presentation; their removal is also not undoable. ;; In these cases the mouse-face text properties need to take over --- ;; but they do not give nested highlighting. (slime-ensure-presentation-overlay start end presentation)))) (defun slime-ensure-presentation-overlay (start end presentation) (unless (find presentation (overlays-at start) :key (lambda (overlay) (overlay-get overlay 'slime-repl-presentation))) (let ((overlay (make-overlay start end (current-buffer) t nil))) (overlay-put overlay 'slime-repl-presentation presentation) (overlay-put overlay 'mouse-face 'slime-repl-output-mouseover-face) (overlay-put overlay 'help-echo (if (eq major-mode 'slime-repl-mode) "mouse-2: copy to input; mouse-3: menu" "mouse-2: inspect; mouse-3: menu")) (overlay-put overlay 'face 'slime-repl-inputed-output-face) (overlay-put overlay 'keymap slime-presentation-map)))) (defun slime-remove-presentation-properties (from to presentation) (let ((inhibit-read-only t)) (remove-text-properties from to `(,presentation t syntax-table t rear-nonsticky t)) (when (eq (get-text-property from 'slime-repl-presentation) presentation) (remove-text-properties from (1+ from) `(slime-repl-presentation t))) (when (eq (get-text-property (1- to) 'slime-repl-presentation) presentation) (remove-text-properties (1- to) to `(slime-repl-presentation t))) (dolist (overlay (overlays-at from)) (when (eq (overlay-get overlay 'slime-repl-presentation) presentation) (delete-overlay overlay))))) (defun slime-insert-possibly-as-rectangle (&rest strings) (if (zerop (current-column)) (apply #'insert strings) (dolist (string strings) (if (string= string "\n") (newline) (let ((lines (split-string string "\n"))) (when (rest lines) (save-excursion (dotimes (i (length lines)) (newline)))) (insert-rectangle lines) (when (rest lines) (forward-char 1) (delete-backward-char 1))))))) (defun slime-insert-presentation (string output-id) (cond ((not slime-repl-enable-presentations) (slime-insert-possibly-as-rectangle string)) (t (let ((start (point))) (slime-insert-possibly-as-rectangle string) (slime-add-presentation-properties start (point) output-id t))))) (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) (when slime-repl-enable-presentations (require 'bridge) (defun bridge-insert (process output) (slime-output-filter process (or output ""))) (install-bridge) (setq bridge-destination-insert nil) (setq bridge-source-insert nil) (setq bridge-handlers (list* '("<" . slime-mark-presentation-start-handler) '(">" . slime-mark-presentation-end-handler) bridge-handlers))) (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)) 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)) ;;(display-buffer (slime-output-buffer)) (delete-other-windows) (sit-for 0) (slime-repl-send-string "(swank:io-speed-test 5000 1)") (let ((proc (slime-inferior-process))) (when proc (switch-to-buffer (process-buffer proc)) (goto-char (point-max))))) (defun slime-write-string (string &optional id target) "Insert STRING in the REPL buffer. If ID is non-nil, insert STRING as a presentation. 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 are reserved for future extension, ;; for instance asynchronous output in scratch buffers. --mkoeppe (ecase target ((nil) ; Regular process output (with-current-buffer (slime-output-buffer) (slime-with-output-end-mark (if id (slime-insert-presentation string id) (slime-propertize-region '(face slime-repl-output-face) (insert string))) (when (and (= (point) slime-repl-prompt-start-mark) (not (bolp))) (insert "\n") (set-marker slime-output-end (1- (point))))))) (:repl-result (with-current-buffer (slime-output-buffer) (goto-char (point-max)) (let ((result-start (point))) (if id (slime-insert-presentation string id) (slime-propertize-region `(face slime-repl-result-face) (insert string))) (if (>= (marker-position slime-output-end) (point)) ;; If the output-end marker was moved by our insertion, ;; set it back to the beginning of the REPL result. (set-marker slime-output-end result-start))))))) (defun slime-switch-to-output-buffer (&optional connection) "Select the output buffer, preferably in a different window." (interactive (list (if prefix-arg (slime-choose-connection)))) (let ((slime-dispatching-connection (or connection slime-dispatching-connection))) (set-buffer (slime-output-buffer)) (unless (eq (current-buffer) (window-buffer)) (pop-to-buffer (current-buffer) t)) (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 input-end ;; ;; output-start and input-start are right inserting markers; ;; output-end and input-end left inserting. ;; ;; We maintain the following invariant: ;; ;; output-start <= output-end <= input-start <= input-end. ;; ;; 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 blindly insert at that point, we break the invariant stated ;; above, because the output-end marker is left inserting. The macro ;; `slime-with-output-end-mark' handles this complication by moving ;; the input-start marker to an appropriate place. The macro also ;; updates window-point if necessary, and tries to keep the prompt in ;; the first column by inserting a newline. ;; ;; A "synchronous" evaluation request proceeds as follows: the user ;; inserts some text between input-start and input-end and then hits ;; return. We send the text between the input markers 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. ;; `slime-repl-insert-prompt' does this. ;; ;; 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). Output that doesn't belong the ;; evaluation request should not be inserted before the result, but ;; immediately before the prompt. To achieve this, we move the ;; output-end mark to prompt-start after a short delay (by starting a ;; timer in `slime-repl-insert-prompt'). In summary: synchronous ;; output should go before the result, asynchronous 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-input-end-mark) (defvar slime-repl-last-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.")) ;;;;; 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) ("\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) ("\C-e" 'slime-repl-eol) ("\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\C-t" 'slime-repl-clear-buffer) ("\C-c\C-u" 'slime-repl-kill-input) ("\C-c\C-n" 'slime-repl-next-prompt) ("\C-c\C-p" 'slime-repl-previous-prompt) ("\M-\C-a" 'slime-repl-beginning-of-defun) ("\M-\C-e" 'slime-repl-end-of-defun) ("\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) (slime-repl-safe-load-history) (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) (when slime-use-autodoc-mode (slime-autodoc-mode 1)) (when slime-repl-enable-presentations ;; Respect the syntax text properties of presentations. (set (make-local-variable 'parse-sexp-lookup-properties) t)) ;; We only want REPL prompts as start of the "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-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-presentation-whole-p (presentation start end &optional object) (let ((object (or object (current-buffer)))) (string= (etypecase object (buffer (with-current-buffer object (buffer-substring-no-properties start end))) (string (substring-no-properties object start end))) (slime-presentation-text presentation)))) (defun slime-presentations-around-point (point &optional object) (let ((object (or object (current-buffer)))) (loop for (key value . rest) on (text-properties-at point object) by 'cddr when (slime-presentation-p key) collect key))) (defun slime-presentation-start-p (tag) (memq tag '(:start :start-and-end))) (defun slime-presentation-stop-p (tag) (memq tag '(:end :start-and-end))) (defun* slime-presentation-start (point presentation &optional (object (current-buffer))) "Find start of `presentation' at `point' in `object'. Return buffer index and whether a start-tag was found." (let* ((this-presentation (get-text-property point presentation object))) (while (not (slime-presentation-start-p this-presentation)) (let ((change-point (previous-single-property-change point presentation object))) (unless change-point (return-from slime-presentation-start (values (etypecase object (buffer (with-current-buffer object 1)) (string 0)) nil))) (setq this-presentation (get-text-property change-point presentation object)) (unless this-presentation (return-from slime-presentation-start (values point nil))) (setq point change-point))) (values point t))) (defun* slime-presentation-end (point presentation &optional (object (current-buffer))) "Find end of presentation at `point' in `object'. Return buffer index (after last character of the presentation) and whether an end-tag was found." (let* ((this-presentation (get-text-property point presentation object))) (while (not (slime-presentation-stop