;; init-elisp.el -*- lexical-binding: t -*- (require 'init-custom) (require 'init-funcs) ;; Emacs lisp mode (use-package elisp-mode :ensure nil :defines flycheck-disabled-checkers :bind (:map emacs-lisp-mode-map ("C-c C-x" . ielm) ("C-c C-c" . eval-defun) ("C-c C-b" . eval-buffer)) :hook (emacs-lisp-mode . (lambda () "Disable the checkdoc checker." (setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc)))) :config (when (boundp 'elisp-flymake-byte-compile-load-path) (add-to-list 'elisp-flymake-byte-compile-load-path load-path)) (with-no-warnings ;; Align indent keywords ;; @see https://emacs.stackexchange.com/questions/10230/how-to-indent-keywords-aligned (defun my-lisp-indent-function (indent-point state) "This function is the normal value of the variable `lisp-indent-function'. The function `calculate-lisp-indent' calls this to determine if the arguments of a Lisp function call should be indented specially. INDENT-POINT is the position at which the line being indented begins. Point is located at the point to indent under (for default indentation); STATE is the `parse-partial-sexp' state for that position. If the current line is in a call to a Lisp function that has a non-nil property `lisp-indent-function' (or the deprecated `lisp-indent-hook'), it specifies how to indent. The property value can be: * `defun', meaning indent `defun'-style \(this is also the case if there is no property and the function has a name that begins with \"def\", and three or more arguments); * an integer N, meaning indent the first N arguments specially (like ordinary function arguments), and then indent any further arguments like a body; * a function to call that returns the indentation (or nil). `lisp-indent-function' calls this function with the same two arguments that it itself received. This function returns either the indentation to use, or nil if the Lisp function does not specify a special indentation." (let ((normal-indent (current-column)) (orig-point (point))) (goto-char (1+ (elt state 1))) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) (cond ;; car of form doesn't seem to be a symbol, or is a keyword ((and (elt state 2) (or (not (looking-at "\\sw\\|\\s_")) (looking-at ":"))) (if (not (> (save-excursion (forward-line 1) (point)) calculate-lisp-indent-last-sexp)) (progn (goto-char calculate-lisp-indent-last-sexp) (beginning-of-line) (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t))) ;; Indent under the list or under the first sexp on the same ;; line as calculate-lisp-indent-last-sexp. Note that first ;; thing on that line has to be complete sexp since we are ;; inside the innermost containing sexp. (backward-prefix-chars) (current-column)) ((and (save-excursion (goto-char indent-point) (skip-syntax-forward " ") (not (looking-at ":"))) (save-excursion (goto-char orig-point) (looking-at ":"))) (save-excursion (goto-char (+ 2 (elt state 1))) (current-column))) (t (let ((function (buffer-substring (point) (progn (forward-sexp 1) (point)))) method) (setq method (or (function-get (intern-soft function) 'lisp-indent-function) (get (intern-soft function) 'lisp-indent-hook))) (cond ((or (eq method 'defun) (and (null method) (length> function 3) (string-match "\\`def" function))) (lisp-indent-defform state indent-point)) ((integerp method) (lisp-indent-specform method state indent-point normal-indent)) (method (funcall method indent-point state)))))))) (add-hook 'emacs-lisp-mode-hook (lambda () (setq-local lisp-indent-function #'my-lisp-indent-function))) ;; Add remove buttons for advices (add-hook 'help-mode-hook 'cursor-sensor-mode) (defun function-advices (function) "Return FUNCTION's advices." (let ((flist (indirect-function function)) advices) (while (advice--p flist) (setq advices `(,@advices ,(advice--car flist))) (setq flist (advice--cdr flist))) advices)) (defun add-remove-advice-button (advice function) (when (and (functionp advice) (functionp function)) (let ((inhibit-read-only t) (msg (format "Remove advice `%s'" advice))) (insert "\t") (insert-button "Remove" 'face 'custom-button 'cursor-sensor-functions `((lambda (&rest _) ,msg)) 'help-echo msg 'action (lambda (_) (when (yes-or-no-p msg) (message "%s from function `%s'" msg function) (advice-remove function advice) (if (eq major-mode 'helpful-mode) (helpful-update) (revert-buffer nil t)))) 'follow-link t)))) (defun add-button-to-remove-advice (buffer-or-name function) "Add a button to remove advice." (with-current-buffer buffer-or-name (save-excursion (goto-char (point-min)) (let ((ad-list (function-advices function))) (while (re-search-forward "^\\(?:This function has \\)?:[-a-z]+ advice: \\(.+\\)$" nil t) (let ((advice (car ad-list))) (add-remove-advice-button advice function) (setq ad-list (delq advice ad-list)))))))) (define-advice describe-function-1 (:after (function) advice-remove-button) (add-button-to-remove-advice (help-buffer) function)) (with-eval-after-load 'helpful (define-advice helpful-update (:after () advice-remove-button) (when helpful--callable-p (add-button-to-remove-advice (current-buffer) helpful--sym)))) ;; Remove hooks (defun remove-hook-at-point () "Remove the hook at the point in the *Help* buffer." (interactive) (unless (memq major-mode '(help-mode helpful-mode)) (error "Only for help-mode or helpful-mode")) (let ((orig-point (point))) (save-excursion (when-let ((hook (progn (goto-char (point-min)) (symbol-at-point))) (func (when (and (or (re-search-forward (format "^Value:?[\s|\n]") nil t) (goto-char orig-point)) (sexp-at-point)) (end-of-sexp) (backward-char 1) (catch 'break (while t (condition-case _err (backward-sexp) (scan-error (throw 'break nil))) (let ((bounds (bounds-of-thing-at-point 'sexp))) (when (<= (car bounds) orig-point (cdr bounds)) (throw 'break (sexp-at-point))))))))) (when (yes-or-no-p (format "Remove %s from %s? " func hook)) (remove-hook hook func) (if (eq major-mode 'helpful-mode) (helpful-update) (revert-buffer nil t))))))) (bind-key "r" #'remove-hook-at-point help-mode-map))) ;; Syntax highlighting of known Elisp symbols (use-package highlight-defined :straight t :ensure t :defer t :hook ((emacs-lisp-mode inferior-emacs-lisp-mode) . highlight-defined-mode)) ;; Show function arglist or variable docstring ;; `global-eldoc-mode' is enabled by default. (use-package eldoc :ensure nil :diminish) ;; A better *Help* buffer (use-package helpful :straight t :ensure t :defer t :bind (([remap describe-function] . helpful-callable) ([remap describe-command] . helpful-command) ([remap describe-variable] . helpful-variable) ([remap describe-key] . helpful-key) ([remap describe-symbol] . helpful-symbol) ("C-c C-d" . helpful-at-point) :map helpful-mode-map ("r" . remove-hook-at-point)) :hook (helpful-mode . cursor-sensor-mode) ; for remove-advice button :init (with-no-warnings (with-eval-after-load 'counsel (setq counsel-describe-function-function #'helpful-callable counsel-describe-variable-function #'helpful-variable counsel-describe-symbol-function #'helpful-symbol counsel-descbinds-function #'helpful-callable)) (with-eval-after-load 'apropos ;; patch apropos buttons to call helpful instead of help (dolist (fun-bt '(apropos-function apropos-macro apropos-command)) (button-type-put fun-bt 'action (lambda (button) (helpful-callable (button-get button 'apropos-symbol))))) (dolist (var-bt '(apropos-variable apropos-user-option)) (button-type-put var-bt 'action (lambda (button) (helpful-variable (button-get button 'apropos-symbol))))))) :config (with-no-warnings ;; Open the buffer in other window (defun my-helpful--navigate (button) "Navigate to the path this BUTTON represents." (find-file-other-window (substring-no-properties (button-get button 'path))) ;; We use `get-text-property' to work around an Emacs 25 bug: ;; http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=f7c4bad17d83297ee9a1b57552b1944020f23aea (-when-let (pos (get-text-property button 'position (marker-buffer button))) (helpful--goto-char-widen pos))) (advice-add #'helpful--navigate :override #'my-helpful--navigate))) ;;;; For ERT ;;(use-package overseer ;; :diminish ;; :hook (emacs-lisp-mode . overseer-mode)) (provide 'init-elisp) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; init-elisp.el ends here