From 20dd62e4405b449e9dca047b3fa4b445b85a3073 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Fri, 27 Dec 2024 16:26:40 +0100 Subject: [PATCH] New commands 'elisp-next/prev-occurrence' --- lisp/progmodes/elisp-mode.el | 95 +++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index 4003628dcb0..d5a28f0041e 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -57,7 +57,9 @@ All commands in `lisp-mode-shared-map' are inherited by this map." "C-c C-f" #'elisp-byte-compile-file "C-c C-b" #'elisp-byte-compile-buffer "C-M-q" #'indent-pp-sexp - "C-c M-e" #'macrostep-expand) + "C-c M-e" #'macrostep-expand + "C-c C-n" #'elisp-next-occurrence + "C-c C-p" #'elisp-prev-occurrence) (easy-menu-define emacs-lisp-mode-menu emacs-lisp-mode-map "Menu for Emacs Lisp mode." @@ -1033,34 +1035,39 @@ namespace but with lower confidence." (elisp--xref-filter-definitions defs 'any sym)) (elisp--xref-filter-definitions defs namespace sym)))))))) +(defun elisp-local-references (pos) + "Return references to local variable at POS as (BEG . LEN) cons cells." + (let (all cur) + (save-excursion + (goto-char pos) + (beginning-of-defun) + (scope (lambda (_type beg len bin) + (when (<= beg pos (+ beg len)) + (setq cur bin)) + (when bin (setf (alist-get beg all) (list len bin)))) + (current-buffer))) + (seq-keep + (pcase-lambda (`(,sym ,len ,bin)) (when (equal bin cur) (cons sym len))) + all))) + (cl-defmethod xref-backend-references :around ((backend (eql 'elisp)) identifier) (let* ((pos (get-text-property 0 'pos identifier)) - all dec) - (when pos - (save-excursion - (goto-char pos) - (beginning-of-defun) - (scope (lambda (_type beg len bin) - (when (<= beg pos (+ beg len)) - (setq dec bin)) - (when bin (setf (alist-get beg all) (list len bin)))) - (current-buffer)))) - (if dec + (all (elisp-local-references pos))) + (if all (let (res) - (pcase-dolist (`(,sym ,len ,bin) all) - (when (equal bin dec) - (let* ((beg-end (save-excursion - (goto-char sym) - (cons (pos-bol) (pos-eol)))) - (beg (car beg-end)) - (end (cdr beg-end)) - (line (buffer-substring-no-properties beg end)) - (cur (- sym beg))) - (add-face-text-property cur (+ len cur) - 'xref-match t line) - (push (xref-make line (xref-make-buffer-location - (current-buffer) sym)) - res)))) + (pcase-dolist (`(,sym . ,len) all) + (let* ((beg-end (save-excursion + (goto-char sym) + (cons (pos-bol) (pos-eol)))) + (beg (car beg-end)) + (end (cdr beg-end)) + (line (buffer-substring-no-properties beg end)) + (cur (- sym beg))) + (add-face-text-property cur (+ len cur) + 'xref-match t line) + (push (xref-make line (xref-make-buffer-location + (current-buffer) sym)) + res))) res) (cl-call-next-method backend identifier)))) @@ -2278,7 +2285,7 @@ Otherwise, call `eval-buffer'." "Byte compile the file the current buffer is visiting. If LOAD is non-nil, load the resulting .elc file. When called interactively, this is the prefix argument." - (interactive "P") + (interactive "P" elisp-mode) (unless buffer-file-name (error "This buffer is not visiting a file")) (byte-compile-file buffer-file-name) @@ -2289,7 +2296,7 @@ interactively, this is the prefix argument." "Byte compile the current buffer, but don't write a file. If LOAD is non-nil, load byte-compiled data. When called interactively, this is the prefix argument." - (interactive "P") + (interactive "P" elisp-mode) (let ((bfn buffer-file-name) file elc) (require 'bytecomp) @@ -2305,7 +2312,7 @@ interactively, this is the prefix argument." (byte-compile-log-warning-function (lambda (string position &optional fill level) (if bfn - ;; Massage the warnings to that they point to + ;; Massage the warnings so that they point to ;; this file, not the one in /tmp. (let ((byte-compile-current-file bfn) (byte-compile-root-dir (file-name-directory bfn))) @@ -2329,6 +2336,36 @@ interactively, this is the prefix argument." (when (file-exists-p elc) (delete-file elc)))))) +(defun elisp-prev-occurrence (n) + "Go to previous Nth occurrence of local variable at point." + (interactive "p" emacs-lisp-mode) + (elisp-next-occurrence (- n))) + +(defun elisp-next-occurrence (n) + "Go to next Nth occurrence of local variable at point." + (interactive "p" emacs-lisp-mode) + (if-let ((pos (point)) + (next (nth (1- (abs n)) + (sort (seq-filter (pcase-lambda (`(,beg . ,_)) + (and (eq (< pos beg) (< 0 n)) + (not (= pos beg)))) + (or (elisp-local-references pos) + (user-error "No local variable at point"))) + :reverse (< n 0)))) + (beg (car next)) + (end (+ beg (cdr next)))) + (progn + (goto-char beg) + (pulse-momentary-highlight-region beg end 'highlight) + (when elisp-next-occurrence-repeat-map + (set-transient-map elisp-next-occurrence-repeat-map))) + (user-error "No next occurrence"))) + +(defvar-keymap elisp-next-occurrence-repeat-map + :doc "Repeat keymap for `elisp-next-occurrence' and `elisp-prev-occurrence'." + "C-n" #'elisp-next-occurrence + "C-p" #'elisp-prev-occurrence) + (put 'read-symbol-shorthands 'safe-local-variable #'consp) -- 2.39.5