selime.el (7660B)
1 ;;; selime.el --- Extra convenience functions for editing Elisp -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020 Jamie Beardslee 4 5 ;; Author: Jamie Beardslee <jdb@jamzattack.xyz> 6 ;; URL: https://git.jamzattack.xyz/selime 7 ;; Version: 2020.10.25 8 ;; Package-Requires: ((emacs "25.1")) 9 ;; Keywords: lisp 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; My attempt at emulating slime-mode for Emacs Lisp. A bunch of 27 ;; things work, but there are still a couple of functions to add and 28 ;; improve. 29 30 ;;; Code: 31 32 (require 'disass) 33 (require 'trace) 34 (require 'ielm) 35 (require 'help-fns) 36 (require 'pp) 37 (when (featurep 'helpful) 38 (require 'helpful)) 39 40 41 ;;; Help 42 43 (defun selime-describe-function () 44 "If package \"helpful\" is installed, call `helpful-callable', 45 otherwise call `describe-function'" 46 (interactive) 47 (if (featurep 'helpful) 48 (call-interactively #'helpful-callable) 49 (call-interactively #'describe-function))) 50 51 (defun selime-describe-variable () 52 "If package \"helpful\" is installed, call `helpful-variable', 53 otherwise call `describe-variable'" 54 (interactive) 55 (if (featurep 'helpful) 56 (call-interactively #'helpful-variable) 57 (call-interactively #'describe-variable))) 58 59 (defun selime-describe-symbol () 60 "If package \"helpful\" is installed, call `helpful-at-point', 61 otherwise call `describe-symbol' on the symbol at point" 62 (interactive) 63 (if (featurep 'helpful) 64 (call-interactively #'helpful-at-point) 65 (describe-symbol (or (symbol-at-point) 66 (user-error "There is no symbol at point"))))) 67 68 69 ;;; Compilation 70 71 (defun selime-compile-file (&optional file) 72 "Compiles the the current file without loading it. 73 74 With prefix arg, read FILE name from minibuffer." 75 (interactive (list (if current-prefix-arg 76 (read-file-name "Compile file: ") 77 (buffer-file-name (current-buffer))))) 78 (if file 79 (byte-compile-file file) 80 (user-error "Buffer is not associated with a file"))) 81 82 (defun selime-compile-and-load-file (&optional file) 83 "Compiles and load the current buffer. 84 85 If the buffer is narrowed or not visiting a file, this will 86 compile the buffer directly. Otherwise, it will generate an elc 87 file and load it. With a prefix arg, prompt for a FILE to 88 compile and load." 89 (interactive (list (cond 90 (current-prefix-arg 91 (read-file-name "Compile and load file: ")) 92 ((buffer-narrowed-p) 93 nil) 94 (t 95 (buffer-file-name (current-buffer)))))) 96 (save-excursion 97 (if file 98 (byte-compile-file file t) 99 (eval-buffer (byte-compile-from-buffer (current-buffer)))))) 100 101 (defun selime-compile-last-sexp (&optional insert) 102 "Compile and evaluate the last sexp. 103 104 Print the result in the echo area. With prefix argument, INSERT 105 value in current buffer after the form." 106 (interactive "P") 107 (declare (interactive-only)) 108 (let ((value (eval 109 (byte-compile-sexp 110 (elisp--preceding-sexp)) 111 lexical-binding))) 112 (if insert 113 (prin1 value (current-buffer)) 114 (message "%s" (prin1-to-string value))))) 115 116 117 ;;; Debugging 118 119 (defvar selime-trace-buffer 120 "*selime trace*") 121 122 (defun selime-toggle-trace (function) 123 "Toggle trace for FUNCTION." 124 (interactive (list 125 (intern 126 (read-string "(Un)trace: " 127 (symbol-name 128 (symbol-at-point)))))) 129 (if (trace-is-traced function) 130 (untrace-function function) 131 (trace-function function selime-trace-buffer))) 132 133 (defun selime-macroexpand () 134 "Macro expand the preceding sexp. 135 136 This will show the expanded form in the \"*selime macroexpand*\" 137 buffer. If point is already in the macroexpand buffer, replace 138 the sexp with its expansion." 139 (interactive) 140 (let ((buffer (get-buffer-create "*selime macroexpand*")) 141 (sexp (elisp--preceding-sexp))) 142 (if (eq (current-buffer) buffer) 143 (save-excursion 144 (let ((bounds (bounds-of-thing-at-point 'sexp))) 145 (delete-region (car bounds) (1+ (cdr bounds)))) 146 (insert (pp-to-string (macroexpand-1 sexp)))) 147 (pp-display-expression (macroexpand-1 sexp) 148 buffer)) 149 (with-current-buffer buffer 150 (indent-region (point-min) (point-max))))) 151 152 (defun selime-disassemble (function) 153 "If point is on a FUNCTION, disassemble it. 154 155 Otherwise, prompt for a function to disassemble." 156 (interactive (list (let ((fun (symbol-at-point))) 157 (if (fboundp fun) 158 fun 159 (intern 160 (completing-read "Disassemble function: " obarray #'fboundp t)))))) 161 (disassemble function)) 162 163 164 ;;; Convenience 165 166 (defun selime-ielm (&optional buffer-name) 167 "Open IELM in another window. 168 169 With prefix arg BUFFER-NAME, prompt for the name of the new IELM 170 buffer." 171 (interactive (list (when current-prefix-arg 172 (read-string "IELM Buffer Name: ")))) 173 (let ((buffer (get-buffer-create (or buffer-name "*ielm*")))) 174 (switch-to-buffer-other-window buffer) 175 (inferior-emacs-lisp-mode))) 176 177 (defun selime-insert-package-prefix () 178 "Insert the buffer's filename for use as a prefix." 179 (interactive) 180 (insert (format "%s-" 181 (file-name-sans-extension 182 (file-name-nondirectory 183 (or (buffer-file-name) 184 (user-error "Buffer is not visiting a file"))))))) 185 186 (defun selime-insert-version () 187 "Update or insert a version number in an Elisp file. 188 Uses the current date formatted as %Y.%m.%d (e.g. 1970.01.01)." 189 (interactive) 190 (declare (interactive-only t)) 191 (let ((new-version (format-time-string " %Y.%m.%d"))) 192 (save-excursion 193 (save-restriction 194 (widen) 195 (goto-char (point-min)) 196 (or (re-search-forward "^;; Version:" nil t) 197 (when (y-or-n-p "Add a version? ") 198 (goto-char (point-min)) 199 (or (re-search-forward "^;; Author:" nil t) 200 (user-error "No version or author statement found. Try `auto-insert'")) 201 (forward-line 1) 202 (insert ";; Version:\n\n") 203 (forward-char -2))) 204 (kill-line) 205 (insert new-version))))) 206 207 208 ;;; Selime-mode 209 210 (defvar selime-mode-map 211 (let ((map (make-sparse-keymap))) 212 (define-key map (kbd "C-c C-d C-d") 'selime-describe-symbol) 213 (define-key map (kbd "C-c C-d d") 'selime-describe-symbol) 214 (define-key map (kbd "C-c C-d C-f") 'selime-describe-function) 215 (define-key map (kbd "C-c C-d f") 'selime-describe-function) 216 (define-key map (kbd "C-c C-d C-v") 'selime-describe-variable) 217 (define-key map (kbd "C-c C-d v") 'selime-describe-variable) 218 (define-key map (kbd "C-c C-c") 'compile-defun) 219 (define-key map (kbd "C-c M-d") 'selime-disassemble) 220 (define-key map (kbd "C-c C-m") 'selime-macroexpand) 221 (define-key map (kbd "C-c C-k") 'selime-compile-and-load-file) 222 (define-key map (kbd "C-c M-k") 'selime-compile-file) 223 (define-key map (kbd "C-c C-z") 'selime-ielm) 224 (define-key map (kbd "C-c C-t") 'selime-toggle-trace) 225 (define-key map (kbd "C-c C-e") 'selime-compile-last-sexp) 226 (define-key map (kbd "C-c SPC") 'selime-insert-package-prefix) 227 (define-key map (kbd "C-c =") 'selime-insert-version) 228 map)) 229 230 ;;;###autoload 231 (define-minor-mode selime-mode 232 "Enable Slime-style keybindings for elisp buffers. 233 234 \\{selime-mode-map}" 235 nil "" selime-mode-map) 236 237 (provide 'selime) 238 ;;; selime.el ends here