From d538f0a8dbccdba620dfb4bb9fd5f7a4e7449d2c Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Fri, 17 Jan 2025 12:27:12 +0100 Subject: [PATCH] (elisp-extract-local-variable): Various improvements --- lisp/progmodes/elisp-mode.el | 109 +++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index ad65e0700aa..3544e9bb7fc 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -2371,58 +2371,91 @@ interactively, this is the prefix argument." (defvar elisp-extract-local-variable-name-history nil) -(defun elisp-extract-to-local-variable (beg end target var) - "Extract BEG to END region to local VAR bound at TARGET." +(defun elisp-extract-to-local-variable (regions target var) + "Extract REGIONS to a new local VAR and bind it at TARGET. + +REGIONS is a list of cons cells (BEG . END), each specifying a +region (from BEG to END) to replace with a reference to VAR. + +TARGET is the start position of a form FORM to wrap with a `let' that +introduces VAR. If FORM is already a `let' form, add another binding to +FORM instead of wrapping it with another `let'. + +Interactively, use the active region as the sole element of REGIONS, and +prompt for TARGET and VAR. With a prefix argument, extend REGIONS with +any additional occurrences of the form in the active region in the scope +of TARGET." (interactive - (let ((beg (region-beginning)) - (end (region-end)) - (max 0) - (targets nil)) + (let ((beg (region-beginning)) (end (region-end)) (max 0) targets) + (unless (use-region-p) (user-error "No active region")) (save-excursion (goto-char beg) (beginning-of-defun-raw) (scope (lambda (type sbeg len bin) (and (<= sbeg beg) (memq type '(function macro special-form top-level)) - (push (save-excursion (nth 1 (syntax-ppss sbeg))) targets)) + (push (nth 1 (syntax-ppss sbeg)) targets)) (let ((send (+ sbeg len))) (and (<= beg sbeg send end) (numberp bin) (< bin beg) (setq max (max max bin))))))) (let* ((target - (if-let ((avy-action #'ignore) - (targets (seq-drop-while - (apply-partially #'> max) - (sort (seq-intersection - (nth 9 (syntax-ppss)) targets #'=))))) - (or (avy-process targets) (keyboard-quit)) + (if-let ((avy-action #'ignore) ;Just return selection. + (cands (seq-drop-while + (apply-partially #'> max) + (sort (seq-intersection + (nth 9 (syntax-ppss)) targets #'=))))) + (or (avy-process cands) (keyboard-quit)) (user-error "No valid targets"))) - (tarend (save-excursion (goto-char target) (scan-sexps (point) 1))) - (ovbeg (make-overlay target (1+ target))) - (ovend (make-overlay tarend (1+ tarend)))) - (overlay-put ovbeg 'face 'show-paren-match) - (overlay-put ovend 'face 'show-paren-match) - (list beg end target + (tarend (save-excursion + (goto-char target) + (1- (scan-sexps (point) 1)))) + others ovothers ovbeg ovend) + (when (and current-prefix-arg (fboundp 'el-search-forward)) + (save-excursion + (goto-char target) + (while (el-search-forward + (list '\` (read (buffer-substring beg end))) + tarend t) + (let ((obeg (point)) (oend (1- (scan-sexps (point) 1)))) + (goto-char oend) + (when (or (< oend beg) (< end obeg)) ;No overlap. + (push (cons obeg oend) others) + (let ((ovb (make-overlay obeg (1+ obeg))) + (ove (make-overlay oend (1+ oend)))) + (overlay-put ovb 'face 'isearch) + (overlay-put ove 'face 'isearch) + (push ovb ovothers) + (push ove ovothers))))))) + (setq ovbeg (make-overlay target (1+ target))) + (setq ovend (make-overlay tarend (1+ tarend))) + (overlay-put ovbeg 'face 'lazy-highlight) + (overlay-put ovend 'face 'lazy-highlight) + (list `((,beg . ,end) . ,others) target (unwind-protect - (read-string (format-prompt "Extract region to local var called" "v") - nil 'elisp-extract-local-variable-name-history "v") - (delete-overlay ovbeg) - (delete-overlay ovend))))) + (read-string + (format-prompt "Extract region to var called" "v") + nil 'elisp-extract-local-variable-name-history "v") + (mapc #'delete-overlay `(,ovbeg ,ovend . ,ovothers)))))) emacs-lisp-mode) - (let ((reg (delete-and-extract-region beg end))) - (goto-char beg) - (insert var) - (let ((pos (copy-marker (point)))) - (goto-char target) - (pcase (save-excursion (read (current-buffer))) - (`(,(or 'let 'let* 'if-let* 'when-let* 'while-let*) . ,_) - (down-list 2) - (insert "(" var " " reg ")\n")) - (_ - (insert "(let ((" var " " reg "))\n") - (goto-char (scan-sexps (point) 1)) - (insert ")"))) - (prog-indent-sexp 'defun) - (goto-char pos)))) + (let* ((beg-end (car regions)) (beg (car beg-end)) (end (cdr beg-end)) + (pos (copy-marker beg t)) + (reg (buffer-substring-no-properties beg end))) + (pcase-dolist + (`(,b . ,e) (sort regions :key #'cdr :reverse t)) + (goto-char b) + (delete-region b e) + (insert var)) + (goto-char target) + (if (memq (car-safe (save-excursion (read (current-buffer)))) + '(let let* if-let* when-let* while-let*)) + ;; Add binding to existing `let'. + (progn (down-list 2) (insert "(" var " " reg ")\n")) + ;; Wrap with new `let'. + (insert "(let ((" var " " reg "))\n") + (goto-char (scan-sexps (point) 1)) + (insert ")")) + (prog-indent-sexp 'defun) + (goto-char pos))) (put 'read-symbol-shorthands 'safe-local-variable #'consp) -- 2.39.5