dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

sly.el (295256B)


      1 ;;; sly.el --- Sylvester the Cat's Common Lisp IDE  -*- lexical-binding: t; -*-
      2 
      3 ;; Version: 1.0.43
      4 ;; URL: https://github.com/joaotavora/sly
      5 ;; Package-Requires: ((emacs "24.3"))
      6 ;; Keywords: languages, lisp, sly
      7 
      8 ;;     Copyright (C) 2003  Eric Marsden, Luke Gorrie, Helmut Eller
      9 ;;     Copyright (C) 2004,2005,2006  Luke Gorrie, Helmut Eller
     10 ;;     Copyright (C) 2007,2008,2009  Helmut Eller, Tobias C. Rittweiler
     11 ;;     Copyright (C) 2014 João Távora
     12 ;;     For a detailed list of contributors, see the manual.
     13 
     14 ;; This program is free software: you can redistribute it and/or modify
     15 ;; it under the terms of the GNU General Public License as published by
     16 ;; the Free Software Foundation, either version 3 of the License, or
     17 ;; (at your option) any later version.
     18 
     19 ;; This program is distributed in the hope that it will be useful,
     20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     22 ;; GNU General Public License for more details.
     23 
     24 ;; You should have received a copy of the GNU General Public License
     25 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     26 
     27 ;;; Commentary:
     28 ;;
     29 ;;        _____    __   __  __
     30 ;;       / ___/   / /   \ \/ /               |\      _,,,---,,_
     31 ;;       \__ \   / /     \  /                /,`.-'`'    -.  ;-;;,_
     32 ;;      ___/ /  / /___   / /                |,4-  ) )-,_..;\ (  `'-'
     33 ;;     /____/  /_____/  /_/                '---''(_/--'  `-'\_)
     34 ;;
     35 ;;
     36 ;; SLY is Sylvester the Cat's Common Lisp IDE.
     37 ;;
     38 ;; SLY is a direct fork of SLIME, and contains the following
     39 ;; improvements over it:
     40 ;;
     41 ;; * A full-featured REPL based on Emacs's `comint.el`;
     42 ;; * Live code annotations via a new `sly-stickers` contrib;
     43 ;; * Consistent button interface. Every Lisp object can be copied to the REPL;
     44 ;; * flex-style completion out-of-the-box, using  Emacs's completion API.
     45 ;;   Company, Helm, and others supported natively, no plugin required;
     46 ;; * Cleanly ASDF-loaded by default, including contribs, enabled out-of-the-box;
     47 ;; * Multiple inspectors and multiple REPLs;
     48 ;; * An interactive trace dialog with interactive objects.  Copies function calls
     49 ;;   to the REPL;
     50 ;; * "Presentations" replaced by interactive backreferences which
     51 ;;   highlight the object and remain stable throughout the REPL session;
     52 ;;
     53 ;; SLY is a fork of SLIME. We track its bugfixes, particularly to the
     54 ;; implementation backends.  All SLIME's familar features (debugger,
     55 ;; inspector, xref, etc...) are still available, with improved overall
     56 ;; UX.
     57 ;;
     58 ;; See the NEWS.md file (should be sitting alongside this file) for
     59 ;; more information
     60 
     61 ;;; Code:
     62 
     63 (require 'cl-lib)
     64 
     65 (eval-and-compile
     66   (if (version< emacs-version "24.3")
     67       (error "Sly requires at least Emacs 24.3")))
     68 
     69 (eval-and-compile
     70   (or (require 'hyperspec nil t)
     71       (require 'hyperspec "lib/hyperspec")))
     72 (require 'thingatpt)
     73 (require 'comint)
     74 (require 'pp)
     75 (require 'easymenu)
     76 (require 'arc-mode)
     77 (require 'etags)
     78 (require 'apropos)
     79 (require 'bytecomp) ;; for `byte-compile-current-file' and
     80 ;; `sly-byte-compile-hotspots'.
     81 
     82 (require 'sly-common     "lib/sly-common")
     83 (require 'sly-messages   "lib/sly-messages")
     84 (require 'sly-buttons    "lib/sly-buttons")
     85 (require 'sly-completion "lib/sly-completion")
     86 
     87 (require 'gv) ; for gv--defsetter
     88 
     89 (eval-when-compile
     90   (require 'compile)
     91   (require 'gud))
     92 
     93 (defvar sly-path nil
     94   "Directory containing the SLY package.
     95 This is used to load the supporting Common Lisp library, Slynk.
     96 The default value is automatically computed from the location of the
     97 Emacs Lisp package.")
     98 
     99 ;; Determine `sly-path' at load time, regardless of filename (.el or
    100 ;; .elc) being loaded.
    101 ;;
    102 (setq sly-path
    103       (if load-file-name
    104           (file-name-directory load-file-name)
    105         (error "[sly] fatal: impossible to determine sly-path")))
    106 
    107 (defun sly-slynk-path ()
    108   "Path where the bundled Slynk server is located."
    109   (expand-file-name "slynk/" sly-path))
    110 
    111 ;;;###autoload
    112 (define-obsolete-variable-alias 'sly-setup-contribs
    113   'sly-contribs "2.3.2")
    114 ;;;###autoload
    115 (defvar sly-contribs '(sly-fancy)
    116   "A list of contrib packages to load with SLY.")
    117 
    118 ;;;###autoload
    119 (defun sly-setup (&optional contribs)
    120   "Have SLY load and use extension modules CONTRIBS.
    121 CONTRIBS defaults to `sly-contribs' and is a list (LIB1 LIB2...)
    122 symbols of `provide'd and `require'd Elisp libraries.
    123 
    124 If CONTRIBS is nil, `sly-contribs' is *not* affected, otherwise
    125 it is set to CONTRIBS.
    126 
    127 However, after `require'ing LIB1, LIB2 ..., this command invokes
    128 additional initialization steps associated with each element
    129 LIB1, LIB2, which can theoretically be reverted by
    130 `sly-disable-contrib.'
    131 
    132 Notably, one of the extra initialization steps is affecting the
    133 value of `sly-required-modules' (which see) thus affecting the
    134 libraries loaded in the Slynk servers.
    135 
    136 If SLY is currently connected to a Slynk and a contrib in
    137 CONTRIBS has never been loaded, that Slynk is told to load the
    138 associated Slynk extension module.
    139 
    140 To ensure that a particular contrib is loaded, use
    141 `sly-enable-contrib' instead."
    142   ;; FIXME: The contract should be like some hypothetical
    143   ;; `sly-refresh-contribs'
    144   ;;
    145   (interactive)
    146   (when contribs
    147     (setq sly-contribs contribs))
    148   (sly--setup-contribs))
    149 
    150 (defvaralias 'sly-required-modules 'sly-contrib--required-slynk-modules)
    151 
    152 (defvar sly-contrib--required-slynk-modules '()
    153   "Alist of (MODULE . (WHERE CONTRIB)) for slynk-provided features.
    154 
    155 MODULE is a symbol naming a specific Slynk feature, WHERE is
    156 the full pathname to the directory where the file(s)
    157 providing the feature are found and CONTRIB is a symbol as found
    158 in `sly-contribs.'")
    159 
    160 (cl-defmacro sly--contrib-safe (contrib &body body)
    161   "Run BODY catching and resignalling any errors for CONTRIB"
    162   (declare (indent 1))
    163   `(condition-case-unless-debug e
    164        (progn
    165          ,@body)
    166      (error (sly-error "There's an error in %s: %s"
    167                        ,contrib
    168                        e))))
    169 
    170 (defun sly--setup-contribs ()
    171   "Load and initialize contribs."
    172   ;; active    != enabled
    173   ;;   ^            ^
    174   ;;   |            |
    175   ;;   v            v
    176   ;; forgotten != disabled
    177   (add-to-list 'load-path (expand-file-name "contrib" sly-path))
    178   (mapc (lambda (c)
    179           (sly--contrib-safe c (require c)))
    180         sly-contribs)
    181   (let* ((all-active-contribs
    182           ;; these are the contribs the user chose to activate
    183           ;;
    184           (mapcar #'sly-contrib--find-contrib
    185                   (cl-reduce #'append (mapcar #'sly-contrib--all-dependencies
    186                                               sly-contribs))))
    187          (defined-but-forgotten-contribs
    188            ;; "forgotten contribs" are the ones the chose not to
    189            ;; activate but whose definitions we have seen
    190            ;;
    191            (cl-remove-if #'(lambda (contrib)
    192                              (memq contrib all-active-contribs))
    193                          (sly-contrib--all-contribs))))
    194     ;; Disable any forgotten contribs that are enabled right now.
    195     ;;
    196     (cl-loop for to-disable in defined-but-forgotten-contribs
    197              when (sly--contrib-safe to-disable
    198                     (sly-contrib--enabled-p to-disable))
    199              do (funcall (sly-contrib--disable to-disable)))
    200     ;; Enable any active contrib that is *not* enabled right now.
    201     ;;
    202     (cl-loop for to-enable in all-active-contribs
    203              unless (sly--contrib-safe to-enable
    204                       (sly-contrib--enabled-p to-enable))
    205              do (funcall (sly-contrib--enable to-enable)))
    206     ;; Some contribs add stuff to `sly-mode-hook' or
    207     ;; `sly-editing-hook', so make sure we re-run those hooks now.
    208     (when all-active-contribs
    209       (defvar sly-editing-mode)         ;FIXME: Forward reference!
    210       (cl-loop for buffer in (buffer-list)
    211                do (with-current-buffer buffer
    212                     (when sly-editing-mode (sly-editing-mode 1)))))))
    213 
    214 (eval-and-compile
    215   (defun sly-version (&optional interactive file)
    216     "Read SLY's version of its own sly.el file.
    217 If FILE is passed use that instead to discover the version."
    218     (interactive "p")
    219     (let ((version
    220            (with-temp-buffer
    221              (insert-file-contents
    222               (or file
    223                   (expand-file-name "sly.el" sly-path))
    224               nil 0 200)
    225              (and (search-forward-regexp
    226                    ";;[[:space:]]*Version:[[:space:]]*\\(.*\\)$" nil t)
    227                   (match-string 1)))))
    228       (if interactive
    229           (sly-message "SLY %s" version)
    230         version))))
    231 
    232 (defvar sly-protocol-version nil)
    233 
    234 (setq sly-protocol-version
    235       ;; Compile the version string into the generated .elc file, but
    236       ;; don't actualy affect `sly-protocol-version' until load-time.
    237       ;;
    238       (eval-when-compile (sly-version nil (or load-file-name
    239                                               byte-compile-current-file))))
    240 
    241 
    242 ;;;; Customize groups
    243 ;;
    244 ;;;;; sly
    245 
    246 (defgroup sly nil
    247   "Interaction with the Superior Lisp Environment."
    248   :prefix "sly-"
    249   :group 'applications)
    250 
    251 ;;;;; sly-ui
    252 
    253 (defgroup sly-ui nil
    254   "Interaction with the Superior Lisp Environment."
    255   :prefix "sly-"
    256   :group 'sly)
    257 
    258 (defcustom sly-truncate-lines t
    259   "Set `truncate-lines' in popup buffers.
    260 This applies to buffers that present lines as rows of data, such as
    261 debugger backtraces and apropos listings."
    262   :type 'boolean
    263   :group 'sly-ui)
    264 
    265 (defcustom sly-kill-without-query-p nil
    266   "If non-nil, kill SLY processes without query when quitting Emacs.
    267 This applies to the *inferior-lisp* buffer and the network connections."
    268   :type 'boolean
    269   :group 'sly-ui)
    270 
    271 ;;;;; sly-lisp
    272 
    273 (defgroup sly-lisp nil
    274   "Lisp server configuration."
    275   :prefix "sly-"
    276   :group 'sly)
    277 
    278 (defcustom sly-ignore-protocol-mismatches nil
    279   "If non-nil, ignore protocol mismatches between SLY and Slynk.
    280 Programatically, this variable can be let-bound around calls to
    281 `sly' or `sly-connect'."
    282   :type 'boolean
    283   :group 'sly)
    284 
    285 (defcustom sly-init-function 'sly-init-using-asdf
    286   "Function bootstrapping slynk on the remote.
    287 
    288 Value is a function of two arguments: SLYNK-PORTFILE and an
    289 ingored argument for backward compatibility. Function should
    290 return a string issuing very first commands issued by Sly to
    291 the remote-connection process. Some time after this there should
    292 be a port number ready in SLYNK-PORTFILE."
    293   :type '(choice (const :tag "Use ASDF"
    294                         sly-init-using-asdf)
    295                  (const :tag "Use legacy slynk-loader.lisp"
    296                         sly-init-using-slynk-loader))
    297   :group 'sly-lisp)
    298 
    299 (define-obsolete-variable-alias 'sly-backend
    300   'sly-slynk-loader-backend "3.0")
    301 
    302 (defcustom sly-slynk-loader-backend "slynk-loader.lisp"
    303   "The name of the slynk-loader that loads the Slynk server.
    304 Only applicable if `sly-init-function' is set to
    305 `sly-init-using-slynk-loader'. This name is interpreted
    306 relative to the directory containing sly.el, but could also be
    307 set to an absolute filename."
    308   :type 'string
    309   :group 'sly-lisp)
    310 
    311 (defcustom sly-connected-hook nil
    312   "List of functions to call when SLY connects to Lisp."
    313   :type 'hook
    314   :group 'sly-lisp)
    315 
    316 (defcustom sly-enable-evaluate-in-emacs nil
    317   "*If non-nil, the inferior Lisp can evaluate arbitrary forms in Emacs.
    318 The default is nil, as this feature can be a security risk."
    319   :type '(boolean)
    320   :group 'sly-lisp)
    321 
    322 (defcustom sly-lisp-host "localhost"
    323   "The default hostname (or IP address) to connect to."
    324   :type 'string
    325   :group 'sly-lisp)
    326 
    327 (defcustom sly-port 4005
    328   "Port to use as the default for `sly-connect'."
    329   :type 'integer
    330   :group 'sly-lisp)
    331 
    332 (defvar sly-connect-host-history (list sly-lisp-host))
    333 (defvar sly-connect-port-history (list (prin1-to-string sly-port)))
    334 
    335 (defvar sly-net-valid-coding-systems
    336   '((iso-latin-1-unix nil "iso-latin-1-unix")
    337     (iso-8859-1-unix  nil "iso-latin-1-unix")
    338     (binary           nil "iso-latin-1-unix")
    339     (utf-8-unix       t   "utf-8-unix")
    340     (emacs-mule-unix  t   "emacs-mule-unix")
    341     (euc-jp-unix      t   "euc-jp-unix"))
    342   "A list of valid coding systems.
    343 Each element is of the form: (NAME MULTIBYTEP CL-NAME)")
    344 
    345 (defun sly-find-coding-system (name)
    346   "Return the coding system for the symbol NAME.
    347 The result is either an element in `sly-net-valid-coding-systems'
    348 of nil."
    349   (let ((probe (assq name sly-net-valid-coding-systems)))
    350     (when (and probe (if (fboundp 'check-coding-system)
    351                          (ignore-errors (check-coding-system (car probe)))
    352                        (eq (car probe) 'binary)))
    353       probe)))
    354 
    355 (defcustom sly-net-coding-system
    356   (car (cl-find-if 'sly-find-coding-system
    357                    sly-net-valid-coding-systems :key 'car))
    358   "Coding system used for network connections.
    359 See also `sly-net-valid-coding-systems'."
    360   :type (cons 'choice
    361               (mapcar (lambda (x)
    362                         (list 'const (car x)))
    363                       sly-net-valid-coding-systems))
    364   :group 'sly-lisp)
    365 
    366 ;;;;; sly-mode
    367 
    368 (defgroup sly-mode nil
    369   "Settings for sly-mode Lisp source buffers."
    370   :prefix "sly-"
    371   :group 'sly)
    372 
    373 ;;;;; sly-mode-faces
    374 
    375 (defgroup sly-mode-faces nil
    376   "Faces in sly-mode source code buffers."
    377   :prefix "sly-"
    378   :group 'sly-mode)
    379 
    380 (defface sly-error-face
    381   `((((class color) (background light))
    382      (:underline "tomato"))
    383     (((class color) (background dark))
    384      (:underline "tomato"))
    385     (t (:underline t)))
    386   "Face for errors from the compiler."
    387   :group 'sly-mode-faces)
    388 
    389 (defface sly-warning-face
    390   `((((class color) (background light))
    391      (:underline "orange"))
    392     (((class color) (background dark))
    393      (:underline "coral"))
    394     (t (:underline t)))
    395   "Face for warnings from the compiler."
    396   :group 'sly-mode-faces)
    397 
    398 (defface sly-style-warning-face
    399   `((((class color) (background light))
    400      (:underline "olive drab"))
    401     (((class color) (background dark))
    402      (:underline "khaki"))
    403     (t (:underline t)))
    404   "Face for style-warnings from the compiler."
    405   :group 'sly-mode-faces)
    406 
    407 (defface sly-note-face
    408   `((((class color) (background light))
    409      (:underline "brown3"))
    410     (((class color) (background dark))
    411      (:underline "light goldenrod"))
    412     (t (:underline t)))
    413   "Face for notes from the compiler."
    414   :group 'sly-mode-faces)
    415 
    416 ;;;;; sly-db
    417 
    418 (defgroup sly-debugger nil
    419   "Backtrace options and fontification."
    420   :prefix "sly-db-"
    421   :group 'sly)
    422 
    423 (defmacro define-sly-db-faces (&rest faces)
    424   "Define the set of SLY-DB faces.
    425 Each face specifiation is (NAME DESCRIPTION &optional PROPERTIES).
    426 NAME is a symbol; the face will be called sly-db-NAME-face.
    427 DESCRIPTION is a one-liner for the customization buffer.
    428 PROPERTIES specifies any default face properties."
    429   `(progn ,@(cl-loop for face in faces
    430                      collect `(define-sly-db-face ,@face))))
    431 
    432 (defmacro define-sly-db-face (name description &optional default)
    433   (let ((facename (intern (format "sly-db-%s-face" (symbol-name name)))))
    434     `(defface ,facename
    435        (list (list t ,default))
    436        ,(format "Face for %s." description)
    437        :group 'sly-debugger)))
    438 
    439 (define-sly-db-faces
    440   (topline        "the top line describing the error")
    441   (condition "the condition class" '(:inherit error))
    442   (section        "the labels of major sections in the debugger buffer"
    443                   '(:inherit header-line))
    444   (frame-label    "backtrace frame numbers"
    445                   '(:inherit shadow))
    446   (restart        "restart descriptions")
    447   (restart-number "restart numbers (correspond to keystrokes to invoke)"
    448                   '(:inherit shadow))
    449   (frame-line     "function names and arguments in the backtrace")
    450   (restartable-frame-line
    451    "frames which are surely restartable"
    452    '(:inherit font-lock-constant-face))
    453   (non-restartable-frame-line
    454    "frames which are surely not restartable")
    455   (local-name     "local variable names")
    456   (catch-tag      "catch tags"))
    457 
    458 
    459 ;;;;; Key bindings
    460 (defvar sly-doc-map
    461   (let ((map (make-sparse-keymap)))
    462     (define-key map (kbd "C-a") 'sly-apropos)
    463     (define-key map (kbd "C-z") 'sly-apropos-all)
    464     (define-key map (kbd "C-p") 'sly-apropos-package)
    465     (define-key map (kbd "C-d") 'sly-describe-symbol)
    466     (define-key map (kbd "C-f") 'sly-describe-function)
    467     (define-key map (kbd "C-h") 'sly-documentation-lookup)
    468     (define-key map (kbd "~") 'common-lisp-hyperspec-format)
    469     (define-key map (kbd "C-g") 'common-lisp-hyperspec-glossary-term)
    470     (define-key map (kbd "#") 'common-lisp-hyperspec-lookup-reader-macro)
    471     map))
    472 
    473 (defvar sly-who-map
    474   (let ((map (make-sparse-keymap)))
    475     (define-key map (kbd "C-c") 'sly-who-calls)
    476     (define-key map (kbd "C-w") 'sly-calls-who)
    477     (define-key map (kbd "C-r") 'sly-who-references)
    478     (define-key map (kbd "C-b") 'sly-who-binds)
    479     (define-key map (kbd "C-s") 'sly-who-sets)
    480     (define-key map (kbd "C-m") 'sly-who-macroexpands)
    481     (define-key map (kbd "C-a") 'sly-who-specializes)
    482     map))
    483 
    484 (defvar sly-selector-map (let ((map (make-sparse-keymap)))
    485                            (define-key map "c" 'sly-list-connections)
    486                            (define-key map "t" 'sly-list-threads)
    487                            (define-key map "d" 'sly-db-pop-to-debugger-maybe)
    488                            (define-key map "e" 'sly-pop-to-events-buffer)
    489                            (define-key map "i" 'sly-inferior-lisp-buffer)
    490                            (define-key map "l" 'sly-switch-to-most-recent)
    491                            map)
    492   "A keymap for frequently used SLY shortcuts.
    493 Access to this keymap can be installed in in
    494 `sly-mode-map', using something like
    495 
    496    (global-set-key (kbd \"C-z\") sly-selector-map)
    497 
    498 This will bind C-z to this prefix map, one keystroke away from
    499 the available shortcuts:
    500 
    501 \\{sly-selector-map}
    502 As usual, users or extensions can plug in
    503 any command into it using
    504 
    505   (define-key sly-selector-map (kbd \"k\") 'sly-command)
    506 
    507 Where \"k\" is the key to bind and \"sly-command\" is any
    508 interactive command.\".")
    509 
    510 (defvar sly-prefix-map
    511   (let ((map (make-sparse-keymap)))
    512     (define-key map (kbd "C-r")   'sly-eval-region)
    513     (define-key map (kbd ":")     'sly-interactive-eval)
    514     (define-key map (kbd "C-e")   'sly-interactive-eval)
    515     (define-key map (kbd "E")     'sly-edit-value)
    516     (define-key map (kbd "C-l")   'sly-load-file)
    517     (define-key map (kbd "C-b")   'sly-interrupt)
    518     (define-key map (kbd "M-d")   'sly-disassemble-symbol)
    519     (define-key map (kbd "C-t")   'sly-toggle-trace-fdefinition)
    520     (define-key map (kbd "I")     'sly-inspect)
    521     (define-key map (kbd "C-x t") 'sly-list-threads)
    522     (define-key map (kbd "C-x n") 'sly-next-connection)
    523     (define-key map (kbd "C-x c") 'sly-list-connections)
    524     (define-key map (kbd "C-x p") 'sly-prev-connection)
    525     (define-key map (kbd "<")     'sly-list-callers)
    526     (define-key map (kbd ">")     'sly-list-callees)
    527     ;; Include DOC keys...
    528     (define-key map (kbd "C-d")  sly-doc-map)
    529     ;; Include XREF WHO-FOO keys...
    530     (define-key map (kbd "C-w")  sly-who-map)
    531     ;; `sly-selector-map' used to be bound to "C-c C-s" by default,
    532     ;; but sly-stickers has a better binding for that.
    533     ;;
    534     ;; (define-key map (kbd "C-s") sly-selector-map)
    535     map))
    536 
    537 (defvar sly-mode-map
    538   (let ((map (make-sparse-keymap)))
    539     ;; These used to be a `sly-parent-map'
    540     (define-key map (kbd "M-.")     'sly-edit-definition)
    541     (define-key map (kbd "M-,")     'sly-pop-find-definition-stack)
    542     (define-key map (kbd "M-_")     'sly-edit-uses)    ; for German layout
    543     (define-key map (kbd "M-?")     'sly-edit-uses)    ; for USian layout
    544     (define-key map (kbd "C-x 4 .") 'sly-edit-definition-other-window)
    545     (define-key map (kbd "C-x 5 .") 'sly-edit-definition-other-frame)
    546     (define-key map (kbd "C-x C-e") 'sly-eval-last-expression)
    547     (define-key map (kbd "C-M-x")   'sly-eval-defun)
    548     ;; Include PREFIX keys...
    549     (define-key map (kbd "C-c")     sly-prefix-map)
    550     ;; Completion
    551     (define-key map (kbd "C-c TAB") 'completion-at-point)
    552     ;; Evaluating
    553     (define-key map (kbd "C-c C-p") 'sly-pprint-eval-last-expression)
    554     ;; Macroexpand
    555     (define-key map (kbd "C-c C-m") 'sly-expand-1)
    556     (define-key map (kbd "C-c M-m") 'sly-macroexpand-all)
    557     ;; Misc
    558     (define-key map (kbd "C-c C-u") 'sly-undefine-function)
    559     map))
    560 
    561 (defvar sly-editing-mode-map
    562   (let ((map (make-sparse-keymap)))
    563     (define-key map (kbd "M-p")     'sly-previous-note)
    564     (define-key map (kbd "M-n")     'sly-next-note)
    565     (define-key map (kbd "C-c M-c") 'sly-remove-notes)
    566     (define-key map (kbd "C-c C-k") 'sly-compile-and-load-file)
    567     (define-key map (kbd "C-c M-k") 'sly-compile-file)
    568     (define-key map (kbd "C-c C-c") 'sly-compile-defun)
    569     map))
    570 
    571 (defvar sly-popup-buffer-mode-map
    572   (let ((map (make-sparse-keymap)))
    573     (define-key map (kbd "q") 'quit-window)
    574     map))
    575 
    576 
    577 ;;;; Minor modes
    578 
    579 ;;;;; sly-mode
    580 (defvar sly-buffer-connection)
    581 (defvar sly-dispatching-connection)
    582 (defvar sly-current-thread)
    583 
    584 ;; exceptional forward decl
    585 (defvar company-tooltip-align-annotations)
    586 
    587 ;;;###autoload
    588 (define-minor-mode sly-mode
    589   "Minor mode for horizontal SLY functionality."
    590   nil nil nil
    591   ;; Company-mode should have this by default
    592   ;; See gh#166
    593   (set (make-local-variable 'company-tooltip-align-annotations) t))
    594 
    595 ;;;###autoload
    596 (define-minor-mode sly-editing-mode
    597   "Minor mode for editing `lisp-mode' buffers."
    598   nil nil nil
    599   (sly-mode 1)
    600   (setq-local lisp-indent-function 'sly-common-lisp-indent-function))
    601 
    602 (define-minor-mode sly-popup-buffer-mode
    603   "Minor mode for all read-only SLY buffers"
    604   nil nil nil
    605   (sly-mode 1)
    606   (sly-interactive-buttons-mode 1)
    607   (setq buffer-read-only t))
    608 
    609 
    610 ;;;;;; Mode-Line
    611 (defface sly-mode-line
    612   '((t (:inherit font-lock-constant-face
    613                  :weight bold)))
    614   "Face for package-name in SLY's mode line."
    615   :group 'sly)
    616 
    617 (defvar sly--mode-line-format `(:eval (sly--mode-line-format)))
    618 
    619 (put 'sly--mode-line-format 'risky-local-variable t)
    620 
    621 (defvar sly-menu) ;; forward referenced
    622 
    623 (defvar sly-extra-mode-line-constructs nil
    624   "A list of mode-line constructs to add to SLY's mode-line.
    625 Each construct is separated by a \"/\" and may be a regular
    626 mode-line construct or a symbol naming a function of no arguments
    627 that returns one such construct.")
    628 
    629 (defun sly--mode-line-format ()
    630   (let* ((conn (sly-current-connection))
    631          (conn (and (process-live-p conn) conn))
    632          (name (or (and conn
    633                         (sly-connection-name conn))
    634                    "*"))
    635          (pkg (sly-current-package))
    636          (format-number (lambda (n) (cond ((and n (not (zerop n)))
    637                                            (format "%d" n))
    638                                           (n "-")
    639                                           (t "*"))))
    640          (package-name (and pkg
    641                             (sly--pretty-package-name pkg)))
    642          (pending (and conn
    643                        (length (sly-rex-continuations conn))))
    644          (sly-dbs (and conn (length (sly-db-buffers conn)))))
    645     `((:propertize "sly"
    646                    face sly-mode-line
    647                    keymap ,(let ((map (make-sparse-keymap)))
    648                              (define-key map [mode-line down-mouse-1]
    649                                sly-menu)
    650                              map)
    651                    mouse-face mode-line-highlight
    652                    help-echo "mouse-1: pop-up SLY menu"
    653                    )
    654       " "
    655       (:propertize ,name
    656                    face sly-mode-line
    657                    keymap ,(let ((map (make-sparse-keymap)))
    658                              (define-key map [mode-line mouse-1] 'sly-prev-connection)
    659                              (define-key map [mode-line mouse-2] 'sly-list-connections)
    660                              (define-key map [mode-line mouse-3] 'sly-next-connection)
    661                              map)
    662                    mouse-face mode-line-highlight
    663                    help-echo ,(concat "mouse-1: previous connection\n"
    664                                       "mouse-2: list connections\n"
    665                                       "mouse-3: next connection"))
    666       "/"
    667       ,(or package-name "*")
    668       "/"
    669       (:propertize ,(funcall format-number pending)
    670                    help-echo ,(if conn (format "%s pending events outgoing\n%s"
    671                                                pending
    672                                                (concat "mouse-1: go to *sly-events* buffer"
    673                                                        "mouse-3: forget pending continuations"))
    674                                 "No current connection")
    675                    mouse-face mode-line-highlight
    676                    face ,(cond ((and pending (cl-plusp pending))
    677                                 'warning)
    678                                (t
    679                                 'sly-mode-line))
    680                    keymap ,(let ((map (make-sparse-keymap)))
    681                              (define-key map [mode-line mouse-1] 'sly-pop-to-events-buffer)
    682                              (define-key map [mode-line mouse-3] 'sly-forget-pending-events)
    683                              map))
    684       "/"
    685       (:propertize ,(funcall format-number sly-dbs)
    686                    help-echo ,(if conn (format "%s SLY-DB buffers waiting\n%s"
    687                                                pending
    688                                                "mouse-1: go to first one")
    689                                 "No current connection")
    690                    mouse-face mode-line-highlight
    691                    face ,(cond ((and sly-dbs (cl-plusp sly-dbs))
    692                                 'warning)
    693                                (t
    694                                 'sly-mode-line))
    695                    keymap ,(let ((map (make-sparse-keymap)))
    696                              (define-key map [mode-line mouse-1] 'sly-db-pop-to-debugger)
    697                              map))
    698       ,@(cl-loop for construct in sly-extra-mode-line-constructs
    699                  collect "/"
    700                  collect (if (and (symbolp construct)
    701                                   (fboundp construct))
    702                              (condition-case _oops
    703                                  (funcall construct)
    704                                (error "*sly-invalid*"))
    705                            construct)))))
    706 
    707 (defun sly--refresh-mode-line ()
    708   (force-mode-line-update t))
    709 
    710 (defun sly--pretty-package-name (name)
    711   "Return a pretty version of a package name NAME."
    712   (cond ((string-match "^#?:\\(.*\\)$" name)
    713          (match-string 1 name))
    714         ((string-match "^\"\\(.*\\)\"$" name)
    715          (match-string 1 name))
    716         (t name)))
    717 
    718 (add-to-list 'mode-line-misc-info
    719              `(sly-mode (" [" sly--mode-line-format "] ")))
    720 
    721 
    722 ;;;; Framework'ey bits
    723 ;;;
    724 ;;; This section contains some standard SLY idioms: basic macros,
    725 ;;; ways of showing messages to the user, etc. All the code in this
    726 ;;; file should use these functions when applicable.
    727 ;;;
    728 ;;;;; Syntactic sugar
    729 
    730 (cl-defmacro sly--when-let ((var value) &rest body)
    731   "Evaluate VALUE, if the result is non-nil bind it to VAR and eval BODY.
    732 
    733 \(fn (VAR VALUE) &rest BODY)"
    734   (declare (indent 1))
    735   `(let ((,var ,value))
    736      (when ,var ,@body)))
    737 
    738 (cl-defmacro sly--when-let* (bindings &rest body)
    739   "Same as `sly--when-let', but for multiple BINDINGS"
    740   (declare (indent 1))
    741   (if bindings
    742       `(sly--when-let ,(car bindings)
    743          (sly--when-let* ,(cdr bindings) ,@body))
    744     `(progn ,@body)))
    745 
    746 (defmacro sly-dcase (value &rest patterns)
    747   (declare (indent 1)
    748            (debug (sexp &rest (sexp &rest form))))
    749   "Dispatch VALUE to one of PATTERNS.
    750 A cross between `case' and `destructuring-bind'.
    751 The pattern syntax is:
    752   ((HEAD . ARGS) . BODY)
    753 The list of patterns is searched for a HEAD `eq' to the car of
    754 VALUE. If one is found, the BODY is executed with ARGS bound to the
    755 corresponding values in the CDR of VALUE."
    756   (let ((operator (cl-gensym "op-"))
    757         (operands (cl-gensym "rand-"))
    758         (tmp (cl-gensym "tmp-")))
    759     `(let* ((,tmp ,value)
    760             (,operator (car ,tmp))
    761             (,operands (cdr ,tmp)))
    762        (cl-case ,operator
    763          ,@(mapcar (lambda (clause)
    764                      (if (eq (car clause) t)
    765                          `(t ,@(cdr clause))
    766                        (cl-destructuring-bind ((op &rest rands) &rest body)
    767                            clause
    768                          `(,op (cl-destructuring-bind ,rands ,operands
    769                                  . ,(or body
    770                                         '((ignore)) ; suppress some warnings
    771                                         ))))))
    772                    patterns)
    773          ,@(if (eq (caar (last patterns)) t)
    774                '()
    775              `((t (sly-error "Elisp sly-dcase failed: %S" ,tmp))))))))
    776 
    777 ;;;;; Very-commonly-used functions
    778 
    779 ;; Interface
    780 (cl-defun sly-buffer-name (type &key connection hidden suffix)
    781   (cl-assert (keywordp type))
    782   (mapconcat #'identity
    783              `(,@(if hidden `(" "))
    784                "*sly-"
    785                ,(downcase (substring (symbol-name type) 1))
    786                ,@(if connection
    787                      `(" for "
    788                        ,(sly-connection-name
    789                          (if (eq connection t)
    790                              (sly-current-connection)
    791                            connection))))
    792                ,@(if suffix
    793                      `(" ("
    794                        ,suffix
    795                        ")"))
    796                "*")
    797              ""))
    798 
    799 (defun sly-recenter (target &optional move-point)
    800   "Make the region between point and TARGET visible.
    801 Minimize window motion if possible.  If MOVE-POINT allow point to
    802 move to make TARGET visible."
    803   (unless (pos-visible-in-window-p target)
    804     (redisplay)
    805     (let ((screen-line (- (line-number-at-pos)
    806                           (line-number-at-pos (window-start))))
    807           (window-end (line-number-at-pos (window-end)))
    808           (window-start (line-number-at-pos (window-start)))
    809           (target-line (line-number-at-pos target))
    810           recenter-arg)
    811       (cond ((> (point) target)
    812              (setq recenter-arg (+ screen-line (- window-start target-line)))
    813              (if (or (not move-point)
    814                      (<= recenter-arg (window-height)))
    815                  (recenter recenter-arg)
    816                (goto-char target)
    817                (recenter -1)
    818                (move-to-window-line -1)))
    819             ((<= (point) target)
    820              (setq recenter-arg (- screen-line (- target-line window-end)))
    821              (if (or (not move-point)
    822                      (> recenter-arg 0))
    823                  (recenter (max recenter-arg 0))
    824                (goto-char target)
    825                (recenter 0)
    826                (move-to-window-line 0)))))))
    827 
    828 ;; Interface
    829 (defun sly-set-truncate-lines ()
    830   "Apply `sly-truncate-lines' to the current buffer."
    831   (when sly-truncate-lines
    832     (set (make-local-variable 'truncate-lines) t)))
    833 
    834 ;; Interface
    835 (defun sly-read-package-name (prompt &optional initial-value allow-blank)
    836   "Read a package name from the minibuffer, prompting with PROMPT.
    837 If ALLOW-BLANK may return nil to signal no particular package
    838 selected."
    839   (let* ((completion-ignore-case t)
    840          (res (sly-completing-read
    841                (concat "[sly] " prompt)
    842                (sly-eval
    843                 `(slynk:list-all-package-names t))
    844                nil (not allow-blank) initial-value)))
    845     (unless (zerop (length res))
    846       res)))
    847 
    848 ;; Interface
    849 (defmacro sly-propertize-region (props &rest body)
    850   "Execute BODY and add PROPS to all the text it inserts.
    851 More precisely, PROPS are added to the region between the point's
    852 positions before and after executing BODY."
    853   (declare (indent 1) (debug (sexp &rest form)))
    854   (let ((start (cl-gensym)))
    855     `(let ((,start (point)))
    856        (prog1 (progn ,@body)
    857          (add-text-properties ,start (point) ,props)))))
    858 
    859 (defun sly-add-face (face string)
    860   (declare (indent 1))
    861   (add-text-properties 0 (length string) (list 'face face) string)
    862   string)
    863 
    864 ;; Interface
    865 (defsubst sly-insert-propertized (props &rest args)
    866   "Insert all ARGS and then add text-PROPS to the inserted text."
    867   (sly-propertize-region props (apply #'insert args)))
    868 
    869 (defmacro sly-with-rigid-indentation (level &rest body)
    870   "Execute BODY and then rigidly indent its text insertions.
    871 Assumes all insertions are made at point."
    872   (declare (indent 1))
    873   (let ((start (cl-gensym)) (l (cl-gensym)))
    874     `(let ((,start (point)) (,l ,(or level '(current-column))))
    875        (prog1 (progn ,@body)
    876          (sly-indent-rigidly ,start (point) ,l)))))
    877 
    878 (defun sly-indent-rigidly (start end column)
    879   ;; Similar to `indent-rigidly' but doesn't inherit text props.
    880   (let ((indent (make-string column ?\ )))
    881     (save-excursion
    882       (goto-char end)
    883       (beginning-of-line)
    884       (while (and (<= start (point))
    885                   (progn
    886                     (insert-before-markers indent)
    887                     (zerop (forward-line -1))))))))
    888 
    889 (defun sly-insert-indented (&rest strings)
    890   "Insert all arguments rigidly indented."
    891   (sly-with-rigid-indentation nil
    892     (apply #'insert strings)))
    893 
    894 (defun sly-compose (&rest functions)
    895   "Compose unary FUNCTIONS right-associatively, returning a function"
    896   #'(lambda (x)
    897       (cl-reduce #'funcall functions :initial-value x :from-end t)))
    898 
    899 (defun sly-curry (fun &rest args)
    900   "Partially apply FUN to ARGS.  The result is a new function."
    901   (lambda (&rest more) (apply fun (append args more))))
    902 
    903 (defun sly-rcurry (fun &rest args)
    904   "Like `sly-curry' but ARGS on the right are applied."
    905   (lambda (&rest more) (apply fun (append more args))))
    906 
    907 
    908 ;;;;; Temporary popup buffers
    909 
    910 ;; keep compiler quiet
    911 (defvar sly-buffer-package)
    912 (defvar sly-buffer-connection)
    913 
    914 
    915 ;; Interface
    916 (cl-defmacro sly-with-popup-buffer ((name &key package connection select
    917                                           same-window-p
    918                                           mode)
    919                                     &body body)
    920   "Similar to `with-output-to-temp-buffer'.
    921 Bind standard-output and initialize some buffer-local variables.
    922 Restore window configuration when closed.  NAME is the name of
    923 the buffer to be created.  PACKAGE is the value
    924 `sly-buffer-package'.  CONNECTION is the value for
    925 `sly-buffer-connection', if nil, no explicit connection is
    926 associated with the buffer.  If t, the current connection is
    927 taken.  MODE is the name of a major mode which will be enabled.
    928 Non-nil SELECT indicates the buffer should be switched to, unless
    929 it is `:hidden' meaning the buffer should not even be
    930 displayed. SELECT can also be `:raise' meaning the buffer should
    931 be switched to and the frame raised.  SAME-WINDOW-P is a form
    932 indicating if the popup *can* happen in the same window. The
    933 forms SELECT and SAME-WINDOW-P are evaluated at runtime, not
    934 macroexpansion time.
    935 "
    936   (declare (indent 1)
    937            (debug (sexp &rest form)))
    938   (let* ((package-sym (cl-gensym "package-"))
    939          (connection-sym (cl-gensym "connection-"))
    940          (select-sym (cl-gensym "select"))
    941          (major-mode-sym (cl-gensym "select")))
    942     `(let ((,package-sym ,(if (eq package t)
    943                               `(sly-current-package)
    944                             package))
    945            (,connection-sym ,(if (eq connection t)
    946                                  `(sly-current-connection)
    947                                connection))
    948            (,major-mode-sym major-mode)
    949            (,select-sym ,select)
    950            (view-read-only nil))
    951        (with-current-buffer (get-buffer-create ,name)
    952          (let ((inhibit-read-only t)
    953                (standard-output (current-buffer)))
    954            (erase-buffer)
    955            ,@(cond (mode
    956                     `((funcall ,mode)))
    957                    (t
    958                     `((sly-popup-buffer-mode 1))))
    959            (setq sly-buffer-package ,package-sym
    960                  sly-buffer-connection ,connection-sym)
    961            (set-syntax-table lisp-mode-syntax-table)
    962            ,@body
    963            (unless (eq ,select-sym :hidden)
    964              (let ((window (display-buffer
    965                             (current-buffer)
    966                             (if ,(cond (same-window-p same-window-p)
    967                                        (mode `(eq ,major-mode-sym ,mode)))
    968                                 nil
    969                               t))))
    970                (when ,select-sym
    971                  (if window
    972                      (select-window window t))))
    973              (if (eq ,select-sym :raise) (raise-frame)))
    974            (current-buffer))))))
    975 
    976 ;;;;; Filename translation
    977 ;;;
    978 ;;; Filenames passed between Emacs and Lisp should be translated using
    979 ;;; these functions. This way users who run Emacs and Lisp on separate
    980 ;;; machines have a chance to integrate file operations somehow.
    981 
    982 (defvar sly-to-lisp-filename-function #'convert-standard-filename
    983   "Function to translate Emacs filenames to CL namestrings.")
    984 (defvar sly-from-lisp-filename-function #'identity
    985   "Function to translate CL namestrings to Emacs filenames.")
    986 
    987 (defun sly-to-lisp-filename (filename)
    988   "Translate the string FILENAME to a Lisp filename."
    989   (funcall sly-to-lisp-filename-function (substring-no-properties filename)))
    990 
    991 (defun sly-from-lisp-filename (filename)
    992   "Translate the Lisp filename FILENAME to an Emacs filename."
    993   (funcall sly-from-lisp-filename-function filename))
    994 
    995 
    996 ;;;; Starting SLY
    997 ;;;
    998 ;;; This section covers starting an inferior-lisp, compiling and
    999 ;;; starting the server, initiating a network connection.
   1000 
   1001 ;;;;; Entry points
   1002 
   1003 ;; We no longer load inf-lisp, but we use this variable for backward
   1004 ;; compatibility.
   1005 (defcustom inferior-lisp-program "lisp"
   1006   "Program name for starting a Lisp subprocess to Emacs.
   1007 Can be a string naming a program, a whitespace-separated string
   1008 of \"EXECUTABLE ARG1 ARG2\" or a list (EXECUTABLE ARGS...) where
   1009 EXECUTABLE and ARGS are strings."
   1010   :type 'string
   1011   :group 'sly-lisp)
   1012 
   1013 (defvar sly-lisp-implementations nil
   1014   "*A list of known Lisp implementations.
   1015 The list should have the form:
   1016   ((NAME (PROGRAM PROGRAM-ARGS...) &key KEYWORD-ARGS) ...)
   1017 
   1018 NAME is a symbol for the implementation.
   1019 PROGRAM and PROGRAM-ARGS are strings used to start the Lisp process.
   1020 For KEYWORD-ARGS see `sly-start'.
   1021 
   1022 Here's an example:
   1023  ((cmucl (\"/opt/cmucl/bin/lisp\" \"-quiet\") :init sly-init-command)
   1024   (acl (\"acl7\") :coding-system emacs-mule))")
   1025 
   1026 (defcustom sly-command-switch-to-existing-lisp 'ask
   1027   "Should the `sly' command start new lisp if one is available?"
   1028   :type '(choice (const :tag "Ask the user" ask)
   1029                  (const :tag "Always" 'always)
   1030                  (const :tag "Never" 'never)))
   1031 
   1032 (defcustom sly-auto-select-connection 'ask
   1033   "Controls auto selection after the default connection was closed."
   1034   :group 'sly-mode
   1035   :type '(choice (const never)
   1036                  (const always)
   1037                  (const ask)))
   1038 
   1039 (defcustom sly-default-lisp nil
   1040   "A symbol naming the preferred Lisp implementation.
   1041 See `sly-lisp-implementations'"
   1042   :type 'function
   1043   :group 'sly-mode)
   1044 
   1045 ;; dummy definitions for the compiler
   1046 (defvar sly-net-processes)
   1047 (defvar sly-default-connection)
   1048 
   1049 ;;;###autoload
   1050 (cl-defun sly (&optional command coding-system interactive)
   1051   "Start a Lisp implementation and connect to it.
   1052 
   1053   COMMAND designates a the Lisp implementation to start as an
   1054 \"inferior\" process to the Emacs process. It is either a
   1055 pathname string pathname to a lisp executable, a list (EXECUTABLE
   1056 ARGS...), or a symbol indexing
   1057 `sly-lisp-implementations'. CODING-SYSTEM is a symbol overriding
   1058 `sly-net-coding-system'.
   1059 
   1060 Interactively, both COMMAND and CODING-SYSTEM are nil and the
   1061 prefix argument controls the precise behaviour:
   1062 
   1063 - With no prefix arg, try to automatically find a Lisp.  First
   1064   consult `sly-command-switch-to-existing-lisp' and analyse open
   1065   connections to maybe switch to one of those.  If a new lisp is
   1066   to be created, first lookup `sly-lisp-implementations', using
   1067   `sly-default-lisp' as a default strategy.  Then try
   1068   `inferior-lisp-program' if it looks like it points to a valid
   1069   lisp.  Failing that, guess the location of a lisp
   1070   implementation.
   1071 
   1072 - With a positive prefix arg (one C-u), prompt for a command
   1073   string that starts a Lisp implementation.
   1074 
   1075 - With a negative prefix arg (M-- M-x sly, for example) prompt
   1076   for a symbol indexing one of the entries in
   1077   `sly-lisp-implementations'"
   1078   (interactive (list nil nil t))
   1079   (sly--when-let*
   1080       ((active (and interactive
   1081                     (not current-prefix-arg)
   1082                     (sly--purge-connections)))
   1083        (target (or (and (eq sly-command-switch-to-existing-lisp 'ask)
   1084                         (sly-prompt-for-connection
   1085                          "[sly] Switch to open connection?\n\
   1086   (Customize `sly-command-switch-to-existing-lisp' to avoid this prompt.)\n\
   1087   Connections: " nil "(start a new one)"))
   1088                    (and (eq sly-command-switch-to-existing-lisp 'always)
   1089                         (car active)))))
   1090     (sly-message "Switching to `%s'" (sly-connection-name target))
   1091     (sly-connection-list-default-action target)
   1092     (cl-return-from sly nil))
   1093   (let ((command (or command inferior-lisp-program))
   1094         (sly-net-coding-system (or coding-system sly-net-coding-system)))
   1095     (apply #'sly-start
   1096            (cond (interactive
   1097                   (sly--read-interactive-args))
   1098                  (t
   1099                   (if sly-lisp-implementations
   1100                       (sly--lookup-lisp-implementation
   1101                        sly-lisp-implementations
   1102                        (or (and (symbolp command) command)
   1103                            sly-default-lisp
   1104                            (car (car sly-lisp-implementations))))
   1105                     (let ((command-and-args (if (listp command)
   1106                                                 command
   1107                                               (split-string command))))
   1108                       `(:program ,(car command-and-args)
   1109                                  :program-args ,(cdr command-and-args)))))))))
   1110 
   1111 (defvar sly-inferior-lisp-program-history '()
   1112   "History list of command strings.  Used by M-x sly.")
   1113 
   1114 (defun sly--read-interactive-args ()
   1115   "Return the list of args which should be passed to `sly-start'.
   1116 Helper for M-x sly"
   1117   (cond ((not current-prefix-arg)
   1118          (cond (sly-lisp-implementations
   1119                 (sly--lookup-lisp-implementation sly-lisp-implementations
   1120                                                  (or sly-default-lisp
   1121                                                      (car (car sly-lisp-implementations)))))
   1122                (t (cl-destructuring-bind (program &rest args)
   1123                       (split-string-and-unquote
   1124                        (sly--guess-inferior-lisp-program t))
   1125                     (list :program program :program-args args)))))
   1126         ((eq current-prefix-arg '-)
   1127          (let ((key (sly-completing-read
   1128                      "Lisp name: " (mapcar (lambda (x)
   1129                                              (list (symbol-name (car x))))
   1130                                            sly-lisp-implementations)
   1131                      nil t)))
   1132            (sly--lookup-lisp-implementation sly-lisp-implementations (intern key))))
   1133         (t
   1134          (cl-destructuring-bind (program &rest program-args)
   1135              (split-string-and-unquote
   1136               (read-shell-command "[sly] Run lisp: "
   1137                                   (sly--guess-inferior-lisp-program nil)
   1138                                   'sly-inferior-lisp-program-history))
   1139            (let ((coding-system
   1140                   (if (eq 16 (prefix-numeric-value current-prefix-arg))
   1141                       (read-coding-system "[sly] Set sly-coding-system: "
   1142                                           sly-net-coding-system)
   1143                     sly-net-coding-system)))
   1144              (list :program program :program-args program-args
   1145                    :coding-system coding-system))))))
   1146 
   1147 
   1148 (defun sly--lookup-lisp-implementation (table name)
   1149   (let ((arguments (cl-rest (assoc name table))))
   1150     (unless arguments
   1151       (error "Could not find lisp implementation with the name '%S'" name))
   1152     (when (and (= (length arguments) 1)
   1153                (functionp (cl-first arguments)))
   1154       (setf arguments (funcall (cl-first arguments))))
   1155     (cl-destructuring-bind ((prog &rest args) &rest keys) arguments
   1156       (cl-list* :name name :program prog :program-args args keys))))
   1157 
   1158 (defun sly-inferior-lisp-buffer (sly-process-or-connection &optional pop-to-buffer)
   1159   "Return PROCESS's buffer. With POP-TO-BUFFER, pop to it."
   1160   (interactive (list (sly-process) t))
   1161   (let ((buffer (cond ((and sly-process-or-connection
   1162                             (process-get sly-process-or-connection
   1163                                          'sly-inferior-lisp-process))
   1164                        (process-buffer sly-process-or-connection))
   1165                       (sly-process-or-connection
   1166                        ;; call ourselves recursively with a
   1167                        ;; sly-started process
   1168                        ;;
   1169                        (sly-inferior-lisp-buffer (sly-process sly-process-or-connection)
   1170                                                  pop-to-buffer )))))
   1171     (cond ((and buffer
   1172                 pop-to-buffer)
   1173            (pop-to-buffer buffer))
   1174           ((and pop-to-buffer
   1175                 sly-process-or-connection)
   1176            (sly-message "No *inferior lisp* process for current connection!"))
   1177           (pop-to-buffer
   1178            (sly-error "No *inferior lisp* buffer")))
   1179     buffer))
   1180 
   1181 (defun sly--guess-inferior-lisp-program (&optional interactive)
   1182   "Compute pathname to a seemingly valid lisp implementation.
   1183 If ERRORP, error if such a thing cannot be found"
   1184   (let ((inferior-lisp-program-and-args
   1185          (and inferior-lisp-program
   1186               (if (listp inferior-lisp-program)
   1187                   inferior-lisp-program
   1188                 (split-string-and-unquote inferior-lisp-program)))))
   1189     (if (and inferior-lisp-program-and-args
   1190              (executable-find (car inferior-lisp-program-and-args)))
   1191         (combine-and-quote-strings inferior-lisp-program-and-args)
   1192       (let ((guessed (cl-some #'executable-find
   1193                               '("lisp" "sbcl" "clisp" "cmucl"
   1194                                 "acl" "alisp"))))
   1195         (cond ((and guessed
   1196                     (or (not interactive)
   1197                         noninteractive
   1198                         (sly-y-or-n-p
   1199                          "Can't find `inferior-lisp-program' (set to `%s'). Use `%s' instead? "
   1200                          inferior-lisp-program guessed)))
   1201                guessed)
   1202               (interactive
   1203                (sly-error
   1204                 (substitute-command-keys
   1205                  "Can't find a suitable Lisp. Use \\[sly-info] to read about `Multiple Lisps'")))
   1206               (t
   1207                nil))))))
   1208 
   1209 (cl-defun sly-start (&key (program
   1210                            (sly-error "must supply :program"))
   1211                           program-args
   1212                           directory
   1213                           (coding-system sly-net-coding-system)
   1214                           (init sly-init-function)
   1215                           name
   1216                           (buffer (format "*sly-started inferior-lisp for %s*"
   1217                                           (file-name-nondirectory program)))
   1218                           init-function
   1219                           env)
   1220   "Start a Lisp process and connect to it.
   1221 This function is intended for programmatic use if `sly' is not
   1222 flexible enough.
   1223 
   1224 PROGRAM and PROGRAM-ARGS are the filename and argument strings
   1225   for the subprocess.
   1226 INIT is a function that should return a string to load and start
   1227   Slynk. The function will be called with the PORT-FILENAME and ENCODING as
   1228   arguments.  INIT defaults to `sly-init-function'.
   1229 CODING-SYSTEM a symbol for the coding system. The default is
   1230   sly-net-coding-system
   1231 ENV environment variables for the subprocess (see `process-environment').
   1232 INIT-FUNCTION function to call right after the connection is established.
   1233 BUFFER the name of the buffer to use for the subprocess.
   1234 NAME a symbol to describe the Lisp implementation
   1235 DIRECTORY change to this directory before starting the process.
   1236 "
   1237   (let ((args (list :program program :program-args program-args :buffer buffer
   1238                     :coding-system coding-system :init init :name name
   1239                     :init-function init-function :env env)))
   1240     (sly-check-coding-system coding-system)
   1241     (let ((proc (sly-maybe-start-lisp program program-args env
   1242                                       directory buffer)))
   1243       (sly-inferior-connect proc args)
   1244       (sly-inferior-lisp-buffer proc))))
   1245 
   1246 ;;;###autoload
   1247 (defun sly-connect (host port &optional _coding-system interactive-p)
   1248   "Connect to a running Slynk server. Return the connection.
   1249 With prefix arg, asks if all connections should be closed
   1250 before."
   1251   (interactive (list (read-from-minibuffer
   1252                       "[sly] Host: " (cl-first sly-connect-host-history)
   1253                       nil nil '(sly-connect-host-history . 1))
   1254                      (string-to-number
   1255                       (read-from-minibuffer
   1256                        "[sly] Port: " (cl-first sly-connect-port-history)
   1257                        nil nil '(sly-connect-port-history . 1)))
   1258                      nil t))
   1259   (when (and interactive-p
   1260              sly-net-processes
   1261              current-prefix-arg
   1262              (sly-y-or-n-p "[sly] Close all connections first? "))
   1263     (sly-disconnect-all))
   1264   (sly-message "Connecting to Slynk on port %S.." port)
   1265   (let* ((process (sly-net-connect host port))
   1266          (sly-dispatching-connection process))
   1267     (sly-setup-connection process)))
   1268 
   1269 ;;;;; Start inferior lisp
   1270 ;;;
   1271 ;;; Here is the protocol for starting SLY via `M-x sly':
   1272 ;;;
   1273 ;;;   1. Emacs starts an inferior Lisp process.
   1274 ;;;   2. Emacs tells Lisp (via stdio) to load and start Slynk.
   1275 ;;;   3. Lisp recompiles the Slynk if needed.
   1276 ;;;   4. Lisp starts the Slynk server and writes its TCP port to a temp file.
   1277 ;;;   5. Emacs reads the temp file to get the port and then connects.
   1278 ;;;   6. Emacs prints a message of warm encouragement for the hacking ahead.
   1279 ;;;
   1280 ;;; Between steps 2-5 Emacs polls for the creation of the temp file so
   1281 ;;; that it can make the connection. This polling may continue for a
   1282 ;;; fair while if Slynk needs recompilation.
   1283 
   1284 (defvar sly-connect-retry-timer nil
   1285   "Timer object while waiting for an inferior-lisp to start.")
   1286 
   1287 (defun sly-abort-connection ()
   1288   "Abort connection the current connection attempt."
   1289   (interactive)
   1290   (cond (sly-connect-retry-timer
   1291          (sly-cancel-connect-retry-timer)
   1292          (sly-message "Cancelled connection attempt."))
   1293         (t (error "Not connecting"))))
   1294 
   1295 ;;; Starting the inferior Lisp and loading Slynk:
   1296 
   1297 (defun sly-maybe-start-lisp (program program-args env directory buffer)
   1298   "Return a new or existing inferior lisp process."
   1299   (cond ((not (comint-check-proc buffer))
   1300          (sly-start-lisp program program-args env directory buffer))
   1301         (t (sly-start-lisp program program-args env directory
   1302                            (generate-new-buffer-name buffer)))))
   1303 
   1304 (defvar sly-inferior-process-start-hook nil
   1305   "Hook called whenever a new process gets started.")
   1306 
   1307 (defun sly-start-lisp (program program-args env directory buffer)
   1308   "Does the same as `inferior-lisp' but less ugly.
   1309 Return the created process."
   1310   (with-current-buffer (get-buffer-create buffer)
   1311     (when directory
   1312       (cd (expand-file-name directory)))
   1313     (comint-mode)
   1314     (let ((process-environment (append env process-environment))
   1315           (process-connection-type nil))
   1316       (comint-exec (current-buffer) "inferior-lisp" program nil program-args))
   1317     (lisp-mode-variables t)
   1318     (let ((proc (get-buffer-process (current-buffer))))
   1319       (process-put proc 'sly-inferior-lisp-process t)
   1320       (set-process-query-on-exit-flag proc (not sly-kill-without-query-p))
   1321       (run-hooks 'sly-inferior-process-start-hook)
   1322       proc)))
   1323 
   1324 (defun sly-inferior-connect (process args)
   1325   "Start a Slynk server in the inferior Lisp and connect."
   1326   (sly-delete-slynk-port-file 'quiet)
   1327   (sly-start-slynk-server process args)
   1328   (sly-read-port-and-connect process))
   1329 
   1330 (defun sly-start-slynk-server (inf-process args)
   1331   "Start a Slynk server on the inferior lisp."
   1332   (cl-destructuring-bind (&key coding-system init &allow-other-keys) args
   1333     (with-current-buffer (process-buffer inf-process)
   1334       (process-put inf-process 'sly-inferior-lisp-args args)
   1335       (let ((str (funcall init (sly-slynk-port-file) coding-system)))
   1336         (goto-char (process-mark inf-process))
   1337         (insert-before-markers str)
   1338         (process-send-string inf-process str)))))
   1339 
   1340 (defun sly-inferior-lisp-args (inf-process)
   1341   "Return the initial process arguments.
   1342 See `sly-start'."
   1343   (process-get inf-process 'sly-inferior-lisp-args))
   1344 
   1345 (defun sly-init-using-asdf (port-filename coding-system)
   1346   "Return a string to initialize Lisp using ASDF.
   1347 Fall back to `sly-init-using-slynk-loader' if ASDF fails."
   1348   (format "%S\n\n"
   1349           `(cond ((ignore-errors
   1350                     (funcall 'require "asdf")
   1351                     (funcall (read-from-string "asdf:version-satisfies")
   1352                              (funcall (read-from-string "asdf:asdf-version"))
   1353                              "2.019"))
   1354                   (push (pathname ,(sly-to-lisp-filename (sly-slynk-path)))
   1355                         (symbol-value
   1356                          (read-from-string "asdf:*central-registry*")))
   1357                   (funcall
   1358                    (read-from-string "asdf:load-system")
   1359                    :slynk)
   1360                   (funcall
   1361                    (read-from-string "slynk:start-server")
   1362                    ,(sly-to-lisp-filename port-filename)))
   1363                  (t
   1364                   ,(read (sly-init-using-slynk-loader port-filename
   1365                                                       coding-system))))))
   1366 
   1367 ;; XXX load-server & start-server used to be separated. maybe that was  better.
   1368 (defun sly-init-using-slynk-loader (port-filename _coding-system)
   1369   "Return a string to initialize Lisp."
   1370   (let ((loader (sly-to-lisp-filename
   1371                  (expand-file-name sly-slynk-loader-backend (sly-slynk-path)))))
   1372     ;; Return a single form to avoid problems with buffered input.
   1373     (format "%S\n\n"
   1374             `(progn
   1375                (load ,loader :verbose t)
   1376                (funcall (read-from-string "slynk-loader:init"))
   1377                (funcall (read-from-string "slynk:start-server")
   1378                         ,port-filename)))))
   1379 
   1380 (defun sly-slynk-port-file ()
   1381   "Filename where the SLYNK server writes its TCP port number."
   1382   (expand-file-name (format "sly.%S" (emacs-pid)) (sly-temp-directory)))
   1383 
   1384 (defun sly-temp-directory ()
   1385   (cond ((fboundp 'temp-directory) (temp-directory))
   1386         ((boundp 'temporary-file-directory) temporary-file-directory)
   1387         (t "/tmp/")))
   1388 
   1389 (defun sly-delete-slynk-port-file (&optional quiet)
   1390   (condition-case data
   1391       (delete-file (sly-slynk-port-file))
   1392     (error
   1393      (cl-ecase quiet
   1394        ((nil) (signal (car data) (cdr data)))
   1395        (quiet)
   1396        (sly-message (sly-message "Unable to delete slynk port file %S"
   1397                                  (sly-slynk-port-file)))))))
   1398 
   1399 (defun sly-read-port-and-connect (inferior-process)
   1400   (sly-attempt-connection inferior-process nil 1))
   1401 
   1402 (defcustom sly-connection-poll-interval 0.3
   1403   "Seconds to wait between connection attempts when first connecting."
   1404   :type 'number
   1405   :group 'sly-ui)
   1406 
   1407 (defun sly-attempt-connection (process retries attempt)
   1408   ;; A small one-state machine to attempt a connection with
   1409   ;; timer-based retries.
   1410   (sly-cancel-connect-retry-timer)
   1411   (let ((file (sly-slynk-port-file)))
   1412     (unless (active-minibuffer-window)
   1413       (sly-message "Polling %S .. %d (Abort with `M-x sly-abort-connection'.)"
   1414                    file attempt))
   1415     (cond ((and (file-exists-p file)
   1416                 (> (nth 7 (file-attributes file)) 0)) ; file size
   1417            (let ((port (sly-read-slynk-port))
   1418                  (args (sly-inferior-lisp-args process)))
   1419              (sly-delete-slynk-port-file 'message)
   1420              (let ((c (sly-connect sly-lisp-host port
   1421                                    (plist-get args :coding-system))))
   1422                (sly-set-inferior-process c process))))
   1423           ((and retries (zerop retries))
   1424            (sly-message "Gave up connecting to Slynk after %d attempts." attempt))
   1425           ((eq (process-status process) 'exit)
   1426            (sly-message "Failed to connect to Slynk: inferior process exited."))
   1427           (t
   1428            (when (and (file-exists-p file)
   1429                       (zerop (nth 7 (file-attributes file))))
   1430              (sly-message "(Zero length port file)")
   1431              ;; the file may be in the filesystem but not yet written
   1432              (unless retries (setq retries 3)))
   1433            (cl-assert (not sly-connect-retry-timer))
   1434            (setq sly-connect-retry-timer
   1435                  (run-with-timer
   1436                   sly-connection-poll-interval nil
   1437                   #'sly-timer-call #'sly-attempt-connection
   1438                   `((sly-ignore-protocol-mismatches . ,sly-ignore-protocol-mismatches))
   1439                   process (and retries (1- retries))
   1440                   (1+ attempt)))))))
   1441 
   1442 (defun sly-timer-call (fun env &rest args)
   1443   "Call function FUN with ARGS, reporting all errors.
   1444 
   1445 FUN is called with the overriding dynamic environment in ENV, an
   1446 alist of bindings.
   1447 
   1448 The default condition handler for timer functions (see
   1449 `timer-event-handler') ignores errors."
   1450   (condition-case data
   1451       (cl-progv (mapcar #'car env) (mapcar #'cdr env)
   1452         (apply fun args))
   1453     ((debug error)
   1454      (debug nil (list "Error in timer" fun args data)))))
   1455 
   1456 (defun sly-cancel-connect-retry-timer ()
   1457   (when sly-connect-retry-timer
   1458     (cancel-timer sly-connect-retry-timer)
   1459     (setq sly-connect-retry-timer nil)))
   1460 
   1461 (defun sly-read-slynk-port ()
   1462   "Read the Slynk server port number from the `sly-slynk-port-file'."
   1463   (save-excursion
   1464     (with-temp-buffer
   1465       (insert-file-contents (sly-slynk-port-file))
   1466       (goto-char (point-min))
   1467       (let ((port (read (current-buffer))))
   1468         (cl-assert (integerp port))
   1469         port))))
   1470 
   1471 (defun sly-toggle-debug-on-slynk-error ()
   1472   (interactive)
   1473   (if (sly-eval `(slynk:toggle-debug-on-slynk-error))
   1474       (sly-message "Debug on SLYNK error enabled.")
   1475     (sly-message "Debug on SLYNK error disabled.")))
   1476 
   1477 ;;; Words of encouragement
   1478 
   1479 (defun sly-user-first-name ()
   1480   (let ((name (if (string= (user-full-name) "")
   1481                   (user-login-name)
   1482                 (user-full-name))))
   1483     (string-match "^[^ ]*" name)
   1484     (capitalize (match-string 0 name))))
   1485 
   1486 (defvar sly-words-of-encouragement
   1487   `("Let the hacking commence!"
   1488     "Hacks and glory await!"
   1489     "Hack and be merry!"
   1490     "Your hacking starts... NOW!"
   1491     "May the source be with you!"
   1492     "Take this REPL, brother, and may it serve you well."
   1493     "Lemonodor-fame is but a hack away!"
   1494     "Are we consing yet?"
   1495     ,(format "%s, this could be the start of a beautiful program."
   1496              (sly-user-first-name)))
   1497   "Scientifically-proven optimal words of hackerish encouragement.")
   1498 
   1499 (defun sly-random-words-of-encouragement ()
   1500   "Return a string of hackerish encouragement."
   1501   (eval (nth (random (length sly-words-of-encouragement))
   1502              sly-words-of-encouragement)
   1503         t))
   1504 
   1505 
   1506 ;;;; Networking
   1507 ;;;
   1508 ;;; This section covers the low-level networking: establishing
   1509 ;;; connections and encoding/decoding protocol messages.
   1510 ;;;
   1511 ;;; Each SLY protocol message beings with a 6-byte header followed
   1512 ;;; by an S-expression as text. The sexp must be readable both by
   1513 ;;; Emacs and by Common Lisp, so if it contains any embedded code
   1514 ;;; fragments they should be sent as strings:
   1515 ;;;
   1516 ;;; The set of meaningful protocol messages are not specified
   1517 ;;; here. They are defined elsewhere by the event-dispatching
   1518 ;;; functions in this file and in slynk.lisp.
   1519 
   1520 (defvar sly-net-processes nil
   1521   "List of processes (sockets) connected to Lisps.")
   1522 
   1523 (defvar sly-net-process-close-hooks '()
   1524   "List of functions called when a sly network connection closes.
   1525 The functions are called with the process as their argument.")
   1526 
   1527 (defun sly-secret ()
   1528   "Find the magic secret from the user's home directory.
   1529 Return nil if the file doesn't exist or is empty; otherwise the
   1530 first line of the file."
   1531   (condition-case _err
   1532       (with-temp-buffer
   1533         (insert-file-contents "~/.sly-secret")
   1534         (goto-char (point-min))
   1535         (buffer-substring (point-min) (line-end-position)))
   1536     (file-error nil)))
   1537 
   1538 ;;; Interface
   1539 (defvar sly--net-connect-counter 0)
   1540 
   1541 (defun sly-send-secret (proc)
   1542   (sly--when-let (secret (sly-secret))
   1543     (let* ((payload (encode-coding-string secret 'utf-8-unix))
   1544            (string (concat (sly-net-encode-length (length payload))
   1545                            payload)))
   1546       (process-send-string proc string))))
   1547 
   1548 (defun sly-net-connect (host port)
   1549   "Establish a connection with a CL."
   1550   (let* ((inhibit-quit nil)
   1551          (name (format "sly-%s" (cl-incf sly--net-connect-counter)))
   1552          (connection (open-network-stream name nil host port))
   1553          (buffer (sly-make-net-buffer (format " *%s*" name))))
   1554     (push connection sly-net-processes)
   1555     (set-process-plist connection `(sly--net-connect-counter
   1556                                     ,sly--net-connect-counter))
   1557     (set-process-buffer connection buffer)
   1558     (set-process-filter connection 'sly-net-filter)
   1559     (set-process-sentinel connection 'sly-net-sentinel)
   1560     (set-process-query-on-exit-flag connection (not sly-kill-without-query-p))
   1561     (when (fboundp 'set-process-coding-system)
   1562       (set-process-coding-system connection 'binary 'binary))
   1563     (sly-send-secret connection)
   1564     connection))
   1565 
   1566 (defun sly-make-net-buffer (name)
   1567   "Make a buffer suitable for a network process."
   1568   (let ((buffer (generate-new-buffer name)))
   1569     (with-current-buffer buffer
   1570       (buffer-disable-undo)
   1571       (set (make-local-variable 'kill-buffer-query-functions) nil))
   1572     buffer))
   1573 
   1574 ;;;;; Coding system madness
   1575 
   1576 (defun sly-check-coding-system (coding-system)
   1577   "Signal an error if CODING-SYSTEM isn't a valid coding system."
   1578   (interactive)
   1579   (let ((props (sly-find-coding-system coding-system)))
   1580     (unless props
   1581       (error "Invalid sly-net-coding-system: %s. %s"
   1582              coding-system (mapcar #'car sly-net-valid-coding-systems)))
   1583     (when (and (cl-second props) (boundp 'default-enable-multibyte-characters))
   1584       (cl-assert default-enable-multibyte-characters))
   1585     t))
   1586 
   1587 (defun sly-coding-system-mulibyte-p (coding-system)
   1588   (cl-second (sly-find-coding-system coding-system)))
   1589 
   1590 (defun sly-coding-system-cl-name (coding-system)
   1591   (cl-third (sly-find-coding-system coding-system)))
   1592 
   1593 ;;; Interface
   1594 (defvar sly-net-send-translator nil
   1595   "If non-nil, function to translate outgoing sexps for the wire.")
   1596 
   1597 (defun sly--sanitize-or-lose (form)
   1598   "Sanitize FORM for Slynk or error."
   1599   (cl-typecase form
   1600     (number)
   1601     (symbol 'fonix)
   1602     (string (set-text-properties 0 (length form) nil form))
   1603     (cons (sly--sanitize-or-lose (car form))
   1604           (sly--sanitize-or-lose (cdr form)))
   1605     (t (sly-error "Can't serialize %s for Slynk." form)))
   1606   form)
   1607 
   1608 (defun sly-net-send (sexp proc)
   1609   "Send a SEXP to Lisp over the socket PROC.
   1610 This is the lowest level of communication. The sexp will be READ and
   1611 EVAL'd by Lisp."
   1612   (let* ((print-circle nil)
   1613          (print-quoted nil)
   1614          (sexp (sly--sanitize-or-lose sexp))
   1615          (sexp (if (and sly-net-send-translator
   1616                         (fboundp sly-net-send-translator))
   1617                    (funcall sly-net-send-translator sexp)
   1618                  sexp))
   1619          (payload (encode-coding-string
   1620                    (concat (sly-prin1-to-string sexp) "\n")
   1621                    'utf-8-unix))
   1622          (string (concat (sly-net-encode-length (length payload))
   1623                          payload)))
   1624     (sly-log-event sexp proc)
   1625     (process-send-string proc string)))
   1626 
   1627 (defun sly-safe-encoding-p (coding-system string)
   1628   "Return true iff CODING-SYSTEM can safely encode STRING."
   1629   (or (let ((candidates (find-coding-systems-string string))
   1630             (base (coding-system-base coding-system)))
   1631         (or (equal candidates '(undecided))
   1632             (memq base candidates)))
   1633       (and (not (multibyte-string-p string))
   1634            (not (sly-coding-system-mulibyte-p coding-system)))))
   1635 
   1636 (defun sly-net-close (connection reason &optional debug _force)
   1637   "Close the network connection CONNECTION because REASON."
   1638   (process-put connection 'sly-net-close-reason reason)
   1639   (setq sly-net-processes (remove connection sly-net-processes))
   1640   (when (eq connection sly-default-connection)
   1641     (setq sly-default-connection nil))
   1642   ;; Run hooks
   1643   ;;
   1644   (unless debug
   1645     (run-hook-with-args 'sly-net-process-close-hooks connection))
   1646   ;; We close the socket connection by killing its hidden
   1647   ;; *sly-<number>* buffer, but we first unset the connection's
   1648   ;; sentinel otherwise we could get a second `sly-net-close' call. In
   1649   ;; case the buffer is already killed (we killed it manually), this
   1650   ;; function is probably running as a result of that, and rekilling
   1651   ;; it is harmless.
   1652   ;;
   1653   (set-process-sentinel connection nil)
   1654   (when debug
   1655     (set-process-filter connection nil))
   1656   (if debug
   1657       (delete-process connection) ; leave the buffer
   1658     (kill-buffer (process-buffer connection))))
   1659 
   1660 (defun sly-net-sentinel (process message)
   1661   (let ((reason (format "Lisp connection closed unexpectedly: %s" message)))
   1662     (sly-message reason)
   1663     (sly-net-close process reason)))
   1664 
   1665 ;;; Socket input is handled by `sly-net-filter', which decodes any
   1666 ;;; complete messages and hands them off to the event dispatcher.
   1667 
   1668 (defun sly-net-filter (process string)
   1669   "Accept output from the socket and process all complete messages."
   1670   (with-current-buffer (process-buffer process)
   1671     (goto-char (point-max))
   1672     (insert string))
   1673   (sly-process-available-input process))
   1674 
   1675 (defun sly-process-available-input (process)
   1676   "Process all complete messages that have arrived from Lisp."
   1677   (with-current-buffer (process-buffer process)
   1678     (while (sly-net-have-input-p)
   1679       (let ((event (sly-net-read-or-lose process))
   1680             (ok nil))
   1681         (sly-log-event event process)
   1682         (unwind-protect
   1683             (save-current-buffer
   1684               (sly-dispatch-event event process)
   1685               (setq ok t))
   1686           (unless ok
   1687             (run-at-time 0 nil 'sly-process-available-input process)))))))
   1688 
   1689 (defsubst sly-net-decode-length ()
   1690   (string-to-number (buffer-substring (point) (+ (point) 6))
   1691                     16))
   1692 
   1693 (defun sly-net-have-input-p ()
   1694   "Return true if a complete message is available."
   1695   (goto-char (point-min))
   1696   (and (>= (buffer-size) 6)
   1697        (>= (- (buffer-size) 6) (sly-net-decode-length))))
   1698 
   1699 (defun sly-handle-net-read-error (error)
   1700   (let ((packet (buffer-string)))
   1701     (sly-with-popup-buffer ((sly-buffer-name :error
   1702                                              :connection (get-buffer-process (current-buffer))))
   1703       (princ (format "%s\nin packet:\n%s" (error-message-string error) packet))
   1704       (goto-char (point-min)))
   1705     (cond ((sly-y-or-n-p "Skip this packet? ")
   1706            `(:emacs-skipped-packet ,packet))
   1707           (t
   1708            (when (sly-y-or-n-p "Enter debugger instead? ")
   1709              (debug 'error error))
   1710            (signal (car error) (cdr error))))))
   1711 
   1712 (defun sly-net-read-or-lose (process)
   1713   (condition-case error
   1714       (sly-net-read)
   1715     (error
   1716      (sly-net-close process "Fatal net-read error" t)
   1717      (error "net-read error: %S" error))))
   1718 
   1719 (defun sly-net-read ()
   1720   "Read a message from the network buffer."
   1721   (goto-char (point-min))
   1722   (let* ((length (sly-net-decode-length))
   1723          (start (+ (point) 6))
   1724          (end (+ start length)))
   1725     (cl-assert (cl-plusp length))
   1726     (prog1 (save-restriction
   1727              (narrow-to-region start end)
   1728              (condition-case error
   1729                  (progn
   1730                    (decode-coding-region start end 'utf-8-unix)
   1731                    (setq end (point-max))
   1732                    (read (current-buffer)))
   1733                (error
   1734                 (sly-handle-net-read-error error))))
   1735       (delete-region (point-min) end))))
   1736 
   1737 (defun sly-net-encode-length (n)
   1738   (format "%06x" n))
   1739 
   1740 (defun sly-prin1-to-string (sexp)
   1741   "Like `prin1-to-string' but don't octal-escape non-ascii characters.
   1742 This is more compatible with the CL reader."
   1743   (let (print-escape-nonascii
   1744         print-escape-newlines
   1745         print-length
   1746         print-level)
   1747     (prin1-to-string sexp)))
   1748 
   1749 
   1750 ;;;; Connections
   1751 ;;;
   1752 ;;; "Connections" are the high-level Emacs<->Lisp networking concept.
   1753 ;;;
   1754 ;;; Emacs has a connection to each Lisp process that it's interacting
   1755 ;;; with. Typically there would only be one, but a user can choose to
   1756 ;;; connect to many Lisps simultaneously.
   1757 ;;;
   1758 ;;; A connection consists of a control socket, optionally an extra
   1759 ;;; socket dedicated to receiving Lisp output (an optimization), and a
   1760 ;;; set of connection-local state variables.
   1761 ;;;
   1762 ;;; The state variables are stored as buffer-local variables in the
   1763 ;;; control socket's process-buffer and are used via accessor
   1764 ;;; functions. These variables include things like the *FEATURES* list
   1765 ;;; and Unix Pid of the Lisp process.
   1766 ;;;
   1767 ;;; One connection is "current" at any given time. This is:
   1768 ;;;   `sly-dispatching-connection' if dynamically bound, or
   1769 ;;;   `sly-buffer-connection' if this is set buffer-local, or
   1770 ;;;   `sly-default-connection' otherwise.
   1771 ;;;
   1772 ;;; When you're invoking commands in your source files you'll be using
   1773 ;;; `sly-default-connection'. This connection can be interactively
   1774 ;;; reassigned via the connection-list buffer.
   1775 ;;;
   1776 ;;; When a command creates a new buffer it will set
   1777 ;;; `sly-buffer-connection' so that commands in the new buffer will
   1778 ;;; use the connection that the buffer originated from. For example,
   1779 ;;; the apropos command creates the *Apropos* buffer and any command
   1780 ;;; in that buffer (e.g. `M-.') will go to the same Lisp that did the
   1781 ;;; apropos search. REPL buffers are similarly tied to their
   1782 ;;; respective connections.
   1783 ;;;
   1784 ;;; When Emacs is dispatching some network message that arrived from a
   1785 ;;; connection it will dynamically bind `sly-dispatching-connection'
   1786 ;;; so that the event will be processed in the context of that
   1787 ;;; connection.
   1788 ;;;
   1789 ;;; This is mostly transparent. The user should be aware that he can
   1790 ;;; set the default connection to pick which Lisp handles commands in
   1791 ;;; Lisp-mode source buffers, and sly hackers should be aware that
   1792 ;;; they can tie a buffer to a specific connection. The rest takes
   1793 ;;; care of itself.
   1794 
   1795 (defvar sly-dispatching-connection nil
   1796   "Network process currently executing.
   1797 This is dynamically bound while handling messages from Lisp; it
   1798 overrides `sly-buffer-connection' and `sly-default-connection'.")
   1799 
   1800 (make-variable-buffer-local
   1801  (defvar sly-buffer-connection nil
   1802    "Network connection to use in the current buffer.
   1803 This overrides `sly-default-connection'."))
   1804 
   1805 (defvar sly-default-connection nil
   1806   "Network connection to use by default.
   1807 Used for all Lisp communication, except when overridden by
   1808 `sly-dispatching-connection' or `sly-buffer-connection'.")
   1809 
   1810 (defun sly-current-connection ()
   1811   "Return the connection to use for Lisp interaction.
   1812 Return nil if there's no connection."
   1813   (or sly-dispatching-connection
   1814       sly-buffer-connection
   1815       sly-default-connection))
   1816 
   1817 (defun sly-connection ()
   1818   "Return the connection to use for Lisp interaction.
   1819 Signal an error if there's no connection."
   1820   (let ((conn (sly-current-connection)))
   1821     (cond ((and (not conn) sly-net-processes)
   1822            (or (sly-auto-select-connection)
   1823                (error "Connections available, but none selected.")))
   1824           ((not conn)
   1825            (or (sly-auto-start)
   1826                (error "No current SLY connection.")))
   1827           ((not (process-live-p conn))
   1828            (error "Current connection %s is closed." conn))
   1829           (t conn))))
   1830 
   1831 (define-obsolete-variable-alias 'sly-auto-connect
   1832   'sly-auto-start "2.5")
   1833 (defcustom sly-auto-start 'never
   1834   "Controls auto connection when information from lisp process is needed.
   1835 This doesn't mean it will connect right after SLY is loaded."
   1836   :group 'sly-mode
   1837   :type '(choice (const never)
   1838                  (const always)
   1839                  (const ask)))
   1840 
   1841 (defun sly-auto-start ()
   1842   (cond ((or (eq sly-auto-start 'always)
   1843              (and (eq sly-auto-start 'ask)
   1844                   (sly-y-or-n-p "No connection.  Start SLY? ")))
   1845          (save-window-excursion
   1846            (sly)
   1847            (while (not (sly-current-connection))
   1848              (sleep-for 1))
   1849            (sly-connection)))
   1850         (t nil)))
   1851 
   1852 (cl-defmacro sly-with-connection-buffer ((&optional process) &rest body)
   1853   "Execute BODY in the process-buffer of PROCESS.
   1854 If PROCESS is not specified, `sly-connection' is used.
   1855 
   1856 \(fn (&optional PROCESS) &body BODY))"
   1857   (declare (indent 1))
   1858   `(with-current-buffer
   1859        (process-buffer (or ,process (sly-connection)
   1860                            (error "No connection")))
   1861      ,@body))
   1862 
   1863 ;;; Connection-local variables:
   1864 
   1865 (defmacro sly-def-connection-var (varname &rest initial-value-and-doc)
   1866   "Define a connection-local variable.
   1867 The value of the variable can be read by calling the function of the
   1868 same name (it must not be accessed directly). The accessor function is
   1869 setf-able.
   1870 
   1871 The actual variable bindings are stored buffer-local in the
   1872 process-buffers of connections. The accessor function refers to
   1873 the binding for `sly-connection'."
   1874   (declare (indent 2))
   1875   `(progn
   1876      ;; Accessor
   1877      (defun ,varname (&optional process)
   1878        ,(cl-second initial-value-and-doc)
   1879        (let ((process (or process
   1880                           (sly-current-connection)
   1881                           (error "Can't access prop %s for no connection" ',varname))))
   1882          (or (process-get process ',varname)
   1883              (let ((once ,(cl-first initial-value-and-doc)))
   1884                (process-put process ',varname once)
   1885                once))))
   1886      ;; Setf
   1887      (gv-define-setter ,varname (store &optional process)
   1888        `(let ((process (or ,process
   1889                            (sly-current-connection)
   1890                            (error "Can't access prop %s for no connection" ',',varname)))
   1891               (store-once ,store))
   1892           (process-put process ',',varname store-once)
   1893           store-once))
   1894      '(\, varname)))
   1895 
   1896 (sly-def-connection-var sly-connection-number nil
   1897   "Serial number of a connection.
   1898 Bound in the connection's process-buffer.")
   1899 
   1900 (sly-def-connection-var sly-lisp-features '()
   1901   "The symbol-names of Lisp's *FEATURES*.
   1902 This is automatically synchronized from Lisp.")
   1903 
   1904 (sly-def-connection-var sly-lisp-modules '()
   1905   "The strings of Lisp's *MODULES*.")
   1906 
   1907 (sly-def-connection-var sly-pid nil
   1908   "The process id of the Lisp process.")
   1909 
   1910 (sly-def-connection-var sly-lisp-implementation-type nil
   1911   "The implementation type of the Lisp process.")
   1912 
   1913 (sly-def-connection-var sly-lisp-implementation-version nil
   1914   "The implementation type of the Lisp process.")
   1915 
   1916 (sly-def-connection-var sly-lisp-implementation-name nil
   1917   "The short name for the Lisp implementation.")
   1918 
   1919 (sly-def-connection-var sly-lisp-implementation-program nil
   1920   "The argv[0] of the process running the Lisp implementation.")
   1921 
   1922 (sly-def-connection-var sly-connection-name nil
   1923   "The short name for connection.")
   1924 
   1925 (sly-def-connection-var sly-inferior-process nil
   1926   "The inferior process for the connection if any.")
   1927 
   1928 (sly-def-connection-var sly-communication-style nil
   1929   "The communication style.")
   1930 
   1931 (sly-def-connection-var sly-machine-instance nil
   1932   "The name of the (remote) machine running the Lisp process.")
   1933 
   1934 (sly-def-connection-var sly-connection-coding-systems nil
   1935   "Coding systems supported by the Lisp process.")
   1936 
   1937 ;;;;; Connection setup
   1938 
   1939 (defvar sly-connection-counter 0
   1940   "The number of SLY connections made. For generating serial numbers.")
   1941 
   1942 ;;; Interface
   1943 (defun sly-setup-connection (process)
   1944   "Make a connection out of PROCESS."
   1945   (let ((sly-dispatching-connection process))
   1946     (sly-init-connection-state process)
   1947     (sly-select-connection process)
   1948     (sly--setup-contribs)
   1949     process))
   1950 
   1951 (defun sly-init-connection-state (proc)
   1952   "Initialize connection state in the process-buffer of PROC."
   1953   ;; To make life simpler for the user: if this is the only open
   1954   ;; connection then reset the connection counter.
   1955   (when (equal sly-net-processes (list proc))
   1956     (setq sly-connection-counter 0))
   1957   (sly-with-connection-buffer ()
   1958     (setq sly-buffer-connection proc))
   1959   (setf (sly-connection-number proc) (cl-incf sly-connection-counter))
   1960   ;; We do the rest of our initialization asynchronously. The current
   1961   ;; function may be called from a timer, and if we setup the REPL
   1962   ;; from a timer then it mysteriously uses the wrong keymap for the
   1963   ;; first command.
   1964   (let ((sly-current-thread t))
   1965     (sly-eval-async '(slynk:connection-info)
   1966       (sly-curry #'sly-set-connection-info proc)
   1967       nil
   1968       `((sly-ignore-protocol-mismatches . ,sly-ignore-protocol-mismatches)))))
   1969 
   1970 (defun sly--trampling-rename-buffer (newname)
   1971   "Rename current buffer NEWNAME, trampling over existing ones."
   1972   (let ((existing (get-buffer newname)))
   1973     (unless (eq existing
   1974                 (current-buffer))
   1975       ;; Trample over any existing buffers on reconnection
   1976       (when existing
   1977         (let ((kill-buffer-query-functions nil))
   1978           (kill-buffer existing)))
   1979       (rename-buffer newname))))
   1980 
   1981 (defun sly-set-connection-info (connection info)
   1982   "Initialize CONNECTION with INFO received from Lisp."
   1983   (let ((sly-dispatching-connection connection)
   1984         (sly-current-thread t))
   1985     (cl-destructuring-bind (&key pid style lisp-implementation machine
   1986                                  features version modules encoding
   1987                                  &allow-other-keys) info
   1988       (sly-check-version version connection)
   1989       (setf (sly-pid) pid
   1990             (sly-communication-style) style
   1991             (sly-lisp-features) features
   1992             (sly-lisp-modules) modules)
   1993       (cl-destructuring-bind (&key type name version program)
   1994           lisp-implementation
   1995         (setf (sly-lisp-implementation-type) type
   1996               (sly-lisp-implementation-version) version
   1997               (sly-lisp-implementation-name) name
   1998               (sly-lisp-implementation-program) program
   1999               (sly-connection-name) (sly-generate-connection-name name)))
   2000       (cl-destructuring-bind (&key instance ((:type _)) ((:version _))) machine
   2001         (setf (sly-machine-instance) instance))
   2002       (cl-destructuring-bind (&key coding-systems) encoding
   2003         (setf (sly-connection-coding-systems) coding-systems)))
   2004     (let ((args (sly--when-let (p (sly-inferior-process))
   2005                   (sly-inferior-lisp-args p))))
   2006       (sly--when-let (name (plist-get args ':name))
   2007         (unless (string= (sly-lisp-implementation-name) name)
   2008           (setf (sly-connection-name)
   2009                 (sly-generate-connection-name (symbol-name name)))))
   2010       (sly-contrib--load-slynk-dependencies)
   2011       (run-hooks 'sly-connected-hook)
   2012       (sly--when-let (fun (plist-get args ':init-function))
   2013         (funcall fun)))
   2014     ;; Give the events buffer its final name
   2015     (with-current-buffer (sly--events-buffer connection)
   2016       (sly--trampling-rename-buffer (sly-buffer-name
   2017                                      :events
   2018                                      :connection connection)))
   2019     ;; Rename the inferior lisp buffer if there is one (i.e. when
   2020     ;; started via `M-x sly')
   2021     ;;
   2022     (let ((inferior-lisp-buffer (sly-inferior-lisp-buffer
   2023                                  (sly-process connection))))
   2024       (when inferior-lisp-buffer
   2025         (with-current-buffer inferior-lisp-buffer
   2026           (sly--trampling-rename-buffer (sly-buffer-name
   2027                                          :inferior-lisp
   2028                                          :connection connection)))))
   2029     (sly-message "Connected. %s" (sly-random-words-of-encouragement))))
   2030 
   2031 (defun sly-check-version (version conn)
   2032   (or (equal version sly-protocol-version)
   2033       (null sly-protocol-version)
   2034       sly-ignore-protocol-mismatches
   2035       (sly-y-or-n-p
   2036        (format "Versions differ: %s (sly) vs. %s (slynk). Continue? "
   2037                sly-protocol-version version))
   2038       (sly-net-close conn "Versions differ")
   2039       (top-level)))
   2040 
   2041 (defun sly-generate-connection-name (lisp-name)
   2042   (when (file-exists-p lisp-name)
   2043     (setq lisp-name (file-name-nondirectory lisp-name)))
   2044   (cl-loop for i from 1
   2045            for name = lisp-name then (format "%s<%d>" lisp-name i)
   2046            while (cl-find name sly-net-processes
   2047                           :key #'sly-connection-name :test #'equal)
   2048            finally (cl-return name)))
   2049 
   2050 (defun sly-select-new-default-connection (conn)
   2051   "If dead CONN was the default connection, select a new one."
   2052   (when (eq conn sly-default-connection)
   2053     (when sly-net-processes
   2054       (sly-select-connection (car sly-net-processes))
   2055       (sly-message "Default connection closed; default is now #%S (%S)"
   2056                    (sly-connection-number)
   2057                    (sly-connection-name)))))
   2058 
   2059 (defcustom sly-keep-buffers-on-connection-close '(:mrepl)
   2060   "List of buffers to keep around after a connection closes."
   2061   :group 'sly-mode
   2062   :type '(repeat
   2063           (choice
   2064            (const :tag "Debugger" :db)
   2065            (const :tag "Repl" :mrepl)
   2066            (const :tag "Ispector" :inspector)
   2067            (const :tag "Stickers replay" :stickers-replay)
   2068            (const :tag "Error" :error)
   2069            (const :tag "Source" :source)
   2070            (const :tag "Compilation" :compilation)
   2071            (const :tag "Apropos" :apropos)
   2072            (const :tag "Xref" :xref)
   2073            (const :tag "Macroexpansion" :macroexpansion)
   2074            (symbol :tag "Other"))))
   2075 
   2076 (defun sly-kill-stale-connection-buffers (conn) ;
   2077   "If CONN had some stale buffers, kill them.
   2078 Respect `sly-keep-buffers-on-connection-close'."
   2079   (let ((buffer-list (buffer-list))
   2080         (matchers
   2081          (mapcar
   2082           (lambda (type)
   2083             (format ".*%s.*$"
   2084                     ;; XXX: this is synched with `sly-buffer-name'.
   2085                     (regexp-quote (format "*sly-%s"
   2086                                           (downcase (substring (symbol-name type)
   2087                                                                1))))))
   2088           (cl-set-difference '(:db
   2089                                :mrepl
   2090                                :inspector
   2091                                :stickers-replay
   2092                                :error
   2093                                :source
   2094                                :compilation
   2095                                :apropos
   2096                                :xref
   2097                                :macroexpansion)
   2098                              sly-keep-buffers-on-connection-close))))
   2099     (cl-loop for buffer in buffer-list
   2100              when (and (cl-some (lambda (matcher)
   2101                                   (string-match matcher (buffer-name buffer)))
   2102                                 matchers)
   2103                        (with-current-buffer buffer
   2104                          (eq sly-buffer-connection conn)))
   2105              do (kill-buffer buffer))))
   2106 
   2107 (add-hook 'sly-net-process-close-hooks 'sly-select-new-default-connection)
   2108 (add-hook 'sly-net-process-close-hooks 'sly-kill-stale-connection-buffers 'append)
   2109 
   2110 ;;;;; Commands on connections
   2111 
   2112 (defun sly--purge-connections ()
   2113   "Purge `sly-net-processes' of dead processes, return living."
   2114   (cl-loop for process in sly-net-processes
   2115            if (process-live-p process)
   2116            collect process
   2117            else do
   2118            (sly-warning "process %s in `sly-net-processes' dead. Force closing..." process)
   2119            (sly-net-close process "process state invalid" nil t)))
   2120 
   2121 (defun sly-prompt-for-connection (&optional prompt connections dont-require-match)
   2122   (let* ((connections (or connections (sly--purge-connections)))
   2123          (connection-names (cl-loop for process in
   2124                                     (sort connections
   2125                                           #'(lambda (p1 _p2)
   2126                                               (eq p1 (sly-current-connection))))
   2127                                     collect (sly-connection-name process)))
   2128          (connection-names (if dont-require-match
   2129                                (cons dont-require-match
   2130                                      connection-names)
   2131                              connection-names))
   2132          (connection-name (and connection-names
   2133                                (sly-completing-read
   2134                                 (or prompt "Connection: ")
   2135                                 connection-names
   2136                                 nil (not dont-require-match))))
   2137          (target (cl-find connection-name sly-net-processes :key #'sly-connection-name
   2138                           :test #'string=)))
   2139     (cond (target target)
   2140           ((and dont-require-match (or (zerop (length connection-name))
   2141                                        (string= connection-name dont-require-match)))
   2142            nil)
   2143           (connection-name
   2144            (sly-error "No such connection"))
   2145           (t
   2146            (sly-error "No connections")))))
   2147 
   2148 (defun sly-auto-select-connection ()
   2149   (let* ((c0 (car (sly--purge-connections)))
   2150          (c (cond ((eq sly-auto-select-connection 'always) c0)
   2151                   ((and (eq sly-auto-select-connection 'ask)
   2152                         (sly-prompt-for-connection "Choose a new default connection: "))))))
   2153     (when c
   2154       (sly-select-connection c)
   2155       (sly-message "Switching to connection: %s" (sly-connection-name c))
   2156       c)))
   2157 
   2158 (defvar sly-select-connection-hook nil)
   2159 
   2160 (defun sly-select-connection (process)
   2161   "Make PROCESS the default connection."
   2162   (setq sly-default-connection process)
   2163   (run-hooks 'sly-select-connection-hook))
   2164 
   2165 (define-obsolete-function-alias 'sly-cycle-connections 'sly-next-connection "1.0.0-beta")
   2166 
   2167 (defun sly-next-connection (arg &optional dont-wrap)
   2168   "Switch to the next SLY connection, cycling through all connections.
   2169 Skip ARG-1 connections. Negative ARG means cycle back. DONT-WRAP
   2170 means don't wrap around when last connection is reached."
   2171   (interactive "p")
   2172   (cl-labels ((connection-full-name
   2173                (c)
   2174                (format "%s %s" (sly-connection-name c) (process-contact c))))
   2175     (cond ((not sly-net-processes)
   2176            (sly-error "No connections to cycle"))
   2177           ((null (cdr sly-net-processes))
   2178            (sly-message "Only one connection: %s" (connection-full-name (car sly-net-processes))))
   2179           (t
   2180            (let* ((dest (append (member (sly-current-connection)
   2181                                         sly-net-processes)
   2182                                 (unless dont-wrap sly-net-processes)))
   2183                   (len (length sly-net-processes))
   2184                   (target (nth (mod arg len)
   2185                                dest)))
   2186              (unless target
   2187                (sly-error "No more connections"))
   2188              (sly-select-connection target)
   2189              (if (and sly-buffer-connection
   2190                       (not (eq sly-buffer-connection target)))
   2191                  (sly-message "switched to: %s but buffer remains in: %s"
   2192                               (connection-full-name target)
   2193                               (connection-full-name sly-buffer-connection))
   2194                (sly-message "switched to: %s (%s/%s)" (connection-full-name target)
   2195                             (1+ (cl-position target sly-net-processes))
   2196                             len))
   2197              (sly--refresh-mode-line))))))
   2198 
   2199 (defun sly-prev-connection (arg &optional dont-wrap)
   2200   "Switch to the previous SLY connection, cycling through all connections.
   2201 See `sly-next-connection' for other args."
   2202   (interactive "p")
   2203   (sly-next-connection (- arg) dont-wrap))
   2204 
   2205 (defun sly-disconnect (&optional interactive)
   2206   "Close the current connection."
   2207   (interactive (list t))
   2208   (let ((connection (if interactive
   2209                         (sly-prompt-for-connection "Connection to disconnect: ")
   2210                       (sly-current-connection))))
   2211     (sly-net-close connection "Disconnecting")))
   2212 
   2213 (defun sly-disconnect-all ()
   2214   "Disconnect all connections."
   2215   (interactive)
   2216   (mapc #'(lambda (process)
   2217             (sly-net-close process "Disconnecting all connections"))
   2218         sly-net-processes))
   2219 
   2220 (defun sly-connection-port (connection)
   2221   "Return the remote port number of CONNECTION."
   2222   (cadr (process-contact connection)))
   2223 
   2224 (defun sly-process (&optional connection)
   2225   "Return the Lisp process for CONNECTION (default `sly-connection').
   2226 Return nil if there's no process object for the connection."
   2227   (let ((proc (sly-inferior-process connection)))
   2228     (if (and proc
   2229              (memq (process-status proc) '(run stop)))
   2230         proc)))
   2231 
   2232 ;; Non-macro version to keep the file byte-compilable.
   2233 (defun sly-set-inferior-process (connection process)
   2234   (setf (sly-inferior-process connection) process))
   2235 
   2236 (defun sly-use-sigint-for-interrupt (&optional connection)
   2237   (let ((c (or connection (sly-connection))))
   2238     (cl-ecase (sly-communication-style c)
   2239       ((:fd-handler nil) t)
   2240       ((:spawn :sigio) nil))))
   2241 
   2242 (defvar sly-inhibit-pipelining t
   2243   "*If true, don't send background requests if Lisp is already busy.")
   2244 
   2245 (defun sly-background-activities-enabled-p ()
   2246   (and (let ((con (sly-current-connection)))
   2247          (and con
   2248               (eq (process-status con) 'open)))
   2249        (or (not (sly-busy-p))
   2250            (not sly-inhibit-pipelining))))
   2251 
   2252 
   2253 ;;;; Communication protocol
   2254 
   2255 ;;;;; Emacs Lisp programming interface
   2256 ;;;
   2257 ;;; The programming interface for writing Emacs commands is based on
   2258 ;;; remote procedure calls (RPCs). The basic operation is to ask Lisp
   2259 ;;; to apply a named Lisp function to some arguments, then to do
   2260 ;;; something with the result.
   2261 ;;;
   2262 ;;; Requests can be either synchronous (blocking) or asynchronous
   2263 ;;; (with the result passed to a callback/continuation function).  If
   2264 ;;; an error occurs during the request then the debugger is entered
   2265 ;;; before the result arrives -- for synchronous evaluations this
   2266 ;;; requires a recursive edit.
   2267 ;;;
   2268 ;;; You should use asynchronous evaluations (`sly-eval-async') for
   2269 ;;; most things. Reserve synchronous evaluations (`sly-eval') for
   2270 ;;; the cases where blocking Emacs is really appropriate (like
   2271 ;;; completion) and that shouldn't trigger errors (e.g. not evaluate
   2272 ;;; user-entered code).
   2273 ;;;
   2274 ;;; We have the concept of the "current Lisp package". RPC requests
   2275 ;;; always say what package the user is making them from and the Lisp
   2276 ;;; side binds that package to *BUFFER-PACKAGE* to use as it sees
   2277 ;;; fit. The current package is defined as the buffer-local value of
   2278 ;;; `sly-buffer-package' if set, and otherwise the package named by
   2279 ;;; the nearest IN-PACKAGE as found by text search (cl-first backwards,
   2280 ;;; then forwards).
   2281 ;;;
   2282 ;;; Similarly we have the concept of the current thread, i.e. which
   2283 ;;; thread in the Lisp process should handle the request. The current
   2284 ;;; thread is determined solely by the buffer-local value of
   2285 ;;; `sly-current-thread'. This is usually bound to t meaning "no
   2286 ;;; particular thread", but can also be used to nominate a specific
   2287 ;;; thread. The REPL and the debugger both use this feature to deal
   2288 ;;; with specific threads.
   2289 
   2290 (make-variable-buffer-local
   2291  (defvar sly-current-thread t
   2292    "The id of the current thread on the Lisp side.
   2293 t means the \"current\" thread;
   2294 fixnum a specific thread."))
   2295 
   2296 (make-variable-buffer-local
   2297  (defvar sly-buffer-package nil
   2298    "The Lisp package associated with the current buffer.
   2299 This is set only in buffers bound to specific packages."))
   2300 
   2301 ;;; `sly-rex' is the RPC primitive which is used to implement both
   2302 ;;; `sly-eval' and `sly-eval-async'. You can use it directly if
   2303 ;;; you need to, but the others are usually more convenient.
   2304 
   2305 (defvar sly-rex-extra-options-functions nil
   2306   "Functions returning extra options to send with `sly-rex'.")
   2307 
   2308 (cl-defmacro sly-rex ((&rest _)
   2309                       (sexp &optional
   2310                             (package '(sly-current-package))
   2311                             (thread 'sly-current-thread))
   2312                       &rest continuations)
   2313   "(sly-rex (VAR ...) (SEXP &optional PACKAGE THREAD) CLAUSES ...)
   2314 
   2315 Remote EXecute SEXP.
   2316 
   2317 SEXP is evaluated and the princed version is sent to Lisp.
   2318 
   2319 PACKAGE is evaluated and Lisp binds *BUFFER-PACKAGE* to this package.
   2320 The default value is (sly-current-package).
   2321 
   2322 CLAUSES is a list of patterns with same syntax as
   2323 `sly-dcase'.  The result of the evaluation of SEXP is
   2324 dispatched on CLAUSES.  The result is either a sexp of the
   2325 form (:ok VALUE) or (:abort CONDITION).  CLAUSES is executed
   2326 asynchronously.
   2327 
   2328 Note: don't use backquote syntax for SEXP, because various Emacs
   2329 versions cannot deal with that."
   2330   (declare (indent 2)
   2331            (debug (sexp (form &optional sexp sexp)
   2332                         &rest (sexp &rest form))))
   2333   (let ((result (cl-gensym)))
   2334     `(sly-dispatch-event
   2335       (cl-list* :emacs-rex ,sexp ,package ,thread
   2336                 (lambda (,result)
   2337                   (sly-dcase ,result
   2338                     ,@continuations))
   2339                 (cl-loop for fn in sly-rex-extra-options-functions
   2340                          append (funcall fn))))))
   2341 
   2342 ;;; Interface
   2343 (defun sly-current-package ()
   2344   "Return the Common Lisp package in the current context.
   2345 If `sly-buffer-package' has a value then return that, otherwise
   2346 search for and read an `in-package' form."
   2347   (or sly-buffer-package
   2348       (save-restriction
   2349         (widen)
   2350         (sly-find-buffer-package))))
   2351 
   2352 (defvar sly-find-buffer-package-function 'sly-search-buffer-package
   2353   "*Function to use for `sly-find-buffer-package'.
   2354 The result should be the package-name (a string)
   2355 or nil if nothing suitable can be found.")
   2356 
   2357 (defun sly-find-buffer-package ()
   2358   "Figure out which Lisp package the current buffer is associated with."
   2359   (funcall sly-find-buffer-package-function))
   2360 
   2361 (make-variable-buffer-local
   2362  (defvar sly-package-cache nil
   2363    "Cons of the form (buffer-modified-tick . package)"))
   2364 
   2365 ;; When modifing this code consider cases like:
   2366 ;;  (in-package #.*foo*)
   2367 ;;  (in-package #:cl)
   2368 ;;  (in-package :cl)
   2369 ;;  (in-package "CL")
   2370 ;;  (in-package |CL|)
   2371 ;;  (in-package #+ansi-cl :cl #-ansi-cl 'lisp)
   2372 
   2373 (defun sly-search-buffer-package ()
   2374   (let ((case-fold-search t)
   2375         (regexp (concat "^[ \t]*(\\(cl:\\|common-lisp:\\)?in-package\\>[ \t']*"
   2376                         "\\([^)]+\\)[ \t]*)")))
   2377     (save-excursion
   2378       (when (or (re-search-backward regexp nil t)
   2379                 (re-search-forward regexp nil t))
   2380         (match-string-no-properties 2)))))
   2381 
   2382 ;;; Synchronous requests are implemented in terms of asynchronous
   2383 ;;; ones. We make an asynchronous request with a continuation function
   2384 ;;; that `throw's its result up to a `catch' and then enter a loop of
   2385 ;;; handling I/O until that happens.
   2386 
   2387 (defvar sly--stack-eval-tags nil
   2388   "List of stack-tags of waiting on the elisp stack.
   2389 This is used by the sly-db debugger to decide whether to enter a
   2390 `recursive-edit', so that if a synchronous `sly-eval' request
   2391 errors and brings us a Slynk debugger, we can fix the error,
   2392 invoke a restart and still get the return value of the `sly-eval'
   2393 as if nothing had happened.")
   2394 
   2395 (defun sly-eval (sexp &optional package cancel-on-input cancel-on-input-retval)
   2396   "Evaluate SEXP in Slynk's PACKAGE and return the result.
   2397 If CANCEL-ON-INPUT cancel the request immediately if the user
   2398 wants to input, and return CANCEL-ON-INPUT-RETVAL."
   2399   (when (null package) (setq package (sly-current-package)))
   2400   (let* ((catch-tag (make-symbol (format "sly-result-%d"
   2401                                          (sly-continuation-counter))))
   2402          (sly--stack-eval-tags (cons catch-tag sly--stack-eval-tags))
   2403          (cancelled nil)
   2404          (check-conn
   2405           (lambda ()
   2406             (unless (eq (process-status (sly-connection)) 'open)
   2407               (error "Lisp connection closed unexpectedly"))))
   2408          (retval
   2409           (unwind-protect
   2410               (catch catch-tag
   2411                 (sly-rex ()
   2412                     (sexp package)
   2413                   ((:ok value)
   2414                    (unless cancelled
   2415                      (unless (member catch-tag sly--stack-eval-tags)
   2416                        (error "Reply to nested `sly-eval' request with tag=%S sexp=%S"
   2417                               catch-tag sexp))
   2418                      (throw catch-tag (list #'identity value))))
   2419                   ((:abort _condition)
   2420                    (unless cancelled
   2421                      (throw catch-tag
   2422                             (list #'error "Synchronous Lisp Evaluation aborted")))))
   2423                 (cond (cancel-on-input
   2424                        ;; Setting `inhibit-quit' to t helps with
   2425                        ;; callers that wrap us in `while-no-input',
   2426                        ;; like `fido-mode' and Helm.  It doesn't seem
   2427                        ;; to create any specific problems, since
   2428                        ;; `sit-for' exits immediately given input
   2429                        ;; anyway.  This include the C-g input, and
   2430                        ;; thus even with `inhibit-quit' set to t, quit
   2431                        ;; happens immediately.
   2432                        (unwind-protect
   2433                            (let ((inhibit-quit t)) (while (sit-for 30)))
   2434                          (setq cancelled t))
   2435                        (funcall check-conn))
   2436                       (t
   2437                        (while t
   2438                          (funcall check-conn)
   2439                          (accept-process-output nil 30))))
   2440                 (list #'identity cancel-on-input-retval))
   2441             ;; Protect against user quit during
   2442             ;; `accept-process-output' or `sit-for', so that if the
   2443             ;; Lisp is alive and replies, we don't get an error.
   2444             (setq cancelled t))))
   2445     (apply (car retval) (cdr retval))))
   2446 
   2447 (defun sly-eval-async (sexp &optional cont package env)
   2448   "Evaluate SEXP on the superior Lisp and call CONT with the result.
   2449 
   2450 CONT is called with the overriding dynamic environment in ENV, an
   2451 alist of bindings"
   2452   (declare (indent 1))
   2453   (let ((buffer (current-buffer)))
   2454     (sly-rex ()
   2455         (sexp (or package (sly-current-package)))
   2456       ((:ok result)
   2457        (when cont
   2458          (set-buffer buffer)
   2459          (cl-progv (mapcar #'car env) (mapcar #'cdr env)
   2460            (if debug-on-error
   2461                (funcall cont result)
   2462              (condition-case err
   2463                  (funcall cont result)
   2464                (error
   2465                 (sly-message "`sly-eval-async' errored: %s"
   2466                              (if (and (eq 'error (car err))
   2467                                       (stringp (cadr err)))
   2468                                  (cadr err)
   2469                                err))))))))
   2470       ((:abort condition)
   2471        (sly-message "Evaluation aborted on %s." condition))))
   2472   ;; Guard against arbitrary return values which once upon a time
   2473   ;; showed up in the minibuffer spuriously (due to a bug in
   2474   ;; sly-autodoc.)  If this ever happens again, returning the
   2475   ;; following will make debugging much easier:
   2476   :sly-eval-async)
   2477 
   2478 ;;; These functions can be handy too:
   2479 
   2480 (defun sly-connected-p ()
   2481   "Return true if the Slynk connection is open."
   2482   (not (null sly-net-processes)))
   2483 
   2484 (defun sly-check-connected ()
   2485   "Signal an error if we are not connected to Lisp."
   2486   (unless (sly-connected-p)
   2487     (error "Not connected. Use `%s' to start a Lisp."
   2488            (substitute-command-keys "\\[sly]"))))
   2489 
   2490 ;; UNUSED
   2491 (defun sly-debugged-connection-p (conn)
   2492   ;; This previously was (AND (SLY-DB-DEBUGGED-CONTINUATIONS CONN) T),
   2493   ;; but an SLY-DB buffer may exist without having continuations
   2494   ;; attached to it, e.g. the one resulting from `sly-interrupt'.
   2495   (cl-loop for b in (sly-db-buffers)
   2496            thereis (with-current-buffer b
   2497                      (eq sly-buffer-connection conn))))
   2498 
   2499 (defun sly-busy-p (&optional conn)
   2500   "True if Lisp has outstanding requests.
   2501 Debugged requests are ignored."
   2502   (let ((debugged (sly-db-debugged-continuations (or conn (sly-connection)))))
   2503     (cl-remove-if (lambda (id)
   2504                     (memq id debugged))
   2505                   (sly-rex-continuations)
   2506                   :key #'car)))
   2507 
   2508 (defun sly-sync ()
   2509   "Block until the most recent request has finished."
   2510   (when (sly-rex-continuations)
   2511     (let ((tag (caar (sly-rex-continuations))))
   2512       (while (cl-find tag (sly-rex-continuations) :key #'car)
   2513         (accept-process-output nil 0.1)))))
   2514 
   2515 (defun sly-ping ()
   2516   "Check that communication works."
   2517   (interactive)
   2518   (sly-message "%s" (sly-eval "PONG")))
   2519 
   2520 ;;;;; Protocol event handler (the guts)
   2521 ;;;
   2522 ;;; This is the protocol in all its glory. The input to this function
   2523 ;;; is a protocol event that either originates within Emacs or arrived
   2524 ;;; over the network from Lisp.
   2525 ;;;
   2526 ;;; Each event is a list beginning with a keyword and followed by
   2527 ;;; arguments. The keyword identifies the type of event. Events
   2528 ;;; originating from Emacs have names starting with :emacs- and events
   2529 ;;; from Lisp don't.
   2530 
   2531 (sly-def-connection-var sly-rex-continuations '()
   2532   "List of (ID . FUNCTION) continuations waiting for RPC results.")
   2533 
   2534 (sly-def-connection-var sly-continuation-counter 0
   2535   "Continuation serial number counter.")
   2536 
   2537 (defvar sly-event-hooks)
   2538 
   2539 (defun sly-dispatch-event (event &optional process)
   2540   (let ((sly-dispatching-connection (or process (sly-connection))))
   2541     (or (run-hook-with-args-until-success 'sly-event-hooks event)
   2542         (sly-dcase event
   2543           ((:emacs-rex form package thread continuation &rest extra-options)
   2544            (when (and (sly-use-sigint-for-interrupt) (sly-busy-p))
   2545              (sly-display-oneliner "; pipelined request... %S" form))
   2546            (let ((id (cl-incf (sly-continuation-counter))))
   2547              ;; JT@2020-12-10: FIXME: Force inhibit-quit here to
   2548              ;; ensure atomicity between `sly-send' and the `push'?
   2549              ;; See Github#385..
   2550              (sly-send `(:emacs-rex ,form ,package ,thread ,id ,@extra-options))
   2551              (push (cons id continuation) (sly-rex-continuations))
   2552              (sly--refresh-mode-line)))
   2553           ((:return value id)
   2554            (let ((rec (assq id (sly-rex-continuations))))
   2555              (cond (rec (setf (sly-rex-continuations)
   2556                               (remove rec (sly-rex-continuations)))
   2557                         (funcall (cdr rec) value)
   2558                         (sly--refresh-mode-line))
   2559                    (t
   2560                     (error "Unexpected reply: %S %S" id value)))))
   2561           ((:debug-activate thread level &optional _ignored)
   2562            (cl-assert thread)
   2563            (sly-db--ensure-initialized thread level))
   2564           ((:debug thread level condition restarts frames conts)
   2565            (cl-assert thread)
   2566            (sly-db-setup thread level condition restarts frames conts))
   2567           ((:debug-return thread level stepping)
   2568            (cl-assert thread)
   2569            (sly-db-exit thread level stepping))
   2570           ((:emacs-interrupt thread)
   2571            (sly-send `(:emacs-interrupt ,thread)))
   2572           ((:read-from-minibuffer thread tag prompt initial-value)
   2573            (sly-read-from-minibuffer-for-slynk thread tag prompt
   2574                                                initial-value))
   2575           ((:y-or-n-p thread tag question)
   2576            (sly-remote-y-or-n-p thread tag question))
   2577           ((:emacs-return-string thread tag string)
   2578            (sly-send `(:emacs-return-string ,thread ,tag ,string)))
   2579           ((:new-features features)
   2580            (setf (sly-lisp-features) features))
   2581           ((:indentation-update info)
   2582            (sly-handle-indentation-update info))
   2583           ((:eval-no-wait form)
   2584            (sly-check-eval-in-emacs-enabled)
   2585            (eval (read form) t))
   2586           ((:eval thread tag form-string)
   2587            (sly-check-eval-in-emacs-enabled)
   2588            (sly-eval-for-lisp thread tag form-string))
   2589           ((:emacs-return thread tag value)
   2590            (sly-send `(:emacs-return ,thread ,tag ,value)))
   2591           ((:ed what)
   2592            (sly-ed what))
   2593           ((:inspect what thread tag)
   2594            (let ((hook (when (and thread tag)
   2595                          (sly-curry #'sly-send
   2596                                     `(:emacs-return ,thread ,tag nil)))))
   2597              (sly--open-inspector what :kill-hook hook :switch :raise)))
   2598           ((:background-message message)
   2599            (sly-temp-message 1 3 "[background-message] %s" message))
   2600           ((:debug-condition thread message)
   2601            (cl-assert thread)
   2602            (sly-message "[debug-condition] %s" message))
   2603           ((:ping thread tag)
   2604            (sly-send `(:emacs-pong ,thread ,tag)))
   2605           ((:reader-error packet condition)
   2606            (sly-with-popup-buffer ((sly-buffer-name :error
   2607                                                     :connection sly-dispatching-connection))
   2608              (princ (format "Invalid protocol message:\n%s\n\n%s"
   2609                             condition packet))
   2610              (goto-char (point-min)))
   2611            (error "Invalid protocol message"))
   2612           ((:invalid-rpc id message)
   2613            (setf (sly-rex-continuations)
   2614                  (cl-remove id (sly-rex-continuations) :key #'car))
   2615            (error "Invalid rpc: %s" message))
   2616           ((:emacs-skipped-packet _pkg))
   2617           ((:test-delay seconds) ; for testing only
   2618            (sit-for seconds))
   2619           ((:channel-send id msg)
   2620            (sly-channel-send (or (sly-find-channel id)
   2621                                  (error "Invalid channel id: %S %S" id msg))
   2622                              msg))
   2623           ((:emacs-channel-send id msg)
   2624            (sly-send `(:emacs-channel-send ,id ,msg)))
   2625           ((:invalid-channel channel-id reason)
   2626            (error "Invalid remote channel %s: %s" channel-id reason))))))
   2627 
   2628 (defvar sly--send-last-command nil
   2629   "Value of `this-command' at time of last `sly-send' call.")
   2630 
   2631 (defun sly-send (sexp)
   2632   "Send SEXP directly over the wire on the current connection."
   2633   (setq sly--send-last-command this-command)
   2634   (sly-net-send sexp (sly-connection)))
   2635 
   2636 (defun sly-reset ()
   2637   "Clear all pending continuations and erase connection buffer."
   2638   (interactive)
   2639   (setf (sly-rex-continuations) '())
   2640   (mapc #'kill-buffer (sly-db-buffers))
   2641   (sly-with-connection-buffer ()
   2642     (erase-buffer)))
   2643 
   2644 (defun sly-send-sigint ()
   2645   (interactive)
   2646   (signal-process (sly-pid) 'SIGINT))
   2647 
   2648 ;;;;; Channels
   2649 
   2650 ;;; A channel implements a set of operations.  Those operations can be
   2651 ;;; invoked by sending messages to the channel.  Channels are used for
   2652 ;;; protocols which can't be expressed naturally with RPCs, e.g. for
   2653 ;;; streaming data over the wire.
   2654 ;;;
   2655 ;;; A channel can be "remote" or "local".  Remote channels are
   2656 ;;; represented by integers.  Local channels are structures.  Messages
   2657 ;;; sent to a closed (remote) channel are ignored.
   2658 
   2659 (sly-def-connection-var sly-channels '()
   2660   "Alist of the form (ID . CHANNEL).")
   2661 
   2662 (sly-def-connection-var sly-channels-counter 0
   2663   "Channel serial number counter.")
   2664 
   2665 (cl-defstruct (sly-channel (:conc-name sly-channel.)
   2666                            (:constructor
   2667                             sly-make-channel% (operations name id plist)))
   2668   operations name id plist)
   2669 
   2670 (defun sly-make-channel (operations &optional name)
   2671   (let* ((id (cl-incf (sly-channels-counter)))
   2672          (ch (sly-make-channel% operations name id nil)))
   2673     (push (cons id ch) (sly-channels))
   2674     ch))
   2675 
   2676 (defun sly-close-channel (channel)
   2677   (setf (sly-channel.operations channel) 'closed-channel)
   2678   (let ((probe (assq (sly-channel.id channel)
   2679                      (and (sly-current-connection)
   2680                           (sly-channels)))))
   2681     (cond (probe (setf (sly-channels) (delete probe (sly-channels))))
   2682           (t (error "Can't close invalid channel: %s" channel)))))
   2683 
   2684 (defun sly-find-channel (id)
   2685   (cdr (assq id (sly-channels))))
   2686 
   2687 (defun sly-channel-send (channel message)
   2688   (apply (or (gethash (car message) (sly-channel.operations channel))
   2689              (error "Unsupported operation %S for channel %d"
   2690                     (car message)
   2691                     (sly-channel.id channel)))
   2692          channel (cdr message)))
   2693 
   2694 (defun sly-channel-put (channel prop value)
   2695   (setf (sly-channel.plist channel)
   2696         (plist-put (sly-channel.plist channel) prop value)))
   2697 
   2698 (defun sly-channel-get (channel prop)
   2699   (plist-get (sly-channel.plist channel) prop))
   2700 
   2701 (eval-and-compile
   2702   (defun sly-channel-method-table-name (type)
   2703     (intern (format "sly-%s-channel-methods" type))))
   2704 
   2705 (defmacro sly-define-channel-type (name)
   2706   (declare (indent defun))
   2707   (let ((tab (sly-channel-method-table-name name)))
   2708     `(defvar ,tab (make-hash-table :size 10))))
   2709 
   2710 (defmacro sly-define-channel-method (type method args &rest body)
   2711   (declare (indent 3) (debug (&define sexp name lambda-list
   2712                                       def-body)))
   2713   `(puthash ',method
   2714             (lambda (self . ,args) ,@body)
   2715             ,(sly-channel-method-table-name type)))
   2716 
   2717 (defun sly-send-to-remote-channel (channel-id msg)
   2718   (sly-dispatch-event `(:emacs-channel-send ,channel-id ,msg)))
   2719 
   2720 ;;;;; Event logging to *sly-events*
   2721 ;;;
   2722 ;;; The *sly-events* buffer logs all protocol messages for debugging
   2723 ;;; purposes. 
   2724 
   2725 (defvar sly-log-events t
   2726   "*Log protocol events to the *sly-events* buffer.")
   2727 
   2728 (defun sly-log-event (event process)
   2729   "Record the fact that EVENT occurred in PROCESS."
   2730   (when sly-log-events
   2731     (with-current-buffer (sly--events-buffer process)
   2732       ;; trim?
   2733       (when (> (buffer-size) 100000)
   2734         (goto-char (/ (buffer-size) 2))
   2735         (re-search-forward "^(" nil t)
   2736         (delete-region (point-min) (point)))
   2737       (goto-char (point-max))
   2738       (unless (bolp) (insert "\n"))
   2739       (cond ((and (stringp event)
   2740                   (string-match "^;" event))
   2741              (insert-before-markers event))
   2742             (t
   2743              (save-excursion
   2744                (sly-pprint-event event (current-buffer)))))
   2745       (goto-char (point-max)))))
   2746 
   2747 (defun sly-pprint-event (event buffer)
   2748   "Pretty print EVENT in BUFFER with limited depth and width."
   2749   (let ((print-length 20)
   2750         (print-level 6)
   2751         (pp-escape-newlines t))
   2752     ;; HACK workaround for gh#183
   2753     (condition-case _oops (pp event buffer) (error (print event buffer)))))
   2754 
   2755 (defun sly--events-buffer (process)
   2756   "Return or create the event log buffer."
   2757   (let* ((probe (process-get process 'sly--events-buffer))
   2758          (buffer (or (and (buffer-live-p probe)
   2759                           probe)
   2760                      (let ((buffer (get-buffer-create
   2761                                     (apply #'sly-buffer-name
   2762                                            :events
   2763                                            (if (sly-connection-name process)
   2764                                                `(:connection ,process)
   2765                                              `(:suffix ,(format "%s" process)))))))
   2766                        (with-current-buffer buffer
   2767                          (buffer-disable-undo)
   2768                          (when (fboundp 'lisp-data-mode) ; Emacs >= 28 only
   2769                            (funcall 'lisp-data-mode))
   2770                          (set (make-local-variable 'sly-buffer-connection) process)
   2771                          (sly-mode 1))
   2772                        (process-put process 'sly--events-buffer buffer)
   2773                        buffer))))
   2774     buffer))
   2775 
   2776 (defun sly-pop-to-events-buffer (process)
   2777   "Pop to the SLY events buffer for PROCESS"
   2778   (interactive (list (sly-current-connection)))
   2779   (pop-to-buffer (sly--events-buffer process)))
   2780 
   2781 (defun sly-switch-to-most-recent (mode)
   2782   "Switch to most recent buffer in MODE, a major-mode symbol.
   2783 With prefix argument, prompt for MODE"
   2784   (interactive
   2785    (list (if current-prefix-arg
   2786              (intern (sly-completing-read
   2787                       "Switch to most recent buffer in what mode? "
   2788                       (mapcar #'symbol-name '(lisp-mode
   2789                                               emacs-lisp-mode))
   2790                       nil t))
   2791            'lisp-mode)))
   2792   (cl-loop for buffer in (buffer-list)
   2793            when (and (with-current-buffer buffer (eq major-mode mode))
   2794                      (not (eq buffer (current-buffer)))
   2795                      (not (string-match "^ " (buffer-name buffer))))
   2796            do (pop-to-buffer buffer) and return buffer))
   2797 
   2798 (defun sly-forget-pending-events (process)
   2799   "Forget any outgoing events for the PROCESS"
   2800   (interactive (list (sly-current-connection)))
   2801   (setf (sly-rex-continuations process) nil))
   2802 
   2803 
   2804 ;;;;; Cleanup after a quit
   2805 
   2806 (defun sly-restart-inferior-lisp ()
   2807   "Kill and restart the Lisp subprocess."
   2808   (interactive)
   2809   (cl-assert (sly-inferior-process) () "No inferior lisp process")
   2810   (sly-quit-lisp-internal (sly-connection) 'sly-restart-sentinel t))
   2811 
   2812 (defun sly-restart-sentinel (connection _message)
   2813   "When CONNECTION dies, start a similar inferior lisp process.
   2814 Also rearrange windows."
   2815   (cl-assert (process-status connection) 'closed)
   2816   (let* ((moribund-proc (sly-inferior-process connection))
   2817          (args (sly-inferior-lisp-args moribund-proc))
   2818          (buffer (buffer-name (process-buffer moribund-proc))))
   2819     (sly-net-close connection "Restarting inferior lisp process")
   2820     (sly-inferior-connect (sly-start-lisp (plist-get args :program)
   2821                                           (plist-get args :program-args)
   2822                                           (plist-get args :env)
   2823                                           nil
   2824                                           buffer)
   2825                           args)))
   2826 
   2827 
   2828 ;;;; Compilation and the creation of compiler-note annotations
   2829 
   2830 (defvar sly-highlight-compiler-notes t
   2831   "*When non-nil annotate buffers with compilation notes etc.")
   2832 
   2833 (defcustom sly-compilation-finished-hook '(sly-maybe-show-compilation-log)
   2834   "Hook called after compilation.
   2835 Each function is called with four arguments (SUCCESSP NOTES BUFFER LOADP)
   2836 SUCCESSP indicates if the compilation was successful.
   2837 NOTES is a list of compilation notes.
   2838 BUFFER is the buffer just compiled, or nil if a string was compiled.
   2839 LOADP is the value of the LOAD flag passed to `sly-compile-file', or t
   2840 if a string."
   2841   :group 'sly-mode
   2842   :type 'hook
   2843   :options '(sly-maybe-show-compilation-log
   2844              sly-show-compilation-log
   2845              sly-maybe-show-xrefs-for-notes
   2846              sly-goto-first-note))
   2847 
   2848 ;; FIXME: I doubt that anybody uses this directly and it seems to be
   2849 ;; only an ugly way to pass arguments.
   2850 (defvar sly-compilation-policy nil
   2851   "When non-nil compile with these optimization settings.")
   2852 
   2853 (defun sly-compute-policy (arg)
   2854   "Return the policy for the prefix argument ARG."
   2855   (let ((between (lambda (min n max)
   2856                    (cond ((< n min) min)
   2857                          ((> n max) max)
   2858                          (t n)))))
   2859     (let ((n (prefix-numeric-value arg)))
   2860       (cond ((not arg)   sly-compilation-policy)
   2861             ((cl-plusp n)   `((cl:debug . ,(funcall between 0 n 3))))
   2862             ((eq arg '-) `((cl:speed . 3)))
   2863             (t           `((cl:speed . ,(funcall between 0 (abs n) 3))))))))
   2864 
   2865 (cl-defstruct (sly-compilation-result
   2866                (:type list)
   2867                (:conc-name sly-compilation-result.)
   2868                (:constructor nil)
   2869                (:copier nil))
   2870   tag notes successp duration loadp faslfile)
   2871 
   2872 (defvar sly-last-compilation-result nil
   2873   "The result of the most recently issued compilation.")
   2874 
   2875 (defun sly-compiler-notes ()
   2876   "Return all compiler notes, warnings, and errors."
   2877   (sly-compilation-result.notes sly-last-compilation-result))
   2878 
   2879 (defun sly-compile-and-load-file (&optional policy)
   2880   "Compile and load the buffer's file and highlight compiler notes.
   2881 
   2882 With (positive) prefix argument the file is compiled with maximal
   2883 debug settings (`C-u'). With negative prefix argument it is compiled for
   2884 speed (`M--'). If a numeric argument is passed set debug or speed settings
   2885 to it depending on its sign.
   2886 
   2887 Each source location that is the subject of a compiler note is
   2888 underlined and annotated with the relevant information. The commands
   2889 `sly-next-note' and `sly-previous-note' can be used to navigate
   2890 between compiler notes and to display their full details."
   2891   (interactive "P")
   2892   (sly-compile-file t (sly-compute-policy policy)))
   2893 
   2894 (defcustom sly-compile-file-options '()
   2895   "Plist of additional options that C-c C-k should pass to Lisp.
   2896 Currently only :fasl-directory is supported."
   2897   :group 'sly-lisp
   2898   :type '(plist :key-type symbol :value-type (file :must-match t)))
   2899 
   2900 (defun sly-compile-file (&optional load policy)
   2901   "Compile current buffer's file and highlight resulting compiler notes.
   2902 
   2903 See `sly-compile-and-load-file' for further details."
   2904   (interactive)
   2905   (unless buffer-file-name
   2906     (error "Buffer %s is not associated with a file." (buffer-name)))
   2907   (check-parens)
   2908   (when (and (buffer-modified-p)
   2909              (or (not compilation-ask-about-save)
   2910                  (sly-y-or-n-p (format "Save file %s? " (buffer-file-name)))))
   2911     (save-buffer))
   2912   (let ((file (sly-to-lisp-filename (buffer-file-name)))
   2913         (options (sly-simplify-plist `(,@sly-compile-file-options
   2914                                        :policy ,policy))))
   2915     (sly-eval-async
   2916         `(slynk:compile-file-for-emacs ,file ,(if load t nil)
   2917                                        . ,(sly-hack-quotes options))
   2918       #'(lambda (result)
   2919           (sly-compilation-finished result (current-buffer))))
   2920     (sly-message "Compiling %s..." file)))
   2921 
   2922 (defun sly-hack-quotes (arglist)
   2923   ;; eval is the wrong primitive, we really want funcall
   2924   (cl-loop for arg in arglist collect `(quote ,arg)))
   2925 
   2926 (defun sly-simplify-plist (plist)
   2927   (cl-loop for (key val) on plist by #'cddr
   2928            append (cond ((null val) '())
   2929                         (t (list key val)))))
   2930 
   2931 (defun sly-compile-defun (&optional raw-prefix-arg)
   2932   "Compile the current toplevel form.
   2933 
   2934 With (positive) prefix argument the form is compiled with maximal
   2935 debug settings (`C-u'). With negative prefix argument it is compiled for
   2936 speed (`M--'). If a numeric argument is passed set debug or speed settings
   2937 to it depending on its sign."
   2938   (interactive "P")
   2939   (let ((sly-compilation-policy (sly-compute-policy raw-prefix-arg)))
   2940     (if (use-region-p)
   2941         (sly-compile-region (region-beginning) (region-end))
   2942       (apply #'sly-compile-region (sly-region-for-defun-at-point)))))
   2943 
   2944 (defvar sly-compile-region-function 'sly-compile-region-as-string
   2945   "Function called by `sly-compile-region' to do actual work.")
   2946 
   2947 (defun sly-compile-region (start end)
   2948   "Compile the region."
   2949   (interactive "r")
   2950   ;; Check connection before running hooks things like
   2951   ;; sly-flash-region don't make much sense if there's no connection
   2952   (sly-connection)
   2953   (funcall sly-compile-region-function start end))
   2954 
   2955 (defun sly-compile-region-as-string (start end)
   2956   (sly-flash-region start end)
   2957   (sly-compile-string (buffer-substring-no-properties start end) start))
   2958 
   2959 (defun sly-compile-string (string start-offset)
   2960   (let* ((position (sly-compilation-position start-offset)))
   2961     (sly-eval-async
   2962         `(slynk:compile-string-for-emacs
   2963           ,string
   2964           ,(buffer-name)
   2965           ',position
   2966           ,(if (buffer-file-name) (sly-to-lisp-filename (buffer-file-name)))
   2967           ',sly-compilation-policy)
   2968       #'(lambda (result)
   2969           (sly-compilation-finished result nil)))))
   2970 
   2971 (defun sly-compilation-position (start-offset)
   2972   (let ((line (save-excursion
   2973                 (goto-char start-offset)
   2974                 (list (line-number-at-pos) (1+ (current-column))))))
   2975     `((:position ,start-offset) (:line ,@line))))
   2976 
   2977 (defcustom sly-load-failed-fasl 'never
   2978   "Which action to take when COMPILE-FILE set FAILURE-P to T.
   2979 NEVER doesn't load the fasl
   2980 ALWAYS loads the fasl
   2981 ASK asks the user."
   2982   :type '(choice (const never)
   2983                  (const always)
   2984                  (const ask)))
   2985 
   2986 (defun sly-load-failed-fasl-p ()
   2987   (cl-ecase sly-load-failed-fasl
   2988     (never nil)
   2989     (always t)
   2990     (ask (sly-y-or-n-p "Compilation failed.  Load fasl file anyway? "))))
   2991 
   2992 (defun sly-compilation-finished (result buffer &optional message)
   2993   (let ((notes (sly-compilation-result.notes result))
   2994         (duration (sly-compilation-result.duration result))
   2995         (successp (sly-compilation-result.successp result))
   2996         (faslfile (sly-compilation-result.faslfile result))
   2997         (loadp (sly-compilation-result.loadp result)))
   2998     (setf sly-last-compilation-result result)
   2999     (sly-show-note-counts notes duration (cond ((not loadp) successp)
   3000                                                (t (and faslfile successp)))
   3001                           (or (not buffer) loadp)
   3002                           message)
   3003     (when sly-highlight-compiler-notes
   3004       (sly-highlight-notes notes))
   3005     (when (and loadp faslfile
   3006                (or successp
   3007                    (sly-load-failed-fasl-p)))
   3008       (sly-eval-async `(slynk:load-file ,faslfile)))
   3009     (run-hook-with-args 'sly-compilation-finished-hook successp notes buffer loadp)))
   3010 
   3011 (defun sly-show-note-counts (notes secs successp loadp &optional message)
   3012   (sly-message (concat
   3013                 (cond ((and successp loadp)
   3014                        "Compiled and loaded")
   3015                       (successp "Compilation finished")
   3016                       (t (sly-add-face 'font-lock-warning-face
   3017                            "Compilation failed")))
   3018                 (if (null notes) ". (No warnings)" ": ")
   3019                 (mapconcat
   3020                  (lambda (msgs)
   3021                    (cl-destructuring-bind (sev . notes) msgs
   3022                      (let ((len (length notes)))
   3023                        (format "%d %s%s" len (sly-severity-label sev)
   3024                                (if (= len 1) "" "s")))))
   3025                  (sort (sly-alistify notes #'sly-note.severity #'eq)
   3026                        (lambda (x y) (sly-severity< (car y) (car x))))
   3027                  "  ")
   3028                 (if secs (format "  [%.2f secs]" secs))
   3029                 message)))
   3030 
   3031 (defun sly-highlight-notes (notes)
   3032   "Highlight compiler notes, warnings, and errors in the buffer."
   3033   (interactive (list (sly-compiler-notes)))
   3034   (with-temp-message "Highlighting notes..."
   3035     (save-excursion
   3036       (save-restriction
   3037         (widen)                  ; highlight notes on the whole buffer
   3038         (sly-remove-notes (point-min) (point-max))
   3039         (mapc #'sly--add-in-buffer-note notes)))))
   3040 
   3041 
   3042 ;;;;; Recompilation.
   3043 
   3044 ;; FIXME: This whole idea is questionable since it depends so
   3045 ;; crucially on precise source-locs.
   3046 
   3047 (defun sly-recompile-location (location)
   3048   (save-excursion
   3049     (sly-move-to-source-location location)
   3050     (sly-compile-defun)))
   3051 
   3052 (defun sly-recompile-locations (locations cont)
   3053   (sly-eval-async
   3054       `(slynk:compile-multiple-strings-for-emacs
   3055         ',(cl-loop for loc in locations collect
   3056                    (save-excursion
   3057                      (sly-move-to-source-location loc)
   3058                      (cl-destructuring-bind (start end)
   3059                          (sly-region-for-defun-at-point)
   3060                        (list (buffer-substring-no-properties start end)
   3061                              (buffer-name)
   3062                              (sly-current-package)
   3063                              start
   3064                              (if (buffer-file-name)
   3065                                  (sly-to-lisp-filename (buffer-file-name))
   3066                                nil)))))
   3067         ',sly-compilation-policy)
   3068     cont))
   3069 
   3070 
   3071 ;;;;; Compiler notes list
   3072 
   3073 (defun sly-one-line-ify (string)
   3074   "Return a single-line version of STRING.
   3075 Each newlines and following indentation is replaced by a single space."
   3076   (with-temp-buffer
   3077     (insert string)
   3078     (goto-char (point-min))
   3079     (while (re-search-forward "\n[\n \t]*" nil t)
   3080       (replace-match " "))
   3081     (buffer-string)))
   3082 
   3083 (defun sly-xref--get-xrefs-for-notes (notes)
   3084   (let ((xrefs))
   3085     (dolist (note notes)
   3086       (let* ((location (cl-getf note :location))
   3087              (fn (cadr (assq :file (cdr location))))
   3088              (file (assoc fn xrefs))
   3089              (node
   3090               (list (format "%s: %s"
   3091                             (cl-getf note :severity)
   3092                             (sly-one-line-ify (cl-getf note :message)))
   3093                     location)))
   3094         (when fn
   3095           (if file
   3096               (push node (cdr file))
   3097             (setf xrefs (cl-acons fn (list node) xrefs))))))
   3098     xrefs))
   3099 
   3100 (defun sly-maybe-show-xrefs-for-notes (_successp notes _buffer _loadp)
   3101   "Show the compiler notes NOTES if they come from more than one file."
   3102   (let ((xrefs (sly-xref--get-xrefs-for-notes notes)))
   3103     (when (cdr xrefs)                   ; >1 file
   3104       (sly-xref--show-results
   3105        xrefs 'definition "Compiler notes" (sly-current-package)))))
   3106 
   3107 (defun sly-maybe-show-compilation-log (successp notes buffer loadp)
   3108   "Display the log on failed compilations or if NOTES is non-nil."
   3109   (sly-show-compilation-log successp notes buffer loadp
   3110                             (if successp :hidden nil)))
   3111 
   3112 (defun sly-show-compilation-log (successp notes buffer loadp &optional select)
   3113   "Create and display the compilation log buffer."
   3114   (interactive (list (sly-compiler-notes)))
   3115   (sly-with-popup-buffer ((sly-buffer-name :compilation)
   3116                           :mode 'compilation-mode
   3117                           :select select)
   3118     (sly--insert-compilation-log successp notes buffer loadp)
   3119     (insert "Compilation "
   3120             (if successp "successful" "failed")
   3121             ".")))
   3122 
   3123 (defvar sly-compilation-log--notes (make-hash-table)
   3124   "Hash-table (NOTE -> (BUFFER POSITION)) for finding notes in
   3125   the SLY compilation log")
   3126 
   3127 (defun sly--insert-compilation-log (_successp notes _buffer _loadp)
   3128   "Insert NOTES in format suitable for `compilation-mode'."
   3129   (clrhash sly-compilation-log--notes)
   3130   (cl-multiple-value-bind (grouped-notes canonicalized-locs-table)
   3131       (sly-group-and-sort-notes notes)
   3132     (with-temp-message "Preparing compilation log..."
   3133       (let ((inhibit-read-only t)
   3134             (inhibit-modification-hooks t)) ; inefficient font-lock-hook
   3135         (insert (format "cd %s\n%d compiler notes:\n\n"
   3136                         default-directory (length notes)))
   3137         (cl-loop for notes in grouped-notes
   3138                  for loc = (gethash (cl-first notes) canonicalized-locs-table)
   3139                  for start = (point)
   3140                  do
   3141                  (cl-loop for note in notes
   3142                           do (puthash note
   3143                                       (cons (current-buffer) start)
   3144                                       sly-compilation-log--notes))
   3145                  (insert
   3146                   (sly--compilation-note-group-button
   3147                    (sly-canonicalized-location-to-string loc) notes)
   3148                   ":")
   3149                  (sly-insert-note-group notes)
   3150                  (insert "\n")
   3151                  (add-text-properties start (point) `(field ,notes))))
   3152       (set (make-local-variable 'compilation-skip-threshold) 0)
   3153       (setq next-error-last-buffer (current-buffer)))))
   3154 
   3155 (defun sly-insert-note-group (notes)
   3156   "Insert a group of compiler messages."
   3157   (insert "\n")
   3158   (dolist (note notes)
   3159     (insert "  " (sly-severity-label (sly-note.severity note)) ": ")
   3160     (let ((start (point)))
   3161       (insert (sly-note.message note))
   3162       (let ((ctx (sly-note.source-context note)))
   3163         (if ctx (insert "\n" ctx)))
   3164       (sly-indent-block start 4))
   3165     (insert "\n")))
   3166 
   3167 (defun sly-indent-block (start column)
   3168   "If the region back to START isn't a one-liner indent it."
   3169   (when (< start (line-beginning-position))
   3170     (save-excursion
   3171       (goto-char start)
   3172       (insert "\n"))
   3173     (sly-indent-rigidly start (point) column)))
   3174 
   3175 (defun sly-canonicalized-location (location)
   3176   "Return a list (FILE LINE COLUMN) for sly-location LOCATION.
   3177 This is quite an expensive operation so use carefully."
   3178   (save-excursion
   3179     (sly-goto-location-buffer (sly-location.buffer location))
   3180     (save-excursion
   3181       (sly-move-to-source-location location)
   3182       (list (or (buffer-file-name) (buffer-name))
   3183             (save-restriction
   3184               (widen)
   3185               (line-number-at-pos))
   3186             (1+ (current-column))))))
   3187 
   3188 (defun sly-canonicalized-location-to-string (loc)
   3189   (if loc
   3190       (cl-destructuring-bind (filename line col) loc
   3191         (format "%s:%d:%d"
   3192                 (cond ((not filename) "")
   3193                       ((let ((rel (file-relative-name filename)))
   3194                          (if (< (length rel) (length filename))
   3195                              rel)))
   3196                       (t filename))
   3197                 line col))
   3198     (format "Unknown location")))
   3199 
   3200 (defun sly-group-and-sort-notes (notes)
   3201   "First sort, then group NOTES according to their canonicalized locs."
   3202   (let ((locs (make-hash-table :test #'eq)))
   3203     (mapc (lambda (note)
   3204             (let ((loc (sly-note.location note)))
   3205               (when (sly-location-p loc)
   3206                 (puthash note (sly-canonicalized-location loc) locs))))
   3207           notes)
   3208     (cl-values (sly-group-similar
   3209                 (lambda (n1 n2)
   3210                   (equal (gethash n1 locs nil) (gethash n2 locs t)))
   3211                 (let* ((bottom most-negative-fixnum)
   3212                        (+default+ (list "" bottom bottom)))
   3213                   (sort notes
   3214                         (lambda (n1 n2)
   3215                           (cl-destructuring-bind (filename1 line1 col1)
   3216                               (gethash n1 locs +default+)
   3217                             (cl-destructuring-bind (filename2 line2 col2)
   3218                                 (gethash n2 locs +default+)
   3219                               (cond ((string-lessp filename1 filename2) t)
   3220                                     ((string-lessp filename2 filename1) nil)
   3221                                     ((< line1 line2) t)
   3222                                     ((> line1 line2) nil)
   3223                                     (t (< col1 col2)))))))))
   3224                locs)))
   3225 
   3226 (defun sly-note.severity (note)
   3227   (plist-get note :severity))
   3228 
   3229 (defun sly-note.message (note)
   3230   (plist-get note :message))
   3231 
   3232 (defun sly-note.source-context (note)
   3233   (plist-get note :source-context))
   3234 
   3235 (defun sly-note.location (note)
   3236   (plist-get note :location))
   3237 
   3238 (defun sly-severity-label (severity)
   3239   (cl-subseq (symbol-name severity) 1))
   3240 
   3241 
   3242 
   3243 ;;;;; Adding a single compiler note
   3244 ;;;;;
   3245 (defun sly-choose-overlay-region (note)
   3246   "Choose the start and end points for an overlay over NOTE.
   3247 If the location's sexp is a list spanning multiple lines, then the
   3248 region around the first element is used.
   3249 Return nil if there's no useful source location."
   3250   (let ((location (sly-note.location note)))
   3251     (when location
   3252       (sly-dcase location
   3253         ((:error _))                 ; do nothing
   3254         ((:location file pos _hints)
   3255          (cond ((eq (car file) ':source-form) nil)
   3256                ((eq (sly-note.severity note) :read-error)
   3257                 (sly-choose-overlay-for-read-error location))
   3258                ((equal pos '(:eof))
   3259                 (list (1- (point-max)) (point-max)))
   3260                (t
   3261                 (sly-choose-overlay-for-sexp location))))))))
   3262 
   3263 (defun sly-choose-overlay-for-read-error (location)
   3264   (let ((pos (sly-location-offset location)))
   3265     (save-excursion
   3266       (goto-char pos)
   3267       (cond ((sly-symbol-at-point)
   3268              ;; package not found, &c.
   3269              (list (sly-symbol-start-pos) (sly-symbol-end-pos)))
   3270             (t
   3271              (list pos (1+ pos)))))))
   3272 
   3273 (defun sly-choose-overlay-for-sexp (location)
   3274   (sly-move-to-source-location location)
   3275   (skip-chars-forward "'#`")
   3276   (let ((start (point)))
   3277     (ignore-errors (sly-forward-sexp))
   3278     (if (sly-same-line-p start (point))
   3279         (list start (point))
   3280       (list (1+ start)
   3281             (progn (goto-char (1+ start))
   3282                    (ignore-errors (forward-sexp 1))
   3283                    (point))))))
   3284 (defun sly-same-line-p (pos1 pos2)
   3285   "Return t if buffer positions POS1 and POS2 are on the same line."
   3286   (save-excursion (goto-char (min pos1 pos2))
   3287                   (<= (max pos1 pos2) (line-end-position))))
   3288 
   3289 (defvar sly-severity-face-plist
   3290   (list :error         'sly-error-face
   3291         :read-error    'sly-error-face
   3292         :warning       'sly-warning-face
   3293         :redefinition  'sly-style-warning-face
   3294         :style-warning 'sly-style-warning-face
   3295         :note          'sly-note-face))
   3296 
   3297 (defun sly-severity-face (severity)
   3298   "Return the name of the font-lock face representing SEVERITY."
   3299   (or (plist-get sly-severity-face-plist severity)
   3300       (error "No face for: %S" severity)))
   3301 
   3302 (defvar sly-severity-order
   3303   '(:note :style-warning :redefinition :warning :error :read-error))
   3304 
   3305 (defun sly-severity< (sev1 sev2)
   3306   "Return true if SEV1 is less severe than SEV2."
   3307   (< (cl-position sev1 sly-severity-order)
   3308      (cl-position sev2 sly-severity-order)))
   3309 
   3310 (defun sly-forward-positioned-source-path (source-path)
   3311   "Move forward through a sourcepath from a fixed position.
   3312 The point is assumed to already be at the outermost sexp, making the
   3313 first element of the source-path redundant."
   3314   (ignore-errors
   3315     (sly-forward-sexp)
   3316     (beginning-of-defun))
   3317   (sly--when-let (source-path (cdr source-path))
   3318     (down-list 1)
   3319     (sly-forward-source-path source-path)))
   3320 
   3321 (defun sly-forward-source-path (source-path)
   3322   (let ((origin (point)))
   3323     (condition-case nil
   3324         (progn
   3325           (cl-loop for (count . more) on source-path
   3326                    do (progn
   3327                         (sly-forward-sexp count)
   3328                         (when more (down-list 1))))
   3329           ;; Align at beginning
   3330           (sly-forward-sexp)
   3331           (beginning-of-sexp))
   3332       (error (goto-char origin)))))
   3333 
   3334 
   3335 ;; FIXME: really fix this mess
   3336 ;; FIXME: the check shouln't be done here anyway but by M-. itself.
   3337 
   3338 (defun sly-filesystem-toplevel-directory ()
   3339   ;; Windows doesn't have a true toplevel root directory, and all
   3340   ;; filenames look like "c:/foo/bar/quux.baz" from an Emacs
   3341   ;; perspective anyway.
   3342   (if (memq system-type '(ms-dos windows-nt))
   3343       ""
   3344     (file-name-as-directory "/")))
   3345 
   3346 (defun sly-file-name-merge-source-root (target-filename buffer-filename)
   3347   "Returns a filename where the source root directory of TARGET-FILENAME
   3348 is replaced with the source root directory of BUFFER-FILENAME.
   3349 
   3350 If no common source root could be determined, return NIL.
   3351 
   3352 E.g. (sly-file-name-merge-source-root
   3353        \"/usr/local/src/joe/upstream/sbcl/code/late-extensions.lisp\"
   3354        \"/usr/local/src/joe/hacked/sbcl/compiler/deftype.lisp\")
   3355 
   3356         ==> \"/usr/local/src/joe/hacked/sbcl/code/late-extensions.lisp\"
   3357 "
   3358   (let ((target-dirs (split-string (file-name-directory target-filename)
   3359                                    "/" t))
   3360         (buffer-dirs (split-string (file-name-directory buffer-filename)
   3361                                    "/" t)))
   3362     ;; Starting from the end, we look if one of the TARGET-DIRS exists
   3363     ;; in BUFFER-FILENAME---if so, it and everything left from that dirname
   3364     ;; is considered to be the source root directory of BUFFER-FILENAME.
   3365     (cl-loop with target-suffix-dirs = nil
   3366              with buffer-dirs* = (reverse buffer-dirs)
   3367              with target-dirs* = (reverse target-dirs)
   3368              for target-dir in target-dirs*
   3369              do (let  ((concat-dirs (lambda (dirs)
   3370                                       (apply #'concat
   3371                                              (mapcar #'file-name-as-directory
   3372                                                      dirs))))
   3373                        (pos (cl-position target-dir buffer-dirs*
   3374                                          :test #'equal)))
   3375                   (if (not pos)    ; TARGET-DIR not in BUFFER-FILENAME?
   3376                       (push target-dir target-suffix-dirs)
   3377                     (let* ((target-suffix
   3378                                         ; PUSH reversed for us!
   3379                             (funcall concat-dirs target-suffix-dirs))
   3380                            (buffer-root
   3381                             (funcall concat-dirs
   3382                                      (reverse (nthcdr pos buffer-dirs*)))))
   3383                       (cl-return (concat (sly-filesystem-toplevel-directory)
   3384                                          buffer-root
   3385                                          target-suffix
   3386                                          (file-name-nondirectory
   3387                                           target-filename)))))))))
   3388 
   3389 (defun sly-highlight-differences-in-dirname (base-dirname contrast-dirname)
   3390   "Returns a copy of BASE-DIRNAME where all differences between
   3391 BASE-DIRNAME and CONTRAST-DIRNAME are propertized with a
   3392 highlighting face."
   3393   (setq base-dirname (file-name-as-directory base-dirname))
   3394   (setq contrast-dirname (file-name-as-directory contrast-dirname))
   3395   (let ((base-dirs (split-string base-dirname "/" t))
   3396         (contrast-dirs (split-string contrast-dirname "/" t)))
   3397     (with-temp-buffer
   3398       (cl-loop initially (insert (sly-filesystem-toplevel-directory))
   3399                for base-dir in base-dirs do
   3400                (let ((pos (cl-position base-dir contrast-dirs :test #'equal)))
   3401                  (cond ((not pos)
   3402                         (sly-insert-propertized '(face highlight) base-dir)
   3403                         (insert "/"))
   3404                        (t
   3405                         (insert (file-name-as-directory base-dir))
   3406                         (setq contrast-dirs
   3407                               (nthcdr (1+ pos) contrast-dirs))))))
   3408       (buffer-substring (point-min) (point-max)))))
   3409 
   3410 (defvar sly-warn-when-possibly-tricked-by-M-. t
   3411   "When working on multiple source trees simultaneously, the way
   3412 `sly-edit-definition' (M-.) works can sometimes be confusing:
   3413 
   3414 `M-.' visits locations that are present in the current Lisp image,
   3415 which works perfectly well as long as the image reflects the source
   3416 tree that one is currently looking at.
   3417 
   3418 In the other case, however, one can easily end up visiting a file
   3419 in a different source root directory (the one corresponding to
   3420 the Lisp image), and is thus easily tricked to modify the wrong
   3421 source files---which can lead to quite some stressfull cursing.
   3422 
   3423 If this variable is T, a warning message is issued to raise the
   3424 user's attention whenever `M-.' is about opening a file in a
   3425 different source root that also exists in the source root
   3426 directory of the user's current buffer.
   3427 
   3428 There's no guarantee that all possible cases are covered, but
   3429 if you encounter such a warning, it's a strong indication that
   3430 you should check twice before modifying.")
   3431 
   3432 (defun sly-maybe-warn-for-different-source-root (target-filename
   3433                                                  buffer-filename)
   3434   (let ((guessed-target (sly-file-name-merge-source-root target-filename
   3435                                                          buffer-filename)))
   3436     (when (and guessed-target
   3437                (not (equal guessed-target target-filename))
   3438                (file-exists-p guessed-target))
   3439       (sly-message "Attention: This is `%s'."
   3440                    (concat (sly-highlight-differences-in-dirname
   3441                             (file-name-directory target-filename)
   3442                             (file-name-directory guessed-target))
   3443                            (file-name-nondirectory target-filename))))))
   3444 
   3445 (defun sly-check-location-filename-sanity (filename)
   3446   (when sly-warn-when-possibly-tricked-by-M-.
   3447     (cl-macrolet ((truename-safe (file) `(and ,file (file-truename ,file))))
   3448       (let ((target-filename (truename-safe filename))
   3449             (buffer-filename (truename-safe (buffer-file-name))))
   3450         (when (and target-filename
   3451                    buffer-filename)
   3452           (sly-maybe-warn-for-different-source-root
   3453            target-filename buffer-filename))))))
   3454 
   3455 (defun sly-check-location-buffer-name-sanity (buffer-name)
   3456   (sly-check-location-filename-sanity
   3457    (buffer-file-name (get-buffer buffer-name))))
   3458 
   3459 
   3460 
   3461 (defun sly-goto-location-buffer (buffer)
   3462   (sly-dcase buffer
   3463     ((:file filename)
   3464      (let ((filename (sly-from-lisp-filename filename)))
   3465        (sly-check-location-filename-sanity filename)
   3466        (set-buffer (or (get-file-buffer filename)
   3467                        (let ((find-file-suppress-same-file-warnings t))
   3468                          (find-file-noselect filename))))))
   3469     ((:buffer buffer-name)
   3470      (sly-check-location-buffer-name-sanity buffer-name)
   3471      (set-buffer buffer-name))
   3472     ((:buffer-and-file buffer filename)
   3473      (sly-goto-location-buffer
   3474       (if (get-buffer buffer)
   3475           (list :buffer buffer)
   3476         (list :file filename))))
   3477     ((:source-form string)
   3478      (set-buffer (get-buffer-create (sly-buffer-name :source)))
   3479      (erase-buffer)
   3480      (lisp-mode)
   3481      (insert string)
   3482      (goto-char (point-min)))
   3483     ((:zip file entry)
   3484      (require 'arc-mode)
   3485      (set-buffer (find-file-noselect file t))
   3486      (goto-char (point-min))
   3487      (re-search-forward (concat "  " entry "$"))
   3488      (let ((buffer (save-window-excursion
   3489                      (archive-extract)
   3490                      (current-buffer))))
   3491        (set-buffer buffer)
   3492        (goto-char (point-min))))))
   3493 
   3494 (defun sly-goto-location-position (position)
   3495   (sly-dcase position
   3496     ((:position pos)
   3497      (goto-char 1)
   3498      (forward-char (- (1- pos) (sly-eol-conversion-fixup (1- pos)))))
   3499     ((:offset start offset)
   3500      (goto-char start)
   3501      (forward-char offset))
   3502     ((:line start &optional column)
   3503      (goto-char (point-min))
   3504      (beginning-of-line start)
   3505      (cond (column (move-to-column column))
   3506            (t (skip-chars-forward " \t"))))
   3507     ((:function-name name)
   3508      (let ((case-fold-search t)
   3509            (name (regexp-quote name)))
   3510        (goto-char (point-min))
   3511        (when (or
   3512               (re-search-forward
   3513                (format "\\s *(def\\(\\s_\\|\\sw\\)*\\s +(*%s\\S_"
   3514                        (regexp-quote name)) nil t)
   3515               (re-search-forward
   3516                (format "[( \t]%s\\>\\(\\s \\|$\\)" name) nil t))
   3517          (goto-char (match-beginning 0)))))
   3518     ((:method name specializers &rest qualifiers)
   3519      (sly-search-method-location name specializers qualifiers))
   3520     ((:source-path source-path start-position)
   3521      (cond (start-position
   3522             (goto-char start-position)
   3523             (sly-forward-positioned-source-path source-path))
   3524            (t
   3525             (sly-forward-source-path source-path))))
   3526     ((:eof)
   3527      (goto-char (point-max)))))
   3528 
   3529 (defun sly-eol-conversion-fixup (n)
   3530   ;; Return the number of \r\n eol markers that we need to cross when
   3531   ;; moving N chars forward.  N is the number of chars but \r\n are
   3532   ;; counted as 2 separate chars.
   3533   (if (zerop n) 0
   3534     (cl-case (coding-system-eol-type buffer-file-coding-system)
   3535       ((1)
   3536        (save-excursion
   3537          (cl-do ((pos (+ (point) n))
   3538                  (count 0 (1+ count)))
   3539              ((>= (point) pos) (1- count))
   3540            (forward-line)
   3541            (cl-decf pos))))
   3542       (t 0))))
   3543 
   3544 (defun sly-search-method-location (name specializers qualifiers)
   3545   ;; Look for a sequence of words (def<something> method name
   3546   ;; qualifers specializers don't look for "T" since it isn't requires
   3547   ;; (arg without t) as class is taken as such.
   3548   (let* ((case-fold-search t)
   3549          (name (regexp-quote name))
   3550          (qualifiers (mapconcat (lambda (el) (concat ".+?\\<" el "\\>"))
   3551                                 qualifiers ""))
   3552          (specializers (mapconcat
   3553                         (lambda (el)
   3554                           (if (eql (aref el 0) ?\()
   3555                               (let ((spec (read el)))
   3556                                 (if (eq (car spec) 'EQL)
   3557                                     (concat
   3558                                      ".*?\\n\\{0,1\\}.*?(EQL.*?'\\{0,1\\}"
   3559                                      (format "%s" (cl-second spec)) ")")
   3560                                   (error "don't understand specializer: %s,%s"
   3561                                          el (car spec))))
   3562                             (concat ".+?\n\\{0,1\\}.+?\\<" el "\\>")))
   3563                         (remove "T" specializers) ""))
   3564          (regexp (format "\\s *(def\\(\\s_\\|\\sw\\)*\\s +%s\\s +%s%s" name
   3565                          qualifiers specializers)))
   3566     (or (and (re-search-forward regexp  nil t)
   3567              (goto-char (match-beginning 0)))
   3568         ;;      (sly-goto-location-position `(:function-name ,name))
   3569         )))
   3570 
   3571 (defun sly-search-call-site (fname)
   3572   "Move to the place where FNAME called.
   3573 Don't move if there are multiple or no calls in the current defun."
   3574   (save-restriction
   3575     (narrow-to-defun)
   3576     (let ((start (point))
   3577           (regexp (concat "(" fname "[)\n \t]"))
   3578           (case-fold-search t))
   3579       (cond ((and (re-search-forward regexp nil t)
   3580                   (not (re-search-forward regexp nil t)))
   3581              (goto-char (match-beginning 0)))
   3582             (t (goto-char start))))))
   3583 
   3584 (defun sly-search-edit-path (edit-path)
   3585   "Move to EDIT-PATH starting at the current toplevel form."
   3586   (when edit-path
   3587     (unless (and (= (current-column) 0)
   3588                  (looking-at "("))
   3589       (beginning-of-defun))
   3590     (sly-forward-source-path edit-path)))
   3591 
   3592 (defun sly-move-to-source-location (location &optional noerror)
   3593   "Move to the source location LOCATION.
   3594 If NOERROR don't signal an error,  but return nil.
   3595 
   3596 Several kinds of locations are supported:
   3597 
   3598 <location> ::= (:location <buffer> <position> <hints>)
   3599              | (:error <message>)
   3600 
   3601 <buffer>   ::= (:file <filename>)
   3602              | (:buffer <buffername>)
   3603              | (:buffer-and-file <buffername> <filename>)
   3604              | (:source-form <string>)
   3605              | (:zip <file> <entry>)
   3606 
   3607 <position> ::= (:position <fixnum>) ; 1 based (for files)
   3608              | (:offset <start> <offset>) ; start+offset (for C-c C-c)
   3609              | (:line <line> [<column>])
   3610              | (:function-name <string>)
   3611              | (:source-path <list> <start-position>)
   3612              | (:method <name string> <specializers> . <qualifiers>)"
   3613   (sly-dcase location
   3614     ((:location buffer _position _hints)
   3615      (sly-goto-location-buffer buffer)
   3616      (let ((pos (sly-location-offset location)))
   3617        (cond ((and (<= (point-min) pos) (<= pos (point-max))))
   3618              (widen-automatically (widen))
   3619              (t
   3620               (error "Location is outside accessible part of buffer")))
   3621        (goto-char pos)))
   3622     ((:error message)
   3623      (cond (noerror
   3624             (sly-message "%s" message)
   3625             nil)
   3626            (t
   3627             (error "%s" message))))))
   3628 
   3629 (defun sly--highlight-sexp (&optional start end)
   3630   "Highlight the first sexp after point."
   3631   (let ((start (or start (point)))
   3632         (end (or end (save-excursion (ignore-errors (forward-sexp)) (point)))))
   3633     (sly-flash-region start end)))
   3634 
   3635 (defun sly--highlight-line (&optional timeout)
   3636   (sly-flash-region (+ (line-beginning-position) (current-indentation))
   3637                     (line-end-position)
   3638                     :timeout timeout))
   3639 
   3640 (make-variable-buffer-local
   3641  (defvar sly-xref--popup-method nil
   3642    "Helper for `sly--display-source-location'"))
   3643 
   3644 (cl-defun sly--display-source-location (source-location
   3645                                         &optional noerror (method 'window))
   3646   "Display SOURCE-LOCATION in a window according to METHOD.
   3647 Highlight the resulting sexp. Return the window or raise an
   3648 error, unless NOERROR is nil, in which case return nil.  METHOD
   3649 specifies how to behave when a reference is selected in an xref
   3650 buffer.  If one of symbols `window' or `frame' just
   3651 `display-buffer' accordingly. If nil, just switch to buffer in
   3652 current window. If a cons (WINDOW . METHOD) consider WINDOW the
   3653 \"starting window\" and reconsider METHOD like above: If it is
   3654 nil try to use WINDOW exclusively for showing the location,
   3655 otherwise prevent that window from being reused when popping to a
   3656 new window or frame."
   3657   (cl-labels
   3658       ((pop-it
   3659         (target-buffer method)
   3660         (cond ((eq method 'window)
   3661                (display-buffer target-buffer t))
   3662               ((eq method 'frame)
   3663                (let ((pop-up-frames t))
   3664                  (display-buffer target-buffer t)))
   3665               ((consp method)
   3666                (let* ((window (car method))
   3667                       (sub-method (cdr method)))
   3668                  (cond ((not (window-live-p window))
   3669                         ;; the original window has been deleted: all
   3670                         ;; bets are off!
   3671                         ;;
   3672                         (pop-it target-buffer sub-method))
   3673                        (sub-method
   3674                         ;; shield window from reuse, but restoring
   3675                         ;; any dedicatedness
   3676                         ;;
   3677                         (let ((dedicatedness (window-dedicated-p window)))
   3678                           (unwind-protect
   3679                               (progn
   3680                                 ;; (set-window-dedicated-p window 'soft)
   3681                                 ;;
   3682                                 ;; jt@2018-01-27 commented the line
   3683                                 ;; above because since the fix to
   3684                                 ;; emacs' bug#28814 in Emacs 26.1
   3685                                 ;; (which I myself authored), it won't
   3686                                 ;; work correctly. Best to disable it
   3687                                 ;; for now and eventually copy Emacs's
   3688                                 ;; approach to xref buffers, or better
   3689                                 ;; yet, reuse it.
   3690                                 (pop-it target-buffer sub-method))
   3691                             (set-window-dedicated-p window dedicatedness))))
   3692                        (t
   3693                         ;; make efforts to reuse the window, respecting
   3694                         ;; any `display-buffer' overrides
   3695                         ;;
   3696                         (display-buffer
   3697                          target-buffer
   3698                          `(,(lambda (buffer _alist)
   3699                               (when (window-live-p window)
   3700                                 (set-window-buffer window buffer)
   3701                                 window))))))))
   3702               (t
   3703                (switch-to-buffer target-buffer)
   3704                (selected-window)))))
   3705     (when (eq method 'sly-xref)
   3706       (setq method sly-xref--popup-method))
   3707     (when (sly-move-to-source-location source-location noerror)
   3708       (let ((pos (point)))
   3709         (with-selected-window (pop-it (current-buffer) method)
   3710           (goto-char pos)
   3711           (recenter (if (= (current-column) 0) 1))
   3712           (sly--highlight-sexp)
   3713           (selected-window))))))
   3714 
   3715 (defun sly--pop-to-source-location (source-location &optional method)
   3716   "Pop to SOURCE-LOCATION using METHOD.
   3717 If called from an xref buffer, method will be `sly-xref' and
   3718 thus also honour `sly-xref--popup-method'."
   3719   (let* ((xref-window (selected-window))
   3720          (xref-buffer (window-buffer xref-window)))
   3721     (when (eq method 'sly-xref)
   3722       (quit-restore-window xref-window 'bury))
   3723     (with-current-buffer xref-buffer
   3724       ;; now pop to target
   3725       ;;
   3726       (select-window
   3727        (sly--display-source-location source-location nil method)))
   3728     (set-buffer (window-buffer (selected-window)))))
   3729 
   3730 (defun sly-location-offset (location)
   3731   "Return the position, as character number, of LOCATION."
   3732   (save-restriction
   3733     (widen)
   3734     (condition-case nil
   3735         (sly-goto-location-position
   3736          (sly-location.position location))
   3737       (error (goto-char 0)))
   3738     (let ((hints (sly-location.hints location)))
   3739       (sly--when-let (snippet (cl-getf hints :snippet))
   3740         (sly-isearch snippet))
   3741       (sly--when-let (snippet (cl-getf hints :edit-path))
   3742         (sly-search-edit-path snippet))
   3743       (sly--when-let (fname (cl-getf hints :call-site))
   3744         (sly-search-call-site fname))
   3745       (when (cl-getf hints :align)
   3746         (sly-forward-sexp)
   3747         (beginning-of-sexp)))
   3748     (point)))
   3749 
   3750 
   3751 ;;;;; Incremental search
   3752 ;;
   3753 ;; Search for the longest match of a string in either direction.
   3754 ;;
   3755 ;; This is for locating text that is expected to be near the point and
   3756 ;; may have been modified (but hopefully not near the beginning!)
   3757 
   3758 (defun sly-isearch (string)
   3759   "Find the longest occurence of STRING either backwards of forwards.
   3760 If multiple matches exist the choose the one nearest to point."
   3761   (goto-char
   3762    (let* ((start (point))
   3763           (len1 (sly-isearch-with-function 'search-forward string))
   3764           (pos1 (point)))
   3765      (goto-char start)
   3766      (let* ((len2 (sly-isearch-with-function 'search-backward string))
   3767             (pos2 (point)))
   3768        (cond ((and len1 len2)
   3769               ;; Have a match in both directions
   3770               (cond ((= len1 len2)
   3771                      ;; Both are full matches -- choose the nearest.
   3772                      (if (< (abs (- start pos1))
   3773                             (abs (- start pos2)))
   3774                          pos1 pos2))
   3775                     ((> len1 len2) pos1)
   3776                     ((> len2 len1) pos2)))
   3777              (len1 pos1)
   3778              (len2 pos2)
   3779              (t start))))))
   3780 
   3781 (defun sly-isearch-with-function (search-fn string)
   3782   "Search for the longest substring of STRING using SEARCH-FN.
   3783 SEARCH-FN is either the symbol `search-forward' or `search-backward'."
   3784   (unless (string= string "")
   3785     (cl-loop for i from 1 to (length string)
   3786              while (funcall search-fn (substring string 0 i) nil t)
   3787              for match-data = (match-data)
   3788              do (cl-case search-fn
   3789                   (search-forward  (goto-char (match-beginning 0)))
   3790                   (search-backward (goto-char (1+ (match-end 0)))))
   3791              finally (cl-return (if (null match-data)
   3792                                     nil
   3793                                   ;; Finish based on the last successful match
   3794                                   (store-match-data match-data)
   3795                                   (goto-char (match-beginning 0))
   3796                                   (- (match-end 0) (match-beginning 0)))))))
   3797 
   3798 
   3799 ;;;;; Visiting and navigating the overlays of compiler notes
   3800 (defun sly-note-button-p (button)
   3801   (eq (button-type button) 'sly-in-buffer-note))
   3802 
   3803 (defalias 'sly-next-note 'sly-button-forward)
   3804 (defalias 'sly-previous-note 'sly-button-backward)
   3805 
   3806 (put 'sly-next-note 'sly-button-navigation-command t)
   3807 (put 'sly-previous-note 'sly-button-navigation-command t)
   3808 
   3809 (defun sly-goto-first-note (_successp notes _buffer _loadp)
   3810   "Go to the first note in the buffer."
   3811   (interactive (list (sly-compiler-notes)))
   3812   (when notes
   3813     (goto-char (point-min))
   3814     (sly-next-note 1)))
   3815 
   3816 (defun sly-remove-notes (beg end)
   3817   "Remove `sly-note' annotation buttons from BEG to END."
   3818   (interactive (if (region-active-p)
   3819                    (list (region-beginning) (region-end))
   3820                  (list (point-min) (point-max))))
   3821   (cl-loop for existing in (overlays-in beg end)
   3822            when (sly-note-button-p existing)
   3823            do (delete-overlay existing)))
   3824 
   3825 (defun sly-show-notes (button &rest more-buttons)
   3826   "Present the details of a compiler note to the user."
   3827   (interactive)
   3828   (let ((notes (mapcar (sly-rcurry #'button-get 'sly-note)
   3829                        (cons button more-buttons))))
   3830     (sly-button-flash button :face (let ((color (face-underline-p (button-get button 'face))))
   3831                                      (if color `(:background ,color) 'highlight)))
   3832     ;; If the compilation window is showing, try to land in a suitable
   3833     ;; place there, too...
   3834     ;;
   3835     (let* ((anchor (car notes))
   3836            (compilation-buffer (sly-buffer-name :compilation))
   3837            (compilation-window (get-buffer-window compilation-buffer t)))
   3838       (if compilation-window
   3839           (with-current-buffer compilation-buffer
   3840             (with-selected-window compilation-window
   3841               (let ((buffer-and-pos (gethash anchor
   3842                                              sly-compilation-log--notes)))
   3843                 (when buffer-and-pos
   3844                   (cl-assert (eq (car buffer-and-pos) (current-buffer)))
   3845                   (goto-char (cdr buffer-and-pos))
   3846                   (let ((field-end (field-end (1+ (point)))))
   3847                     (sly-flash-region (point) field-end)
   3848                     (sly-recenter field-end))))
   3849               (sly-message "Showing note in %s" (current-buffer))))
   3850         ;; Else, do the next best thing, which is echo the messages.
   3851         ;;
   3852         (if (cdr notes)
   3853             (sly-message "%s notes:\n%s"
   3854                          (length notes)
   3855                          (mapconcat #'sly-note.message notes "\n"))
   3856           (sly-message "%s" (sly-note.message (car notes))))))))
   3857 
   3858 (define-button-type 'sly-note :supertype 'sly-button)
   3859 
   3860 (define-button-type 'sly-in-buffer-note :supertype 'sly-note
   3861   'keymap (let ((map (copy-keymap button-map)))
   3862             (define-key map "RET" nil)
   3863             map)
   3864   'mouse-action 'sly-show-notes
   3865   'sly-button-echo 'sly-show-notes
   3866   'modification-hooks '(sly--in-buffer-note-modification))
   3867 
   3868 (define-button-type 'sly-compilation-note-group :supertype 'sly-note
   3869   'face nil)
   3870 
   3871 (defun sly--in-buffer-note-modification (button after? _beg _end &optional _len)
   3872   (unless after? (delete-overlay button)))
   3873 
   3874 (defun sly--add-in-buffer-note  (note)
   3875   "Add NOTE as a `sly-in-buffer-note' button to the source buffer."
   3876   (cl-destructuring-bind (&optional beg end)
   3877       (sly-choose-overlay-region note)
   3878     (when beg
   3879       (let* ((contained (sly-button--overlays-between beg end))
   3880              (containers (cl-set-difference (sly-button--overlays-at beg)
   3881                                             contained)))
   3882         (cl-loop for ov in contained do (cl-incf (sly-button--level ov)))
   3883         (let ((but (make-button beg
   3884                                 end
   3885                                 :type 'sly-in-buffer-note
   3886                                 'sly-button-search-id (sly-button-next-search-id)
   3887                                 'sly-note note
   3888                                 'help-echo (format "[sly] %s" (sly-note.message note))
   3889                                 'face (sly-severity-face (sly-note.severity note)))))
   3890           (setf (sly-button--level but)
   3891                 (1+ (cl-reduce #'max containers
   3892                                :key #'sly-button--level
   3893                                :initial-value 0))))))))
   3894 
   3895 (defun sly--compilation-note-group-button  (label notes)
   3896   "Pepare notes as a `sly-compilation-note' button.
   3897 For insertion in the `compilation-mode' buffer"
   3898   (sly--make-text-button label nil :type 'sly-compilation-note-group 'sly-notes-group notes))
   3899 
   3900 
   3901 ;;;; Basic arglisting
   3902 ;;;;
   3903 (defun sly-show-arglist ()
   3904   (let ((op (ignore-errors
   3905               (save-excursion
   3906                 (backward-up-list 1)
   3907                 (down-list 1)
   3908                 (sly-symbol-at-point)))))
   3909     (when op
   3910       (sly-eval-async `(slynk:operator-arglist ,op ,(sly-current-package))
   3911         (lambda (arglist)
   3912           (when arglist
   3913             (sly-message "%s" arglist)))))))
   3914 
   3915 
   3916 ;;;; Edit definition
   3917 
   3918 (defun sly-push-definition-stack ()
   3919   "Add point to find-tag-marker-ring."
   3920   (ring-insert find-tag-marker-ring (point-marker)))
   3921 
   3922 (defun sly-pop-find-definition-stack ()
   3923   "Pop the edit-definition stack and goto the location."
   3924   (interactive)
   3925   (pop-tag-mark))
   3926 
   3927 (cl-defstruct (sly-xref (:conc-name sly-xref.) (:type list))
   3928   dspec location)
   3929 
   3930 (cl-defstruct (sly-location (:conc-name sly-location.) (:type list)
   3931                             (:constructor nil)
   3932                             (:copier nil))
   3933   tag buffer position hints)
   3934 
   3935 (defun sly-location-p (o) (and (consp o) (eq (car o) :location)))
   3936 
   3937 (defun sly-xref-has-location-p (xref)
   3938   (sly-location-p (sly-xref.location xref)))
   3939 
   3940 (defun make-sly-buffer-location (buffer-name position &optional hints)
   3941   `(:location (:buffer ,buffer-name) (:position ,position)
   3942               ,(when hints `(:hints ,hints))))
   3943 
   3944 (defun make-sly-file-location (file-name position &optional hints)
   3945   `(:location (:file ,file-name) (:position ,position)
   3946               ,(when hints `(:hints ,hints))))
   3947 
   3948 
   3949 
   3950 (defun sly-edit-definition (&optional name method)
   3951   "Lookup the definition of the name at point.
   3952 If there's no name at point, or a prefix argument is given, then
   3953 the function name is prompted. METHOD can be nil, or one of
   3954 `window' or `frame' to specify if the new definition should be
   3955 popped, respectively, in the current window, a new window, or a
   3956 new frame."
   3957   (interactive (list (or (and (not current-prefix-arg)
   3958                               (sly-symbol-at-point t))
   3959                          (sly-read-symbol-name "Edit Definition of: "))))
   3960   ;; The hooks might search for a name in a different manner, so don't
   3961   ;; ask the user if it's missing before the hooks are run
   3962   (let ((xrefs (sly-eval `(slynk:find-definitions-for-emacs ,name))))
   3963     (unless xrefs
   3964       (error "No known definition for: %s (in %s)"
   3965              name (sly-current-package)))
   3966     (cl-destructuring-bind (1loc file-alist)
   3967         (sly-analyze-xrefs xrefs)
   3968       (cond (1loc
   3969              (sly-push-definition-stack)
   3970              (sly--pop-to-source-location
   3971               (sly-xref.location (car xrefs)) method))
   3972             ((null (cdr xrefs))      ; ((:error "..."))
   3973              (error "%s" xrefs))
   3974             (t
   3975              (sly-push-definition-stack)
   3976              (sly-xref--show-results file-alist 'definition name
   3977                                      (sly-current-package)
   3978                                      (cons (selected-window)
   3979                                            method)))))))
   3980 
   3981 (defvar sly-edit-uses-xrefs
   3982   '(:calls :macroexpands :binds :references :sets :specializes))
   3983 
   3984 ;;; FIXME. TODO: Would be nice to group the symbols (in each
   3985 ;;;              type-group) by their home-package.
   3986 (defun sly-edit-uses (symbol)
   3987   "Lookup all the uses of SYMBOL."
   3988   (interactive (list (sly-read-symbol-name "Edit Uses of: ")))
   3989   (sly-xref--get-xrefs
   3990    sly-edit-uses-xrefs
   3991    symbol
   3992    (lambda (xrefs type symbol package)
   3993      (cond
   3994       ((and (sly-length= xrefs 1)          ; one group
   3995             (sly-length= (cdar  xrefs) 1)) ; one ref in group
   3996        (cl-destructuring-bind (_ (_ loc)) (cl-first xrefs)
   3997          (sly-push-definition-stack)
   3998          (sly--pop-to-source-location loc)))
   3999       (t
   4000        (sly-push-definition-stack)
   4001        (sly-xref--show-results xrefs type symbol package 'window))))))
   4002 
   4003 (defun sly-analyze-xrefs (xrefs)
   4004   "Find common filenames in XREFS.
   4005 Return a list (SINGLE-LOCATION FILE-ALIST).
   4006 SINGLE-LOCATION is true if all xrefs point to the same location.
   4007 FILE-ALIST is an alist of the form ((FILENAME . (XREF ...)) ...)."
   4008   (list (and xrefs
   4009              (let ((loc (sly-xref.location (car xrefs))))
   4010                (and (sly-location-p loc)
   4011                     (cl-every (lambda (x) (equal (sly-xref.location x) loc))
   4012                               (cdr xrefs)))))
   4013         (sly-alistify xrefs #'sly-xref-group #'equal)))
   4014 
   4015 (defun sly-xref-group (xref)
   4016   (cond ((sly-xref-has-location-p xref)
   4017          (sly-dcase (sly-location.buffer (sly-xref.location xref))
   4018            ((:file filename) filename)
   4019            ((:buffer bufname)
   4020             (let ((buffer (get-buffer bufname)))
   4021               (if buffer
   4022                   (format "%S" buffer) ; "#<buffer foo.lisp>"
   4023                 (format "%s (previously existing buffer)" bufname))))
   4024            ((:buffer-and-file _buffer filename) filename)
   4025            ((:source-form _) "(S-Exp)")
   4026            ((:zip _zip entry) entry)))
   4027         (t
   4028          "(No location)")))
   4029 
   4030 (defun sly-edit-definition-other-window (name)
   4031   "Like `sly-edit-definition' but switch to the other window."
   4032   (interactive (list (sly-read-symbol-name "Symbol: ")))
   4033   (sly-edit-definition name 'window))
   4034 
   4035 (defun sly-edit-definition-other-frame (name)
   4036   "Like `sly-edit-definition' but switch to the other window."
   4037   (interactive (list (sly-read-symbol-name "Symbol: ")))
   4038   (sly-edit-definition name 'frame))
   4039 
   4040 
   4041 
   4042 ;;;;; first-change-hook
   4043 
   4044 (defun sly-first-change-hook ()
   4045   "Notify Lisp that a source file's buffer has been modified."
   4046   ;; Be careful not to disturb anything!
   4047   ;; In particular if we muck up the match-data then query-replace
   4048   ;; breaks. -luke (26/Jul/2004)
   4049   (save-excursion
   4050     (save-match-data
   4051       (when (and (buffer-file-name)
   4052                  (file-exists-p (buffer-file-name))
   4053                  (sly-background-activities-enabled-p))
   4054         (let ((filename (sly-to-lisp-filename (buffer-file-name))))
   4055           (sly-eval-async `(slynk:buffer-first-change ,filename)))))))
   4056 
   4057 (defun sly-setup-first-change-hook ()
   4058   (add-hook 'first-change-hook #'sly-first-change-hook nil t))
   4059 
   4060 (add-hook 'sly-mode-hook 'sly-setup-first-change-hook)
   4061 
   4062 
   4063 ;;;; Eval for Lisp
   4064 
   4065 (defun sly-eval-for-lisp (thread tag form-string)
   4066   (let ((ok nil)
   4067         (value nil)
   4068         (error nil)
   4069         (c (sly-connection)))
   4070     (unwind-protect
   4071         (condition-case err
   4072             (progn
   4073               (sly-check-eval-in-emacs-enabled)
   4074               (setq value (eval (read form-string) t))
   4075               (sly-check-eval-in-emacs-result value)
   4076               (setq ok t))
   4077           ((debug error)
   4078            (setq error err)))
   4079       (let ((result (cond (ok `(:ok ,value))
   4080                           (error `(:error ,(symbol-name (car error))
   4081                                           . ,(mapcar #'prin1-to-string
   4082                                                      (cdr error))))
   4083                           (t `(:abort)))))
   4084         (sly-dispatch-event `(:emacs-return ,thread ,tag ,result) c)))))
   4085 
   4086 (defun sly-check-eval-in-emacs-result (x)
   4087   "Raise an error if X can't be marshaled."
   4088   (or (stringp x)
   4089       (memq x '(nil t))
   4090       (integerp x)
   4091       (keywordp x)
   4092       (and (consp x)
   4093            (let ((l x))
   4094              (while (consp l)
   4095                (sly-check-eval-in-emacs-result (car x))
   4096                (setq l (cdr l)))
   4097              (sly-check-eval-in-emacs-result l)))
   4098       (error "Non-serializable return value: %S" x)))
   4099 
   4100 (defun sly-check-eval-in-emacs-enabled ()
   4101   "Raise an error if `sly-enable-evaluate-in-emacs' isn't true."
   4102   (unless sly-enable-evaluate-in-emacs
   4103     (error (concat "sly-eval-in-emacs disabled for security."
   4104                    "Set sly-enable-evaluate-in-emacs true to enable it."))))
   4105 
   4106 
   4107 ;;;; `ED'
   4108 
   4109 (defvar sly-ed-frame nil
   4110   "The frame used by `sly-ed'.")
   4111 
   4112 (defcustom sly-ed-use-dedicated-frame nil
   4113   "*When non-nil, `sly-ed' will create and reuse a dedicated frame."
   4114   :type 'boolean
   4115   :group 'sly-mode)
   4116 
   4117 (cl-defun sly-ed (what )
   4118   "Edit WHAT.
   4119 
   4120 WHAT can be:
   4121   A filename (string),
   4122   A list (:filename FILENAME &key LINE COLUMN POSITION),
   4123   A function name (:function-name STRING)
   4124   nil.
   4125 
   4126 This is for use in the implementation of COMMON-LISP:ED."
   4127   (when sly-ed-use-dedicated-frame
   4128     (unless (and sly-ed-frame (frame-live-p sly-ed-frame))
   4129       (setq sly-ed-frame (make-frame)))
   4130     (select-frame sly-ed-frame))
   4131   (raise-frame)
   4132   (when what
   4133     (sly-dcase what
   4134       ((:filename file &key line column position bytep)
   4135        (find-file (sly-from-lisp-filename file))
   4136        (when line (sly-goto-line line))
   4137        (when column (move-to-column column))
   4138        (when position
   4139          (goto-char (if bytep
   4140                         (byte-to-position position)
   4141                       position))))
   4142       ((:function-name name)
   4143        (sly-edit-definition name)))))
   4144 
   4145 (defun sly-goto-line (line-number)
   4146   "Move to line LINE-NUMBER (1-based).
   4147 This is similar to `goto-line' but without pushing the mark and
   4148 the display stuff that we neither need nor want."
   4149   (cl-assert (= (buffer-size) (- (point-max) (point-min))) ()
   4150              "sly-goto-line in narrowed buffer")
   4151   (goto-char (point-min))
   4152   (forward-line (1- line-number)))
   4153 
   4154 (defun sly-remote-y-or-n-p (thread tag question)
   4155   (sly-dispatch-event `(:emacs-return ,thread ,tag ,(sly-y-or-n-p question))))
   4156 
   4157 (defun sly-read-from-minibuffer-for-slynk (thread tag prompt initial-value)
   4158   (let ((answer (condition-case nil
   4159                     (sly-read-from-minibuffer prompt initial-value t)
   4160                   (quit nil))))
   4161     (sly-dispatch-event `(:emacs-return ,thread ,tag ,answer))))
   4162 
   4163 ;;;; Interactive evaluation.
   4164 
   4165 (defun sly-interactive-eval (string)
   4166   "Read and evaluate STRING and print value in minibuffer.
   4167 
   4168 A prefix argument(`C-u') inserts the result into the current
   4169 buffer. A negative prefix argument (`M--') will sends it to the
   4170 kill ring."
   4171   (interactive (list (sly-read-from-minibuffer "SLY Eval: ")))
   4172   (cl-case current-prefix-arg
   4173     ((nil)
   4174      (sly-eval-with-transcript `(slynk:interactive-eval ,string)))
   4175     ((-)
   4176      (sly-eval-save string))
   4177     (t
   4178      (sly-eval-print string))))
   4179 
   4180 (defvar sly-transcript-start-hook nil
   4181   "Hook run before start an evalution.")
   4182 (defvar sly-transcript-stop-hook nil
   4183   "Hook run after finishing a evalution.")
   4184 
   4185 (defun sly-display-eval-result (value)
   4186   ;; Use `message', not `sly-message'
   4187   (with-temp-buffer
   4188     (insert value)
   4189     (goto-char (point-min))
   4190     (end-of-line 1)
   4191     (if (or (< (1+ (point)) (point-max))
   4192             (>= (- (point) (point-min)) (frame-width)))
   4193         (sly-show-description value (sly-current-package))
   4194       (message "=> %s" value))))
   4195 
   4196 (defun sly-eval-with-transcript (form)
   4197   "Eval FORM in Lisp.  Display output, if any."
   4198   (run-hooks 'sly-transcript-start-hook)
   4199   (sly-rex () (form)
   4200     ((:ok value)
   4201      (run-hooks 'sly-transcript-stop-hook)
   4202      (sly-display-eval-result value))
   4203     ((:abort condition)
   4204      (run-hooks 'sly-transcript-stop-hook)
   4205      (sly-message "Evaluation aborted on %s." condition))))
   4206 
   4207 (defun sly-eval-print (string)
   4208   "Eval STRING in Lisp; insert any output and the result at point."
   4209   (sly-eval-async `(slynk:eval-and-grab-output ,string)
   4210     (lambda (result)
   4211       (cl-destructuring-bind (output value) result
   4212         (push-mark)
   4213         (let* ((start (point))
   4214                (ppss (syntax-ppss))
   4215                (string-or-comment-p (or (nth 3 ppss) (nth 4 ppss))))
   4216           (insert output (if string-or-comment-p
   4217                              ""
   4218                            " => ") value)
   4219           (unless string-or-comment-p
   4220             (comment-region start (point) 1)))))))
   4221 
   4222 (defun sly-eval-save (string)
   4223   "Evaluate STRING in Lisp and save the result in the kill ring."
   4224   (sly-eval-async `(slynk:eval-and-grab-output ,string)
   4225     (lambda (result)
   4226       (cl-destructuring-bind (output value) result
   4227         (let ((string (concat output value)))
   4228           (kill-new string)
   4229           (sly-message "Evaluation finished; pushed result to kill ring."))))))
   4230 
   4231 (defun sly-eval-describe (form)
   4232   "Evaluate FORM in Lisp and display the result in a new buffer."
   4233   (sly-eval-async form (sly-rcurry #'sly-show-description
   4234                                    (sly-current-package))))
   4235 
   4236 (defvar sly-description-autofocus nil
   4237   "If non-nil select description windows on display.")
   4238 
   4239 (defun sly-show-description (string package)
   4240   ;; So we can have one description buffer open per connection. Useful
   4241   ;; for comparing the output of DISASSEMBLE across implementations.
   4242   ;; FIXME: could easily be achieved with M-x rename-buffer
   4243   (let ((bufname (sly-buffer-name :description)))
   4244     (sly-with-popup-buffer (bufname :package package
   4245                                     :connection t
   4246                                     :select sly-description-autofocus
   4247                                     :mode 'lisp-mode)
   4248       (sly-popup-buffer-mode)
   4249       (princ string)
   4250       (goto-char (point-min)))))
   4251 
   4252 (defun sly-last-expression ()
   4253   (buffer-substring-no-properties
   4254    (save-excursion (backward-sexp) (point))
   4255    (point)))
   4256 
   4257 (defun sly-eval-last-expression ()
   4258   "Evaluate the expression preceding point."
   4259   (interactive)
   4260   (sly-interactive-eval (sly-last-expression)))
   4261 
   4262 (defun sly-eval-defun ()
   4263   "Evaluate the current toplevel form.
   4264 Use `sly-re-evaluate-defvar' if the from starts with '(defvar'"
   4265   (interactive)
   4266   (let ((form (apply #'buffer-substring-no-properties
   4267                      (sly-region-for-defun-at-point))))
   4268     (cond ((string-match "^(defvar " form)
   4269            (sly-re-evaluate-defvar form))
   4270           (t
   4271            (sly-interactive-eval form)))))
   4272 
   4273 (defun sly-eval-region (start end)
   4274   "Evaluate region."
   4275   (interactive "r")
   4276   (sly-eval-with-transcript
   4277    `(slynk:interactive-eval-region
   4278      ,(buffer-substring-no-properties start end))))
   4279 
   4280 (defun sly-pprint-eval-region (start end)
   4281   "Evaluate region; pprint the value in a buffer."
   4282   (interactive "r")
   4283   (sly-eval-describe
   4284    `(slynk:pprint-eval
   4285      ,(buffer-substring-no-properties start end))))
   4286 
   4287 (defun sly-eval-buffer ()
   4288   "Evaluate the current buffer.
   4289 The value is printed in the echo area."
   4290   (interactive)
   4291   (sly-eval-region (point-min) (point-max)))
   4292 
   4293 (defun sly-re-evaluate-defvar (form)
   4294   "Force the re-evaluaton of the defvar form before point.
   4295 
   4296 First make the variable unbound, then evaluate the entire form."
   4297   (interactive (list (sly-last-expression)))
   4298   (sly-eval-with-transcript `(slynk:re-evaluate-defvar ,form)))
   4299 
   4300 (defun sly-pprint-eval-last-expression ()
   4301   "Evaluate the form before point; pprint the value in a buffer."
   4302   (interactive)
   4303   (sly-eval-describe `(slynk:pprint-eval ,(sly-last-expression))))
   4304 
   4305 (defun sly-eval-print-last-expression (string)
   4306   "Evaluate sexp before point; print value into the current buffer"
   4307   (interactive (list (sly-last-expression)))
   4308   (insert "\n")
   4309   (sly-eval-print string))
   4310 
   4311 ;;;; Edit Lisp value
   4312 ;;;
   4313 (defun sly-edit-value (form-string)
   4314   "\\<sly-edit-value-mode-map>\
   4315 Edit the value of a setf'able form in a new buffer.
   4316 The value is inserted into a temporary buffer for editing and then set
   4317 in Lisp when committed with \\[sly-edit-value-commit]."
   4318   (interactive
   4319    (list (sly-read-from-minibuffer "Edit value (evaluated): "
   4320                                    (sly-sexp-at-point))))
   4321   (sly-eval-async `(slynk:value-for-editing ,form-string)
   4322     (let ((form-string form-string)
   4323           (package (sly-current-package)))
   4324       (lambda (result)
   4325         (sly-edit-value-callback form-string result
   4326                                  package)))))
   4327 
   4328 (make-variable-buffer-local
   4329  (defvar sly-edit-form-string nil
   4330    "The form being edited by `sly-edit-value'."))
   4331 
   4332 (define-minor-mode sly-edit-value-mode
   4333   "Mode for editing a Lisp value."
   4334   nil
   4335   " Edit-Value"
   4336   '(("\C-c\C-c" . sly-edit-value-commit)))
   4337 
   4338 (defun sly-edit-value-callback (form-string current-value package)
   4339   (let* ((name (generate-new-buffer-name (format "*Edit %s*" form-string)))
   4340          (buffer (sly-with-popup-buffer (name :package package
   4341                                               :connection t
   4342                                               :select t
   4343                                               :mode 'lisp-mode)
   4344                    (sly-mode 1)
   4345                    (sly-edit-value-mode 1)
   4346                    (setq sly-edit-form-string form-string)
   4347                    (insert current-value)
   4348                    (current-buffer))))
   4349     (with-current-buffer buffer
   4350       (setq buffer-read-only nil)
   4351       (sly-message "Type C-c C-c when done"))))
   4352 
   4353 (defun sly-edit-value-commit ()
   4354   "Commit the edited value to the Lisp image.
   4355 \\(See `sly-edit-value'.)"
   4356   (interactive)
   4357   (if (null sly-edit-form-string)
   4358       (error "Not editing a value.")
   4359     (let ((value (buffer-substring-no-properties (point-min) (point-max))))
   4360       (let ((buffer (current-buffer)))
   4361         (sly-eval-async `(slynk:commit-edited-value ,sly-edit-form-string
   4362                                                     ,value)
   4363           (lambda (_)
   4364             (with-current-buffer buffer
   4365               (quit-window t))))))))
   4366 
   4367 ;;;; Tracing
   4368 
   4369 (defun sly-untrace-all ()
   4370   "Untrace all functions."
   4371   (interactive)
   4372   (sly-eval `(slynk:untrace-all)))
   4373 
   4374 (defun sly-toggle-trace-fdefinition (spec)
   4375   "Toggle trace."
   4376   (interactive (list (sly-read-from-minibuffer
   4377                       "(Un)trace: " (sly-symbol-at-point))))
   4378   (sly-message "%s" (sly-eval `(slynk:slynk-toggle-trace ,spec))))
   4379 
   4380 
   4381 
   4382 (defun sly-disassemble-symbol (symbol-name)
   4383   "Display the disassembly for SYMBOL-NAME."
   4384   (interactive (list (sly-read-symbol-name "Disassemble: ")))
   4385   (sly-eval-describe `(slynk:disassemble-form ,(concat "'" symbol-name))))
   4386 
   4387 (defun sly-undefine-function (symbol-name)
   4388   "Unbind the function slot of SYMBOL-NAME."
   4389   (interactive (list (sly-read-symbol-name "fmakunbound: " t)))
   4390   (sly-eval-async `(slynk:undefine-function ,symbol-name)
   4391     (lambda (result) (sly-message "%s" result))))
   4392 
   4393 (defun sly-unintern-symbol (symbol-name package)
   4394   "Unintern the symbol given with SYMBOL-NAME PACKAGE."
   4395   (interactive (list (sly-read-symbol-name "Unintern symbol: " t)
   4396                      (sly-read-package-name "from package: "
   4397                                             (sly-current-package))))
   4398   (sly-eval-async `(slynk:unintern-symbol ,symbol-name ,package)
   4399     (lambda (result) (sly-message "%s" result))))
   4400 
   4401 (defun sly-delete-package (package-name)
   4402   "Delete the package with name PACKAGE-NAME."
   4403   (interactive (list (sly-read-package-name "Delete package: "
   4404                                             (sly-current-package))))
   4405   (sly-eval-async `(cl:delete-package
   4406                     (slynk::guess-package ,package-name))))
   4407 
   4408 (defun sly-load-file (filename)
   4409   "Load the Lisp file FILENAME."
   4410   (interactive (list
   4411                 (read-file-name "[sly] Load file: " nil nil
   4412                                 nil (if (buffer-file-name)
   4413                                         (file-name-nondirectory
   4414                                          (buffer-file-name))))))
   4415   (let ((lisp-filename (sly-to-lisp-filename (expand-file-name filename))))
   4416     (sly-eval-with-transcript `(slynk:load-file ,lisp-filename))))
   4417 
   4418 (defvar sly-change-directory-hooks nil
   4419   "Hook run by `sly-change-directory'.
   4420 The functions are called with the new (absolute) directory.")
   4421 
   4422 (defun sly-change-directory (directory)
   4423   "Make DIRECTORY become Lisp's current directory.
   4424 Return whatever slynk:set-default-directory returns."
   4425   (let ((dir (expand-file-name directory)))
   4426     (prog1 (sly-eval `(slynk:set-default-directory
   4427                        ,(sly-to-lisp-filename dir)))
   4428       (sly-with-connection-buffer nil (cd-absolute dir))
   4429       (run-hook-with-args 'sly-change-directory-hooks dir))))
   4430 
   4431 (defun sly-cd (directory)
   4432   "Make DIRECTORY become Lisp's current directory.
   4433 Return whatever slynk:set-default-directory returns."
   4434   (interactive (list (read-directory-name "[sly] Directory: " nil nil t)))
   4435   (sly-message "default-directory: %s" (sly-change-directory directory)))
   4436 
   4437 (defun sly-pwd ()
   4438   "Show Lisp's default directory."
   4439   (interactive)
   4440   (sly-message "Directory %s" (sly-eval `(slynk:default-directory))))
   4441 
   4442 
   4443 ;;;; Documentation
   4444 
   4445 (defvar sly-documentation-lookup-function
   4446   'sly-hyperspec-lookup)
   4447 
   4448 (defun sly-documentation-lookup ()
   4449   "Generalized documentation lookup. Defaults to hyperspec lookup."
   4450   (interactive)
   4451   (call-interactively sly-documentation-lookup-function))
   4452 
   4453 ;;;###autoload
   4454 (defun sly-hyperspec-lookup (symbol-name)
   4455   "A wrapper for `hyperspec-lookup'"
   4456   (interactive (list (common-lisp-hyperspec-read-symbol-name
   4457                       (sly-symbol-at-point))))
   4458   (hyperspec-lookup symbol-name))
   4459 
   4460 (defun sly-describe-symbol (symbol-name)
   4461   "Describe the symbol at point."
   4462   (interactive (list (sly-read-symbol-name "Describe symbol: ")))
   4463   (when (not symbol-name)
   4464     (error "No symbol given"))
   4465   (sly-eval-describe `(slynk:describe-symbol ,symbol-name)))
   4466 
   4467 (defun sly-documentation (symbol-name)
   4468   "Display function- or symbol-documentation for SYMBOL-NAME."
   4469   (interactive (list (sly-read-symbol-name "Documentation for symbol: ")))
   4470   (when (not symbol-name)
   4471     (error "No symbol given"))
   4472   (sly-eval-describe
   4473    `(slynk:documentation-symbol ,symbol-name)))
   4474 
   4475 (defun sly-describe-function (symbol-name)
   4476   (interactive (list (sly-read-symbol-name "Describe symbol's function: ")))
   4477   (when (not symbol-name)
   4478     (error "No symbol given"))
   4479   (sly-eval-describe `(slynk:describe-function ,symbol-name)))
   4480 
   4481 (defface sly-apropos-symbol
   4482   '((t (:inherit sly-part-button-face)))
   4483   "Face for the symbol name in Apropos output."
   4484   :group 'sly)
   4485 
   4486 (defface sly-apropos-label
   4487   '((t (:inherit italic)))
   4488   "Face for label (`Function', `Variable' ...) in Apropos output."
   4489   :group 'sly)
   4490 
   4491 (defun sly-apropos-summary (string case-sensitive-p package only-external-p)
   4492   "Return a short description for the performed apropos search."
   4493   (concat (if case-sensitive-p "Case-sensitive " "")
   4494           "Apropos for "
   4495           (format "%S" string)
   4496           (if package (format " in package %S" package) "")
   4497           (if only-external-p " (external symbols only)" "")))
   4498 
   4499 (defun sly-apropos (string &optional only-external-p package
   4500                            case-sensitive-p)
   4501   "Show all bound symbols whose names match STRING. With prefix
   4502 arg, you're interactively asked for parameters of the search.
   4503 With M-- (negative) prefix arg, prompt for package only. "
   4504   (interactive
   4505    (cond ((eq '- current-prefix-arg)
   4506           (list (sly-read-from-minibuffer "Apropos external symbols: ")
   4507                 t
   4508                 (sly-read-package-name "Package (blank for all): "
   4509                                        nil 'allow-blank)
   4510                 nil))
   4511          (current-prefix-arg
   4512           (list (sly-read-from-minibuffer "Apropos: ")
   4513                 (sly-y-or-n-p "External symbols only? ")
   4514                 (sly-read-package-name "Package (blank for all): "
   4515                                        nil 'allow-blank)
   4516                 (sly-y-or-n-p "Case-sensitive? ")))
   4517          (t
   4518           (list (sly-read-from-minibuffer "Apropos external symbols: ") t nil nil))))
   4519   (sly-eval-async
   4520       `(slynk-apropos:apropos-list-for-emacs ,string ,only-external-p
   4521                                              ,case-sensitive-p ',package)
   4522     (sly-rcurry #'sly-show-apropos string package
   4523                 (sly-apropos-summary string case-sensitive-p
   4524                                      package only-external-p))))
   4525 
   4526 (defun sly-apropos-all ()
   4527   "Shortcut for (sly-apropos <string> nil nil)"
   4528   (interactive)
   4529   (sly-apropos (sly-read-from-minibuffer "Apropos all symbols: ") nil nil))
   4530 
   4531 (defun sly-apropos-package (package &optional internal)
   4532   "Show apropos listing for symbols in PACKAGE.
   4533 With prefix argument include internal symbols."
   4534   (interactive (list (let ((pkg (sly-read-package-name "Package: ")))
   4535                        (if (string= pkg "") (sly-current-package) pkg))
   4536                      current-prefix-arg))
   4537   (sly-apropos "" (not internal) package))
   4538 
   4539 (defvar sly-apropos-mode-map
   4540   (let ((map (make-sparse-keymap)))
   4541     map))
   4542 
   4543 (define-derived-mode sly-apropos-mode apropos-mode "SLY-Apropos"
   4544   "SLY Apropos Mode
   4545 
   4546 TODO"
   4547   (sly-mode))
   4548 
   4549 (defun sly-show-apropos (plists string package summary)
   4550   (cond ((null plists)
   4551          (sly-message "No apropos matches for %S" string))
   4552         (t
   4553          (sly-with-popup-buffer ((sly-buffer-name :apropos
   4554                                                   :connection t)
   4555                                  :package package :connection t
   4556                                  :mode 'sly-apropos-mode)
   4557            (if (boundp 'header-line-format)
   4558                (setq header-line-format summary)
   4559              (insert summary "\n\n"))
   4560            (sly-set-truncate-lines)
   4561            (sly-print-apropos plists (not package))
   4562            (set-syntax-table lisp-mode-syntax-table)
   4563            (goto-char (point-min))))))
   4564 
   4565 (define-button-type 'sly-apropos-symbol :supertype 'sly-part
   4566   'face nil
   4567   'action 'sly-button-goto-source ;default action
   4568   'sly-button-inspect
   4569   #'(lambda (name _type)
   4570       (sly-inspect (format "(quote %s)" name)))
   4571   'sly-button-goto-source
   4572   #'(lambda (name _type)
   4573       (sly-edit-definition name 'window))
   4574   'sly-button-describe
   4575   #'(lambda (name _type)
   4576       (sly-eval-describe `(slynk:describe-symbol ,name))))
   4577 
   4578 (defun sly--package-designator-prefix (designator)
   4579   (unless (listp designator)
   4580     (error "unknown designator type"))
   4581   (concat (cadr designator)
   4582           (if (cl-caddr designator) ":" "::")))
   4583 
   4584 (defun sly-apropos-designator-string (designator)
   4585   (concat (sly--package-designator-prefix designator)
   4586           (car designator)))
   4587 
   4588 (defun sly-apropos-insert-symbol (designator item bounds package-designator-searched-p)
   4589   (let ((label (sly-apropos-designator-string designator)))
   4590     (setq label
   4591           (sly--make-text-button label nil
   4592                                  'face 'sly-apropos-symbol
   4593                                  'part-args (list item nil)
   4594                                  'part-label "Symbol"
   4595                                  :type 'sly-apropos-symbol))
   4596     (cl-loop
   4597      with offset = (if package-designator-searched-p
   4598                        0
   4599                      (length (sly--package-designator-prefix designator)))
   4600      for bound in bounds
   4601      for (start end) = (if (listp bound) bound (list bound (1+ bound)))
   4602      do
   4603      (put-text-property (+ start offset) (+ end offset) 'face 'highlight label)
   4604      finally (insert label))))
   4605 
   4606 (defun sly-print-apropos (plists package-designator-searched-p)
   4607   (cl-loop
   4608    for plist in plists
   4609    for designator = (plist-get plist :designator)
   4610    for item = (substring-no-properties
   4611                (sly-apropos-designator-string designator))
   4612    do
   4613    (sly-apropos-insert-symbol designator item (plist-get plist :bounds) package-designator-searched-p)
   4614    (terpri)
   4615    (cl-loop for (prop value) on plist by #'cddr
   4616             for start = (point)
   4617             unless (memq prop '(:designator
   4618                                 :package
   4619                                 :bounds))
   4620             do
   4621             (let ((namespace (upcase-initials
   4622                               (replace-regexp-in-string
   4623                                "-" " " (substring (symbol-name prop) 1)))))
   4624               (princ "  ")
   4625               (insert (propertize namespace
   4626                                   'face 'sly-apropos-label))
   4627               (princ ": ")
   4628               (princ (cond ((and value
   4629                                  (not (eq value :not-documented)))
   4630                             value)
   4631                            (t
   4632                             "(not documented)")))
   4633               (add-text-properties
   4634                start (point)
   4635                (list 'action 'sly-button-describe
   4636                      'sly-button-describe
   4637                      #'(lambda (name type)
   4638                          (sly-eval-describe `(slynk:describe-definition-for-emacs ,name
   4639                                                                                   ,type)))
   4640                      'part-args (list item prop)
   4641                      'button t 'apropos-label namespace))
   4642               (terpri)))))
   4643 
   4644 (defun sly-apropos-describe (name type)
   4645   (sly-eval-describe `(slynk:describe-definition-for-emacs ,name ,type)))
   4646 
   4647 (require 'info)
   4648 (defun sly-info--file ()
   4649   (or (cl-some (lambda (subdir)
   4650                  (cl-flet ((existing-file
   4651                             (name) (let* ((path (expand-file-name subdir sly-path))
   4652                                           (probe (expand-file-name name path)))
   4653                                      (and (file-exists-p probe) probe))))
   4654                    (or (existing-file "sly.info")
   4655                        (existing-file "sly.info.gz"))))
   4656                (append '("doc" ".") Info-directory-list))
   4657       (sly-error
   4658        "No sly.info, run `make -C doc sly.info' from a SLY git checkout")))
   4659 
   4660 (require 'info)
   4661 
   4662 (defvar sly-info--cached-node-names nil)
   4663 
   4664 (defun sly-info--node-names (file)
   4665   (or sly-info--cached-node-names
   4666       (setq sly-info--cached-node-names
   4667             (with-temp-buffer
   4668               (info file (current-buffer))
   4669               (ignore-errors
   4670                 (Info-build-node-completions))))))
   4671 
   4672 ;;;###autoload
   4673 (defun sly-info (file &optional node)
   4674   "Read SLY manual"
   4675   (interactive
   4676    (let ((file (sly-info--file)))
   4677      (list file
   4678            (sly-completing-read "Manual node? (`Top' to read the whole manual): "
   4679                                 (remove '("*") (sly-info--node-names file))
   4680                                 nil t))))
   4681   (info (if node (format "(%s)%s" file node) file)))
   4682 
   4683 
   4684 ;;;; XREF: cross-referencing
   4685 
   4686 (defvar sly-xref-mode-map
   4687   (let ((map (make-sparse-keymap)))
   4688     (define-key map (kbd "RET") 'sly-xref-goto)
   4689     (define-key map (kbd "SPC") 'sly-xref-show)
   4690     (define-key map (kbd "n") 'sly-xref-next-line)
   4691     (define-key map (kbd "p") 'sly-xref-prev-line)
   4692     (define-key map (kbd ".") 'sly-xref-next-line)
   4693     (define-key map (kbd ",") 'sly-xref-prev-line)
   4694     (define-key map (kbd "C-c C-c") 'sly-recompile-xref)
   4695     (define-key map (kbd "C-c C-k") 'sly-recompile-all-xrefs)
   4696 
   4697     (define-key map (kbd "q")     'quit-window)
   4698     (set-keymap-parent map button-buffer-map)
   4699 
   4700     map))
   4701 
   4702 (define-derived-mode sly-xref-mode lisp-mode "Xref"
   4703   "sly-xref-mode: Major mode for cross-referencing.
   4704 \\<sly-xref-mode-map>\
   4705 The most important commands:
   4706 \\[sly-xref-show]       - Display referenced source and keep xref window.
   4707 \\[sly-xref-goto]       - Jump to referenced source and dismiss xref window.
   4708 
   4709 \\{sly-xref-mode-map}"
   4710   (setq font-lock-defaults nil)
   4711   (setq delayed-mode-hooks nil)
   4712   (setq buffer-read-only t)
   4713   (sly-mode))
   4714 
   4715 (defun sly-next-line/not-add-newlines ()
   4716   (interactive)
   4717   (let ((next-line-add-newlines nil))
   4718     (forward-line 1)))
   4719 
   4720 
   4721 ;;;;; XREF results buffer and window management
   4722 
   4723 (cl-defmacro sly-with-xref-buffer ((_xref-type _symbol &optional package)
   4724                                    &body body)
   4725   "Execute BODY in a xref buffer, then show that buffer."
   4726   (declare (indent 1))
   4727   `(sly-with-popup-buffer ((sly-buffer-name :xref
   4728                                             :connection t)
   4729                            :package ,package
   4730                            :connection t
   4731                            :select t
   4732                            :mode 'sly-xref-mode)
   4733      (sly-set-truncate-lines)
   4734      ,@body))
   4735 
   4736 ;; TODO: Have this button support more options, not just "show source"
   4737 ;; and "goto-source"
   4738 (define-button-type 'sly-xref :supertype 'sly-part
   4739   'action 'sly-button-goto-source ;default action
   4740   'mouse-action 'sly-button-goto-source ;default action
   4741   'sly-button-show-source #'(lambda (location)
   4742                               (sly-xref--show-location location))
   4743   'sly-button-goto-source #'(lambda (location)
   4744                               (sly--pop-to-source-location location 'sly-xref)))
   4745 
   4746 (defun sly-xref-button (label location)
   4747   (sly--make-text-button label nil
   4748                          :type 'sly-xref
   4749                          'part-args (list location)
   4750                          'part-label "Location"))
   4751 
   4752 (defun sly-insert-xrefs (xref-alist)
   4753   "Insert XREF-ALIST in the current-buffer.
   4754 XREF-ALIST is of the form ((GROUP . ((LABEL LOCATION) ...)) ...).
   4755 GROUP and LABEL are for decoration purposes.  LOCATION is a
   4756 source-location."
   4757   (cl-loop for (group . refs) in xref-alist do
   4758            (sly-insert-propertized '(face bold) group "\n")
   4759            (cl-loop for (label location) in refs
   4760                     for start = (point)
   4761                     do
   4762                     (insert
   4763                      " "
   4764                      (sly-xref-button (sly-one-line-ify label) location)
   4765                      "\n")
   4766                     (add-text-properties start (point) (list 'sly-location location))))
   4767   ;; Remove the final newline to prevent accidental window-scrolling
   4768   (backward-delete-char 1))
   4769 
   4770 (defun sly-xref-next-line (arg)
   4771   (interactive "p")
   4772   (let ((button (forward-button arg)))
   4773     (when button (sly-button-show-source button))))
   4774 
   4775 (defun sly-xref-prev-line (arg)
   4776   (interactive "p")
   4777   (sly-xref-next-line (- arg)))
   4778 
   4779 (defun sly-xref--show-location (loc)
   4780   (cl-ecase (car loc)
   4781     (:location (sly--display-source-location loc))
   4782     (:error (sly-message "%s" (cadr loc)))
   4783     ((nil))))
   4784 
   4785 (defun sly-xref--show-results (xrefs _type symbol package &optional method)
   4786   "Maybe show a buffer listing the cross references XREFS.
   4787 METHOD is used to set `sly-xref--popup-method', which see."
   4788   (cond ((null xrefs)
   4789          (sly-message "No references found for %s." symbol)
   4790          nil)
   4791         (t
   4792          (sly-with-xref-buffer (_type _symbol package)
   4793            (sly-insert-xrefs xrefs)
   4794            (setq sly-xref--popup-method method)
   4795            (goto-char (point-min))
   4796            (current-buffer)))))
   4797 
   4798 
   4799 ;;;;; XREF commands
   4800 
   4801 (defun sly-who-calls (symbol)
   4802   "Show all known callers of the function SYMBOL."
   4803   (interactive (list (sly-read-symbol-name "Who calls: " t)))
   4804   (sly-xref :calls symbol))
   4805 
   4806 (defun sly-calls-who (symbol)
   4807   "Show all known functions called by the function SYMBOL."
   4808   (interactive (list (sly-read-symbol-name "Who calls: " t)))
   4809   (sly-xref :calls-who symbol))
   4810 
   4811 (defun sly-who-references (symbol)
   4812   "Show all known referrers of the global variable SYMBOL."
   4813   (interactive (list (sly-read-symbol-name "Who references: " t)))
   4814   (sly-xref :references symbol))
   4815 
   4816 (defun sly-who-binds (symbol)
   4817   "Show all known binders of the global variable SYMBOL."
   4818   (interactive (list (sly-read-symbol-name "Who binds: " t)))
   4819   (sly-xref :binds symbol))
   4820 
   4821 (defun sly-who-sets (symbol)
   4822   "Show all known setters of the global variable SYMBOL."
   4823   (interactive (list (sly-read-symbol-name "Who sets: " t)))
   4824   (sly-xref :sets symbol))
   4825 
   4826 (defun sly-who-macroexpands (symbol)
   4827   "Show all known expanders of the macro SYMBOL."
   4828   (interactive (list (sly-read-symbol-name "Who macroexpands: " t)))
   4829   (sly-xref :macroexpands symbol))
   4830 
   4831 (defun sly-who-specializes (symbol)
   4832   "Show all known methods specialized on class SYMBOL."
   4833   (interactive (list (sly-read-symbol-name "Who specializes: " t)))
   4834   (sly-xref :specializes symbol))
   4835 
   4836 (defun sly-list-callers (symbol-name)
   4837   "List the callers of SYMBOL-NAME in a xref window."
   4838   (interactive (list (sly-read-symbol-name "List callers: ")))
   4839   (sly-xref :callers symbol-name))
   4840 
   4841 (defun sly-list-callees (symbol-name)
   4842   "List the callees of SYMBOL-NAME in a xref window."
   4843   (interactive (list (sly-read-symbol-name "List callees: ")))
   4844   (sly-xref :callees symbol-name))
   4845 
   4846 (defun sly-xref (type symbol &optional continuation)
   4847   "Make an XREF request to Lisp."
   4848   (sly-eval-async
   4849       `(slynk:xref ',type ',symbol)
   4850     (sly-rcurry (lambda (result type symbol package cont)
   4851                   (and (sly-xref-implemented-p type result)
   4852                        (let* ((file-alist (cadr (sly-analyze-xrefs result))))
   4853                          (funcall (or cont 'sly-xref--show-results)
   4854                                   file-alist type symbol package))))
   4855                 type
   4856                 symbol
   4857                 (sly-current-package)
   4858                 continuation)))
   4859 
   4860 (defun sly-xref-implemented-p (type xrefs)
   4861   "Tell if xref TYPE is available according to XREFS."
   4862   (cond ((eq xrefs :not-implemented)
   4863          (sly-display-oneliner "%s is not implemented yet on %s."
   4864                                (sly-xref-type type)
   4865                                (sly-lisp-implementation-name))
   4866          nil)
   4867         (t t)))
   4868 
   4869 (defun sly-xref-type (type)
   4870   "Return a human readable version of xref TYPE."
   4871   (format "who-%s" (sly-cl-symbol-name type)))
   4872 
   4873 (defun sly-xref--get-xrefs (types symbol &optional continuation)
   4874   "Make multiple XREF requests at once."
   4875   (sly-eval-async
   4876       `(slynk:xrefs ',types ',symbol)
   4877     #'(lambda (result)
   4878         (funcall (or continuation
   4879                      #'sly-xref--show-results)
   4880                  (cl-loop for (key . val) in result
   4881                           collect (cons (sly-xref-type key) val))
   4882                  types symbol (sly-current-package)))))
   4883 
   4884 
   4885 ;;;;; XREF navigation
   4886 
   4887 (defun sly-xref-location-at-point ()
   4888   (save-excursion
   4889     ;; When the end of the last line is at (point-max) we can't find
   4890     ;; the text property there. Going to bol avoids this problem.
   4891     (beginning-of-line 1)
   4892     (or (get-text-property (point) 'sly-location)
   4893         (error "No reference at point."))))
   4894 
   4895 (defun sly-xref-dspec-at-point ()
   4896   (save-excursion
   4897     (beginning-of-line 1)
   4898     (with-syntax-table lisp-mode-syntax-table
   4899       (forward-sexp)                    ; skip initial whitespaces
   4900       (backward-sexp)
   4901       (sly-sexp-at-point))))
   4902 
   4903 (defun sly-all-xrefs ()
   4904   (let ((xrefs nil))
   4905     (save-excursion
   4906       (goto-char (point-min))
   4907       (while (zerop (forward-line 1))
   4908         (sly--when-let (loc (get-text-property (point) 'sly-location))
   4909           (let* ((dspec (sly-xref-dspec-at-point))
   4910                  (xref  (make-sly-xref :dspec dspec :location loc)))
   4911             (push xref xrefs)))))
   4912     (nreverse xrefs)))
   4913 
   4914 (defun sly-xref-goto ()
   4915   "Goto the cross-referenced location at point."
   4916   (interactive)
   4917   (sly--pop-to-source-location (sly-xref-location-at-point) 'sly-xref))
   4918 
   4919 (defun sly-xref-show ()
   4920   "Display the xref at point in the other window."
   4921   (interactive)
   4922   (sly--display-source-location (sly-xref-location-at-point)))
   4923 
   4924 (defun sly-search-property (prop &optional backward prop-value-fn)
   4925   "Search the next text range where PROP is non-nil.
   4926 Return the value of PROP.
   4927 If BACKWARD is non-nil, search backward.
   4928 If PROP-VALUE-FN is non-nil use it to extract PROP's value."
   4929   (let ((next-candidate (if backward
   4930                             #'previous-single-char-property-change
   4931                           #'next-single-char-property-change))
   4932         (prop-value-fn  (or prop-value-fn
   4933                             (lambda ()
   4934                               (get-text-property (point) prop))))
   4935         (start (point))
   4936         (prop-value))
   4937     (while (progn
   4938              (goto-char (funcall next-candidate (point) prop))
   4939              (not (or (setq prop-value (funcall prop-value-fn))
   4940                       (eobp)
   4941                       (bobp)))))
   4942     (cond (prop-value)
   4943           (t (goto-char start) nil))))
   4944 
   4945 (defun sly-recompile-xref (&optional raw-prefix-arg)
   4946   "Recompile definition at point.
   4947 Uses prefix arguments like `sly-compile-defun'."
   4948   (interactive "P")
   4949   (let ((sly-compilation-policy (sly-compute-policy raw-prefix-arg)))
   4950     (let ((location (sly-xref-location-at-point))
   4951           (dspec    (sly-xref-dspec-at-point)))
   4952       (sly-recompile-locations
   4953        (list location)
   4954        (sly-rcurry #'sly-xref-recompilation-cont
   4955                    (list dspec) (current-buffer))))))
   4956 
   4957 (defun sly-recompile-all-xrefs (&optional raw-prefix-arg)
   4958   "Recompile all definitions.
   4959 Uses prefix arguments like `sly-compile-defun'."
   4960   (interactive "P")
   4961   (let ((sly-compilation-policy (sly-compute-policy raw-prefix-arg)))
   4962     (let ((dspecs) (locations))
   4963       (dolist (xref (sly-all-xrefs))
   4964         (when (sly-xref-has-location-p xref)
   4965           (push (sly-xref.dspec xref) dspecs)
   4966           (push (sly-xref.location xref) locations)))
   4967       (sly-recompile-locations
   4968        locations
   4969        (sly-rcurry #'sly-xref-recompilation-cont
   4970                    dspecs (current-buffer))))))
   4971 
   4972 (defun sly-xref-recompilation-cont (results dspecs buffer)
   4973   ;; Extreme long-windedness to insert status of recompilation;
   4974   ;; sometimes Elisp resembles more of an Ewwlisp.
   4975 
   4976   ;; FIXME: Should probably throw out the whole recompilation cruft
   4977   ;; anyway.  -- helmut
   4978   ;; TODO: next iteration of fixme cleanup this is going in a contrib -- jt
   4979   (with-current-buffer buffer
   4980     (sly-compilation-finished (sly-aggregate-compilation-results results)
   4981                               nil)
   4982     (save-excursion
   4983       (sly-xref-insert-recompilation-flags
   4984        dspecs (cl-loop for r in results collect
   4985                        (or (sly-compilation-result.successp r)
   4986                            (and (sly-compilation-result.notes r)
   4987                                 :complained)))))))
   4988 
   4989 (defun sly-aggregate-compilation-results (results)
   4990   `(:compilation-result
   4991     ,(cl-reduce #'append (mapcar #'sly-compilation-result.notes results))
   4992     ,(cl-every #'sly-compilation-result.successp results)
   4993     ,(cl-reduce #'+ (mapcar #'sly-compilation-result.duration results))))
   4994 
   4995 (defun sly-xref-insert-recompilation-flags (dspecs compilation-results)
   4996   (let* ((buffer-read-only nil)
   4997          (max-column (sly-column-max)))
   4998     (goto-char (point-min))
   4999     (cl-loop for dspec in dspecs
   5000              for result in compilation-results
   5001              do (save-excursion
   5002                   (cl-loop for dspec2 = (progn (search-forward dspec)
   5003                                                (sly-xref-dspec-at-point))
   5004                            until (equal dspec2 dspec))
   5005                   (end-of-line) ; skip old status information.
   5006                   (insert-char ?\  (1+ (- max-column (current-column))))
   5007                   (insert (format "[%s]"
   5008                                   (cl-case result
   5009                                     ((t)   :success)
   5010                                     ((nil) :failure)
   5011                                     (t     result))))))))
   5012 
   5013 
   5014 ;;;; Macroexpansion
   5015 
   5016 (defvar sly-macroexpansion-minor-mode-map
   5017   (let ((map (make-sparse-keymap)))
   5018     (define-key map (kbd "g") 'sly-macroexpand-again)
   5019     (define-key map (kbd "a") 'sly-macroexpand-all-inplace)
   5020     (define-key map (kbd "q") 'quit-window)
   5021     (define-key map [remap sly-macroexpand-1] 'sly-macroexpand-1-inplace)
   5022     (define-key map [remap sly-macroexpand-all] 'sly-macroexpand-all-inplace)
   5023     (define-key map [remap sly-compiler-macroexpand-1] 'sly-compiler-macroexpand-1-inplace)
   5024     (define-key map [remap sly-expand-1] 'sly-expand-1-inplace)
   5025     (define-key map [remap undo] 'sly-macroexpand-undo)
   5026     map))
   5027 
   5028 (define-minor-mode sly-macroexpansion-minor-mode
   5029   "SLY mode for macroexpansion"
   5030   nil
   5031   " Macroexpand"
   5032   nil
   5033   (read-only-mode 1))
   5034 
   5035 (defun sly-macroexpand-undo (&optional arg)
   5036   (interactive)
   5037   ;; Emacs 22.x introduced `undo-only' which
   5038   ;; works by binding `undo-no-redo' to t. We do
   5039   ;; it this way so we don't break prior Emacs
   5040   ;; versions.
   5041   (cl-macrolet ((undo-only (arg) `(let ((undo-no-redo t)) (undo ,arg))))
   5042     (let ((inhibit-read-only t))
   5043       (when (fboundp 'sly-remove-edits)
   5044         (sly-remove-edits (point-min) (point-max)))
   5045       (undo-only arg))))
   5046 
   5047 (defvar sly-eval-macroexpand-expression nil
   5048   "Specifies the last macroexpansion preformed.
   5049 This variable specifies both what was expanded and how.")
   5050 
   5051 (defun sly-eval-macroexpand (expander &optional string)
   5052   (let ((string (or string
   5053                     (sly-sexp-at-point 'interactive))))
   5054     (setq sly-eval-macroexpand-expression `(,expander ,string))
   5055     (sly-eval-async sly-eval-macroexpand-expression
   5056       #'sly-initialize-macroexpansion-buffer)))
   5057 
   5058 (defun sly-macroexpand-again ()
   5059   "Reperform the last macroexpansion."
   5060   (interactive)
   5061   (sly-eval-async sly-eval-macroexpand-expression
   5062     (sly-rcurry #'sly-initialize-macroexpansion-buffer
   5063                 (current-buffer))))
   5064 
   5065 (defun sly-initialize-macroexpansion-buffer (expansion &optional buffer)
   5066   (pop-to-buffer (or buffer (sly-create-macroexpansion-buffer)))
   5067   (setq buffer-undo-list nil) ; Get rid of undo information from
   5068                                         ; previous expansions.
   5069   (let ((inhibit-read-only t)
   5070         (buffer-undo-list t)) ; Make the initial insertion not be undoable.
   5071     (erase-buffer)
   5072     (insert expansion)
   5073     (goto-char (point-min))
   5074     (if (fboundp 'font-lock-ensure)
   5075         (font-lock-ensure)
   5076       (with-no-warnings (font-lock-fontify-buffer)))))
   5077 
   5078 (defun sly-create-macroexpansion-buffer ()
   5079   (let ((name (sly-buffer-name :macroexpansion)))
   5080     (sly-with-popup-buffer (name :package t :connection t
   5081                                  :mode 'lisp-mode)
   5082       (sly-macroexpansion-minor-mode 1)
   5083       (setq font-lock-keywords-case-fold-search t)
   5084       (current-buffer))))
   5085 
   5086 (defun sly-eval-macroexpand-inplace (expander)
   5087   "Substitute the sexp at point with its macroexpansion.
   5088 
   5089 NB: Does not affect sly-eval-macroexpand-expression"
   5090   (interactive)
   5091   (let* ((bounds (sly-bounds-of-sexp-at-point 'interactive)))
   5092     (let* ((start (copy-marker (car bounds)))
   5093            (end (copy-marker (cdr bounds)))
   5094            (point (point))
   5095            (buffer (current-buffer)))
   5096       (sly-eval-async
   5097           `(,expander ,(buffer-substring-no-properties start end))
   5098         (lambda (expansion)
   5099           (with-current-buffer buffer
   5100             (let ((buffer-read-only nil))
   5101               (when (fboundp 'sly-remove-edits)
   5102                 (sly-remove-edits (point-min) (point-max)))
   5103               (goto-char start)
   5104               (delete-region start end)
   5105               (sly-insert-indented expansion)
   5106               (goto-char point))))))))
   5107 
   5108 (defun sly-macroexpand-1 (&optional repeatedly)
   5109   "Display the macro expansion of the form at point.
   5110 The form is expanded with CL:MACROEXPAND-1 or, if a prefix
   5111 argument is given, with CL:MACROEXPAND."
   5112   (interactive "P")
   5113   (sly-eval-macroexpand
   5114    (if repeatedly 'slynk:slynk-macroexpand 'slynk:slynk-macroexpand-1)))
   5115 
   5116 (defun sly-macroexpand-1-inplace (&optional repeatedly)
   5117   (interactive "P")
   5118   (sly-eval-macroexpand-inplace
   5119    (if repeatedly 'slynk:slynk-macroexpand 'slynk:slynk-macroexpand-1)))
   5120 
   5121 (defun sly-macroexpand-all (&optional just-one)
   5122   "Display the recursively macro expanded sexp at point.
   5123 With optional JUST-ONE prefix arg, use CL:MACROEXPAND-1."
   5124   (interactive "P")
   5125   (sly-eval-macroexpand (if just-one
   5126                             'slynk:slynk-macroexpand-1
   5127                           'slynk:slynk-macroexpand-all)))
   5128 
   5129 (defun sly-macroexpand-all-inplace ()
   5130   "Display the recursively macro expanded sexp at point."
   5131   (interactive)
   5132   (sly-eval-macroexpand-inplace 'slynk:slynk-macroexpand-all))
   5133 
   5134 (defun sly-compiler-macroexpand-1 (&optional repeatedly)
   5135   "Display the compiler-macro expansion of sexp at point."
   5136   (interactive "P")
   5137   (sly-eval-macroexpand
   5138    (if repeatedly
   5139        'slynk:slynk-compiler-macroexpand
   5140      'slynk:slynk-compiler-macroexpand-1)))
   5141 
   5142 (defun sly-compiler-macroexpand-1-inplace (&optional repeatedly)
   5143   "Display the compiler-macro expansion of sexp at point."
   5144   (interactive "P")
   5145   (sly-eval-macroexpand-inplace
   5146    (if repeatedly
   5147        'slynk:slynk-compiler-macroexpand
   5148      'slynk:slynk-compiler-macroexpand-1)))
   5149 
   5150 (defun sly-expand-1 (&optional repeatedly)
   5151   "Display the macro expansion of the form at point.
   5152 
   5153 The form is expanded with CL:MACROEXPAND-1 or, if a prefix
   5154 argument is given, with CL:MACROEXPAND.
   5155 
   5156 Contrary to `sly-macroexpand-1', if the form denotes a compiler
   5157 macro, SLYNK-BACKEND:COMPILER-MACROEXPAND or
   5158 SLYNK-BACKEND:COMPILER-MACROEXPAND-1 are used instead."
   5159   (interactive "P")
   5160   (sly-eval-macroexpand
   5161    (if repeatedly
   5162        'slynk:slynk-expand
   5163      'slynk:slynk-expand-1)))
   5164 
   5165 (defun sly-expand-1-inplace (&optional repeatedly)
   5166   "Display the macro expansion of the form at point.
   5167 The form is expanded with CL:MACROEXPAND-1 or, if a prefix
   5168 argument is given, with CL:MACROEXPAND."
   5169   (interactive "P")
   5170   (sly-eval-macroexpand-inplace
   5171    (if repeatedly
   5172        'slynk:slynk-expand
   5173      'slynk:slynk-expand-1)))
   5174 
   5175 (defun sly-format-string-expand (&optional string)
   5176   "Expand the format-string at point and display it.
   5177 With prefix arg, or if no string at point, prompt the user for a
   5178 string to expand.
   5179 "
   5180   (interactive (list (or (and (not current-prefix-arg)
   5181                               (sly-string-at-point))
   5182                          (sly-read-from-minibuffer "Expand format: "
   5183                                                    (sly-string-at-point)))))
   5184   (sly-eval-macroexpand 'slynk:slynk-format-string-expand
   5185                         string))
   5186 
   5187 
   5188 ;;;; Subprocess control
   5189 
   5190 (defun sly-interrupt ()
   5191   "Interrupt Lisp."
   5192   (interactive)
   5193   (cond ((sly-use-sigint-for-interrupt) (sly-send-sigint))
   5194         (t (sly-dispatch-event `(:emacs-interrupt ,sly-current-thread)))))
   5195 
   5196 (defun sly-quit ()
   5197   (error "Not implemented properly.  Use `sly-interrupt' instead."))
   5198 
   5199 (defun sly-quit-lisp (&optional kill interactive)
   5200   "Quit lisp, kill the inferior process and associated buffers."
   5201   (interactive (list current-prefix-arg t))
   5202   (let ((connection (if interactive
   5203                         (sly-prompt-for-connection "Connection to quit: ")
   5204                       (sly-current-connection))))
   5205     (sly-quit-lisp-internal connection 'sly-quit-sentinel kill)))
   5206 
   5207 (defun sly-quit-lisp-internal (connection sentinel kill)
   5208   "Kill SLY socket connection CONNECTION.
   5209 Do this by evaluating (SLYNK:QUIT-LISP) in it, and don't wait for
   5210 it to reply as usual with other evaluations.  If it's non-nil,
   5211 setup SENTINEL to run on CONNECTION when it finishes dying.  If
   5212 KILL is t, and there is such a thing, also kill the inferior lisp
   5213 process associated with CONNECTION."
   5214   (let ((sly-dispatching-connection connection))
   5215     (sly-eval-async '(slynk:quit-lisp))
   5216     (set-process-filter connection  nil)
   5217     (let ((attempt 0)
   5218           (dying-p nil))
   5219       (set-process-sentinel
   5220        connection
   5221        (lambda (connection status)
   5222          (setq dying-p t)
   5223          (sly-message "Connection %s is dying (%s)" connection status)
   5224          (let ((inf-process (sly-inferior-process connection)))
   5225            (cond ((and kill
   5226                        inf-process
   5227                        (not (memq (process-status inf-process) '(exit signal))))
   5228                   (sly-message "Quitting %s: also killing the inferior process %s"
   5229                                connection inf-process)
   5230                   (kill-process inf-process))
   5231                  ((and kill
   5232                        inf-process)
   5233                   (sly-message "Quitting %s: inferior process was already dead"
   5234                                connection
   5235                                inf-process))
   5236                  ((and
   5237                    kill
   5238                    (not inf-process))
   5239                   (sly-message "Quitting %s: No inferior process to kill!"
   5240                                connection
   5241                                inf-process))))
   5242          (when sentinel
   5243            (funcall sentinel connection status))))
   5244       (sly-message
   5245        "Waiting for connection %s to die by itself..." connection)
   5246       (while (and (< (cl-incf attempt) 30)
   5247                   (not dying-p))
   5248         (sleep-for 0.1))
   5249       (unless dying-p
   5250         (sly-message
   5251          "Connection %s didn't die by itself. Killing it." connection)
   5252         (delete-process connection)))))
   5253 
   5254 (defun sly-quit-sentinel (process _message)
   5255   (cl-assert (process-status process) 'closed)
   5256   (let* ((inferior (sly-inferior-process process))
   5257          (inferior-buffer (if inferior (process-buffer inferior))))
   5258     (when inferior (delete-process inferior))
   5259     (when inferior-buffer (kill-buffer inferior-buffer))
   5260     (sly-net-close process "Quitting lisp")
   5261     (sly-message "Connection closed.")))
   5262 
   5263 
   5264 ;;;; Debugger (SLY-DB)
   5265 
   5266 (defvar sly-db-hook nil
   5267   "Hook run on entry to the debugger.")
   5268 
   5269 (defcustom sly-db-initial-restart-limit 6
   5270   "Maximum number of restarts to display initially."
   5271   :group 'sly-debugger
   5272   :type 'integer)
   5273 
   5274 
   5275 ;;;;; Local variables in the debugger buffer
   5276 
   5277 ;; Small helper.
   5278 (defun sly-make-variables-buffer-local (&rest variables)
   5279   (mapcar #'make-variable-buffer-local variables))
   5280 
   5281 (sly-make-variables-buffer-local
   5282  (defvar sly-db-condition nil
   5283    "A list (DESCRIPTION TYPE) describing the condition being debugged.")
   5284 
   5285  (defvar sly-db-restarts nil
   5286    "List of (NAME DESCRIPTION) for each available restart.")
   5287 
   5288  (defvar sly-db-level nil
   5289    "Current debug level (recursion depth) displayed in buffer.")
   5290 
   5291  (defvar sly-db-backtrace-start-marker nil
   5292    "Marker placed at the first frame of the backtrace.")
   5293 
   5294  (defvar sly-db-restart-list-start-marker nil
   5295    "Marker placed at the first restart in the restart list.")
   5296 
   5297  (defvar sly-db-continuations nil
   5298    "List of ids for pending continuation."))
   5299 
   5300 ;;;;; SLY-DB macros
   5301 
   5302 ;; some macros that we need to define before the first use
   5303 
   5304 (defmacro sly-db-in-face (name string)
   5305   "Return STRING propertised with face sly-db-NAME-face."
   5306   (declare (indent 1))
   5307   (let ((facename (intern (format "sly-db-%s-face" (symbol-name name))))
   5308         (var (cl-gensym "string")))
   5309     `(let ((,var ,string))
   5310        (sly-add-face ',facename ,var)
   5311        ,var)))
   5312 
   5313 
   5314 ;;;;; sly-db-mode
   5315 
   5316 (defvar sly-db-mode-syntax-table
   5317   (let ((table (copy-syntax-table lisp-mode-syntax-table)))
   5318     ;; We give < and > parenthesis syntax, so that #< ... > is treated
   5319     ;; as a balanced expression.  This enables autodoc-mode to match
   5320     ;; #<unreadable> actual arguments in the backtraces with formal
   5321     ;; arguments of the function.  (For Lisp mode, this is not
   5322     ;; desirable, since we do not wish to get a mismatched paren
   5323     ;; highlighted everytime we type < or >.)
   5324     (modify-syntax-entry ?< "(" table)
   5325     (modify-syntax-entry ?> ")" table)
   5326     table)
   5327   "Syntax table for SLY-DB mode.")
   5328 
   5329 (defvar sly-db-mode-map
   5330   (let ((map (make-sparse-keymap)))
   5331     (define-key map "n"    'sly-db-down)
   5332     (define-key map "p"    'sly-db-up)
   5333     (define-key map "\M-n" 'sly-db-details-down)
   5334     (define-key map "\M-p" 'sly-db-details-up)
   5335     (define-key map "<"    'sly-db-beginning-of-backtrace)
   5336     (define-key map ">"    'sly-db-end-of-backtrace)
   5337 
   5338     (define-key map "a"    'sly-db-abort)
   5339     (define-key map "q"    'sly-db-abort)
   5340     (define-key map "c"    'sly-db-continue)
   5341     (define-key map "A"    'sly-db-break-with-system-debugger)
   5342     (define-key map "B"    'sly-db-break-with-default-debugger)
   5343     (define-key map "P"    'sly-db-print-condition)
   5344     (define-key map "I"    'sly-db-invoke-restart-by-name)
   5345     (define-key map "C"    'sly-db-inspect-condition)
   5346     (define-key map ":"    'sly-interactive-eval)
   5347     (define-key map "Q"    'sly-db-quit)
   5348 
   5349     (set-keymap-parent map button-buffer-map)
   5350     map))
   5351 
   5352 (define-derived-mode sly-db-mode fundamental-mode "sly-db"
   5353   "Superior lisp debugger mode.
   5354 In addition to ordinary SLY commands, the following are
   5355 available:\\<sly-db-mode-map>
   5356 
   5357 Commands to invoke restarts:
   5358    \\[sly-db-quit]   - quit
   5359    \\[sly-db-abort]   - abort
   5360    \\[sly-db-continue]   - continue
   5361    \\[sly-db-invoke-restart-0]-\\[sly-db-invoke-restart-9] - restart shortcuts
   5362    \\[sly-db-invoke-restart-by-name]   - invoke restart by name
   5363 
   5364 Navigation commands:
   5365    \\[forward-button] - next interactive button
   5366    \\[sly-db-down]   - down
   5367    \\[sly-db-up]   - up
   5368    \\[sly-db-details-down] - down, with details
   5369    \\[sly-db-details-up] - up, with details
   5370    \\[sly-db-beginning-of-backtrace]   - beginning of backtrace
   5371    \\[sly-db-end-of-backtrace]   - end of backtrace
   5372 
   5373 Commands to examine and operate on the selected frame:\\<sly-db-frame-map>
   5374    \\[sly-db-show-frame-source]   - show frame source
   5375    \\[sly-db-goto-source]   - go to frame source
   5376    \\[sly-db-toggle-details] - toggle details
   5377    \\[sly-db-disassemble]   - dissassemble frame
   5378    \\[sly-db-eval-in-frame]   - prompt for a form to eval in frame
   5379    \\[sly-db-pprint-eval-in-frame]   - eval in frame and pretty print result
   5380    \\[sly-db-inspect-in-frame]   - inspect in frame's context
   5381    \\[sly-db-restart-frame]   - restart frame
   5382    \\[sly-db-return-from-frame]   - return from frame
   5383 
   5384 Miscellaneous commands:\\<sly-db-mode-map>
   5385    \\[sly-db-step]   - step
   5386    \\[sly-db-break-with-default-debugger]   - switch to native debugger
   5387    \\[sly-db-break-with-system-debugger]   - switch to system debugger (gdb)
   5388    \\[sly-interactive-eval]   - eval
   5389    \\[sly-db-inspect-condition]   - inspect signalled condition
   5390 
   5391 Full list of commands:
   5392 
   5393 \\{sly-db-mode-map}
   5394 
   5395 Full list of frame-specific commands:
   5396 
   5397 \\{sly-db-frame-map}"
   5398   (erase-buffer)
   5399   (set-syntax-table sly-db-mode-syntax-table)
   5400   (sly-set-truncate-lines)
   5401   ;; Make original sly-connection "sticky" for SLY-DB commands in this buffer
   5402   (setq sly-buffer-connection (sly-connection))
   5403   (setq buffer-read-only t)
   5404   (sly-mode 1)
   5405   (sly-interactive-buttons-mode 1))
   5406 
   5407 ;; Keys 0-9 are shortcuts to invoke particular restarts.
   5408 (dotimes (number 10)
   5409   (let ((fname (intern (format "sly-db-invoke-restart-%S" number)))
   5410         (docstring (format "Invoke restart numbered %S." number)))
   5411     ;; FIXME: In Emacs≥25, you could avoid `eval' and use
   5412     ;;     (defalias .. (lambda .. (:documentation docstring) ...))
   5413     ;; instead!
   5414     (eval `(defun ,fname ()
   5415              ,docstring
   5416              (interactive)
   5417              (sly-db-invoke-restart ,number))
   5418           t)
   5419     (define-key sly-db-mode-map (number-to-string number) fname)))
   5420 
   5421 
   5422 ;;;;; SLY-DB buffer creation & update
   5423 
   5424 (defcustom sly-db-focus-debugger 'auto
   5425   "Control if debugger window gets focus immediately.
   5426 
   5427 If nil, the window is never focused automatically; if the symbol
   5428 `auto', the window is only focused if the user has performed no
   5429 other commands in the meantime (i.e. he/she is expecting a
   5430 possible debugger); any other non-nil value means to always
   5431 automatically focus the debugger window."
   5432   :group 'sly-debugger
   5433   :type '(choice (const always) (const never) (const auto)))
   5434 
   5435 (defun sly-filter-buffers (predicate)
   5436   "Return a list of where PREDICATE returns true.
   5437 PREDICATE is executed in the buffer to test."
   5438   (cl-remove-if-not (lambda (%buffer)
   5439                       (with-current-buffer %buffer
   5440                         (funcall predicate)))
   5441                     (buffer-list)))
   5442 
   5443 (defun sly-db-buffers (&optional connection)
   5444   "Return a list of all sly-db buffers (belonging to CONNECTION.)"
   5445   (if connection
   5446       (sly-filter-buffers (lambda ()
   5447                             (and (eq sly-buffer-connection connection)
   5448                                  (eq major-mode 'sly-db-mode))))
   5449     (sly-filter-buffers (lambda () (eq major-mode 'sly-db-mode)))))
   5450 
   5451 (defun sly-db-find-buffer (thread &optional connection)
   5452   (let ((connection (or connection (sly-connection))))
   5453     (cl-find-if (lambda (buffer)
   5454                   (with-current-buffer buffer
   5455                     (and (eq sly-buffer-connection connection)
   5456                          (eq sly-current-thread thread))))
   5457                 (sly-db-buffers))))
   5458 
   5459 (defun sly-db-pop-to-debugger-maybe (&optional _button)
   5460   "Maybe pop to *sly-db* buffer for current context."
   5461   (interactive)
   5462   (let ((b (sly-db-find-buffer sly-current-thread)))
   5463     (if b (pop-to-buffer b)
   5464       (sly-error "Can't find a *sly-db* debugger for this context"))))
   5465 
   5466 (defsubst sly-db-get-default-buffer ()
   5467   "Get a sly-db buffer.
   5468 The chosen buffer the default connection's it if exists."
   5469   (car (sly-db-buffers (sly-current-connection))))
   5470 
   5471 (defun sly-db-pop-to-debugger ()
   5472   "Pop to the first *sly-db* buffer if at least one exists."
   5473   (interactive)
   5474   (let ((b (sly-db-get-default-buffer)))
   5475     (if b (pop-to-buffer b)
   5476       (sly-error "No *sly-db* debugger buffers for this connection"))))
   5477 
   5478 (defun sly-db-get-buffer (thread &optional connection)
   5479   "Find or create a sly-db-buffer for THREAD."
   5480   (let ((connection (or connection (sly-connection))))
   5481     (or (sly-db-find-buffer thread connection)
   5482         (let ((name (sly-buffer-name :db :connection connection
   5483                                      :suffix (format "thread %d" thread))))
   5484           (with-current-buffer (generate-new-buffer name)
   5485             (setq sly-buffer-connection connection
   5486                   sly-current-thread thread)
   5487             (current-buffer))))))
   5488 
   5489 (defun sly-db-debugged-continuations (connection)
   5490   "Return the all debugged continuations for CONNECTION across SLY-DB buffers."
   5491   (cl-loop for b in (sly-db-buffers)
   5492            append (with-current-buffer b
   5493                     (and (eq sly-buffer-connection connection)
   5494                          sly-db-continuations))))
   5495 
   5496 (defun sly-db-confirm-buffer-kill ()
   5497   (when (or (not (process-live-p sly-buffer-connection))
   5498             (sly-y-or-n-p "Really kill sly-db buffer and throw to toplevel?"))
   5499     (ignore-errors (sly-db-quit))
   5500     t))
   5501 
   5502 (defun sly-db--display-debugger (_thread)
   5503   "Display (or pop to) sly-db for THREAD as appropriate.
   5504 Also mark the window as a debugger window."
   5505   (let* ((action '(sly-db--display-in-prev-sly-db-window))
   5506          (buffer (current-buffer))
   5507          (win
   5508           (if (cond ((eq sly-db-focus-debugger 'auto)
   5509                      (eq sly--send-last-command last-command))
   5510                     (t sly-db-focus-debugger))
   5511               (progn
   5512                 (pop-to-buffer buffer action)
   5513                 (selected-window))
   5514             (display-buffer buffer action))))
   5515     (set-window-parameter win 'sly-db buffer)
   5516     win))
   5517 
   5518 (defun sly-db-setup (thread level condition restarts frame-specs conts)
   5519   "Setup a new SLY-DB buffer.
   5520 CONDITION is a string describing the condition to debug.
   5521 RESTARTS is a list of strings (NAME DESCRIPTION) for each
   5522 available restart.  FRAME-SPECS is a list of (NUMBER DESCRIPTION
   5523 &optional PLIST) describing the initial portion of the
   5524 backtrace. Frames are numbered from 0.  CONTS is a list of
   5525 pending Emacs continuations."
   5526   (with-current-buffer (sly-db-get-buffer thread)
   5527     (cl-assert (if (equal sly-db-level level)
   5528                    (equal sly-db-condition condition)
   5529                  t)
   5530                () "Bug: sly-db-level is equal but condition differs\n%s\n%s"
   5531                sly-db-condition condition)
   5532     (with-selected-window (sly-db--display-debugger thread)
   5533       (unless (equal sly-db-level level)
   5534         (let ((inhibit-read-only t))
   5535           (sly-db-mode)
   5536           (add-hook 'kill-buffer-query-functions
   5537                     #'sly-db-confirm-buffer-kill
   5538                     nil t)
   5539           (setq sly-current-thread thread)
   5540           (setq sly-db-level level)
   5541           (setq mode-name (format "sly-db[%d]" sly-db-level))
   5542           (setq sly-db-condition condition)
   5543           (setq sly-db-restarts restarts)
   5544           (setq sly-db-continuations conts)
   5545           (sly-db-insert-condition condition)
   5546           (insert "\n\n" (sly-db-in-face section "Restarts:") "\n")
   5547           (setq sly-db-restart-list-start-marker (point-marker))
   5548           (sly-db-insert-restarts restarts 0 sly-db-initial-restart-limit)
   5549           (insert "\n" (sly-db-in-face section "Backtrace:") "\n")
   5550           (setq sly-db-backtrace-start-marker (point-marker))
   5551           (save-excursion
   5552             (if frame-specs
   5553                 (sly-db-insert-frames (sly-db-prune-initial-frames frame-specs) t)
   5554               (insert "[No backtrace]")))
   5555           (run-hooks 'sly-db-hook)
   5556           (set-syntax-table lisp-mode-syntax-table)))
   5557       (sly-recenter (point-min) 'allow-moving-point)
   5558       (when sly--stack-eval-tags
   5559         (sly-message "Entering recursive edit..")
   5560         (recursive-edit)))))
   5561 
   5562 (defun sly-db--display-in-prev-sly-db-window (buffer _alist)
   5563   (let ((window
   5564          (get-window-with-predicate
   5565           #'(lambda (w)
   5566               (let ((value (window-parameter w 'sly-db)))
   5567                 (and value
   5568                      (not (buffer-live-p value))))))))
   5569     (when window
   5570       (display-buffer-record-window 'reuse window buffer)
   5571       (set-window-buffer window buffer)
   5572       window)))
   5573 
   5574 (defun sly-db--ensure-initialized (thread level)
   5575   "Initialize debugger buffer for THREAD.
   5576 If such a buffer exists for LEVEL, it is assumed to have been
   5577 sufficiently initialized, and this function does nothing."
   5578   (let ((buffer (sly-db-find-buffer thread)))
   5579     (unless (and buffer
   5580                  (with-current-buffer buffer
   5581                    (equal sly-db-level level)))
   5582       (sly-rex ()
   5583           ('(slynk:debugger-info-for-emacs 0 10)
   5584            nil thread)
   5585         ((:ok result)
   5586          (apply #'sly-db-setup thread level result))))))
   5587 
   5588 (defvar sly-db-exit-hook nil
   5589   "Hooks run in the debugger buffer just before exit")
   5590 
   5591 (defun sly-db-exit (thread _level &optional stepping)
   5592   "Exit from the debug level LEVEL."
   5593   (sly--when-let (sly-db (sly-db-find-buffer thread))
   5594     (with-current-buffer sly-db
   5595       (setq kill-buffer-query-functions
   5596             (remove 'sly-db-confirm-buffer-kill kill-buffer-query-functions))
   5597       (run-hooks 'sly-db-exit-hook)
   5598       (cond (stepping
   5599              (setq sly-db-level nil)
   5600              (run-with-timer 0.4 nil 'sly-db-close-step-buffer sly-db))
   5601             ((not (eq sly-db (window-buffer (selected-window))))
   5602              ;; A different window selection means an indirect,
   5603              ;; non-interactive exit, we just kill the sly-db buffer.
   5604              (kill-buffer))
   5605             (t
   5606              (quit-window t))))))
   5607 
   5608 (defun sly-db-close-step-buffer (buffer)
   5609   (when (buffer-live-p buffer)
   5610     (with-current-buffer buffer
   5611       (when (not sly-db-level)
   5612         (quit-window t)))))
   5613 
   5614 
   5615 ;;;;;; SLY-DB buffer insertion
   5616 
   5617 (defun sly-db-insert-condition (condition)
   5618   "Insert the text for CONDITION.
   5619 CONDITION should be a list (MESSAGE TYPE EXTRAS).
   5620 EXTRAS is currently used for the stepper."
   5621   (cl-destructuring-bind (msg type extras) condition
   5622     (insert (sly-db-in-face topline msg)
   5623             "\n"
   5624             (sly-db-in-face condition type))
   5625     (sly-db-dispatch-extras extras)))
   5626 
   5627 (defvar sly-db-extras-hooks nil
   5628   "Handlers for the extra options sent in a debugger invocation.
   5629 Each function is called with one argument, a list (OPTION
   5630 VALUE). It should return non-nil iff it can handle OPTION, and
   5631 thus preventing other handlers from trying.
   5632 
   5633 Functions are run in the SLDB buffer.")
   5634 
   5635 (defun sly-db-dispatch-extras (extras)
   5636   ;; this is (mis-)used for the stepper
   5637   (dolist (extra extras)
   5638     (sly-dcase extra
   5639       ((:show-frame-source n)
   5640        (sly-db-show-frame-source n))
   5641       (t
   5642        (or (run-hook-with-args-until-success 'sly-db-extras-hooks extra)
   5643            ;;(error "Unhandled extra element:" extra)
   5644            )))))
   5645 
   5646 (defun sly-db-insert-restarts (restarts start count)
   5647   "Insert RESTARTS and add the needed text props
   5648 RESTARTS should be a list ((NAME DESCRIPTION) ...)."
   5649   (let* ((len (length restarts))
   5650          (end (if count (min (+ start count) len) len)))
   5651     (cl-loop for (name string) in (cl-subseq restarts start end)
   5652              for number from start
   5653              do (insert
   5654                  " " (sly-db-in-face restart-number (number-to-string number))
   5655                  ": "  (sly-make-action-button (format "[%s]" name)
   5656                                                (let ((n number))
   5657                                                  #'(lambda (_button)
   5658                                                      (sly-db-invoke-restart n)))
   5659                                                'restart-number number)
   5660                  " " (sly-db-in-face restart string))
   5661              (insert "\n"))
   5662     (when (< end len)
   5663       (insert (sly-make-action-button
   5664                " --more--"
   5665                #'(lambda (button)
   5666                    (let ((inhibit-read-only t))
   5667                      (delete-region (button-start button)
   5668                                     (1+ (button-end button)))
   5669                      (sly-db-insert-restarts restarts end nil)
   5670                      (sly--when-let (win (get-buffer-window (current-buffer)))
   5671                        (with-selected-window win
   5672                          (sly-recenter (point-max))))))
   5673                'point-entered #'(lambda (_ new) (push-button new)))
   5674               "\n"))))
   5675 
   5676 (defun sly-db-frame-restartable-p (frame-spec)
   5677   (and (plist-get (cl-caddr frame-spec) :restartable) t))
   5678 
   5679 (defun sly-db-prune-initial-frames (frame-specs)
   5680   "Return the prefix of FRAMES-SPECS to initially present to the user.
   5681 Regexp heuristics are used to avoid showing SLYNK-internal frames."
   5682   (let* ((case-fold-search t)
   5683          (rx "^\\([() ]\\|lambda\\)*slynk\\>"))
   5684     (or (cl-loop for frame-spec in frame-specs
   5685                  until (string-match rx (cadr frame-spec))
   5686                  collect frame-spec)
   5687         frame-specs)))
   5688 
   5689 (defun sly-db-insert-frames (frame-specs more)
   5690   "Insert frames for FRAME-SPECS into buffer.
   5691 If MORE is non-nil, more frames are on the Lisp stack."
   5692   (cl-loop
   5693    for frame-spec in frame-specs
   5694    do (sly-db-insert-frame frame-spec)
   5695    finally
   5696    (when more
   5697      (insert (sly-make-action-button
   5698               " --more--\n"
   5699               (lambda (button)
   5700                 (let* ((inhibit-read-only t)
   5701                        (count 40)
   5702                        (from (1+ (car frame-spec)))
   5703                        (to (+ from count))
   5704                        (frames (sly-eval `(slynk:backtrace ,from ,to)))
   5705                        (more (sly-length= frames count)))
   5706                   (delete-region (button-start button)
   5707                                  (button-end button))
   5708                   (save-excursion
   5709                     (sly-db-insert-frames frames more))
   5710                   (sly--when-let (win (get-buffer-window (current-buffer)))
   5711                     (with-selected-window win
   5712                       (sly-recenter (point-max))))))
   5713               'point-entered #'(lambda (_ new) (push-button new)))))))
   5714 
   5715 (defvar sly-db-frame-map
   5716   (let ((map (make-sparse-keymap)))
   5717     (define-key map (kbd "t")   'sly-db-toggle-details)
   5718     (define-key map (kbd "v")   'sly-db-show-frame-source)
   5719     (define-key map (kbd ".")   'sly-db-goto-source)
   5720     (define-key map (kbd "D")   'sly-db-disassemble)
   5721     (define-key map (kbd "e")   'sly-db-eval-in-frame)
   5722     (define-key map (kbd "d")   'sly-db-pprint-eval-in-frame)
   5723     (define-key map (kbd "i")   'sly-db-inspect-in-frame)
   5724     (define-key map (kbd "r")   'sly-db-restart-frame)
   5725     (define-key map (kbd "R")   'sly-db-return-from-frame)
   5726     (define-key map (kbd "RET") 'sly-db-toggle-details)
   5727 
   5728     (define-key map "s"    'sly-db-step)
   5729     (define-key map "x"    'sly-db-next)
   5730     (define-key map "o"    'sly-db-out)
   5731     (define-key map "b"    'sly-db-break-on-return)
   5732 
   5733     (define-key map "\C-c\C-c" 'sly-db-recompile-frame-source)
   5734 
   5735     (set-keymap-parent map sly-part-button-keymap)
   5736     map))
   5737 
   5738 (defvar sly-db-frame-menu-map
   5739   (let ((map (make-sparse-keymap)))
   5740     (cl-macrolet ((item (label sym)
   5741                         `(define-key map [,sym] '(menu-item ,label ,sym))))
   5742       (item "Dissassemble" sly-db-disassemble)
   5743       (item "Eval In Context" sly-db-eval-in-frame)
   5744       (item "Eval and Pretty Print In Context" sly-db-pprint-eval-in-frame)
   5745       (item "Inspect In Context" sly-db-inspect-in-frame)
   5746       (item "Restart" sly-db-restart-frame)
   5747       (item "Return Value" sly-db-return-from-frame)
   5748       (item "Toggle Details" sly-db-toggle-details)
   5749       (item "Show Source" sly-db-show-frame-source)
   5750       (item "Go To Source" sly-db-goto-source))
   5751     (set-keymap-parent map sly-button-popup-part-menu-keymap)
   5752     map))
   5753 
   5754 (define-button-type 'sly-db-frame :supertype 'sly-part
   5755   'keymap sly-db-frame-map
   5756   'part-menu-keymap sly-db-frame-menu-map
   5757   'action 'sly-db-toggle-details
   5758   'mouse-action 'sly-db-toggle-details)
   5759 
   5760 (defun sly-db--guess-frame-function (frame)
   5761   (ignore-errors
   5762     (car (car (read-from-string
   5763                (replace-regexp-in-string "#" ""
   5764                                          (cadr frame)))))))
   5765 
   5766 (defun sly-db-frame-button (label frame face &rest props)
   5767   (apply #'sly--make-text-button label nil :type 'sly-db-frame
   5768          'face face
   5769          'field (car frame)
   5770          'frame-number (car frame)
   5771          'frame-string (cadr frame)
   5772          'part-args (list (car frame)
   5773                           (sly-db--guess-frame-function frame))
   5774          'part-label (format "Frame %d" (car frame))
   5775          props))
   5776 
   5777 (defun sly-db-frame-number-at-point ()
   5778   (let ((button (sly-db-frame-button-near-point)))
   5779     (button-get button 'frame-number)))
   5780 
   5781 (defun sly-db-frame-button-near-point ()
   5782   (or (sly-button-at nil 'sly-db-frame 'no-error)
   5783       (get-text-property (point) 'nearby-frame-button)
   5784       (error "No frame button here")))
   5785 
   5786 (defun sly-db-insert-frame (frame-spec)
   5787   "Insert a frame for FRAME-SPEC."
   5788   (let* ((number (car frame-spec))
   5789          (label (cadr frame-spec))
   5790          (origin (point)))
   5791     (insert
   5792      (propertize (format "%2d: " number)
   5793                  'face 'sly-db-frame-label-face)
   5794      (sly-db-frame-button label frame-spec
   5795                           (if (sly-db-frame-restartable-p frame-spec)
   5796                               'sly-db-restartable-frame-line-face
   5797                             'sly-db-frame-line-face))
   5798      "\n")
   5799     (add-text-properties
   5800      origin (point)
   5801      (list 'field number
   5802            'keymap sly-db-frame-map
   5803            'nearby-frame-button (button-at (- (point) 2))))))
   5804 
   5805 
   5806 ;;;;;; SLY-DB examining text props
   5807 (defun sly-db--goto-last-visible-frame ()
   5808   (goto-char (point-max))
   5809   (while (not (get-text-property (point) 'frame-string))
   5810     (goto-char (previous-single-property-change (point) 'frame-string))))
   5811 
   5812 (defun sly-db-beginning-of-backtrace ()
   5813   "Goto the first frame."
   5814   (interactive)
   5815   (goto-char sly-db-backtrace-start-marker))
   5816 
   5817 
   5818 ;;;;; SLY-DB commands
   5819 (defun sly-db-cycle ()
   5820   "Cycle between restart list and backtrace."
   5821   (interactive)
   5822   (let ((pt (point)))
   5823     (cond ((< pt sly-db-restart-list-start-marker)
   5824            (goto-char sly-db-restart-list-start-marker))
   5825           ((< pt sly-db-backtrace-start-marker)
   5826            (goto-char sly-db-backtrace-start-marker))
   5827           (t
   5828            (goto-char sly-db-restart-list-start-marker)))))
   5829 
   5830 (defun sly-db-end-of-backtrace ()
   5831   "Fetch the entire backtrace and go to the last frame."
   5832   (interactive)
   5833   (sly-db--fetch-all-frames)
   5834   (sly-db--goto-last-visible-frame))
   5835 
   5836 (defun sly-db--fetch-all-frames ()
   5837   (let ((inhibit-read-only t)
   5838         (inhibit-point-motion-hooks t))
   5839     (sly-db--goto-last-visible-frame)
   5840     (let ((last (sly-db-frame-number-at-point)))
   5841       (goto-char (next-single-char-property-change (point) 'frame-string))
   5842       (delete-region (point) (point-max))
   5843       (save-excursion
   5844         (insert "\n")
   5845         (sly-db-insert-frames (sly-eval `(slynk:backtrace ,(1+ last) nil))
   5846                               nil)))))
   5847 
   5848 
   5849 ;;;;;; SLY-DB show source
   5850 (defun sly-db-show-frame-source (frame-number)
   5851   "Highlight FRAME-NUMBER's expression in a source code buffer."
   5852   (interactive (list (sly-db-frame-number-at-point)))
   5853   (sly-eval-async
   5854       `(slynk:frame-source-location ,frame-number)
   5855     (lambda (source-location)
   5856       (sly-dcase source-location
   5857         ((:error message)
   5858          (sly-message "%s" message)
   5859          (ding))
   5860         (t
   5861          (sly--display-source-location source-location))))))
   5862 
   5863 
   5864 ;;;;;; SLY-DB toggle details
   5865 (define-button-type 'sly-db-local-variable :supertype 'sly-part
   5866   'sly-button-inspect
   5867   #'(lambda (frame-id var-id)
   5868       (sly-eval-for-inspector `(slynk:inspect-frame-var ,frame-id
   5869                                                         ,var-id)) )
   5870   'sly-button-pretty-print
   5871   #'(lambda (frame-id var-id)
   5872       (sly-eval-describe `(slynk:pprint-frame-var ,frame-id
   5873                                                   ,var-id)))
   5874   'sly-button-describe
   5875   #'(lambda (frame-id var-id)
   5876       (sly-eval-describe `(slynk:describe-frame-var ,frame-id
   5877                                                     ,var-id))))
   5878 
   5879 (defun sly-db-local-variable-button (label frame-number var-id &rest props)
   5880   (apply #'sly--make-text-button label nil
   5881          :type 'sly-db-local-variable
   5882          'part-args (list frame-number var-id)
   5883          'part-label (format "Local Variable %d" var-id) props))
   5884 
   5885 (defun sly-db-frame-details-region (frame-button)
   5886   "Get (BEG END) for FRAME-BUTTON's details, or nil if hidden"
   5887   (let ((beg (button-end frame-button))
   5888         (end (1- (field-end (button-start frame-button) 'escape))))
   5889     (unless (= beg end) (list beg end))))
   5890 
   5891 (defun sly-db-toggle-details (frame-button)
   5892   "Toggle display of details for the current frame.
   5893 The details include local variable bindings and CATCH-tags."
   5894   (interactive (list (sly-db-frame-button-near-point)))
   5895   (if (sly-db-frame-details-region frame-button)
   5896       (sly-db-hide-frame-details frame-button)
   5897     (sly-db-show-frame-details frame-button)))
   5898 
   5899 (defun sly-db-show-frame-details (frame-button)
   5900   "Show details for FRAME-BUTTON"
   5901   (interactive (list (sly-db-frame-button-near-point)))
   5902   (cl-destructuring-bind (locals catches)
   5903       (sly-eval `(slynk:frame-locals-and-catch-tags
   5904                   ,(button-get frame-button 'frame-number)))
   5905     (let ((inhibit-read-only t)
   5906           (inhibit-point-motion-hooks t))
   5907       (save-excursion
   5908         (goto-char (button-end frame-button))
   5909         (let ((indent1 "      ")
   5910               (indent2 "        "))
   5911           (insert "\n" indent1
   5912                   (sly-db-in-face section (if locals "Locals:" "[No Locals]")))
   5913           (cl-loop for i from 0
   5914                    for var in locals
   5915                    with frame-number = (button-get frame-button 'frame-number)
   5916                    do
   5917                    (cl-destructuring-bind (&key name id value) var
   5918                      (insert "\n"
   5919                              indent2
   5920                              (sly-db-in-face local-name
   5921                                (concat name (if (zerop id)
   5922                                                 ""
   5923                                               (format "#%d" id))))
   5924                              " = "
   5925                              (sly-db-local-variable-button value
   5926                                                            frame-number
   5927                                                            i))))
   5928           (when catches
   5929             (insert "\n" indent1 (sly-db-in-face section "Catch-tags:"))
   5930             (dolist (tag catches)
   5931               (sly-propertize-region `(catch-tag ,tag)
   5932                 (insert "\n" indent2 (sly-db-in-face catch-tag
   5933                                        (format "%s" tag))))))
   5934           ;; The whole details field is propertized accordingly...
   5935           ;;
   5936           (add-text-properties (button-start frame-button) (point)
   5937                                (list 'field (button-get frame-button 'field)
   5938                                      'keymap sly-db-frame-map
   5939                                      'nearby-frame-button frame-button))
   5940           ;; ...but we must remember to remove the 'keymap property from
   5941           ;; any buttons inside the field
   5942           ;;
   5943           (cl-loop for pos = (point) then (button-start button)
   5944                    for button = (previous-button pos)
   5945                    while (and button
   5946                               (> (button-start button)
   5947                                  (button-start frame-button)))
   5948                    do (remove-text-properties (button-start button)
   5949                                               (button-end button)
   5950                                               '(keymap nil))))))
   5951     (sly-recenter (field-end (button-start frame-button) 'escape))))
   5952 
   5953 (defun sly-db-hide-frame-details (frame-button)
   5954   (interactive (list (sly-db-frame-button-near-point)))
   5955   (let* ((inhibit-read-only t)
   5956          (to-delete (sly-db-frame-details-region frame-button)))
   5957     (cl-assert to-delete)
   5958     (when (and (< (car to-delete) (point))
   5959                (< (point) (cadr to-delete)))
   5960       (goto-char (button-start frame-button)))
   5961     (apply #'delete-region to-delete)))
   5962 
   5963 (defun sly-db-disassemble (frame-number)
   5964   "Disassemble the code for frame with FRAME-NUMBER."
   5965   (interactive (list (sly-db-frame-number-at-point)))
   5966   (sly-eval-async `(slynk:sly-db-disassemble ,frame-number)
   5967     (lambda (result)
   5968       (sly-show-description result nil))))
   5969 
   5970 
   5971 ;;;;;; SLY-DB eval and inspect
   5972 
   5973 (defun sly-db-eval-in-frame (frame-number string package)
   5974   "Prompt for an expression and evaluate it in the selected frame."
   5975   (interactive (sly-db-frame-eval-interactive "Eval in frame (%s)> "))
   5976   (sly-eval-async `(slynk:eval-string-in-frame ,string ,frame-number ,package)
   5977     'sly-display-eval-result))
   5978 
   5979 (defun sly-db-pprint-eval-in-frame (frame-number string package)
   5980   "Prompt for an expression, evaluate in selected frame, pretty-print result."
   5981   (interactive (sly-db-frame-eval-interactive "Eval in frame (%s)> "))
   5982   (sly-eval-async
   5983       `(slynk:pprint-eval-string-in-frame ,string ,frame-number ,package)
   5984     (lambda (result)
   5985       (sly-show-description result nil))))
   5986 
   5987 (defun sly-db-frame-eval-interactive (fstring)
   5988   (let* ((frame-number (sly-db-frame-number-at-point))
   5989          (pkg (sly-eval `(slynk:frame-package-name ,frame-number))))
   5990     (list frame-number
   5991           (let ((sly-buffer-package pkg))
   5992             (sly-read-from-minibuffer (format fstring pkg)))
   5993           pkg)))
   5994 
   5995 (defun sly-db-inspect-in-frame (frame-number string)
   5996   "Prompt for an expression and inspect it in the selected frame."
   5997   (interactive (list
   5998                 (sly-db-frame-number-at-point)
   5999                 (sly-read-from-minibuffer
   6000                  "Inspect in frame (evaluated): "
   6001                  (sly-sexp-at-point))))
   6002   (sly-eval-for-inspector `(slynk:inspect-in-frame ,string ,frame-number)))
   6003 
   6004 (defun sly-db-inspect-condition ()
   6005   "Inspect the current debugger condition."
   6006   (interactive)
   6007   (sly-eval-for-inspector '(slynk:inspect-current-condition)))
   6008 
   6009 (defun sly-db-print-condition ()
   6010   (interactive)
   6011   (sly-eval-describe `(slynk:sdlb-print-condition)))
   6012 
   6013 
   6014 ;;;;;; SLY-DB movement
   6015 
   6016 (defun sly-db-down (arg)
   6017   "Move down ARG frames. With negative ARG, move up."
   6018   (interactive "p")
   6019   (cl-loop
   6020    for i from 0 below (abs arg)
   6021    do (cl-loop
   6022        for tries from 0 below 2
   6023        for pos = (point) then next-change
   6024        for next-change = (funcall (if (cl-minusp arg)
   6025                                       #'previous-single-char-property-change
   6026                                     #'next-single-char-property-change)
   6027                                   pos 'frame-number)
   6028        for prop-value = (get-text-property next-change 'frame-number)
   6029        when prop-value do (goto-char next-change)
   6030        until prop-value)))
   6031 
   6032 (defun sly-db-up (arg)
   6033   "Move up ARG frames. With negative ARG, move down."
   6034   (interactive "p")
   6035   (sly-db-down (- (or arg 1))))
   6036 
   6037 (defun sly-db-sugar-move (move-fn arg)
   6038   (let ((current-frame-button (sly-db-frame-button-near-point)))
   6039     (when (and current-frame-button
   6040                (sly-db-frame-details-region current-frame-button))
   6041       (sly-db-hide-frame-details current-frame-button)))
   6042   (funcall move-fn arg)
   6043   (let ((frame-button (sly-db-frame-button-near-point)))
   6044     (when frame-button
   6045       (sly-db-show-frame-source (button-get frame-button 'frame-number))
   6046       (sly-db-show-frame-details frame-button))))
   6047 
   6048 (defun sly-db-details-up (arg)
   6049   "Move up ARG frames and show details."
   6050   (interactive "p")
   6051   (sly-db-sugar-move 'sly-db-up arg))
   6052 
   6053 (defun sly-db-details-down (arg)
   6054   "Move down ARG frames and show details."
   6055   (interactive "p")
   6056   (sly-db-sugar-move 'sly-db-down arg))
   6057 
   6058 
   6059 ;;;;;; SLY-DB restarts
   6060 
   6061 (defun sly-db-quit ()
   6062   "Quit to toplevel."
   6063   (interactive)
   6064   (cl-assert sly-db-restarts () "sly-db-quit called outside of sly-db buffer")
   6065   (sly-rex () ('(slynk:throw-to-toplevel))
   6066     ((:ok x) (error "sly-db-quit returned [%s]" x))
   6067     ((:abort _))))
   6068 
   6069 (defun sly-db-continue ()
   6070   "Invoke the \"continue\" restart."
   6071   (interactive)
   6072   (cl-assert sly-db-restarts () "sly-db-continue called outside of sly-db buffer")
   6073   (sly-rex ()
   6074       ('(slynk:sly-db-continue))
   6075     ((:ok _)
   6076      (sly-message "No restart named continue")
   6077      (ding))
   6078     ((:abort _))))
   6079 
   6080 (defun sly-db-abort ()
   6081   "Invoke the \"abort\" restart."
   6082   (interactive)
   6083   (sly-eval-async '(slynk:sly-db-abort)
   6084     (lambda (v) (sly-message "Restart returned: %S" v))))
   6085 
   6086 (defun sly-db-invoke-restart (restart-number)
   6087   "Invoke the restart number NUMBER.
   6088 Interactively get the number from a button at point."
   6089   (interactive (button-get (sly-button-at (point)) 'restart-number))
   6090   (sly-rex ()
   6091       ((list 'slynk:invoke-nth-restart-for-emacs sly-db-level restart-number))
   6092     ((:ok value) (sly-message "Restart returned: %s" value))
   6093     ((:abort _))))
   6094 
   6095 (defun sly-db-invoke-restart-by-name (restart-name)
   6096   (interactive (list (let ((completion-ignore-case t))
   6097                        (sly-completing-read "Restart: " sly-db-restarts nil t
   6098                                             ""
   6099                                             'sly-db-invoke-restart-by-name))))
   6100   (sly-db-invoke-restart (cl-position restart-name sly-db-restarts
   6101                                       :test 'string= :key 'first)))
   6102 
   6103 (defun sly-db-break-with-default-debugger (&optional dont-unwind)
   6104   "Enter default debugger."
   6105   (interactive "P")
   6106   (sly-rex ()
   6107       ((list 'slynk:sly-db-break-with-default-debugger
   6108              (not (not dont-unwind)))
   6109        nil sly-current-thread)
   6110     ((:abort _))))
   6111 
   6112 (defun sly-db-break-with-system-debugger (&optional lightweight)
   6113   "Enter system debugger (gdb)."
   6114   (interactive "P")
   6115   (sly-attach-gdb sly-buffer-connection lightweight))
   6116 
   6117 (defun sly-attach-gdb (connection &optional lightweight)
   6118   "Run `gud-gdb'on the connection with PID `pid'.
   6119 
   6120 If `lightweight' is given, do not send any request to the
   6121 inferior Lisp (e.g. to obtain default gdb config) but only
   6122 operate from the Emacs side; intended for cases where the Lisp is
   6123 truly screwed up."
   6124   (interactive
   6125    (list (sly-read-connection "Attach gdb to: " (sly-connection)) "P"))
   6126   (let ((pid  (sly-pid connection))
   6127         (file (sly-lisp-implementation-program connection))
   6128         (commands (unless lightweight
   6129                     (let ((sly-dispatching-connection connection))
   6130                       (sly-eval `(slynk:gdb-initial-commands))))))
   6131     (gud-gdb (format "gdb -p %d %s" pid (or file "")))
   6132     (with-current-buffer gud-comint-buffer
   6133       (dolist (cmd commands)
   6134         ;; First wait until gdb was initialized, then wait until current
   6135         ;; command was processed.
   6136         (while (not (looking-back comint-prompt-regexp (line-beginning-position)
   6137                                   nil))
   6138           (sit-for 0.01))
   6139         ;; We do not use `gud-call' because we want the initial commands
   6140         ;; to be displayed by the user so he knows what he's got.
   6141         (insert cmd)
   6142         (comint-send-input)))))
   6143 
   6144 (defun sly-read-connection (prompt &optional initial-value)
   6145   "Read a connection from the minibuffer.
   6146 Return the net process, or nil."
   6147   (cl-assert (memq initial-value sly-net-processes))
   6148   (let* ((to-string (lambda (p)
   6149                       (format "%s (pid %d)"
   6150                               (sly-connection-name p) (sly-pid p))))
   6151          (candidates (mapcar (lambda (p) (cons (funcall to-string p) p))
   6152                              sly-net-processes)))
   6153     (cdr (assoc (sly-completing-read prompt candidates
   6154                                      nil t (funcall to-string initial-value))
   6155                 candidates))))
   6156 
   6157 (defun sly-db-step (frame-number)
   6158   "Step to next basic-block boundary."
   6159   (interactive (list (sly-db-frame-number-at-point)))
   6160   (sly-eval-async `(slynk:sly-db-step ,frame-number)))
   6161 
   6162 (defun sly-db-next (frame-number)
   6163   "Step over call."
   6164   (interactive (list (sly-db-frame-number-at-point)))
   6165   (sly-eval-async `(slynk:sly-db-next ,frame-number)))
   6166 
   6167 (defun sly-db-out (frame-number)
   6168   "Resume stepping after returning from this function."
   6169   (interactive (list (sly-db-frame-number-at-point)))
   6170   (sly-eval-async `(slynk:sly-db-out ,frame-number)))
   6171 
   6172 (defun sly-db-break-on-return (frame-number)
   6173   "Set a breakpoint at the current frame.
   6174 The debugger is entered when the frame exits."
   6175   (interactive (list (sly-db-frame-number-at-point)))
   6176   (sly-eval-async `(slynk:sly-db-break-on-return ,frame-number)
   6177     (lambda (msg) (sly-message "%s" msg))))
   6178 
   6179 (defun sly-db-break (name)
   6180   "Set a breakpoint at the start of the function NAME."
   6181   (interactive (list (sly-read-symbol-name "Function: " t)))
   6182   (sly-eval-async `(slynk:sly-db-break ,name)
   6183     (lambda (msg) (sly-message "%s" msg))))
   6184 
   6185 (defun sly-db-return-from-frame (frame-number string)
   6186   "Reads an expression in the minibuffer and causes the function to
   6187 return that value, evaluated in the context of the frame."
   6188   (interactive (list (sly-db-frame-number-at-point)
   6189                      (sly-read-from-minibuffer "Return from frame: ")))
   6190   (sly-rex ()
   6191       ((list 'slynk:sly-db-return-from-frame frame-number string))
   6192     ((:ok value) (sly-message "%s" value))
   6193     ((:abort _))))
   6194 
   6195 (defun sly-db-restart-frame (frame-number)
   6196   "Causes the frame to restart execution with the same arguments as it
   6197 was called originally."
   6198   (interactive (list (sly-db-frame-number-at-point)))
   6199   (sly-rex ()
   6200       ((list 'slynk:restart-frame frame-number))
   6201     ((:ok value) (sly-message "%s" value))
   6202     ((:abort _))))
   6203 
   6204 (defun sly-toggle-break-on-signals ()
   6205   "Toggle the value of *break-on-signals*."
   6206   (interactive)
   6207   (sly-eval-async `(slynk:toggle-break-on-signals)
   6208     (lambda (msg) (sly-message "%s" msg))))
   6209 
   6210 
   6211 ;;;;;; SLY-DB recompilation commands
   6212 
   6213 (defun sly-db-recompile-frame-source (frame-number &optional raw-prefix-arg)
   6214   (interactive
   6215    (list (sly-db-frame-number-at-point) current-prefix-arg))
   6216   (sly-eval-async
   6217       `(slynk:frame-source-location ,frame-number)
   6218     (let ((policy (sly-compute-policy raw-prefix-arg)))
   6219       (lambda (source-location)
   6220         (sly-dcase source-location
   6221           ((:error message)
   6222            (sly-message "%s" message)
   6223            (ding))
   6224           (t
   6225            (let ((sly-compilation-policy policy))
   6226              (sly-recompile-location source-location))))))))
   6227 
   6228 
   6229 ;;;; Thread control panel
   6230 
   6231 (defvar sly-threads-buffer-timer nil)
   6232 
   6233 (defcustom sly-threads-update-interval nil
   6234   "Interval at which the list of threads will be updated."
   6235   :type '(choice
   6236           (number :value 0.5)
   6237           (const nil))
   6238   :group 'sly-ui)
   6239 
   6240 (defun sly-list-threads ()
   6241   "Display a list of threads."
   6242   (interactive)
   6243   (let ((name (sly-buffer-name :threads
   6244                                :connection t)))
   6245     (sly-with-popup-buffer (name :connection t
   6246                                  :mode 'sly-thread-control-mode)
   6247       (sly-update-threads-buffer (current-buffer))
   6248       (goto-char (point-min))
   6249       (when sly-threads-update-interval
   6250         (when sly-threads-buffer-timer
   6251           (cancel-timer sly-threads-buffer-timer))
   6252         (setq sly-threads-buffer-timer
   6253               (run-with-timer
   6254                sly-threads-update-interval
   6255                sly-threads-update-interval
   6256                'sly-update-threads-buffer
   6257                (current-buffer))))
   6258       (add-hook 'kill-buffer-hook  'sly--threads-buffer-teardown
   6259                 'append 'local))))
   6260 
   6261 (defun sly--threads-buffer-teardown ()
   6262   (when sly-threads-buffer-timer
   6263     (cancel-timer sly-threads-buffer-timer))
   6264   (when (process-live-p sly-buffer-connection)
   6265     (sly-eval-async `(slynk:quit-thread-browser))))
   6266 
   6267 (defun sly-update-threads-buffer (&optional buffer)
   6268   (interactive)
   6269   (with-current-buffer (or buffer
   6270                            (current-buffer))
   6271     (sly-eval-async '(slynk:list-threads)
   6272       #'(lambda (threads)
   6273           (with-current-buffer (current-buffer)
   6274             (sly--display-threads threads))))))
   6275 
   6276 (defun sly-move-point (position)
   6277   "Move point in the current buffer and in the window the buffer is displayed."
   6278   (let ((window (get-buffer-window (current-buffer) t)))
   6279     (goto-char position)
   6280     (when window
   6281       (set-window-point window position))))
   6282 
   6283 (defun sly--display-threads (threads)
   6284   (let* ((inhibit-read-only t)
   6285          (old-thread-id (get-text-property (point) 'thread-id))
   6286          (old-line (line-number-at-pos))
   6287          (old-column (current-column)))
   6288     (erase-buffer)
   6289     (sly-insert-threads threads)
   6290     (let ((new-line (cl-position old-thread-id (cdr threads)
   6291                                  :key #'car :test #'equal)))
   6292       (goto-char (point-min))
   6293       (forward-line (or new-line old-line))
   6294       (move-to-column old-column)
   6295       (sly-move-point (point)))))
   6296 
   6297 (defun sly-transpose-lists (list-of-lists)
   6298   (let ((ncols (length (car list-of-lists))))
   6299     (cl-loop for col-index below ncols
   6300              collect (cl-loop for row in list-of-lists
   6301                               collect (elt row col-index)))))
   6302 
   6303 (defun sly-insert-table-row (line line-props col-props col-widths)
   6304   (sly-propertize-region line-props
   6305     (cl-loop for string in line
   6306              for col-prop in col-props
   6307              for width in col-widths do
   6308              (sly-insert-propertized col-prop string)
   6309              (insert-char ?\ (- width (length string))))))
   6310 
   6311 (defun sly-insert-table (rows header row-properties column-properties)
   6312   "Insert a \"table\" so that the columns are nicely aligned."
   6313   (let* ((ncols (length header))
   6314          (lines (cons header rows))
   6315          (widths (cl-loop for columns in (sly-transpose-lists lines)
   6316                           collect (1+ (cl-loop for cell in columns
   6317                                                maximize (length cell)))))
   6318          (header-line (with-temp-buffer
   6319                         (sly-insert-table-row
   6320                          header nil (make-list ncols nil) widths)
   6321                         (buffer-string))))
   6322     (cond ((boundp 'header-line-format)
   6323            (setq header-line-format header-line))
   6324           (t (insert header-line "\n")))
   6325     (cl-loop for line in rows  for line-props in row-properties do
   6326              (sly-insert-table-row line line-props column-properties widths)
   6327              (insert "\n"))))
   6328 
   6329 (defvar sly-threads-table-properties
   6330   '(nil (face bold)))
   6331 
   6332 (defun sly-insert-threads (threads)
   6333   (let* ((labels (car threads))
   6334          (threads (cdr threads))
   6335          (header (cl-loop for label in labels collect
   6336                           (capitalize (substring (symbol-name label) 1))))
   6337          (rows (cl-loop for thread in threads collect
   6338                         (cl-loop for prop in thread collect
   6339                                  (format "%s" prop))))
   6340          (line-props (cl-loop for (id) in threads for i from 0
   6341                               collect `(thread-index ,i thread-id ,id)))
   6342          (col-props (cl-loop for nil in labels for i from 0 collect
   6343                              (nth i sly-threads-table-properties))))
   6344     (sly-insert-table rows header line-props col-props)))
   6345 
   6346 
   6347 ;;;;; Major mode
   6348 (defvar sly-thread-control-mode-map
   6349   (let ((map (make-sparse-keymap)))
   6350     (define-key map "a" 'sly-thread-attach)
   6351     (define-key map "d" 'sly-thread-debug)
   6352     (define-key map "g" 'sly-update-threads-buffer)
   6353     (define-key map "k" 'sly-thread-kill)
   6354     (define-key map "q" 'quit-window)
   6355     map))
   6356 
   6357 (define-derived-mode sly-thread-control-mode fundamental-mode
   6358   "Threads"
   6359   "SLY Thread Control Panel Mode.
   6360 
   6361 \\{sly-thread-control-mode-map}"
   6362   (when sly-truncate-lines
   6363     (set (make-local-variable 'truncate-lines) t))
   6364   (read-only-mode 1)
   6365   (sly-mode 1)
   6366   (setq buffer-undo-list t))
   6367 
   6368 (defun sly-thread-kill ()
   6369   (interactive)
   6370   (sly-eval `(cl:mapc 'slynk:kill-nth-thread
   6371                       ',(sly-get-properties 'thread-index)))
   6372   (call-interactively 'sly-update-threads-buffer))
   6373 
   6374 (defun sly-get-region-properties (prop start end)
   6375   (cl-loop for position = (if (get-text-property start prop)
   6376                               start
   6377                             (next-single-property-change start prop))
   6378            then (next-single-property-change position prop)
   6379            while (<= position end)
   6380            collect (get-text-property position prop)))
   6381 
   6382 (defun sly-get-properties (prop)
   6383   (if (use-region-p)
   6384       (sly-get-region-properties prop
   6385                                  (region-beginning)
   6386                                  (region-end))
   6387     (let ((value (get-text-property (point) prop)))
   6388       (when value
   6389         (list value)))))
   6390 
   6391 (defun sly-thread-attach ()
   6392   (interactive)
   6393   (let ((id (get-text-property (point) 'thread-index))
   6394         (file (sly-slynk-port-file)))
   6395     (sly-eval-async `(slynk:start-slynk-server-in-thread ,id ,file)))
   6396   (sly-read-port-and-connect nil))
   6397 
   6398 (defun sly-thread-debug ()
   6399   (interactive)
   6400   (let ((id (get-text-property (point) 'thread-index)))
   6401     (sly-eval-async `(slynk:debug-nth-thread ,id))))
   6402 
   6403 
   6404 ;;;;; Connection listing
   6405 
   6406 (defvar sly-connection-list-mode-map
   6407   (let ((map (make-sparse-keymap)))
   6408     (define-key map "d"         'sly-connection-list-make-default)
   6409     (define-key map "g"         'sly-update-connection-list)
   6410     (define-key map (kbd "RET") 'sly-connection-list-default-action)
   6411     (define-key map (kbd "C-m")      'sly-connection-list-default-action)
   6412     (define-key map (kbd "C-k") 'sly-quit-connection-at-point)
   6413     (define-key map (kbd "R")   'sly-restart-connection-at-point)
   6414     (define-key map (kbd "q")   'quit-window)
   6415     map))
   6416 
   6417 (define-derived-mode sly-connection-list-mode tabulated-list-mode
   6418   "SLY-Connections"
   6419   "SLY Connection List Mode.
   6420 
   6421 \\{sly-connection-list-mode-map}"
   6422   (set (make-local-variable 'tabulated-list-format)
   6423        `[("Default" 8) ("Name" 24 t) ("Host" 12)
   6424          ("Port" 6) ("Pid" 6 t) ("Type" 1000 t)])
   6425   (tabulated-list-init-header))
   6426 
   6427 (defun sly--connection-at-point ()
   6428   (or (get-text-property (point) 'tabulated-list-id)
   6429       (error "No connection at point")))
   6430 
   6431 (defvar sly-connection-list-button-action nil)
   6432 
   6433 (defun sly-connection-list-default-action (connection)
   6434   (interactive (list (sly--connection-at-point)))
   6435   (funcall sly-connection-list-button-action connection))
   6436 
   6437 (defun sly-update-connection-list ()
   6438   (interactive)
   6439   (set (make-local-variable 'tabulated-list-entries)
   6440        (mapcar
   6441         #'(lambda (p)
   6442             (list p
   6443                   `[,(if (eq sly-default-connection p) "*" " ")
   6444                     (,(file-name-nondirectory (or (sly-connection-name p)
   6445                                                   "unknown"))
   6446                      action
   6447                      ,#'(lambda (_button)
   6448                           (and sly-connection-list-button-action
   6449                                (funcall sly-connection-list-button-action p))))
   6450                     ,(car (process-contact p))
   6451                     ,(format "%s" (cl-second (process-contact p)))
   6452                     ,(format "%s" (sly-pid p))
   6453                     ,(or (sly-lisp-implementation-type p)
   6454                          "unknown")]))
   6455         (reverse sly-net-processes)))
   6456   (let ((p (point)))
   6457     (tabulated-list-print)
   6458     (goto-char p)))
   6459 
   6460 (defun sly-quit-connection-at-point (connection)
   6461   (interactive (list (sly--connection-at-point)))
   6462   (let ((sly-dispatching-connection connection)
   6463         (end (time-add (current-time) (seconds-to-time 3))))
   6464     (sly-quit-lisp t)
   6465     (while (memq connection sly-net-processes)
   6466       (when (time-less-p end (current-time))
   6467         (sly-message "Quit timeout expired.  Disconnecting.")
   6468         (delete-process connection))
   6469       (sit-for 0 100)))
   6470   (sly-update-connection-list))
   6471 
   6472 (defun sly-restart-connection-at-point (connection)
   6473   (interactive (list (sly--connection-at-point)))
   6474   (let ((sly-dispatching-connection connection))
   6475     (sly-restart-inferior-lisp)))
   6476 
   6477 (defun sly-connection-list-make-default ()
   6478   "Make the connection at point the default connection."
   6479   (interactive)
   6480   (sly-select-connection (sly--connection-at-point))
   6481   (sly-update-connection-list))
   6482 
   6483 (defun sly-list-connections ()
   6484   "Display a list of all connections."
   6485   (interactive)
   6486   (sly-with-popup-buffer ((sly-buffer-name :connections)
   6487                           :mode 'sly-connection-list-mode)
   6488     (sly-update-connection-list)))
   6489 
   6490 
   6491 
   6492 ;;;; Inspector
   6493 
   6494 (defgroup sly-inspector nil
   6495   "Options for the SLY inspector."
   6496   :prefix "sly-inspector-"
   6497   :group 'sly)
   6498 
   6499 (defvar sly--this-inspector-name nil
   6500   "Buffer-local inspector name (a string), or nil")
   6501 
   6502 (cl-defun sly-eval-for-inspector (slyfun-and-args
   6503                                   &key (error-message "Couldn't inspect")
   6504                                   restore-point
   6505                                   save-selected-window
   6506                                   (inspector-name sly--this-inspector-name)
   6507                                   opener)
   6508   (if (cl-some #'listp slyfun-and-args)
   6509       (sly-warning
   6510        "`sly-eval-for-inspector' not meant to be passed a generic form"))
   6511   (let ((pos (and (eq major-mode 'sly-inspector-mode)
   6512                   (sly-inspector-position))))
   6513     (sly-eval-async `(slynk:eval-for-inspector
   6514                       ,sly--this-inspector-name ; current inspector, if any
   6515                       ,inspector-name   ; target inspector, if any
   6516                       ',(car slyfun-and-args)
   6517                       ,@(cdr slyfun-and-args))
   6518       (or opener
   6519           (lambda (results)
   6520             (let ((opener (lambda ()
   6521                             (sly--open-inspector
   6522                              results
   6523                              :point (and restore-point pos)
   6524                              :inspector-name inspector-name
   6525                              :switch (not save-selected-window)))))
   6526               (cond (results
   6527                      (funcall opener))
   6528                     (t
   6529                      (sly-message error-message)))))))))
   6530 
   6531 (defun sly-read-inspector-name ()
   6532   (let* ((names (cl-loop for b in (buffer-list)
   6533                          when (with-current-buffer b
   6534                                 (and (eq sly-buffer-connection
   6535                                          (sly-current-connection))
   6536                                      (eq major-mode 'sly-inspector-mode)))
   6537                          when (buffer-local-value 'sly--this-inspector-name b)
   6538                          collect it))
   6539          (result (sly-completing-read "Inspector name: " (cons "default"
   6540                                                                names)
   6541                                       nil nil nil nil "default")))
   6542     (unless (string= result "default")
   6543       result)))
   6544 
   6545 (defun sly-maybe-read-inspector-name ()
   6546   (or (and current-prefix-arg
   6547            (sly-read-inspector-name))
   6548       sly--this-inspector-name))
   6549 
   6550 (defun sly-inspect (string &optional inspector-name)
   6551   "Eval an expression and inspect the result."
   6552   (interactive
   6553    (let* ((name (sly-maybe-read-inspector-name))
   6554           (string (sly-read-from-minibuffer
   6555                    (concat "Inspect value"
   6556                            (and name
   6557                                 (format " in inspector \"%s\"" name))
   6558                            " (evaluated): ")
   6559                    (sly-sexp-at-point 'interactive nil nil))))
   6560      (list string name)))
   6561   (sly-eval-for-inspector `(slynk:init-inspector ,string)
   6562                           :inspector-name inspector-name))
   6563 
   6564 (defvar sly-inspector-mode-map
   6565   (let ((map (make-sparse-keymap)))
   6566     (define-key map "l" 'sly-inspector-pop)
   6567     (define-key map "n" 'sly-inspector-next)
   6568     (define-key map [mouse-6] 'sly-inspector-pop)
   6569     (define-key map [mouse-7] 'sly-inspector-next)
   6570 
   6571     (define-key map " " 'sly-inspector-next)
   6572     (define-key map "D" 'sly-inspector-describe-inspectee)
   6573     (define-key map "e" 'sly-inspector-eval)
   6574     (define-key map "h" 'sly-inspector-history)
   6575     (define-key map "g" 'sly-inspector-reinspect)
   6576     (define-key map ">" 'sly-inspector-fetch-all)
   6577     (define-key map "q" 'sly-inspector-quit)
   6578 
   6579     (set-keymap-parent map button-buffer-map)
   6580     map))
   6581 
   6582 (define-derived-mode sly-inspector-mode fundamental-mode
   6583   "SLY-Inspector"
   6584   "
   6585 \\{sly-inspector-mode-map}"
   6586   (set-syntax-table lisp-mode-syntax-table)
   6587   (sly-set-truncate-lines)
   6588   (setq buffer-read-only t)
   6589   (sly-mode 1))
   6590 
   6591 (define-button-type 'sly-inspector-part :supertype 'sly-part
   6592   'sly-button-inspect
   6593   #'(lambda (id)
   6594       (sly-eval-for-inspector `(slynk:inspect-nth-part ,id)
   6595                               :inspector-name (sly-maybe-read-inspector-name)))
   6596   'sly-button-pretty-print
   6597   #'(lambda (id)
   6598       (sly-eval-describe `(slynk:pprint-inspector-part ,id)))
   6599   'sly-button-describe
   6600   #'(lambda (id)
   6601       (sly-eval-describe `(slynk:describe-inspector-part ,id)))
   6602   'sly-button-show-source
   6603   #'(lambda (id)
   6604       (sly-eval-async
   6605           `(slynk:find-source-location-for-emacs '(:inspector ,id))
   6606         #'(lambda (result)
   6607             (sly--display-source-location result 'noerror)))))
   6608 
   6609 (defun sly-inspector-part-button (label id &rest props)
   6610   (apply #'sly--make-text-button
   6611          label nil
   6612          :type 'sly-inspector-part
   6613          'part-args (list id)
   6614          'part-label "Inspector Object"
   6615          props))
   6616 
   6617 (defmacro sly-inspector-fontify (face string)
   6618   `(sly-add-face ',(intern (format "sly-inspector-%s-face" face)) ,string))
   6619 
   6620 (cl-defun sly--open-inspector (inspected-parts
   6621                                &key point kill-hook inspector-name (switch t))
   6622   "Display INSPECTED-PARTS in a new inspector window.
   6623 Optionally set point to POINT. If KILL-HOOK is provided, it is
   6624 added to local KILL-BUFFER hooks for the inspector
   6625 buffer. INSPECTOR-NAME is the name of the target inspector, or
   6626 nil if the default one is to be used. SWITCH indicates the
   6627 buffer should be switched to (defaults to t)"
   6628   (sly-with-popup-buffer ((sly-buffer-name :inspector
   6629                                            :connection t
   6630                                            :suffix inspector-name)
   6631                           :mode 'sly-inspector-mode
   6632                           :select switch
   6633                           :same-window-p
   6634                           (and (eq major-mode 'sly-inspector-mode)
   6635                                (or (null inspector-name)
   6636                                    (eq sly--this-inspector-name inspector-name)))
   6637                           :connection t)
   6638     (when kill-hook
   6639       (add-hook 'kill-buffer-hook kill-hook t t))
   6640     (set (make-local-variable 'sly--this-inspector-name) inspector-name)
   6641     (cl-destructuring-bind (&key id title content) inspected-parts
   6642       (cl-macrolet ((fontify (face string)
   6643                              `(sly-inspector-fontify ,face ,string)))
   6644         (insert (sly-inspector-part-button title id 'skip t))
   6645         (while (eq (char-before) ?\n)
   6646           (backward-delete-char 1))
   6647         (insert "\n" (fontify label "--------------------") "\n")
   6648         (save-excursion
   6649           (sly-inspector-insert-content content))
   6650         (when point
   6651           (cl-check-type point cons)
   6652           (ignore-errors
   6653             (goto-char (point-min))
   6654             (forward-line (1- (car point)))
   6655             (move-to-column (cdr point))))))
   6656     (buffer-disable-undo)))
   6657 
   6658 (defvar sly-inspector-limit 500)
   6659 
   6660 (defun sly-inspector-insert-content (content)
   6661   (sly-inspector-fetch-chunk
   6662    content nil
   6663    (lambda (chunk)
   6664      (let ((inhibit-read-only t))
   6665        (sly-inspector-insert-chunk chunk t t)))))
   6666 
   6667 (defun sly-inspector-insert-chunk (chunk prev next)
   6668   "Insert CHUNK at point.
   6669 If PREV resp. NEXT are true insert more-buttons as needed."
   6670   (cl-destructuring-bind (ispecs len start end) chunk
   6671     (when (and prev (> start 0))
   6672       (sly-inspector-insert-more-button start t))
   6673     (mapc #'sly-inspector-insert-ispec ispecs)
   6674     (when (and next (< end len))
   6675       (sly-inspector-insert-more-button end nil))))
   6676 
   6677 (defun sly-inspector-insert-ispec (ispec)
   6678   (insert
   6679    (if (stringp ispec) ispec
   6680      (sly-dcase ispec
   6681        ((:value string id)
   6682         (sly-inspector-part-button string id))
   6683        ((:label string)
   6684         (sly-inspector-fontify label string))
   6685        ((:action string id)
   6686         (sly-make-action-button
   6687          string
   6688          #'(lambda (_button)
   6689              (sly-eval-for-inspector `(slynk::inspector-call-nth-action ,id)
   6690                                      :restore-point t))))))))
   6691 
   6692 (defun sly-inspector-position ()
   6693   "Return a pair (Y-POSITION X-POSITION) representing the
   6694 position of point in the current buffer."
   6695   ;; We make sure we return absolute coordinates even if the user has
   6696   ;; narrowed the buffer.
   6697   ;; FIXME: why would somebody narrow the buffer?
   6698   (save-restriction
   6699     (widen)
   6700     (cons (line-number-at-pos)
   6701           (current-column))))
   6702 
   6703 (defun sly-inspector-pop ()
   6704   "Reinspect the previous object."
   6705   (interactive)
   6706   (sly-eval-for-inspector `(slynk:inspector-pop)
   6707                           :error-message "No previous object"))
   6708 
   6709 (defun sly-inspector-next ()
   6710   "Inspect the next object in the history."
   6711   (interactive)
   6712   (sly-eval-for-inspector `(slynk:inspector-next)
   6713                           :error-message "No next object"))
   6714 
   6715 (defun sly-inspector-quit (&optional reset)
   6716   "Quit the inspector and kill the buffer.
   6717 With optional RESET (true with prefix arg), also reset the
   6718 inspector on the Lisp side."
   6719   (interactive "p")
   6720   (when reset (sly-eval-async `(slynk:quit-inspector)))
   6721   (quit-window))
   6722 
   6723 (defun sly-inspector-describe-inspectee ()
   6724   "Describe the currently inspected object"
   6725   (interactive)
   6726   (sly-eval-describe `(slynk:describe-inspectee)))
   6727 
   6728 (defun sly-inspector-eval (string)
   6729   "Eval an expression in the context of the inspected object.
   6730 The `*' variable will be bound to the inspected object."
   6731   (interactive (list (sly-read-from-minibuffer "Inspector eval: ")))
   6732   (sly-eval-with-transcript `(slynk:inspector-eval ,string)))
   6733 
   6734 (defun sly-inspector-history ()
   6735   "Show the previously inspected objects."
   6736   (interactive)
   6737   (sly-eval-describe `(slynk:inspector-history)))
   6738 
   6739 (defun sly-inspector-reinspect (&optional inspector-name)
   6740   (interactive (list (sly-maybe-read-inspector-name)))
   6741   (sly-eval-for-inspector `(slynk:inspector-reinspect)
   6742                           :inspector-name inspector-name))
   6743 
   6744 (defun sly-inspector-toggle-verbose ()
   6745   (interactive)
   6746   (sly-eval-for-inspector `(slynk:inspector-toggle-verbose)))
   6747 
   6748 (defun sly-inspector-insert-more-button (index previous)
   6749   (insert (sly-make-action-button
   6750            (if previous " [--more--]\n" " [--more--]")
   6751            #'sly-inspector-fetch-more
   6752            'range-args (list index previous))))
   6753 
   6754 (defun sly-inspector-fetch-all ()
   6755   "Fetch all inspector contents and go to the end."
   6756   (interactive)
   6757   (let ((button (button-at (1- (point-max)))))
   6758     (cond ((and button
   6759                 (button-get button 'range-args))
   6760            (let (sly-inspector-limit)
   6761              (sly-inspector-fetch-more button)))
   6762           (t
   6763            (sly-error "No more elements to fetch")))))
   6764 
   6765 (defun sly-inspector-fetch-more (button)
   6766   (cl-destructuring-bind (index prev) (button-get button 'range-args)
   6767     (sly-inspector-fetch-chunk
   6768      (list '() (1+ index) index index) prev
   6769      (sly-rcurry
   6770       (lambda (chunk prev)
   6771         (let ((inhibit-read-only t))
   6772           (delete-region (button-start button) (button-end button))
   6773           (sly-inspector-insert-chunk chunk prev (not prev))))
   6774       prev))))
   6775 
   6776 (defun sly-inspector-fetch-chunk (chunk prev cont)
   6777   (sly-inspector-fetch chunk sly-inspector-limit prev cont))
   6778 
   6779 (defun sly-inspector-fetch (chunk limit prev cont)
   6780   (cl-destructuring-bind (from to)
   6781       (sly-inspector-next-range chunk limit prev)
   6782     (cond ((and from to)
   6783            (sly-eval-for-inspector
   6784             `(slynk:inspector-range ,from ,to)
   6785             :opener (sly-rcurry (lambda (chunk2 chunk1 limit prev cont)
   6786                                   (sly-inspector-fetch
   6787                                    (sly-inspector-join-chunks chunk1 chunk2)
   6788                                    limit prev cont))
   6789                                 chunk limit prev cont)))
   6790           (t (funcall cont chunk)))))
   6791 
   6792 (defun sly-inspector-next-range (chunk limit prev)
   6793   (cl-destructuring-bind (_ len start end) chunk
   6794     (let ((count (- end start)))
   6795       (cond ((and prev (< 0 start) (or (not limit) (< count limit)))
   6796              (list (if limit (max (- end limit) 0) 0) start))
   6797             ((and (not prev) (< end len) (or (not limit) (< count limit)))
   6798              (list end (if limit (+ start limit) most-positive-fixnum)))
   6799             (t '(nil nil))))))
   6800 
   6801 (defun sly-inspector-join-chunks (chunk1 chunk2)
   6802   (cl-destructuring-bind (i1 _l1 s1 e1) chunk1
   6803     (cl-destructuring-bind (i2 l2 s2 e2) chunk2
   6804       (cond ((= e1 s2)
   6805              (list (append i1 i2) l2 s1 e2))
   6806             ((= e2 s1)
   6807              (list (append i2 i1) l2 s2 e1))
   6808             (t (error "Invalid chunks"))))))
   6809 
   6810 
   6811 ;;;; Indentation
   6812 
   6813 (defun sly-update-indentation ()
   6814   "Update indentation for all macros defined in the Lisp system."
   6815   (interactive)
   6816   (sly-eval-async '(slynk:update-indentation-information)))
   6817 
   6818 (defvar sly-indentation-update-hooks)
   6819 
   6820 (defun sly-intern-indentation-spec (spec)
   6821   (cond ((consp spec)
   6822          (cons (sly-intern-indentation-spec (car spec))
   6823                (sly-intern-indentation-spec (cdr spec))))
   6824         ((stringp spec)
   6825          (intern spec))
   6826         (t
   6827          spec)))
   6828 
   6829 ;; FIXME: restore the old version without per-package
   6830 ;; stuff. sly-indentation.el should be able tho disable the simple
   6831 ;; version if needed.
   6832 (defun sly-handle-indentation-update (alist)
   6833   "Update Lisp indent information.
   6834 
   6835 ALIST is a list of (SYMBOL-NAME . INDENT-SPEC) of proposed indentation
   6836 settings for `sly-common-lisp-indent-function'. The appropriate property
   6837 is setup, unless the user already set one explicitly."
   6838   (dolist (info alist)
   6839     (let ((symbol (intern (car info)))
   6840           (indent (sly-intern-indentation-spec (cl-second info)))
   6841           (packages (cl-third info)))
   6842       (if (and (boundp 'sly-common-lisp-system-indentation)
   6843                (fboundp 'sly-update-system-indentation))
   6844           ;; A table provided by sly-cl-indent.el.
   6845           (funcall #'sly-update-system-indentation symbol indent packages)
   6846         ;; Does the symbol have an indentation value that we set?
   6847         (when (equal (get symbol 'sly-common-lisp-indent-function)
   6848                      (get symbol 'sly-indent))
   6849           (put symbol 'sly-common-lisp-indent-function indent)
   6850           (put symbol 'sly-indent indent)))
   6851       (run-hook-with-args 'sly-indentation-update-hooks
   6852                           symbol indent packages))))
   6853 
   6854 
   6855 ;;;; Contrib modules
   6856 
   6857 (defun sly-contrib--load-slynk-dependencies ()
   6858   (let ((needed (cl-remove-if (lambda (s)
   6859                                 (cl-find (symbol-name s)
   6860                                          (sly-lisp-modules)
   6861                                          :key #'downcase
   6862                                          :test #'string=))
   6863                               sly-contrib--required-slynk-modules
   6864                               :key #'car)))
   6865     (when needed
   6866       ;; No asynchronous request because with :SPAWN that could result
   6867       ;; in the attempt to load modules concurrently which may not be
   6868       ;; supported by the host Lisp.
   6869       (sly-eval `(slynk:slynk-add-load-paths ',(cl-remove-duplicates
   6870                                                 (mapcar #'cl-second needed)
   6871                                                 :test #'string=)))
   6872       (let* ((result (sly-eval
   6873                       `(slynk:slynk-require
   6874                         ',(mapcar #'symbol-name (mapcar #'cl-first needed)))))
   6875              (all-modules (cl-first result))
   6876              (loaded-now (cl-second result)))
   6877         ;; check if everything went OK
   6878         ;;
   6879         (cl-loop for n in needed
   6880                  unless (cl-find (cl-first n) loaded-now :test #'string=)
   6881 
   6882                  ;; string= compares symbols and strings nicely
   6883                  ;;
   6884                  do (when (y-or-n-p (format
   6885                                      "\
   6886 Watch out! SLY failed to load SLYNK module %s for contrib %s!\n
   6887 Disable it?" (cl-first n) (cl-third n)))
   6888                       (sly-disable-contrib (cl-third n))
   6889                       (sly-temp-message 3 3 "\
   6890 You'll need to re-enable %s manually with `sly-enable-contrib'\
   6891 if/when you fix the error" (cl-third n))))
   6892         ;; Update the connection-local list of all *MODULES*
   6893         ;;
   6894         (setf (sly-lisp-modules) all-modules)))))
   6895 
   6896 (cl-defstruct (sly-contrib
   6897                (:conc-name sly-contrib--))
   6898   enabled-p
   6899   name
   6900   sly-dependencies
   6901   slynk-dependencies
   6902   enable
   6903   disable
   6904   authors
   6905   license)
   6906 
   6907 (defmacro define-sly-contrib (name _docstring &rest clauses)
   6908   (declare (indent 1))
   6909   (cl-destructuring-bind (&key sly-dependencies
   6910                                slynk-dependencies
   6911                                on-load
   6912                                on-unload
   6913                                authors
   6914                                license)
   6915       (cl-loop for (key . value) in clauses append `(,key ,value))
   6916     (cl-labels
   6917         ((enable-fn (c) (intern (concat (symbol-name c) "-init")))
   6918          (disable-fn (c) (intern (concat (symbol-name c) "-unload")))
   6919          (path-sym (c) (intern (concat (symbol-name c) "--path")))
   6920          (contrib-sym (c) (intern (concat (symbol-name c) "--contrib"))))
   6921       `(progn
   6922          (defvar ,(path-sym name))
   6923          (defvar ,(contrib-sym name))
   6924          (setq ,(path-sym name) (and load-file-name
   6925                                      (file-name-directory load-file-name)))
   6926          (eval-when-compile
   6927            (when byte-compile-current-file; protect against eager macro expansion
   6928              (add-to-list 'load-path
   6929                           (file-name-as-directory
   6930                            (file-name-directory byte-compile-current-file)))))
   6931          (setq ,(contrib-sym name)
   6932                (put 'sly-contribs ',name
   6933                     (make-sly-contrib
   6934                      :name ',name :authors ',authors :license ',license
   6935                      :sly-dependencies ',sly-dependencies
   6936                      :slynk-dependencies ',slynk-dependencies
   6937                      :enable ',(enable-fn name) :disable ',(disable-fn name))))
   6938          ,@(mapcar (lambda (d) `(require ',d)) sly-dependencies)
   6939          (defun ,(enable-fn name) ()
   6940            (mapc #'funcall (mapcar
   6941                             #'sly-contrib--enable
   6942                             (cl-remove-if #'sly-contrib--enabled-p
   6943                                           (list ,@(mapcar #'contrib-sym
   6944                                                           sly-dependencies)))))
   6945            (cl-loop for dep in ',slynk-dependencies
   6946                     do (cl-pushnew (list dep ,(path-sym name) ',name)
   6947                                    sly-contrib--required-slynk-modules
   6948                                    :key #'cl-first))
   6949            ;; FIXME: It's very tricky to do Slynk calls like
   6950            ;; `sly-contrib--load-slynk-dependencies' here, and it this
   6951            ;; should probably loop all connections. Anyway, we try
   6952            ;; ensure this can only happen from an interactive
   6953            ;; `sly-setup' call.
   6954            ;;
   6955            (when (and (eq this-command 'sly-setup)
   6956                       (sly-connected-p))
   6957              (sly-contrib--load-slynk-dependencies))
   6958            ,@on-load
   6959            (setf (sly-contrib--enabled-p ,(contrib-sym name)) t))
   6960          (defun ,(disable-fn name) ()
   6961            ,@on-unload
   6962            (cl-loop for dep in ',slynk-dependencies
   6963                     do (setq sly-contrib--required-slynk-modules
   6964                              (cl-remove dep sly-contrib--required-slynk-modules
   6965                                         :key #'cl-first)))
   6966            (sly-warning "Disabling contrib %s" ',name)
   6967            (mapc #'funcall (mapcar
   6968                             #'sly-contrib--disable
   6969                             (cl-remove-if-not #'sly-contrib--enabled-p
   6970                                               (list ,@(mapcar #'contrib-sym
   6971                                                               sly-dependencies)))))
   6972            (setf (sly-contrib--enabled-p ,(contrib-sym name)) nil))))))
   6973 
   6974 (defun sly-contrib--all-contribs ()
   6975   "All defined `sly-contrib' objects."
   6976   (cl-loop for (nil val) on (symbol-plist 'sly-contribs) by #'cddr
   6977            when (sly-contrib-p val)
   6978            collect val))
   6979 
   6980 (defun sly-contrib--all-dependencies (contrib)
   6981   "Contrib names recursively needed by CONTRIB, including self."
   6982   (sly--contrib-safe contrib
   6983     (cons contrib
   6984           (cl-mapcan #'sly-contrib--all-dependencies
   6985                      (sly-contrib--sly-dependencies
   6986                       (sly-contrib--find-contrib contrib))))))
   6987 
   6988 (defun sly-contrib--find-contrib (designator)
   6989   (if (sly-contrib-p designator)
   6990       designator
   6991     (or (get 'sly-contribs designator)
   6992         (error "Unknown contrib: %S" designator))))
   6993 
   6994 (defun sly-contrib--read-contrib-name ()
   6995   (let ((names (cl-loop for c in (sly-contrib--all-contribs) collect
   6996                         (symbol-name (sly-contrib--name c)))))
   6997     (intern (sly-completing-read "Contrib: " names nil t))))
   6998 
   6999 (defun sly-enable-contrib (name)
   7000   "Attempt to enable contrib NAME."
   7001   (interactive (list (sly-contrib--read-contrib-name)))
   7002   (sly--contrib-safe name
   7003     (funcall (sly-contrib--enable (sly-contrib--find-contrib name)))))
   7004 
   7005 (defun sly-disable-contrib (name)
   7006   "Attempt to disable contrib NAME."
   7007   (interactive (list (sly-contrib--read-contrib-name)))
   7008   (sly--contrib-safe name
   7009     (funcall (sly-contrib--disable (sly-contrib--find-contrib name)))))
   7010 
   7011 
   7012 ;;;;; Pull-down menu
   7013 (easy-menu-define sly-menu sly-mode-map "SLY"
   7014   (let ((C '(sly-connected-p)))
   7015     `("SLY"
   7016       [ "Edit Definition..."       sly-edit-definition ,C ]
   7017       [ "Return From Definition"   sly-pop-find-definition-stack ,C ]
   7018       [ "Complete Symbol"          sly-complete-symbol ,C ]
   7019       "--"
   7020       ("Evaluation"
   7021        [ "Eval Defun"              sly-eval-defun ,C ]
   7022        [ "Eval Last Expression"    sly-eval-last-expression ,C ]
   7023        [ "Eval And Pretty-Print"   sly-pprint-eval-last-expression ,C ]
   7024        [ "Eval Region"             sly-eval-region ,C ]
   7025        [ "Eval Region And Pretty-Print" sly-pprint-eval-region ,C ]
   7026        [ "Interactive Eval..."     sly-interactive-eval ,C ]
   7027        [ "Edit Lisp Value..."      sly-edit-value ,C ]
   7028        [ "Call Defun"              sly-call-defun ,C ])
   7029       ("Debugging"
   7030        [ "Inspect..."              sly-inspect ,C ]
   7031        [ "Macroexpand Once..."     sly-macroexpand-1 ,C ]
   7032        [ "Macroexpand All..."      sly-macroexpand-all ,C ]
   7033        [ "Disassemble..."          sly-disassemble-symbol ,C ])
   7034       ("Compilation"
   7035        [ "Compile Defun"           sly-compile-defun ,C ]
   7036        [ "Compile and Load File"       sly-compile-and-load-file ,C ]
   7037        [ "Compile File"            sly-compile-file ,C ]
   7038        [ "Compile Region"          sly-compile-region ,C ]
   7039        "--"
   7040        [ "Next Note"               sly-next-note t ]
   7041        [ "Previous Note"           sly-previous-note t ]
   7042        [ "Remove Notes"            sly-remove-notes t ]
   7043        [ "List notes"              sly-show-compilation-log t ])
   7044       ("Cross Reference"
   7045        [ "Who Calls..."            sly-who-calls ,C ]
   7046        [ "Who References... "      sly-who-references ,C ]
   7047        [ "Who Sets..."             sly-who-sets ,C ]
   7048        [ "Who Binds..."            sly-who-binds ,C ]
   7049        [ "Who Macroexpands..."     sly-who-macroexpands ,C ]
   7050        [ "Who Specializes..."      sly-who-specializes ,C ]
   7051        [ "List Callers..."         sly-list-callers ,C ]
   7052        [ "List Callees..."         sly-list-callees ,C ]
   7053        [ "Next Location"           sly-next-location t ])
   7054       ("Editing"
   7055        [ "Check Parens"            check-parens t]
   7056        [ "Update Indentation"      sly-update-indentation ,C])
   7057       ("Documentation"
   7058        [ "Describe Symbol..."      sly-describe-symbol ,C ]
   7059        [ "Lookup Documentation..." sly-documentation-lookup t ]
   7060        [ "Apropos..."              sly-apropos ,C ]
   7061        [ "Apropos all..."          sly-apropos-all ,C ]
   7062        [ "Apropos Package..."      sly-apropos-package ,C ]
   7063        [ "Hyperspec..."            sly-hyperspec-lookup t ])
   7064       "--"
   7065       [ "Interrupt Command"        sly-interrupt ,C ]
   7066       [ "Abort Async. Command"     sly-quit ,C ])))
   7067 
   7068 (easy-menu-define sly-sly-db-menu sly-db-mode-map "SLY-DB Menu"
   7069   (let ((C '(sly-connected-p)))
   7070     `("SLY-DB"
   7071       [ "Next Frame" sly-db-down t ]
   7072       [ "Previous Frame" sly-db-up t ]
   7073       [ "Toggle Frame Details" sly-db-toggle-details t ]
   7074       [ "Next Frame (Details)" sly-db-details-down t ]
   7075       [ "Previous Frame (Details)" sly-db-details-up t ]
   7076       "--"
   7077       [ "Eval Expression..." sly-interactive-eval ,C ]
   7078       [ "Eval in Frame..." sly-db-eval-in-frame ,C ]
   7079       [ "Eval in Frame (pretty print)..." sly-db-pprint-eval-in-frame ,C ]
   7080       [ "Inspect In Frame..." sly-db-inspect-in-frame ,C ]
   7081       [ "Inspect Condition Object" sly-db-inspect-condition ,C ]
   7082       "--"
   7083       [ "Restart Frame" sly-db-restart-frame ,C ]
   7084       [ "Return from Frame..." sly-db-return-from-frame ,C ]
   7085       ("Invoke Restart"
   7086        [ "Continue" sly-db-continue ,C ]
   7087        [ "Abort"    sly-db-abort ,C ]
   7088        [ "Step"      sly-db-step ,C ]
   7089        [ "Step next" sly-db-next ,C ]
   7090        [ "Step out"  sly-db-out ,C ]
   7091        )
   7092       "--"
   7093       [ "Quit (throw)" sly-db-quit ,C ]
   7094       [ "Break With Default Debugger" sly-db-break-with-default-debugger ,C ])))
   7095 
   7096 (easy-menu-define sly-inspector-menu sly-inspector-mode-map
   7097   "Menu for the SLY Inspector"
   7098   (let ((C '(sly-connected-p)))
   7099     `("SLY-Inspector"
   7100       [ "Pop Inspectee" sly-inspector-pop ,C ]
   7101       [ "Next Inspectee" sly-inspector-next ,C ]
   7102       [ "Describe this Inspectee" sly-inspector-describe ,C ]
   7103       [ "Eval in context" sly-inspector-eval ,C ]
   7104       [ "Show history" sly-inspector-history ,C ]
   7105       [ "Reinspect" sly-inspector-reinspect ,C ]
   7106       [ "Fetch all parts" sly-inspector-fetch-all ,C ]
   7107       [ "Quit" sly-inspector-quit ,C ])))
   7108 
   7109 
   7110 ;;;; Utilities (no not Paul Graham style)
   7111 
   7112 ;;; FIXME: this looks almost sly `sly-alistify', perhaps the two
   7113 ;;;        functions can be merged.
   7114 (defun sly-group-similar (similar-p list)
   7115   "Return the list of lists of 'similar' adjacent elements of LIST.
   7116 The function SIMILAR-P is used to test for similarity.
   7117 The order of the input list is preserved."
   7118   (if (null list)
   7119       nil
   7120     (let ((accumulator (list (list (car list)))))
   7121       (dolist (x (cdr list))
   7122         (if (funcall similar-p x (caar accumulator))
   7123             (push x (car accumulator))
   7124           (push (list x) accumulator)))
   7125       (nreverse (mapcar #'nreverse accumulator)))))
   7126 
   7127 (defun sly-alistify (list key test)
   7128   "Partition the elements of LIST into an alist.
   7129 KEY extracts the key from an element and TEST is used to compare
   7130 keys."
   7131   (let ((alist '()))
   7132     (dolist (e list)
   7133       (let* ((k (funcall key e))
   7134              (probe (cl-assoc k alist :test test)))
   7135         (if probe
   7136             (push e (cdr probe))
   7137           (push (cons k (list e)) alist))))
   7138     ;; Put them back in order.
   7139     (nreverse (mapc (lambda (ent)
   7140                       (setcdr ent (nreverse (cdr ent))))
   7141                     alist))))
   7142 
   7143 ;;;;; Misc.
   7144 
   7145 (defun sly-length= (list n)
   7146   "Return (= (length LIST) N)."
   7147   (if (zerop n)
   7148       (null list)
   7149     (let ((tail (nthcdr (1- n) list)))
   7150       (and tail (null (cdr tail))))))
   7151 
   7152 (defun sly-length> (seq n)
   7153   "Return (> (length SEQ) N)."
   7154   (cl-etypecase seq
   7155     (list (nthcdr n seq))
   7156     (sequence (> (length seq) n))))
   7157 
   7158 (defun sly-trim-whitespace (str)
   7159   "Chomp leading and tailing whitespace from STR."
   7160   ;; lited from http://www.emacswiki.org/emacs/ElispCookbook
   7161   (replace-regexp-in-string (rx (or (: bos (* (any " \t\n")))
   7162                                     (: (* (any " \t\n")) eos)))
   7163                             ""
   7164                             str))
   7165 
   7166 ;;;;; Buffer related
   7167 
   7168 (defun sly-column-max ()
   7169   (save-excursion
   7170     (goto-char (point-min))
   7171     (cl-loop for column = (prog2 (end-of-line) (current-column) (forward-line))
   7172              until (= (point) (point-max))
   7173              maximizing column)))
   7174 
   7175 ;;;;; CL symbols vs. Elisp symbols.
   7176 
   7177 (defun sly-cl-symbol-name (symbol)
   7178   (let ((n (if (stringp symbol) symbol (symbol-name symbol))))
   7179     (if (string-match ":\\([^:]*\\)$" n)
   7180         (let ((symbol-part (match-string 1 n)))
   7181           (if (string-match "^|\\(.*\\)|$" symbol-part)
   7182               (match-string 1 symbol-part)
   7183             symbol-part))
   7184       n)))
   7185 
   7186 (defun sly-cl-symbol-package (symbol &optional default)
   7187   (let ((n (if (stringp symbol) symbol (symbol-name symbol))))
   7188     (if (string-match "^\\([^:]*\\):" n)
   7189         (match-string 1 n)
   7190       default)))
   7191 
   7192 (defun sly-qualify-cl-symbol-name (symbol-or-name)
   7193   "Return a package-qualified string for SYMBOL-OR-NAME.
   7194 If SYMBOL-OR-NAME doesn't already have a package prefix the
   7195 current package is used."
   7196   (let ((s (if (stringp symbol-or-name)
   7197                symbol-or-name
   7198              (symbol-name symbol-or-name))))
   7199     (if (sly-cl-symbol-package s)
   7200         s
   7201       (format "%s::%s"
   7202               (let* ((package (sly-current-package)))
   7203                 ;; package is a string like ":cl-user"
   7204                 ;; or "CL-USER", or "\"CL-USER\"".
   7205                 (if package
   7206                     (sly--pretty-package-name package)
   7207                   "CL-USER"))
   7208               (sly-cl-symbol-name s)))))
   7209 
   7210 ;;;;; Moving, CL idiosyncracies aware (reader conditionals &c.)
   7211 
   7212 (defmacro sly-point-moves-p (&rest body)
   7213   "Execute BODY and return true if the current buffer's point moved."
   7214   (declare (indent 0))
   7215   (let ((pointvar (cl-gensym "point-")))
   7216     `(let ((,pointvar (point)))
   7217        (save-current-buffer ,@body)
   7218        (/= ,pointvar (point)))))
   7219 
   7220 (defun sly-forward-sexp (&optional count)
   7221   "Like `forward-sexp', but understands reader-conditionals (#- and #+),
   7222 and skips comments."
   7223   (dotimes (_i (or count 1))
   7224     (sly-forward-cruft)
   7225     (forward-sexp)))
   7226 
   7227 (defconst sly-reader-conditionals-regexp
   7228   ;; #!+, #!- are SBCL specific reader-conditional syntax.
   7229   ;; We need this for the source files of SBCL itself.
   7230   (regexp-opt '("#+" "#-" "#!+" "#!-")))
   7231 
   7232 (defsubst sly-forward-reader-conditional ()
   7233   "Move past any reader conditional (#+ or #-) at point."
   7234   (when (looking-at sly-reader-conditionals-regexp)
   7235     (goto-char (match-end 0))
   7236     (let* ((plus-conditional-p (eq (char-before) ?+))
   7237            (result (sly-eval-feature-expression
   7238                     (condition-case e
   7239                         (read (current-buffer))
   7240                       (invalid-read-syntax
   7241                        (signal 'sly-unknown-feature-expression (cdr e)))))))
   7242       (unless (if plus-conditional-p result (not result))
   7243         ;; skip this sexp
   7244         (sly-forward-sexp)))))
   7245 
   7246 (defun sly-forward-cruft ()
   7247   "Move forward over whitespace, comments, reader conditionals."
   7248   (while (sly-point-moves-p (skip-chars-forward " \t\n")
   7249                             (forward-comment (buffer-size))
   7250                             (sly-forward-reader-conditional))))
   7251 
   7252 (defun sly-keywordify (symbol)
   7253   "Make a keyword out of the symbol SYMBOL."
   7254   (let ((name (downcase (symbol-name symbol))))
   7255     (intern (if (eq ?: (aref name 0))
   7256                 name
   7257               (concat ":" name)))))
   7258 
   7259 (put 'sly-incorrect-feature-expression
   7260      'error-conditions '(sly-incorrect-feature-expression error))
   7261 
   7262 (put 'sly-unknown-feature-expression
   7263      'error-conditions '(sly-unknown-feature-expression
   7264                          sly-incorrect-feature-expression
   7265                          error))
   7266 
   7267 ;; FIXME: let it crash
   7268 ;; FIXME: the (null (cdr l)) constraint is bogus
   7269 (defun sly-eval-feature-expression (e)
   7270   "Interpret a reader conditional expression."
   7271   (cond ((symbolp e)
   7272          (memq (sly-keywordify e) (sly-lisp-features)))
   7273         ((and (consp e) (symbolp (car e)))
   7274          (funcall (let ((head (sly-keywordify (car e))))
   7275                     (cl-case head
   7276                       (:and #'cl-every)
   7277                       (:or #'cl-some)
   7278                       (:not
   7279                        (let ((feature-expression e))
   7280                          (lambda (f l)
   7281                            (cond ((null l) t)
   7282                                  ((null (cdr l)) (not (apply f l)))
   7283                                  (t (signal 'sly-incorrect-feature-expression
   7284                                             feature-expression))))))
   7285                       (t (signal 'sly-unknown-feature-expression head))))
   7286                   #'sly-eval-feature-expression
   7287                   (cdr e)))
   7288         (t (signal 'sly-incorrect-feature-expression e))))
   7289 
   7290 ;;;;; Extracting Lisp forms from the buffer or user
   7291 
   7292 (defun sly-region-for-defun-at-point (&optional pos)
   7293   "Return a list (START END) for the positions of defun at POS.
   7294 POS defaults to point"
   7295   (save-excursion
   7296     (save-match-data
   7297       (goto-char (or pos (point)))
   7298       (end-of-defun)
   7299       (let ((end (point)))
   7300         (beginning-of-defun)
   7301         (list (point) end)))))
   7302 
   7303 (defun sly-beginning-of-symbol ()
   7304   "Move to the beginning of the CL-style symbol at point."
   7305   (while (re-search-backward "\\(\\sw\\|\\s_\\|\\s\\.\\|\\s\\\\|[#@|]\\)\\="
   7306                              (when (> (point) 2000) (- (point) 2000))
   7307                              t))
   7308   (re-search-forward "\\=#[-+.<|]" nil t)
   7309   (when (and (eq (char-after) ?@) (eq (char-before) ?\,))
   7310     (forward-char)))
   7311 
   7312 (defsubst sly-end-of-symbol ()
   7313   "Move to the end of the CL-style symbol at point."
   7314   (re-search-forward "\\=\\(\\sw\\|\\s_\\|\\s\\.\\|#:\\|[@|]\\)*"))
   7315 
   7316 (put 'sly-symbol 'end-op 'sly-end-of-symbol)
   7317 (put 'sly-symbol 'beginning-op 'sly-beginning-of-symbol)
   7318 
   7319 (defun sly-symbol-start-pos ()
   7320   "Return the starting position of the symbol under point.
   7321 The result is unspecified if there isn't a symbol under the point."
   7322   (save-excursion (sly-beginning-of-symbol) (point)))
   7323 
   7324 (defun sly-symbol-end-pos ()
   7325   (save-excursion (sly-end-of-symbol) (point)))
   7326 
   7327 (defun sly-bounds-of-symbol-at-point ()
   7328   "Return the bounds of the symbol around point.
   7329 The returned bounds are either nil or non-empty."
   7330   (let ((bounds (bounds-of-thing-at-point 'sly-symbol)))
   7331     (if (and bounds
   7332              (< (car bounds)
   7333                 (cdr bounds)))
   7334         bounds)))
   7335 
   7336 (defun sly-symbol-at-point (&optional interactive)
   7337   "Return the name of the symbol at point, otherwise nil."
   7338   ;; (thing-at-point 'symbol) returns "" in empty buffers
   7339   (let ((bounds (sly-bounds-of-symbol-at-point)))
   7340     (when bounds
   7341       (let ((beg (car bounds)) (end (cdr bounds)))
   7342         (when interactive (sly-flash-region beg end))
   7343         (buffer-substring-no-properties beg end)))))
   7344 
   7345 (defun sly-bounds-of-sexp-at-point (&optional interactive)
   7346   "Return the bounds sexp near point as a pair (or nil).
   7347 With non-nil INTERACTIVE, error if can't find such a thing."
   7348   (or (sly-bounds-of-symbol-at-point)
   7349       (and (equal (char-after) ?\()
   7350            (member (char-before) '(?\' ?\, ?\@))
   7351            ;; hide stuff before ( to avoid quirks with '( etc.
   7352            (save-restriction
   7353              (narrow-to-region (point) (point-max))
   7354              (bounds-of-thing-at-point 'sexp)))
   7355       (bounds-of-thing-at-point 'sexp)
   7356       (and (save-excursion
   7357              (and (ignore-errors
   7358                     (backward-sexp 1)
   7359                     t)
   7360                   (bounds-of-thing-at-point 'sexp))))
   7361       (when interactive
   7362         (user-error "No sexp near point"))))
   7363 
   7364 (cl-defun sly-sexp-at-point (&optional interactive stringp (errorp t))
   7365   "Return the sexp at point as a string, otherwise nil.
   7366 With non-nil INTERACTIVE, flash the region and also error if no
   7367 sexp can be found, unless ERRORP, which defaults to t, is passed
   7368 as nil.  With non-nil STRINGP, only look for strings"
   7369   (catch 'return
   7370     (let ((bounds (sly-bounds-of-sexp-at-point (and interactive
   7371                                                     errorp))))
   7372       (when bounds
   7373         (when (and stringp
   7374                    (not (eq (syntax-class (syntax-after (car bounds)))
   7375                             (char-syntax ?\"))))
   7376           (if (and interactive
   7377                    interactive)
   7378               (user-error "No string at point")
   7379             (throw 'return nil)))
   7380         (when interactive
   7381           (sly-flash-region (car bounds) (cdr bounds)))
   7382         (buffer-substring-no-properties (car bounds)
   7383                                         (cdr bounds))))))
   7384 
   7385 (defun sly-string-at-point (&optional interactive)
   7386   "Returns the string near point as a string, otherwise nil.
   7387 With non-nil INTERACTIVE, flash the region and error if no string
   7388 can be found."
   7389   (sly-sexp-at-point interactive 'stringp))
   7390 
   7391 (defun sly-input-complete-p (start end)
   7392   "Return t if the region from START to END contains a complete sexp."
   7393   (save-excursion
   7394     (goto-char start)
   7395     (cond ((looking-at "\\s *['`#]?[(\"]")
   7396            (ignore-errors
   7397              (save-restriction
   7398                (narrow-to-region start end)
   7399                ;; Keep stepping over blanks and sexps until the end of
   7400                ;; buffer is reached or an error occurs. Tolerate extra
   7401                ;; close parens.
   7402                (cl-loop do (skip-chars-forward " \t\r\n)")
   7403                         until (eobp)
   7404                         do (forward-sexp))
   7405                t)))
   7406           (t t))))
   7407 
   7408 
   7409 ;;;; sly.el in pretty colors
   7410 
   7411 (cl-loop for sym in (list 'sly-def-connection-var
   7412                           'sly-define-channel-type
   7413                           'sly-define-channel-method
   7414                           'define-sly-contrib)
   7415          for regexp = (format "(\\(%S\\)\\s +\\(\\(\\w\\|\\s_\\)+\\)"
   7416                               sym)
   7417          do (font-lock-add-keywords
   7418              'emacs-lisp-mode
   7419              `((,regexp (1 font-lock-keyword-face)
   7420                         (2 font-lock-variable-name-face)))))
   7421 
   7422 ;;;; Finishing up
   7423 
   7424 (defun sly--byte-compile (symbol)
   7425   (require 'bytecomp) ;; tricky interaction between autoload and let.
   7426   (let ((byte-compile-warnings '()))
   7427     (byte-compile symbol)))
   7428 
   7429 (defun sly-byte-compile-hotspots (syms)
   7430   (mapc (lambda (sym)
   7431           (cond ((fboundp sym)
   7432                  (unless (byte-code-function-p (symbol-function sym))
   7433                    (sly--byte-compile sym)))
   7434                 (t (error "%S is not fbound" sym))))
   7435         syms))
   7436 
   7437 (sly-byte-compile-hotspots
   7438  '(sly-alistify
   7439    sly-log-event
   7440    sly--events-buffer
   7441    sly-process-available-input
   7442    sly-dispatch-event
   7443    sly-net-filter
   7444    sly-net-have-input-p
   7445    sly-net-decode-length
   7446    sly-net-read
   7447    sly-print-apropos
   7448    sly-insert-propertized
   7449    sly-beginning-of-symbol
   7450    sly-end-of-symbol
   7451    sly-eval-feature-expression
   7452    sly-forward-sexp
   7453    sly-forward-cruft
   7454    sly-forward-reader-conditional))
   7455 
   7456 ;;;###autoload
   7457 (add-hook 'lisp-mode-hook 'sly-editing-mode)
   7458 
   7459 (cond
   7460  ((or (not (memq 'slime-lisp-mode-hook lisp-mode-hook))
   7461       noninteractive
   7462       (prog1
   7463           (y-or-n-p "[sly] SLIME detected in `lisp-mode-hook', causes keybinding conflicts.  Remove it for this Emacs session?")
   7464         (warn "To restore SLIME in this session, customize `lisp-mode-hook'
   7465 and replace `sly-editing-mode' with `slime-lisp-mode-hook'.")))
   7466   (remove-hook 'lisp-mode-hook 'slime-lisp-mode-hook)
   7467   (dolist (buffer (buffer-list))
   7468     (with-current-buffer buffer
   7469       (when (eq major-mode 'lisp-mode)
   7470         (sly-editing-mode 1)
   7471         (ignore-errors (funcall 'slime-mode -1))))))
   7472  (t
   7473   (warn
   7474    "`sly.el' loaded OK. To use SLY, customize `lisp-mode-hook' and remove `slime-lisp-mode-hook'.")))
   7475 
   7476 (provide 'sly)
   7477 
   7478 ;;; sly.el ends here
   7479 ;; Local Variables:
   7480 ;; coding: utf-8
   7481 ;; End: