dotemacs

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

sly.el (296394B)


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