branch: externals/avy commit 9b1f0bc4279f7ada2e88aeb5892ff5c1ebbf9a0a Merge: c4adda8ca5 955c8dedd6 Author: ELPA Syncer <elpas...@gnu.org> Commit: ELPA Syncer <elpas...@gnu.org>
Merge remote-tracking branch 'refs/remotes/upstream/avy/main' into elpa--merge/avy --- .github/FUNDING.yml | 2 + README.md | 1 + avy.el | 260 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 185 insertions(+), 78 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..b9a5b68820 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +liberapay: abo-abo +patreon: abo_abo diff --git a/README.md b/README.md index 76455de4b2..7f715a798b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[](https://elpa.gnu.org/packages/avy.html) [](https://melpa.org/#/avy) [](https://stable.melpa.org/#/avy) diff --git a/avy.el b/avy.el index 8b258da0c9..d29635d591 100644 --- a/avy.el +++ b/avy.el @@ -1,6 +1,6 @@ ;;; avy.el --- Jump to arbitrary positions in visible text and select text quickly. -*- lexical-binding: t -*- -;; Copyright (C) 2015-2019 Free Software Foundation, Inc. +;; Copyright (C) 2015-2020 Free Software Foundation, Inc. ;; Author: Oleh Krehel <ohwoeo...@gmail.com> ;; URL: https://github.com/abo-abo/avy @@ -192,6 +192,7 @@ If the commands isn't on the list, `avy-style' is used." (?m . avy-action-mark) (?n . avy-action-copy) (?y . avy-action-yank) + (?Y . avy-action-yank-line) (?i . avy-action-ispell) (?z . avy-action-zap-to-char)) "List of actions for `avy-handler-default'. @@ -248,12 +249,16 @@ Typically, these modes don't use the text representation." "In case there is only one candidate jumps directly to it." :type 'boolean) -(defcustom avy-del-last-char-by '(8 127) +(defcustom avy-del-last-char-by '(?\b ?\d) "List of event types, i.e. key presses, that delete the last character read. The default represents `C-h' and `DEL'. See `event-convert-list'." :type 'list) +(defcustom avy-escape-chars '(?\e ?\C-g) + "List of characters that quit avy during `read-char'." + :type 'list) + (defvar avy-ring (make-ring 20) "Hold the window and point history.") @@ -383,7 +388,10 @@ SEQ-LEN is how many elements of KEYS it takes to identify a match." (nreverse path-alist))) (defun avy-order-closest (x) - (abs (- (caar x) (point)))) + (abs (- (if (numberp (car x)) + (car x) + (caar x)) + (point)))) (defvar avy-command nil "Store the current command symbol. @@ -447,15 +455,19 @@ KEYS is the path from the root of `avy-tree' to LEAF." (defvar avy-action nil "Function to call at the end of select.") +(defvar avy-action-oneshot nil + "Function to call once at the end of select.") + (defun avy-handler-default (char) "The default handler for a bad CHAR." (let (dispatch) (cond ((setq dispatch (assoc char avy-dispatch-alist)) - (setq avy-action (cdr dispatch)) + (unless (eq avy-style 'words) + (setq avy-action (cdr dispatch))) (throw 'done 'restart)) - ((memq char '(27 ?\C-g)) + ((memq char avy-escape-chars) ;; exit silently - (throw 'done 'exit)) + (throw 'done 'abort)) ((eq char ??) (avy-show-dispatch-help) (throw 'done 'restart)) @@ -485,8 +497,8 @@ KEYS is the path from the root of `avy-tree' to LEAF." "Store the current incomplete path during `avy-read'.") (defun avy-mouse-event-window (char) - "If CHAR is a mouse event, return the window of the event if any or the selected window. -Return nil if not a mouse event." + "Return the window of mouse event CHAR if any or the selected window. +Return nil if CHAR is not a mouse event." (when (mouse-event-p char) (cond ((windowp (posn-window (event-start char))) (posn-window (event-start char))) @@ -518,13 +530,14 @@ multiple DISPLAY-FN invocations." (funcall cleanup-fn) (if (setq window (avy-mouse-event-window char)) (throw 'done (cons char window)) - ;; Ensure avy-current-path stores the full path prior to - ;; exit so other packages can utilize its value. - (setq avy-current-path - (concat avy-current-path (string (avy--key-to-char char)))) (if (setq branch (assoc char tree)) - (if (eq (car (setq tree (cdr branch))) 'leaf) - (throw 'done (cdr tree))) + (progn + ;; Ensure avy-current-path stores the full path prior to + ;; exit so other packages can utilize its value. + (setq avy-current-path + (concat avy-current-path (string (avy--key-to-char char)))) + (if (eq (car (setq tree (cdr branch))) 'leaf) + (throw 'done (cdr tree)))) (funcall avy-handler-function char))))))) (defun avy-read-de-bruijn (lst keys) @@ -653,7 +666,7 @@ Commands using `avy-with' macro can be resumed." (defmacro avy-with (command &rest body) "Set `avy-keys' according to COMMAND and execute BODY. -Set `avy-style' according to COMMMAND as well." +Set `avy-style' according to COMMAND as well." (declare (indent 1) (debug (form body))) `(let ((avy-keys (or (cdr (assq ',command avy-keys-alist)) @@ -665,7 +678,9 @@ Set `avy-style' according to COMMMAND as well." (setf (symbol-function 'avy-resume) (lambda () (interactive) - ,@body)) + ,@(if (eq command 'avy-goto-char-timer) + (cdr body) + body))) ,@body)) (defun avy-action-goto (pt) @@ -709,6 +724,11 @@ Set `avy-style' according to COMMMAND as well." (yank) t) +(defun avy-action-yank-line (pt) + "Yank sexp starting at PT at the current point." + (let ((avy-command 'avy-goto-line)) + (avy-action-yank pt))) + (defun avy-action-kill-move (pt) "Kill sexp at PT and move there." (goto-char pt) @@ -748,6 +768,11 @@ Set `avy-style' according to COMMMAND as well." (declare-function flyspell-correct-word-before-point "flyspell") +(defcustom avy-flyspell-correct-function #'flyspell-correct-word-before-point + "Function called to correct word by `avy-action-ispell' when +`flyspell-mode' is enabled." + :type 'function) + (defun avy-action-ispell (pt) "Auto correct word at PT." (save-excursion @@ -758,7 +783,7 @@ Set `avy-style' according to COMMMAND as well." (line-beginning-position) (line-end-position))) ((bound-and-true-p flyspell-mode) - (flyspell-correct-word-before-point)) + (funcall avy-flyspell-correct-function)) ((looking-at-p "\\b") (ispell-word)) (t @@ -837,6 +862,7 @@ Set `avy-style' according to COMMMAND as well." (when (< pos (1- (length lst))) (goto-char (caar (nth (1+ pos) lst))))))) +;;;###autoload (defun avy-process (candidates &optional overlay-fn cleanup-fn) "Select one of CANDIDATES using `avy-read'. Use OVERLAY-FN to visualize the decision overlay. @@ -854,19 +880,24 @@ multiple OVERLAY-FN invocations." (res (avy--process-1 candidates overlay-fn cleanup-fn))) (cond ((null res) - (message "zero candidates") - t) + (if (and (eq avy-style 'words) candidates) + (avy-process original-cands overlay-fn cleanup-fn) + (message "zero candidates") + t)) ((eq res 'restart) (avy-process original-cands overlay-fn cleanup-fn)) ;; ignore exit from `avy-handler-function' ((eq res 'exit)) + ((eq res 'abort) + nil) (t (funcall avy-pre-action res) (setq res (car res)) - (funcall (or avy-action 'avy-action-goto) - (if (consp res) - (car res) - res)) + (let ((action (or avy-action avy-action-oneshot 'avy-action-goto))) + (funcall action + (if (consp res) + (car res) + res))) res)))) (define-obsolete-function-alias 'avy--process 'avy-process @@ -950,9 +981,12 @@ When GROUP is non-nil, (BEG . END) should delimit that regex group." (when (avy--visible-p (1- (point))) (when (or (null pred) (funcall pred)) - (push (cons (cons (match-beginning group) - (match-end group)) - wnd) candidates))))))) + (push (cons + (if (numberp group) + (cons (match-beginning group) + (match-end group)) + (funcall group)) + wnd) candidates))))))) (nreverse candidates))) (defvar avy--overlay-offset 0 @@ -985,10 +1019,11 @@ COMPOSE-FN is a lambda that concatenates the old string at BEG with STR." (os-line-prefix (get-text-property 0 'line-prefix old-str)) (os-wrap-prefix (get-text-property 0 'wrap-prefix old-str)) other-ol) - (when os-line-prefix - (add-text-properties 0 1 `(line-prefix ,os-line-prefix) str)) - (when os-wrap-prefix - (add-text-properties 0 1 `(wrap-prefix ,os-wrap-prefix) str)) + (unless (= (length str) 0) + (when os-line-prefix + (add-text-properties 0 1 `(line-prefix ,os-line-prefix) str)) + (when os-wrap-prefix + (add-text-properties 0 1 `(wrap-prefix ,os-wrap-prefix) str))) (when (setq other-ol (cl-find-if (lambda (o) (overlay-get o 'goto-address)) (overlays-at beg))) @@ -1046,19 +1081,22 @@ Do this even when the char is terminating." "Create an overlay with PATH at LEAF. PATH is a list of keys from tree root to LEAF. LEAF is normally ((BEG . END) . WND)." - (let* ((path (mapcar #'avy--key-to-char path)) - (str (propertize (apply #'string (reverse path)) - 'face 'avy-lead-face))) - (when (or avy-highlight-first (> (length str) 1)) - (set-text-properties 0 1 '(face avy-lead-face-0) str)) - (setq str (concat - (propertize avy-current-path - 'face 'avy-lead-face-1) - str)) - (avy--overlay - str - (avy-candidate-beg leaf) nil - (avy-candidate-wnd leaf)))) + (if (with-selected-window (cdr leaf) + (bound-and-true-p visual-line-mode)) + (avy--overlay-at-full path leaf) + (let* ((path (mapcar #'avy--key-to-char path)) + (str (propertize (apply #'string (reverse path)) + 'face 'avy-lead-face))) + (when (or avy-highlight-first (> (length str) 1)) + (set-text-properties 0 1 '(face avy-lead-face-0) str)) + (setq str (concat + (propertize avy-current-path + 'face 'avy-lead-face-1) + str)) + (avy--overlay + str + (avy-candidate-beg leaf) nil + (avy-candidate-wnd leaf))))) (defun avy--overlay-at (path leaf) "Create an overlay with PATH at LEAF. @@ -1223,20 +1261,22 @@ exist." (ignore #'ignore) (t (error "Unexpected style %S" style)))) -(cl-defun avy-jump (regex &key window-flip beg end action pred) +(cl-defun avy-jump (regex &key window-flip beg end action pred group) "Jump to REGEX. The window scope is determined by `avy-all-windows'. When WINDOW-FLIP is non-nil, do the opposite of `avy-all-windows'. BEG and END narrow the scope where candidates are searched. ACTION is a function that takes point position as an argument. -When PRED is non-nil, it's a filter for matching point positions." +When PRED is non-nil, it's a filter for matching point positions. +When GROUP is non-nil, it's either a match group in REGEX, or a function +that returns a cons of match beginning and end." (setq avy-action (or action avy-action)) (let ((avy-all-windows (if window-flip (not avy-all-windows) avy-all-windows))) (avy-process - (avy--regex-candidates regex beg end pred)))) + (avy--regex-candidates regex beg end pred group)))) (defun avy--generic-jump (regex window-flip &optional beg end) "Jump to REGEX. @@ -1281,8 +1321,18 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." The window scope is determined by `avy-all-windows'. When ARG is non-nil, do the opposite of `avy-all-windows'. BEG and END narrow the scope where candidates are searched." - (interactive (list (read-char "char 1: " t) - (read-char "char 2: " t) + (interactive (list (let ((c1 (read-char "char 1: " t))) + (if (memq c1 '(? ?\b)) + (keyboard-quit) + c1)) + (let ((c2 (read-char "char 2: " t))) + (cond ((eq c2 ?) + (keyboard-quit)) + ((memq c2 avy-del-last-char-by) + (keyboard-escape-quit) + (call-interactively 'avy-goto-char-2)) + (t + c2))) current-prefix-arg nil nil)) (when (eq char1 ? ) @@ -1331,12 +1381,14 @@ When ARG is non-nil, do the opposite of `avy-all-windows'." "Jump to one of the current isearch candidates." (interactive) (avy-with avy-isearch - (let ((avy-background nil)) - (avy-process - (avy--regex-candidates (if isearch-regexp - isearch-string - (regexp-quote isearch-string)))) - (isearch-done)))) + (let ((avy-background nil) + (avy-case-fold-search case-fold-search)) + (prog1 + (avy-process + (avy--regex-candidates (if isearch-regexp + isearch-string + (regexp-quote isearch-string)))) + (isearch-done))))) ;;;###autoload (defun avy-goto-word-0 (arg &optional beg end) @@ -1351,6 +1403,20 @@ BEG and END narrow the scope where candidates are searched." :beg beg :end end))) +;;;###autoload +(defun avy-goto-whitespace-end (arg &optional beg end) + "Jump to the end of a whitespace sequence. +The window scope is determined by `avy-all-windows'. +When ARG is non-nil, do the opposite of `avy-all-windows'. +BEG and END narrow the scope where candidates are searched." + (interactive "P") + (avy-with avy-goto-whitespace-end + (avy-jump "[ \t]+\\|\n[ \t]*" + :window-flip arg + :beg beg + :end end + :group (lambda () (cons (point) (1+ (point))))))) + (defun avy-goto-word-0-above (arg) "Jump to a word start between window start and point. The window scope is determined by `avy-all-windows'. @@ -1367,6 +1433,22 @@ When ARG is non-nil, do the opposite of `avy-all-windows'." (avy-with avy-goto-word-0 (avy-goto-word-0 arg (point) (window-end (selected-window) t)))) +(defun avy-goto-whitespace-end-above (arg) + "Jump to the end of a whitespace sequence between point and window end. +The window scope is determined by `avy-all-windows'. +When ARG is non-nil, do the opposite of `avy-all-windows'." + (interactive "P") + (avy-with avy-goto-whitespace-end + (avy-goto-whitespace-end arg (window-start) (point)))) + +(defun avy-goto-whitespace-end-below (arg) + "Jump to the end of a whitespace sequence between window start and point. +The window scope is determined by `avy-all-windows'. +When ARG is non-nil, do the opposite of `avy-all-windows'." + (interactive "P") + (avy-with avy-goto-whitespace-end + (avy-goto-whitespace-end arg (point) (window-end (selected-window) t)))) + ;;;###autoload (defun avy-goto-word-1 (char &optional arg beg end symbol) "Jump to the currently visible CHAR at a word start. @@ -1490,13 +1572,15 @@ BEG and END narrow the scope where candidates are searched." (when (or (null predicate) (and predicate (funcall predicate))) (unless (not (avy--visible-p (point))) - (push (cons (point) (selected-window)) window-cands))) + (push (cons (cons (point) (1+ (point))) + (selected-window)) window-cands))) (subword-backward)) (and (= (point) ws) (or (null predicate) (and predicate (funcall predicate))) (not (get-char-property (point) 'invisible)) - (push (cons (point) (selected-window)) window-cands))) + (push (cons (cons (point) (1+ (point))) + (selected-window)) window-cands))) (setq candidates (nconc candidates window-cands)))))) (avy-process candidates)))) @@ -1526,7 +1610,8 @@ Which one depends on variable `subword-mode'." (defvar visual-line-mode) (defcustom avy-indent-line-overlay nil - "When non-nil, `avy-goto-line' will display the line overlay next to the first non-whitespace character of each line." + "When non-nil, display line overlay next to the first non-whitespace character. +This affects `avy-goto-line'." :type 'boolean) (defun avy--line-cands (&optional arg beg end bottom-up) @@ -1554,9 +1639,7 @@ When BOTTOM-UP is non-nil, display avy candidates from top to bottom" (point))) (selected-window)) candidates)) (if visual-line-mode - (progn - (setq temporary-goal-column 0) - (line-move-visual 1 t)) + (line-move-visual 1 t) (forward-line 1))))))) (if bottom-up candidates @@ -1651,8 +1734,8 @@ The window scope is determined by `avy-all-windows'. When ARG is non-nil, do the opposite of `avy-all-windows'. BEG and END narrow the scope where candidates are searched. When BOTTOM-UP is non-nil, display avy candidates from top to bottom" - (let ((avy-action #'identity) - (avy-style (if avy-linum-mode + (setq avy-action (or avy-action #'identity)) + (let ((avy-style (if avy-linum-mode (progn (message "Goto line:") 'ignore) @@ -1694,7 +1777,7 @@ Otherwise, forward to `goto-line' with ARG." (forward-line (1- (string-to-number line)))) (throw 'done 'exit)))))) (r (avy--line (eq arg 4)))) - (unless (eq r t) + (when (and (not (memq r '(t nil))) (eq avy-action #'identity)) (avy-action-goto r)))))) ;;;###autoload @@ -1972,6 +2055,9 @@ newline." "Whether enter exits avy-goto-char-timer early. If nil it matches newline" :type 'boolean) +(defvar avy-text "" + "Store the input read by `avy--read-candidates'.") + (defun avy--read-candidates (&optional re-builder) "Read as many chars as possible and return their occurrences. At least one char must be read, and then repeatedly one next char @@ -1985,8 +2071,8 @@ RE-BUILDER is a function that takes a string and returns a regex. When nil, `regexp-quote' is used. If a group is captured, the first group is highlighted. Otherwise, the whole regex is highlighted." - (let ((str "") - (re-builder (or re-builder #'regexp-quote)) + (setq avy-text "") + (let ((re-builder (or re-builder #'regexp-quote)) char break overlays regex) (unwind-protect (progn @@ -1996,11 +2082,11 @@ Otherwise, the whole regex is highlighted." (setq char (read-char (format "%d char%s: " (length overlays) - (if (string= str "") - str - (format " (%s)" str))) + (if (string= avy-text "") + avy-text + (format " (%s)" avy-text))) t - (and (not (string= str "")) + (and (not (string= avy-text "")) avy-timeout-seconds)))) ;; Unhighlight (dolist (ov overlays) @@ -2011,21 +2097,21 @@ Otherwise, the whole regex is highlighted." ((= char 13) (if avy-enter-times-out (setq break t) - (setq str (concat str (list ?\n))))) + (setq avy-text (concat avy-text (list ?\n))))) ;; Handle C-h, DEL ((memq char avy-del-last-char-by) - (let ((l (length str))) + (let ((l (length avy-text))) (when (>= l 1) - (setq str (substring str 0 (1- l)))))) + (setq avy-text (substring avy-text 0 (1- l)))))) ;; Handle ESC ((= char 27) (keyboard-quit)) (t - (setq str (concat str (list char))))) + (setq avy-text (concat avy-text (list char))))) ;; Highlight - (when (>= (length str) 1) + (when (>= (length avy-text) 1) (let ((case-fold-search - (or avy-case-fold-search (string= str (downcase str)))) + (or avy-case-fold-search (string= avy-text (downcase avy-text)))) found) (avy-dowindows current-prefix-arg (dolist (pair (avy--find-visible-regions @@ -2033,7 +2119,7 @@ Otherwise, the whole regex is highlighted." (window-end (selected-window) t))) (save-excursion (goto-char (car pair)) - (setq regex (funcall re-builder str)) + (setq regex (funcall re-builder avy-text)) (while (re-search-forward regex (cdr pair) t) (unless (not (avy--visible-p (1- (point)))) (let* ((idx (if (= (length (match-data)) 4) 1 0)) @@ -2056,6 +2142,8 @@ Otherwise, the whole regex is highlighted." (delete-overlay ov)) (avy--done)))) +(defvar avy--old-cands nil) + ;;;###autoload (defun avy-goto-char-timer (&optional arg) "Read one or many consecutive chars and jump to the first one. @@ -2065,8 +2153,8 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." (not avy-all-windows) avy-all-windows))) (avy-with avy-goto-char-timer - (avy-process - (avy--read-candidates))))) + (setq avy--old-cands (avy--read-candidates)) + (avy-process avy--old-cands)))) (defun avy-push-mark () "Store the current point and window." @@ -2094,6 +2182,22 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." (error (set-mark-command 4))))) +;;;###autoload +(defun avy-transpose-lines-in-region () + "Transpose lines in the active region." + (interactive) + (when (and (use-region-p) (> (count-lines (region-beginning) (region-end)) 1)) + (let ((avy-all-windows nil) + (fst-line-point (avy--line nil (region-beginning) (region-end)))) + (when fst-line-point + (let ((snd-line-point (avy--line nil (region-beginning) (region-end)))) + (when snd-line-point + (save-mark-and-excursion + (push-mark fst-line-point) + (goto-char snd-line-point) + (transpose-lines 0)) + (avy-transpose-lines-in-region))))))) + ;; ** Org-mode (defvar org-reverse-note-order) (declare-function org-refile "org")