selime

Superior Elisp mode for Emacs
git clone https://git.jamzattack.xyz/selime
Log | Files | Refs | LICENSE

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