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: