So these modes (and particularly .doom* dotfiles and .dir-locals.el) can benefit from smarter syntax highlighting and integration with other modules.
342 lines
14 KiB
EmacsLisp
342 lines
14 KiB
EmacsLisp
;;; lang/emacs-lisp/config.el -*- lexical-binding: t; -*-
|
|
|
|
(defvar +emacs-lisp-enable-extra-fontification t
|
|
"If non-nil, highlight special forms, and defined functions and variables.")
|
|
|
|
(defvar +emacs-lisp-outline-regexp "[ \t]*;;;\\(;*\\**\\) [^ \t\n]"
|
|
"Regexp to use for `outline-regexp' in `emacs-lisp-mode'.
|
|
This marks a foldable marker for `outline-minor-mode' in elisp buffers.")
|
|
|
|
(defvar +emacs-lisp-linter-warnings
|
|
'(not free-vars ; don't complain about unknown variables
|
|
noruntime ; don't complain about unknown function calls
|
|
unresolved) ; don't complain about undefined functions
|
|
"The value for `byte-compile-warnings' in non-packages.
|
|
|
|
This reduces the verbosity of flycheck in Emacs configs and scripts, which are
|
|
so stateful that the deluge of false positives (from the byte-compiler,
|
|
package-lint, and checkdoc) can be more overwhelming than helpful.
|
|
|
|
See `+emacs-lisp-non-package-mode' for details.")
|
|
|
|
|
|
;; `elisp-mode' is loaded at startup. In order to lazy load its config we need
|
|
;; to pretend it isn't loaded
|
|
(defer-feature! elisp-mode emacs-lisp-mode)
|
|
|
|
|
|
;;
|
|
;;; Config
|
|
|
|
(use-package! elisp-mode
|
|
:mode ("\\.Cask\\'" . emacs-lisp-mode)
|
|
:interpreter ("doomscript" . emacs-lisp-mode)
|
|
:config
|
|
(let ((modes '(emacs-lisp-mode lisp-interaction-mode lisp-data-mode)))
|
|
(set-repl-handler! modes #'+emacs-lisp/open-repl)
|
|
(set-eval-handler! modes #'+emacs-lisp-eval)
|
|
(set-lookup-handlers! `(,@modes helpful-mode)
|
|
:definition #'+emacs-lisp-lookup-definition
|
|
:documentation #'+emacs-lisp-lookup-documentation)
|
|
(set-docsets! modes "Emacs Lisp")
|
|
(set-ligatures! modes :lambda "lambda")
|
|
(set-formatter! 'lisp-indent #'apheleia-indent-lisp-buffer :modes modes)
|
|
(set-rotate-patterns! modes
|
|
:symbols '(("t" "nil")
|
|
("let" "let*")
|
|
("when" "unless")
|
|
("advice-add" "advice-remove")
|
|
("defadvice!" "undefadvice!")
|
|
("add-hook" "remove-hook")
|
|
("add-hook!" "remove-hook!")
|
|
("it" "xit")
|
|
("describe" "xdescribe"))))
|
|
|
|
(setq-hook! 'emacs-lisp-mode-hook
|
|
;; Emacs' built-in elisp files use a hybrid tab->space indentation scheme
|
|
;; with a tab width of 8. Any smaller and the indentation will be
|
|
;; unreadable. Since Emacs' lisp indenter doesn't respect this variable it's
|
|
;; safe to ignore this setting otherwise.
|
|
tab-width 8
|
|
;; Don't treat autoloads or sexp openers as outline headers, we have
|
|
;; hideshow for that.
|
|
outline-regexp +emacs-lisp-outline-regexp
|
|
outline-level #'+emacs-lisp-outline-level)
|
|
|
|
;; DEPRECATED: Remove when 27.x support is dropped.
|
|
(when (< emacs-major-version 28)
|
|
;; As of Emacs 28+, `emacs-lisp-mode' uses a shorter label in the mode-line
|
|
;; ("ELisp/X", where X = l or d, depending on `lexical-binding'). In <=27,
|
|
;; it uses "Emacs-Lisp". The former is more useful, so I backport it:
|
|
(setq-hook! 'emacs-lisp-mode-hook
|
|
mode-name `("ELisp"
|
|
(lexical-binding (:propertize "/l"
|
|
help-echo "Using lexical-binding mode")
|
|
(:propertize "/d"
|
|
help-echo "Using old dynamic scoping mode"
|
|
face warning
|
|
mouse-face mode-line-highlight)))))
|
|
|
|
;; Introduces logic to improve plist indentation in emacs-lisp-mode.
|
|
(advice-add #'calculate-lisp-indent :override #'+emacs-lisp--calculate-lisp-indent-a)
|
|
|
|
;; Variable-width indentation is superior in elisp. Otherwise, `dtrt-indent'
|
|
;; and `editorconfig' would force fixed indentation on elisp.
|
|
(add-to-list 'doom-detect-indentation-excluded-modes 'emacs-lisp-mode)
|
|
|
|
(add-hook! '(emacs-lisp-mode-hook lisp-data-mode-local-vars-hook)
|
|
;; Allow folding of outlines in comments
|
|
#'outline-minor-mode
|
|
;; Make parenthesis depth easier to distinguish at a glance
|
|
#'rainbow-delimiters-mode
|
|
;; Make quoted symbols easier to distinguish from free variables
|
|
#'highlight-quoted-mode
|
|
;; Extend imenu support to Doom constructs
|
|
#'+emacs-lisp-extend-imenu-h
|
|
;; Ensure straight sees modifications to installed packages
|
|
#'+emacs-lisp-init-straight-maybe-h)
|
|
|
|
;; UX: Both Flycheck's and Flymake's two emacs-lisp checkers produce a *lot*
|
|
;; of false positives in non-packages (like Emacs configs or elisp scripts),
|
|
;; so I disable `checkdoc' (`emacs-lisp-checkdoc', `elisp-flymake-checkdoc')
|
|
;; and set `byte-compile-warnings' to a subset that makes more sense (see
|
|
;; `+emacs-lisp-linter-warnings')
|
|
(add-hook! '(flycheck-mode-hook flymake-mode-hook) #'+emacs-lisp-non-package-mode)
|
|
|
|
(defadvice! +syntax--fix-elisp-flymake-load-path (orig-fn &rest args)
|
|
"Set load path for elisp byte compilation Flymake backend"
|
|
:around #'elisp-flymake-byte-compile
|
|
(let ((elisp-flymake-byte-compile-load-path
|
|
(append elisp-flymake-byte-compile-load-path load-path)))
|
|
(apply orig-fn args)))
|
|
|
|
;; Enhance elisp syntax highlighting, by highlighting Doom-specific
|
|
;; constructs, defined symbols, and truncating :pin's in `package!' calls.
|
|
(font-lock-add-keywords
|
|
'emacs-lisp-mode
|
|
(append `(;; custom Doom cookies
|
|
("^;;;###\\(autodef\\|if\\|package\\)[ \n]" (1 font-lock-warning-face t)))
|
|
;; highlight defined, special variables & functions
|
|
(when +emacs-lisp-enable-extra-fontification
|
|
`((+emacs-lisp-highlight-vars-and-faces . +emacs-lisp--face)))))
|
|
|
|
(defadvice! +emacs-lisp-append-value-to-eldoc-a (fn sym)
|
|
"Display variable value next to documentation in eldoc."
|
|
:around #'elisp-get-var-docstring
|
|
(when-let (ret (funcall fn sym))
|
|
(if (boundp sym)
|
|
(concat ret " "
|
|
(let* ((truncated " [...]")
|
|
(print-escape-newlines t)
|
|
(str (symbol-value sym))
|
|
(str (prin1-to-string str))
|
|
(limit (- (frame-width) (length ret) (length truncated) 1)))
|
|
(format (format "%%0.%ds%%s" (max limit 0))
|
|
(propertize str 'face 'warning)
|
|
(if (< (length str) limit) "" truncated))))
|
|
ret)))
|
|
|
|
(map! :localleader
|
|
:map (emacs-lisp-mode-map lisp-interaction-mode-map)
|
|
:desc "Expand macro" "m" #'macrostep-expand
|
|
(:prefix ("d" . "debug")
|
|
"f" #'+emacs-lisp/edebug-instrument-defun-on
|
|
"F" #'+emacs-lisp/edebug-instrument-defun-off)
|
|
(:prefix ("e" . "eval")
|
|
"b" #'eval-buffer
|
|
"d" #'eval-defun
|
|
"e" #'eval-last-sexp
|
|
"r" #'eval-region
|
|
"l" #'load-library)
|
|
(:prefix ("g" . "goto")
|
|
"f" #'find-function
|
|
"v" #'find-variable
|
|
"l" #'find-library)))
|
|
|
|
(use-package! ielm
|
|
:defer t
|
|
:config
|
|
(set-lookup-handlers! 'inferior-emacs-lisp-mode
|
|
:definition #'+emacs-lisp-lookup-definition
|
|
:documentation #'+emacs-lisp-lookup-documentation)
|
|
|
|
;; Adapted from http://www.modernemacs.com/post/comint-highlighting/ to add
|
|
;; syntax highlighting to ielm REPLs.
|
|
(setq ielm-font-lock-keywords
|
|
(append '(("\\(^\\*\\*\\*[^*]+\\*\\*\\*\\)\\(.*$\\)"
|
|
(1 font-lock-comment-face)
|
|
(2 font-lock-constant-face)))
|
|
(when (require 'highlight-numbers nil t)
|
|
(highlight-numbers--get-regexp-for-mode 'emacs-lisp-mode))
|
|
(cl-loop for (matcher . match-highlights)
|
|
in (append lisp-el-font-lock-keywords-2
|
|
lisp-cl-font-lock-keywords-2)
|
|
collect
|
|
`((lambda (limit)
|
|
(when ,(if (symbolp matcher)
|
|
`(,matcher limit)
|
|
`(re-search-forward ,matcher limit t))
|
|
;; Only highlight matches after the prompt
|
|
(> (match-beginning 0) (car comint-last-prompt))
|
|
;; Make sure we're not in a comment or string
|
|
(let ((state (syntax-ppss)))
|
|
(not (or (nth 3 state)
|
|
(nth 4 state))))))
|
|
,@match-highlights)))))
|
|
|
|
|
|
;;
|
|
;;; Packages
|
|
|
|
;;;###package overseer
|
|
(autoload 'overseer-test "overseer" nil t)
|
|
;; Properly lazy load overseer by not loading it so early:
|
|
(remove-hook 'emacs-lisp-mode-hook #'overseer-enable-mode)
|
|
|
|
|
|
(use-package! flycheck-cask
|
|
:when (and (modulep! :checkers syntax)
|
|
(not (modulep! :checkers syntax +flymake)))
|
|
:defer t
|
|
:init
|
|
(add-hook! 'emacs-lisp-mode-hook
|
|
(add-hook 'flycheck-mode-hook #'flycheck-cask-setup nil t)))
|
|
|
|
|
|
(use-package! flycheck-package
|
|
:when (and (modulep! :checkers syntax)
|
|
(not (modulep! :checkers syntax +flymake)))
|
|
:after flycheck
|
|
:config (flycheck-package-setup))
|
|
|
|
|
|
(use-package! elisp-demos
|
|
:defer t
|
|
:init
|
|
(advice-add #'describe-function-1 :after #'elisp-demos-advice-describe-function-1)
|
|
(advice-add #'helpful-update :after #'elisp-demos-advice-helpful-update)
|
|
:config
|
|
;; Add Doom's core and module demo files, so additional demos can be specified
|
|
;; by end-users (in $DOOMDIR/demos.org), by modules (modules/X/Y/demos.org),
|
|
;; or Doom's core (lisp/demos.org).
|
|
(dolist (file (doom-module-locate-paths (doom-module-list) "demos.org"))
|
|
(add-to-list 'elisp-demos-user-files file))
|
|
|
|
;; HACK: These functions open Org files non-interactively without any
|
|
;; performance optimizations. Given how prone org-mode is to being tied to
|
|
;; expensive functionality, this will often introduce unexpected freezes
|
|
;; without this advice.
|
|
;; TODO: PR upstream?
|
|
(defvar org-inhibit-startup)
|
|
(defvar org-mode-hook)
|
|
(defadvice! +emacs-lisp--optimize-org-init-a (fn &rest args)
|
|
"Disable unrelated functionality to optimize calls to `org-mode'."
|
|
:around #'elisp-demos--export-json-file
|
|
:around #'elisp-demos--symbols
|
|
:around #'elisp-demos--syntax-highlight
|
|
(let ((org-inhibit-startup t)
|
|
(doom-inhibit-local-var-hooks t)
|
|
enable-dir-local-variables
|
|
org-mode-hook)
|
|
(apply fn args))))
|
|
|
|
|
|
(use-package! buttercup
|
|
:defer t
|
|
:minor ("/test[/-].+\\.el$" . buttercup-minor-mode)
|
|
:preface
|
|
;; buttercup.el doesn't define a keymap for `buttercup-minor-mode', as we have
|
|
;; to fool its internal `define-minor-mode' call into thinking one exists, so
|
|
;; it will associate it with the mode.
|
|
(defvar buttercup-minor-mode-map (make-sparse-keymap))
|
|
:config
|
|
(set-popup-rule! "^\\*Buttercup\\*$" :size 0.45 :select nil :ttl 0)
|
|
(set-yas-minor-mode! 'buttercup-minor-mode)
|
|
(when (featurep 'evil)
|
|
(add-hook 'buttercup-minor-mode-hook #'evil-normalize-keymaps))
|
|
(map! :localleader
|
|
:map buttercup-minor-mode-map
|
|
:prefix "t"
|
|
"t" #'+emacs-lisp/buttercup-run-file
|
|
"a" #'+emacs-lisp/buttercup-run-project
|
|
"s" #'buttercup-run-at-point))
|
|
|
|
|
|
(use-package! helpful
|
|
;; a better *help* buffer
|
|
:commands helpful--read-symbol
|
|
:hook (helpful-mode . visual-line-mode)
|
|
:init
|
|
;; Make `apropos' et co search more extensively. They're more useful this way.
|
|
(setq apropos-do-all t)
|
|
|
|
(global-set-key [remap describe-function] #'helpful-callable)
|
|
(global-set-key [remap describe-command] #'helpful-command)
|
|
(global-set-key [remap describe-variable] #'helpful-variable)
|
|
(global-set-key [remap describe-key] #'helpful-key)
|
|
;; (global-set-key [remap describe-symbol] #'helpful-symbol)
|
|
|
|
(defun doom-use-helpful-a (fn &rest args)
|
|
"Force FN to use helpful instead of the old describe-* commands."
|
|
(letf! ((#'describe-function #'helpful-function)
|
|
(#'describe-variable #'helpful-variable))
|
|
(apply fn args)))
|
|
|
|
(after! 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))))))
|
|
|
|
;; DEPRECATED: Remove when support for 29 is dropped.
|
|
(when (= emacs-major-version 29)
|
|
(defadvice! doom--find-function-search-for-symbol-save-excursion-a (fn &rest args)
|
|
"Suppress cursor movement by `find-function-search-for-symbol'.
|
|
|
|
Addresses an unwanted side-effect in `find-function-search-for-symbol' on Emacs
|
|
29 where the cursor is moved to a variable's definition if it's defined in the
|
|
current buffer."
|
|
:around #'find-function-search-for-symbol
|
|
(let (buf pos)
|
|
(letf! (defun find-library-name (library)
|
|
(let ((filename (funcall find-library-name library)))
|
|
(with-current-buffer (find-file-noselect filename)
|
|
(setq buf (current-buffer)
|
|
pos (point)))
|
|
filename))
|
|
(prog1 (apply fn args)
|
|
(when (buffer-live-p buf)
|
|
(with-current-buffer buf (goto-char pos))))))))
|
|
:config
|
|
(setq helpful-set-variable-function #'setq!)
|
|
|
|
(cond ((modulep! :completion ivy)
|
|
(setq counsel-describe-function-function #'helpful-callable
|
|
counsel-describe-variable-function #'helpful-variable
|
|
counsel-descbinds-function #'helpful-callable))
|
|
((modulep! :completion helm)
|
|
(dolist (fn '(helm-describe-variable helm-describe-function))
|
|
(advice-add fn :around #'doom-use-helpful-a))))
|
|
|
|
;; Open help:* links with helpful-* instead of describe-*
|
|
(advice-add #'org-link--open-help :around #'doom-use-helpful-a)
|
|
|
|
(map! :map helpful-mode-map
|
|
:ng "o" #'link-hint-open-link
|
|
:n "gr" #'helpful-update))
|
|
|
|
|
|
;;
|
|
;;; Project modes
|
|
|
|
(def-project-mode! +emacs-lisp-ert-mode
|
|
:modes '(emacs-lisp-mode)
|
|
:match "/test[/-].+\\.el$"
|
|
:add-hooks '(overseer-enable-mode))
|