branch: master commit f727b53504076bf752e14b8898a99968ae1db2d4 Merge: 98073fe 8d38a89 Author: Oleh Krehel <ohwoeo...@gmail.com> Commit: Oleh Krehel <ohwoeo...@gmail.com>
Merge commit '8d38a898f23b3105c5d098f0cfb6c3383547e394' from avy --- packages/avy/Makefile | 2 +- packages/avy/README.md | 8 +- packages/avy/avy.el | 213 +++++++++++++++++++++++++++++++++----- packages/avy/doc/Changelog.org | 211 +++++++++++++++++++++++++++++++++++++ packages/avy/targets/avy-init.el | 9 +- 5 files changed, 408 insertions(+), 35 deletions(-) diff --git a/packages/avy/Makefile b/packages/avy/Makefile index 6e00ec3..9303c7c 100644 --- a/packages/avy/Makefile +++ b/packages/avy/Makefile @@ -5,7 +5,7 @@ LOAD = -l avy.el -l avy-test.el .PHONY: all test clean -all: test +all: compile test test: $(emacs) -batch $(LOAD) -f ert-run-tests-batch-and-exit diff --git a/packages/avy/README.md b/packages/avy/README.md index 1479986..9df0ce2 100644 --- a/packages/avy/README.md +++ b/packages/avy/README.md @@ -13,12 +13,12 @@ You can bind some of these useful commands in your config. > Input one char, jump to it with a tree. ```elisp -(global-set-key (kbd "π") 'avy-goto-char) +(global-set-key (kbd "C-:") 'avy-goto-char) ``` -After <kbd>πb</kbd>: +After <kbd>C-: b</kbd>: - + ### `avy-goto-char-2` @@ -93,6 +93,8 @@ It will bind, for example, `avy-isearch` to <kbd>C-'</kbd> in `isearch-mode-map` See the comprehensive custom variable list on [the defcustom wiki page](https://github.com/abo-abo/avy/wiki/defcustom). +See how to write your own avy commands on [the custom-commands wiki page](https://github.com/abo-abo/avy/wiki/custom-commands). + ## Contributing ### Copyright Assignment diff --git a/packages/avy/avy.el b/packages/avy/avy.el index ce48da5..a8a6a25 100644 --- a/packages/avy/avy.el +++ b/packages/avy/avy.el @@ -4,7 +4,7 @@ ;; Author: Oleh Krehel <ohwoeo...@gmail.com> ;; URL: https://github.com/abo-abo/avy -;; Version: 0.2.1 +;; Version: 0.3.0 ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) ;; Keywords: point, location @@ -75,14 +75,15 @@ (const avy-move-line)) :value-type (repeat :tag "Keys" character))) -(defcustom avy-style 'pre +(defcustom avy-style 'at-full "The default method of displaying the overlays. Use `avy-styles-alist' to customize this per-command." :type '(choice (const :tag "Pre" pre) (const :tag "At" at) (const :tag "At Full" at-full) - (const :tag "Post" post))) + (const :tag "Post" post) + (const :tag "De Bruijn" de-bruijn))) (defcustom avy-styles-alist nil "Alist of avy-jump commands to the style for each command. @@ -129,6 +130,15 @@ When nil, punctuation chars will not be matched. \"[!-/:-@[-`{-~]\" will match all printable punctuation chars." :type 'regexp) +(defcustom avy-ignored-modes '(image-mode doc-view-mode pdf-view-mode) + "List of modes to ignore when searching for candidates. +Typically, these modes don't use the text representation.") + +(defvar avy-translate-char-function #'identity + "Function to translate user input key into another key. +For example, to make SPC do the same as ?a, use +\(lambda (c) (if (= c 32) ?a c)).") + (defface avy-lead-face-0 '((t (:foreground "white" :background "#4f57f9"))) "Face used for first non-terminating leading chars.") @@ -137,6 +147,10 @@ When nil, punctuation chars will not be matched. '((t (:foreground "white" :background "gray"))) "Face used for matched leading chars.") +(defface avy-lead-face-2 + '((t (:foreground "white" :background "#f86bf3"))) + "Face used for leading chars.") + (defface avy-lead-face '((t (:foreground "white" :background "#e52b50"))) "Face used for the leading chars.") @@ -145,6 +159,14 @@ When nil, punctuation chars will not be matched. '((t (:foreground "gray40"))) "Face for whole window background during selection.") +(defconst avy-lead-faces '(avy-lead-face + avy-lead-face-0 + avy-lead-face-2 + avy-lead-face + avy-lead-face-0 + avy-lead-face-2) + "Face sequence for `avy--overlay-at-full'.") + ;;* Internals ;;** Tree (defmacro avy-multipop (lst n) @@ -157,6 +179,71 @@ When nil, punctuation chars will not be matched. (nthcdr (1- ,n) (prog1 ,lst (setq ,lst (nthcdr ,n ,lst)))) nil)))) +(defun avy--de-bruijn (keys n) + "De Bruijn sequence for alphabet KEYS and subsequences of length N." + (let* ((k (length keys)) + (a (make-list (* n k) 0)) + sequence) + (cl-labels ((db (T p) + (if (> T n) + (if (eq (% n p) 0) + (setq sequence + (append sequence + (cl-subseq a 1 (1+ p))))) + (setf (nth T a) (nth (- T p) a)) + (db (1+ T) p) + (cl-loop for j from (1+ (nth (- T p) a)) to (1- k) do + (setf (nth T a) j) + (db (1+ T) T))))) + (db 1 1) + (mapcar (lambda (n) + (nth n keys)) + sequence)))) + +(defun avy--path-alist-1 (lst seq-len keys) + "Build a De Bruin sequence from LST. +SEQ-LEN is how many elements of KEYS it takes to identify a match." + (let ((db-seq (avy--de-bruijn keys seq-len)) + prev-pos prev-seq prev-win path-alist) + ;; The De Bruijn seq is cyclic, so append the seq-len - 1 first chars to + ;; the end. + (setq db-seq (nconc db-seq (cl-subseq db-seq 0 (1- seq-len)))) + (cl-labels ((subseq-and-pop () + (when (nth (1- seq-len) db-seq) + (prog1 (cl-subseq db-seq 0 seq-len) + (pop db-seq))))) + (while lst + (let* ((cur (car lst)) + (pos (cond + ;; ace-window has matches of the form (pos . wnd) + ((integerp (car cur)) (car cur)) + ;; avy-jump have form ((start . end) . wnd) + ((consp (car cur)) (caar cur)) + (t (error "Unexpected match representation: %s" cur)))) + (win (cdr cur)) + (path (if prev-pos + (let ((diff (if (eq win prev-win) + (- pos prev-pos) + 0))) + (when (and (> diff 0) (< diff seq-len)) + (while (and (nth (1- seq-len) db-seq) + (not + (eq 0 (cl-search + (cl-subseq prev-seq diff) + (cl-subseq db-seq 0 seq-len))))) + (pop db-seq))) + (subseq-and-pop)) + (subseq-and-pop)))) + (if (not path) + (setq lst nil + path-alist nil) + (push (cons path (car lst)) path-alist) + (setq prev-pos pos + prev-seq path + prev-win win + lst (cdr lst)))))) + (nreverse path-alist))) + (defun avy-tree (lst keys) "Coerce LST into a balanced tree. The degree of the tree is the length of KEYS. @@ -237,7 +324,7 @@ multiple DISPLAY-FN invokations." (push (cons path leaf) avy--leafs))) (dolist (x avy--leafs) (funcall display-fn (car x) (cdr x)))) - (let ((char (read-char)) + (let ((char (funcall avy-translate-char-function (read-char))) branch) (funcall cleanup-fn) (if (setq branch (assoc char tree)) @@ -247,6 +334,40 @@ multiple DISPLAY-FN invokations." (concat avy-current-path (string char)))) (funcall avy-handler-function char)))))) +(defun avy-read-de-bruijn (lst keys) + "Select from LST dispatching on KEYS." + ;; In theory, the De Bruijn sequence B(k,n) has k^n subsequences of length n + ;; (the path length) usable as paths, thus that's the lower bound. Due to + ;; partially overlapping matches, not all subsequences may be usable, so it's + ;; possible that the path-len must be incremented, e.g., if we're matching + ;; for x and a buffer contains xaxbxcx only every second subsequence is + ;; usable for the four matches. + (let* ((path-len (ceiling (log (length lst) (length keys)))) + (alist (avy--path-alist-1 lst path-len keys))) + (while (not alist) + (cl-incf path-len) + (setq alist (avy--path-alist-1 lst path-len keys))) + (let* ((len (length (caar alist))) + (i 0)) + (setq avy-current-path "") + (while (< i len) + (dolist (x (reverse alist)) + (avy--overlay-at-full (reverse (car x)) (cdr x))) + (let ((char (funcall avy-translate-char-function (read-char)))) + (avy--remove-leading-chars) + (setq alist + (delq nil + (mapcar (lambda (x) + (when (eq (caar x) char) + (cons (cdr (car x)) (cdr x)))) + alist))) + (setq avy-current-path + (concat avy-current-path (string char))) + (cl-incf i) + (unless alist + (funcall avy-handler-function char)))) + (cdar alist)))) + ;;** Rest (defun avy-window-list () "Return a list of windows depending on `avy-all-windows'." @@ -271,7 +392,7 @@ multiple DISPLAY-FN invokations." avy-all-windows))) (dolist (wnd (avy-window-list)) (with-selected-window wnd - (unless (memq major-mode '(image-mode doc-view-mode)) + (unless (memq major-mode avy-ignored-modes) ,@body))))) (defmacro avy--with-avy-keys (command &rest body) @@ -295,7 +416,11 @@ POS is either a position or (BEG . END)." ((eq x 'exit)) (t - (select-window (cdr x)) + (let* ((window (cdr x)) + (frame (window-frame window))) + (unless (equal frame (selected-frame)) + (select-frame-set-input-focus frame)) + (select-window window)) (let ((pt (car x))) (when (consp pt) (setq pt (car pt))) @@ -314,9 +439,12 @@ Use OVERLAY-FN to visualize the decision overlay." (t (avy--make-backgrounds (avy-window-list)) - (avy-read (avy-tree candidates avy-keys) - overlay-fn - #'avy--remove-leading-chars))) + (if (eq avy-style 'de-bruijn) + (avy-read-de-bruijn + candidates avy-keys) + (avy-read (avy-tree candidates avy-keys) + overlay-fn + #'avy--remove-leading-chars)))) (avy--done))) (defvar avy--overlays-back nil @@ -453,19 +581,32 @@ LEAF is normally ((BEG . END) . WND)." (beg (if (consp (car leaf)) (caar leaf) (car leaf))) - (wnd (cdr leaf))) - (when (or avy-highlight-first (> (length str) 1)) - (set-text-properties 0 1 '(face avy-lead-face-0) str)) + (wnd (cdr leaf)) + oov) + (dotimes (i len) + (set-text-properties (- len i 1) (- len i) + `(face ,(nth i avy-lead-faces)) + str)) + (when (eq avy-style 'de-bruijn) + (setq str (concat + (propertize avy-current-path + 'face 'avy-lead-face-1) + str)) + (setq len (length str))) (with-selected-window wnd (save-excursion (goto-char beg) - (when (cl-some (lambda (o) - (and (eq (overlay-get o 'category) 'avy) - (eq (overlay-get o 'window) wnd))) - (overlays-in (point) (min (+ (point) len) - (line-end-position)))) - (setq str (substring str 0 1)) - (setq len 1)) + (when (setq oov + (delq nil + (mapcar + (lambda (o) + (and (eq (overlay-get o 'category) 'avy) + (eq (overlay-get o 'window) wnd) + (overlay-start o))) + (overlays-in (point) (min (+ (point) len) + (line-end-position)))))) + (setq len (- (apply #'min oov) beg)) + (setq str (substring str 0 len))) (let ((other-ov (cl-find-if (lambda (o) (and (eq (overlay-get o 'category) 'avy) @@ -537,6 +678,7 @@ LEAF is normally ((BEG . END) . WND)." (at #'avy--overlay-at) (at-full 'avy--overlay-at-full) (post #'avy--overlay-post) + (de-bruijn #'avy--overlay-at-full) (t (error "Unexpected style %S" style)))) (defun avy--generic-jump (regex window-flip style) @@ -568,6 +710,20 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." avy-style))) ;;;###autoload +(defun avy-goto-char-in-line (char) + "Jump to the currently visible CHAR in the current line." + (interactive (list (read-char "char: "))) + (let ((avy-all-windows nil)) + (avy--with-avy-keys avy-goto-char + (avy--goto + (avy--process + (save-restriction + (narrow-to-region (line-beginning-position) + (line-end-position)) + (avy--regex-candidates (regexp-quote (string char)))) + (avy--style-fn avy-style)))))) + +;;;###autoload (defun avy-goto-char-2 (char1 char2 &optional arg) "Jump to the currently visible CHAR1 followed by CHAR2. The window scope is determined by `avy-all-windows' (ARG negates it)." @@ -589,7 +745,7 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." (avy--regex-candidates isearch-string)) (avy-background nil) (candidate - (avy--process candidates #'avy--overlay-post))) + (avy--process candidates (avy--style-fn avy-style)))) (isearch-done) (avy--goto candidate)))) @@ -613,7 +769,7 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." "\\.") ((and avy-word-punc-regexp (string-match avy-word-punc-regexp str)) - str) + (regexp-quote str)) (t (concat "\\b" @@ -778,13 +934,14 @@ The window scope is determined by `avy-all-windows' (ARG negates it)." (interactive "P") (let ((c1 (read-char "char 1: ")) (c2 (read-char "char 2: " nil avy-timeout-seconds))) - (avy--generic-jump - (regexp-quote - (if c2 - (string c1 c2) - (string c1))) - arg - avy-style))) + (avy--with-avy-keys avy-goto-char-timer + (avy--generic-jump + (regexp-quote + (if c2 + (string c1 c2) + (string c1))) + arg + avy-style)))) (define-obsolete-variable-alias 'avy-goto-char-style 'avy-style "0.1.0" diff --git a/packages/avy/doc/Changelog.org b/packages/avy/doc/Changelog.org new file mode 100644 index 0000000..7dbcdf1 --- /dev/null +++ b/packages/avy/doc/Changelog.org @@ -0,0 +1,211 @@ +* 0.3.0 +** Fixes +*** Candidate window reversal +See [[https://github.com/abo-abo/avy/issues/27][#27]]. + +*** Jumping to newlines with =at-full= style +See [[https://github.com/abo-abo/avy/issues/5][#5]]. + +*** Stop =at-full= style from shifting text sometimes +See [[https://github.com/abo-abo/avy/issues/5][#5]]. + +*** Fix =at-full= interaction with tabs +When at a tab, visualize it using =tab-width= spaces. + +See [[https://github.com/abo-abo/avy/issues/43][#43]]. + +*** Fix overlay issue when the same buffer is in two windows + +See [[https://github.com/abo-abo/avy/issues/47][#47]] and http://debbugs.gnu.org/cgi/bugreport.cgi?bug=20607. + +*** Quote punctuation chars + +See [[https://github.com/abo-abo/avy/issues/63][#63]]. + +*** Update screenshot for =avy-goto-char= in README.md +Use ~C-:~ as the new suggested binding instead of the pi char. + +See [[https://github.com/abo-abo/avy/issues/64][#64]]. + +** New Features +*** =avy-goto-line= can now break into =goto-line= +Just enter a digit and you'll be transferred into =goto-line= prompt +with that digit already entered. This means that you can just bind +~M-g g~ to =avy-goto-line= without losing anything. + +See [[https://github.com/abo-abo/avy/issues/29][#29]]. + +*** =avy-goto-line= now works with all kinds of overlay styles +Any of the following do something different now: + +#+begin_src elisp +(setq avy-styles-alist + '((avy-goto-line . post))) +(setq avy-styles-alist + '((avy-goto-line . at))) +(setq avy-styles-alist + '((avy-goto-line . at-full))) +(setq avy-styles-alist + '((avy-goto-line . pre))) +#+end_src + +See [[https://github.com/abo-abo/ace-link/issues/17][#17]]. + +*** New defcustom =avy-case-fold-search= +Non-nil when searches should ignore case, so e.g. =avy-goto-char= "b" +will match both "b" and "B". On by default. Use this to turn off this +behavior: + +#+begin_src elisp +(setq avy-case-fold-search nil) +#+end_src + +See [[https://github.com/abo-abo/avy/issues/34][#34]]. + +*** New command =avy-goto-word-or-subword-1= + +Enter one char, and select a visible word or subword that starts with +it, depending on =subword-mode=. Move the point there. + +See [[https://github.com/abo-abo/avy/issues/33][#33]]. + +*** =avy-move-line= should remove empty line after original one is moved + +See [[https://github.com/abo-abo/avy/issues/40][#40]]. + +*** =avy-move-line= now takes a prefix arg +Use e.g. ~M-3~ before =avy-move-line= to move 3 lines at once. + +*** Most commands can be used non-interactively +Example: +#+begin_src elisp +(defun avy-goto-lp () + (interactive) + (avy-goto-char ?\()) +#+end_src + +This command only goes to the "(" character. This is actually very +similar to [[http://oremacs.com/lispy/#lispy-ace-paren][=lispy-ace-paren=]], except the implementation is only one +line. + +See [[https://github.com/abo-abo/avy/issues/44][#44]]. + +*** (almost) all defcustoms are explained on the wiki + +See [[https://github.com/abo-abo/avy/wiki/defcustom][the defcustom wiki page]]. + +*** Allow all operations to work across frames + +You have to customize =avy-all-windows= for this. By default, it's set +to work on all windows on the current frame. + +To make it work only on the current window, use: +#+begin_src elisp +(setq avy-all-windows nil) +#+end_src + +To make it work on all frames, use: +#+begin_src elisp +(setq avy-all-windows 'all-frames) +#+end_src + +*** New command =avy-goto-char-in-line= + +This is =avy-goto-char= reduced only to the current line. Few +candidates means very short decision chars path. + +See [[https://github.com/abo-abo/avy/issues/49][#49]]. + +*** New overlay style =de-bruijn= + +How to use it: + +#+begin_src elisp +(setq avy-style 'de-bruijn) +#+end_src + +What it does: when your leading chars are clumped up together, it's +impossible to overlay the decision path without shifting the buffer +text a bit. For example, with the word "buffer", you =avy-goto-char= "b", and: + +- the path for the first "f" is "aj" +- the path for the second "f" is "ak" + +It's not possible to overlay 4 characters over "ff" in "buffer". But +to with =de-bruijn= style, which results in the path being "aj" and +"jk". It's possible to overlay "ajk" just fine. + +Pros and cons of =de-bruijn= over other styles: + +- a pro is that it's possible to display the full decision path for + clumped up chars, which is truncated for other styles +- a con is that the decision path is of the same length (e.g. 2 or 3) + for all candidates, while with other styles it's possible to have a + few candidets with a shorter path. + +See [[https://github.com/abo-abo/avy/issues/51][#51]] and [[https://github.com/abo-abo/avy/issues/5][#5]]. + +*** New defcustom =avy-ignored-modes= + +This is meant for visual modes like =doc-view-mode= or =image-mode= +that can have a huge number of chars in a single window. Which results +in a huge number of candidates even in other windows. + +Current setting: + +#+begin_src elisp +(setq avy-ignored-modes '(image-mode doc-view-mode pdf-view-mode)) +#+end_src + +See [[https://github.com/abo-abo/avy/issues/57][#57]]. + +*** New tutorial on writing custom commands + +See the [[https://github.com/abo-abo/avy/wiki/custom-commands][the custom-commands wiki page]] and [[https://github.com/abo-abo/avy/issues/55][#55]]. + +*** New face setup +New variable =avy-lead-faces= will determine the faces used to color +the current decision depth you're in. For example, if to select a +particular candidate you need to press "abc": + +- "a" will be highlighted with a face that corresponds to depth 3 +- "b" will be highlighted with a face that corresponds to depth 2 +- "c" will be highlighted with a face that corresponds to depth 1 + +But if another candidate needs "ef": + +- "e" will be highlighted with a face that corresponds to depth 2 +- "f" will be highlighted with a face that corresponds to depth 1 + +See [[https://github.com/abo-abo/avy/issues/53][#53]]. + +*** New variable =avy-translate-char-function= + +You can use this, for example, to interpret one character as another in =avy-keys=. + +Example: +#+begin_src elisp +(setq avy-translate-char-function + (lambda (c) (if (= c 32) ?a c))) +#+end_src + +This will translate ~SPC~ (32) into ~a~. So you can press either ~a~ or ~SPC~ to mean "a". + +*** =avy-isearch= works for different styles + +See [[https://github.com/abo-abo/avy/issues/61][#61]]. + +*** Switch the default style from =pre= to =at-full= + +I've come to like =at-full= more than =pre= over time. The difference +is that =pre= hides no chars in your buffer, while =at-full= doesn't +shift text. + +Use this to restore the previous default behavior: +#+begin_src elisp +(setq avy-style 'pre) +#+end_src + +* trunk +** Fixes +*** =avy-goto-char-timer= obeys =avy-styles-alist= diff --git a/packages/avy/targets/avy-init.el b/packages/avy/targets/avy-init.el index 21dd0ab..9ce46bb 100644 --- a/packages/avy/targets/avy-init.el +++ b/packages/avy/targets/avy-init.el @@ -22,8 +22,11 @@ (add-to-list 'load-path default-directory) (mapc #'byte-compile-file '("avy.el")) (require 'avy) -(require 'checkdoc) -(with-current-buffer (find-file "avy.el") - (checkdoc-current-buffer t)) +(if (fboundp 'checkdoc-file) + (checkdoc-file "avy.el") + (require 'checkdoc) + (with-current-buffer (find-file "avy.el") + (checkdoc-current-buffer t))) + (global-set-key (kbd "C-c j") 'avy-goto-char) (global-set-key (kbd "C-'") 'avy-goto-char-2)