branch: master commit f5dac473c5461c4834431feac50ea18b6948d4df Merge: b0039dd 09f86fc Author: Oleh Krehel <ohwoeo...@gmail.com> Commit: Oleh Krehel <ohwoeo...@gmail.com>
Merge commit '09f86fca437f1b2e168093824e9d4ee0aea5130a' from swiper --- packages/swiper/counsel.el | 379 ++++++++++++++++++++++++++++++------- packages/swiper/doc/Changelog.org | 296 +++++++++++++++++++++++++++++ packages/swiper/ivy-hydra.el | 15 +- packages/swiper/ivy-test.el | 8 + packages/swiper/ivy.el | 363 ++++++++++++++++++++++++++--------- packages/swiper/swiper.el | 117 ++++++------ 6 files changed, 961 insertions(+), 217 deletions(-) diff --git a/packages/swiper/counsel.el b/packages/swiper/counsel.el index 94c8608..63c9552 100644 --- a/packages/swiper/counsel.el +++ b/packages/swiper/counsel.el @@ -34,6 +34,12 @@ (require 'swiper) +(defvar counsel-completion-beg nil + "Completion bounds start.") + +(defvar counsel-completion-end nil + "Completion bounds end.") + ;;;###autoload (defun counsel-el () "Elisp completion at point." @@ -75,12 +81,6 @@ :initial-input str :action #'counsel--el-action))) -(defvar counsel-completion-beg nil - "Completion bounds start.") - -(defvar counsel-completion-end nil - "Completion bounds end.") - (defun counsel--el-action (symbol) "Insert SYMBOL, erasing the previous one." (when (stringp symbol) @@ -142,6 +142,16 @@ (match-string 1 s) s)))) +(defun counsel-variable-list () + "Return the list of all currently bound variables." + (let (cands) + (mapatoms + (lambda (vv) + (when (or (get vv 'variable-documentation) + (and (boundp vv) (not (keywordp vv)))) + (push (symbol-name vv) cands)))) + cands)) + ;;;###autoload (defun counsel-describe-variable () "Forward to `describe-variable'." @@ -149,13 +159,7 @@ (let ((enable-recursive-minibuffers t)) (ivy-read "Describe variable: " - (let (cands) - (mapatoms - (lambda (vv) - (when (or (get vv 'variable-documentation) - (and (boundp vv) (not (keywordp vv)))) - (push (symbol-name vv) cands)))) - cands) + (counsel-variable-list) :keymap counsel-describe-map :preselect (counsel-symbol-at-point) :history 'counsel-describe-symbol-history @@ -165,6 +169,16 @@ (describe-variable (intern x)))))) +(ivy-set-actions + 'counsel-describe-variable + '(("i" counsel-info-lookup-symbol "info") + ("d" counsel--find-symbol "definition"))) + +(ivy-set-actions + 'counsel-describe-function + '(("i" counsel-info-lookup-symbol "info") + ("d" counsel--find-symbol "definition"))) + ;;;###autoload (defun counsel-describe-function () "Forward to `describe-function'." @@ -262,24 +276,21 @@ (defvar counsel--git-grep-count nil "Store the line count in current repository.") +(defun counsel-more-chars (n) + "Return two fake candidates prompting for at least N input." + (list "" + (format "%d chars more" (- n (length ivy-text))))) + (defun counsel-git-grep-function (string &optional _pred &rest _unused) "Grep in the current git repository for STRING." (if (and (> counsel--git-grep-count 20000) (< (length string) 3)) - (progn - (setq ivy--full-length counsel--git-grep-count) - (list "" - (format "%d chars more" (- 3 (length ivy-text))))) + (counsel-more-chars 3) (let* ((default-directory counsel--git-grep-dir) (cmd (format "git --no-pager grep --full-name -n --no-color -i -e %S" - (ivy--regex string t))) - res) + (ivy--regex string t)))) (if (<= counsel--git-grep-count 20000) - (progn - (setq res (shell-command-to-string cmd)) - (setq ivy--full-length nil) - (split-string res "\n" t)) - (setq ivy--full-length -1) + (split-string (shell-command-to-string cmd) "\n" t) (counsel--gg-candidates (ivy--regex string)) nil)))) @@ -290,40 +301,40 @@ (defun counsel-git-grep-recenter () (interactive) - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (counsel-git-grep-action ivy--current) (recenter-top-bottom))) (defun counsel-git-grep-action (x) (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x) - (let ((file-name (match-string-no-properties 1 x)) - (line-number (match-string-no-properties 2 x))) - (find-file (expand-file-name file-name counsel--git-grep-dir)) - (goto-char (point-min)) - (forward-line (1- (string-to-number line-number))) - (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) - (unless (eq ivy-exit 'done) - (setq swiper--window (selected-window)) - (swiper--cleanup) - (swiper--add-overlays (ivy--regex ivy-text)))))) + (with-ivy-window + (let ((file-name (match-string-no-properties 1 x)) + (line-number (match-string-no-properties 2 x))) + (find-file (expand-file-name file-name counsel--git-grep-dir)) + (goto-char (point-min)) + (forward-line (1- (string-to-number line-number))) + (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) + (unless (eq ivy-exit 'done) + (swiper--cleanup) + (swiper--add-overlays (ivy--regex ivy-text))))))) (defvar counsel-git-grep-history nil "History for `counsel-git-grep'.") ;;;###autoload (defun counsel-git-grep (&optional initial-input) - "Grep for a string in the current git repository." + "Grep for a string in the current git repository. +INITIAL-INPUT can be given as the initial minibuffer input." (interactive) (setq counsel--git-grep-dir (locate-dominating-file default-directory ".git")) (if (null counsel--git-grep-dir) (error "Not in a git repository") (setq counsel--git-grep-count (counsel--gg-count "" t)) - (ivy-read "pattern: " 'counsel-git-grep-function + (ivy-read "git grep: " 'counsel-git-grep-function :initial-input initial-input :matcher #'counsel-git-grep-matcher - :dynamic-collection (when (> counsel--git-grep-count 20000) - 'counsel-git-grep-function) + :dynamic-collection (> counsel--git-grep-count 20000) :keymap counsel-git-grep-map :action #'counsel-git-grep-action :unwind #'swiper--cleanup @@ -336,6 +347,8 @@ (declare-function ffap-guesser "ffap") +(defvar counsel-find-file-map (make-sparse-keymap)) + ;;;###autoload (defun counsel-find-file () "Forward to `find-file'." @@ -344,12 +357,14 @@ :matcher #'counsel--find-file-matcher :action (lambda (x) - (find-file (expand-file-name x ivy--directory))) + (with-ivy-window + (find-file (expand-file-name x ivy--directory)))) :preselect (when counsel-find-file-at-point (require 'ffap) (ffap-guesser)) :require-match 'confirm-after-completion - :history 'file-name-history)) + :history 'file-name-history + :keymap counsel-find-file-map)) (defcustom counsel-find-file-ignore-regexp nil "A regexp of files to ignore while in `counsel-find-file'. @@ -397,13 +412,6 @@ Skip some dotfiles unless `ivy-text' requires them." candidates)) (setq ivy--old-re regexp)))) -(defun counsel-locate-function (str &rest _u) - (if (< (length str) 3) - (list "" - (format "%d chars more" (- 3 (length ivy-text)))) - (counsel--async-command - (concat "locate -i --regex " (ivy--regex str))))) - (defun counsel--async-command (cmd) (let* ((counsel--process " *counsel*") (proc (get-process counsel--process)) @@ -424,20 +432,50 @@ Skip some dotfiles unless `ivy-text' requires them." (with-current-buffer (process-buffer process) (setq ivy--all-candidates (split-string (buffer-string) "\n" t)) (setq ivy--old-cands ivy--all-candidates)) - (ivy--insert-minibuffer - (ivy--format ivy--all-candidates))) + (ivy--exhibit)) (if (string= event "exited abnormally with code 1\n") - (message "Error")))) + (progn + (setq ivy--all-candidates '("Error")) + (setq ivy--old-cands ivy--all-candidates) + (ivy--exhibit))))) + +(defun counsel-locate-action-extern (x) + "Use xdg-open shell command on X." + (call-process shell-file-name nil + nil nil + shell-command-switch + (format "xdg-open %s" (shell-quote-argument x)))) + +(declare-function dired-jump "dired-x") +(defun counsel-locate-action-dired (x) + "Use `dired-jump' on X." + (dired-jump nil x)) + +(defvar counsel-locate-history nil + "History for `counsel-locate'.") + +(ivy-set-actions + 'counsel-locate + '(("x" counsel-locate-action-extern "xdg-open") + ("d" counsel-locate-action-dired "dired"))) + +(defun counsel-locate-function (str &rest _u) + (if (< (length str) 3) + (counsel-more-chars 3) + (counsel--async-command + (concat "locate -i --regex " (ivy--regex str))) + '("" "working..."))) ;;;###autoload (defun counsel-locate () - "Call locate." + "Call locate shell command." (interactive) - (ivy-read "pattern: " nil - :dynamic-collection #'counsel-locate-function - :action (lambda (val) - (when val - (find-file val))))) + (ivy-read "Locate: " #'counsel-locate-function + :dynamic-collection t + :history 'counsel-locate-history + :action (lambda (file) + (when file + (find-file file))))) (defun counsel--generic (completion-fn) "Complete thing at point with COMPLETION-FN." @@ -517,8 +555,12 @@ The libraries are offered from `load-path'." (get-text-property 0 'full-name x))) :keymap counsel-describe-map))) +(defvar counsel-gg-state nil + "The current state of candidates / count sync.") + (defun counsel--gg-candidates (regex) "Return git grep candidates for REGEX." + (setq counsel-gg-state -2) (counsel--gg-count regex) (let* ((default-directory counsel--git-grep-dir) (counsel-gg-process " *counsel-gg*") @@ -543,11 +585,13 @@ The libraries are offered from `load-path'." (with-current-buffer (process-buffer process) (setq ivy--all-candidates (split-string (buffer-string) "\n" t)) (setq ivy--old-cands ivy--all-candidates)) - (unless (eq ivy--full-length -1) - (ivy--insert-minibuffer - (ivy--format ivy--all-candidates)))) + (when (= 0 (cl-incf counsel-gg-state)) + (ivy--exhibit))) (if (string= event "exited abnormally with code 1\n") - (message "Error")))) + (progn + (setq ivy--all-candidates '("Error")) + (setq ivy--old-cands ivy--all-candidates) + (ivy--exhibit))))) (defun counsel--gg-count (regex &optional no-async) "Quickly and asynchronously count the amount of git grep REGEX matches. @@ -574,8 +618,8 @@ When NO-ASYNC is non-nil, do it synchronously." (when (string= event "finished\n") (with-current-buffer (process-buffer process) (setq ivy--full-length (string-to-number (buffer-string)))) - (ivy--insert-minibuffer - (ivy--format ivy--all-candidates))))))))) + (when (= 0 (cl-incf counsel-gg-state)) + (ivy--exhibit))))))))) (defun counsel--M-x-transformer (cmd) "Add a binding to CMD if it's bound in the current window. @@ -608,7 +652,7 @@ Optional INITIAL-INPUT is the initial input in the minibuffer." (lambda (cands) (funcall store - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (mapcar #'counsel--M-x-transformer cands))))) (cands obarray) (pred 'commandp) @@ -660,6 +704,213 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and (custom-available-themes)) :action #'counsel--load-theme-action)) +(defvar rhythmbox-library) +(declare-function rhythmbox-load-library "ext:helm-rhythmbox") +(declare-function dbus-call-method "dbus") +(declare-function rhythmbox-song-uri "ext:helm-rhythmbox") +(declare-function helm-rhythmbox-candidates "ext:helm-rhythmbox") + +(defun counsel-rhythmbox-enqueue-song (song) + "Let Rhythmbox enqueue SONG." + (let ((service "org.gnome.Rhythmbox3") + (path "/org/gnome/Rhythmbox3/PlayQueue") + (interface "org.gnome.Rhythmbox3.PlayQueue")) + (dbus-call-method :session service path interface + "AddToQueue" (rhythmbox-song-uri song)))) + +(defvar counsel-rhythmbox-history nil + "History for `counsel-rhythmbox'.") + +;;;###autoload +(defun counsel-rhythmbox () + "Choose a song from the Rhythmbox library to play or enqueue." + (interactive) + (unless (require 'helm-rhythmbox nil t) + (error "Please install `helm-rhythmbox'")) + (unless rhythmbox-library + (rhythmbox-load-library) + (while (null rhythmbox-library) + (sit-for 0.1))) + (ivy-read "Rhythmbox: " + (helm-rhythmbox-candidates) + :history 'counsel-rhythmbox-history + :action + '(1 + ("p" helm-rhythmbox-play-song "Play song") + ("e" counsel-rhythmbox-enqueue-song "Enqueue song")))) + +(defvar counsel-org-tags nil + "Store the current list of tags.") + +(defvar org-outline-regexp) +(defvar org-indent-mode) +(defvar org-indent-indentation-per-level) +(defvar org-tags-column) +(declare-function org-get-tags-string "org") +(declare-function org-move-to-column "org") + +(defun counsel-org-change-tags (tags) + (let ((current (org-get-tags-string)) + (col (current-column)) + level) + ;; Insert new tags at the correct column + (beginning-of-line 1) + (setq level (or (and (looking-at org-outline-regexp) + (- (match-end 0) (point) 1)) + 1)) + (cond + ((and (equal current "") (equal tags ""))) + ((re-search-forward + (concat "\\([ \t]*" (regexp-quote current) "\\)[ \t]*$") + (point-at-eol) t) + (if (equal tags "") + (delete-region + (match-beginning 0) + (match-end 0)) + (goto-char (match-beginning 0)) + (let* ((c0 (current-column)) + ;; compute offset for the case of org-indent-mode active + (di (if (bound-and-true-p org-indent-mode) + (* (1- org-indent-indentation-per-level) (1- level)) + 0)) + (p0 (if (equal (char-before) ?*) (1+ (point)) (point))) + (tc (+ org-tags-column (if (> org-tags-column 0) (- di) di))) + (c1 (max (1+ c0) (if (> tc 0) tc (- (- tc) (string-width tags))))) + (rpl (concat (make-string (max 0 (- c1 c0)) ?\ ) tags))) + (replace-match rpl t t) + (and c0 indent-tabs-mode (tabify p0 (point))) + tags))) + (t (error "Tags alignment failed"))) + (org-move-to-column col))) + +(defun counsel-org-tag-action (x) + (if (member x counsel-org-tags) + (progn + (setq counsel-org-tags (delete x counsel-org-tags))) + (setq counsel-org-tags (append counsel-org-tags (list x))) + (unless (member x ivy--all-candidates) + (setq ivy--all-candidates (append ivy--all-candidates (list x))))) + (let ((prompt (counsel-org-tag-prompt))) + (setf (ivy-state-prompt ivy-last) prompt) + (setq ivy--prompt (concat "%-4d " prompt))) + (cond ((memq this-command '(ivy-done ivy-alt-done)) + (counsel-org-change-tags + (if counsel-org-tags + (format ":%s:" + (mapconcat #'identity counsel-org-tags ":")) + ""))) + ((eq this-command 'ivy-call) + (delete-minibuffer-contents)))) + +(defun counsel-org-tag-prompt () + (format "Tags (%s): " + (mapconcat #'identity counsel-org-tags ", "))) + +(defvar org-setting-tags) +(defvar org-last-tags-completion-table) +(defvar org-tag-persistent-alist) +(defvar org-tag-alist) +(defvar org-complete-tags-always-offer-all-agenda-tags) + +(declare-function org-at-heading-p "org") +(declare-function org-back-to-heading "org") +(declare-function org-get-buffer-tags "org") +(declare-function org-global-tags-completion-table "org") +(declare-function org-agenda-files "org") +(declare-function org-agenda-set-tags "org-agenda") + +;;;###autoload +(defun counsel-org-tag () + "Add or remove tags in org-mode." + (interactive) + (save-excursion + (unless (org-at-heading-p) + (org-back-to-heading t)) + (setq counsel-org-tags (split-string (org-get-tags-string) ":" t)) + (let ((org-setting-tags t) + (org-last-tags-completion-table + (append org-tag-persistent-alist + (or org-tag-alist (org-get-buffer-tags)) + (and + org-complete-tags-always-offer-all-agenda-tags + (org-global-tags-completion-table + (org-agenda-files)))))) + (ivy-read (counsel-org-tag-prompt) + (lambda (str &rest _unused) + (delete-dups + (all-completions str 'org-tags-completion-function))) + :history 'org-tags-history + :action 'counsel-org-tag-action)))) + +;;;###autoload +(defun counsel-org-tag-agenda () + "Set tags for the current agenda item." + (interactive) + (let ((store (symbol-function 'org-set-tags))) + (unwind-protect + (progn + (fset 'org-set-tags + (symbol-function 'counsel-org-tag)) + (org-agenda-set-tags nil nil)) + (fset 'org-set-tags store)))) + +(defun counsel-ag-function (string &optional _pred &rest _unused) + "Grep in the current directory for STRING." + (if (< (length string) 3) + (counsel-more-chars 3) + (let ((regex (replace-regexp-in-string + "\\\\)" ")" + (replace-regexp-in-string + "\\\\(" "(" + (ivy--regex string))))) + (counsel--async-command + (format "ag --noheading --nocolor %S" regex)) + nil))) + +(defun counsel-ag (&optional initial-input) + "Grep for a string in the current directory using ag. +INITIAL-INPUT can be given as the initial minibuffer input." + (interactive) + (setq counsel--git-grep-dir default-directory) + (ivy-read "ag: " 'counsel-ag-function + :initial-input initial-input + :dynamic-collection t + :history 'counsel-git-grep-history + :action #'counsel-git-grep-action + :unwind #'swiper--cleanup)) + +(defun counsel-recoll-function (string &optional _pred &rest _unused) + "Grep in the current directory for STRING." + (if (< (length string) 3) + (counsel-more-chars 3) + (counsel--async-command + (format "recoll -t -b '%s'" string)) + nil)) + +;; This command uses the recollq command line tool that comes together +;; with the recoll (the document indexing database) source: +;; http://www.lesbonscomptes.com/recoll/download.html +;; You need to build it yourself (together with recoll): +;; cd ./query && make && sudo cp recollq /usr/local/bin +;; You can try the GUI version of recoll with: +;; sudo apt-get install recoll +;; Unfortunately, that does not install recollq. +(defun counsel-recoll (&optional initial-input) + "Search for a string in the recoll database. +You'll be given a list of files that match. +Selecting a file will launch `swiper' for that file. +INITIAL-INPUT can be given as the initial minibuffer input." + (interactive) + (ivy-read "recoll: " 'counsel-recoll-function + :initial-input initial-input + :dynamic-collection t + :history 'counsel-git-grep-history + :action (lambda (x) + (when (string-match "file://\\(.*\\)\\'" x) + (let ((file-name (match-string 1 x))) + (find-file file-name) + (unless (string-match "pdf$" x) + (swiper ivy-text))))))) (provide 'counsel) diff --git a/packages/swiper/doc/Changelog.org b/packages/swiper/doc/Changelog.org new file mode 100644 index 0000000..0f5ac2c --- /dev/null +++ b/packages/swiper/doc/Changelog.org @@ -0,0 +1,296 @@ +#+OPTIONS: toc:nil +* 0.6.0 +** Fixes +*** =swiper-avy= should use only the current window +Not all windows. See [[https://github.com/abo-abo/swiper/issues/117][#117]]. +*** fix wrap-around for =ivy-next-line= +See [[https://github.com/abo-abo/swiper/issues/118][#118]]. +*** =swiper-avy= should do nothing for empty input +See [[https://github.com/abo-abo/avy/issues/50][#50]]. +*** =ivy-alt-done= should require TRAMP if necessary +See [[https://github.com/abo-abo/swiper/pull/145][#145]]. +*** =swiper-query-replace= shouldn't miss the first occurrence +See [[https://github.com/abo-abo/swiper/pull/144][#144]]. +*** =swiper= should not deactivate mark +*** =ivy-mode= should not switch to TRAMP for certain input +See [[https://github.com/abo-abo/swiper/pull/145][#145]]. +*** =counsel-find-file= should work better with TRAMP +"/ssh:foo" should not be cut off +See [[https://github.com/abo-abo/swiper/pull/145][#145]]. +*** =counsel-find-file= supports Windows drive letters +See [[https://github.com/abo-abo/swiper/pull/155][#155]]. +*** =counsel-file-file= should work better with files that contain "~" +See [[https://github.com/abo-abo/swiper/pull/157][#157]]. +*** =counsel-M-x= should respect =ivy-format-function= +See [[https://github.com/abo-abo/swiper/pull/150][#150]]. +*** =counsel-git-grep= should position better on exit +See [[https://github.com/abo-abo/swiper/pull/153][#153]]. +*** =ivy-mode= should re-scale text to minibuffer height +See [[https://github.com/abo-abo/swiper/pull/151][#151]]. +*** =counsel-unicode-char= should use action-style call +See [[https://github.com/abo-abo/swiper/pull/160][#160]]. +*** =ivy-read= should allow % in prompt string +See [[https://github.com/abo-abo/swiper/pull/171][#171]]. +*** =ivy-call= should execute in proper window +See [[https://github.com/abo-abo/swiper/pull/176][#176]]. +** New Features +*** =ivy-mode= +**** Open an Info file on the file system +When in =Info-mode=, press ~g~ and select either "(./)" or "(../)" to +switch to file name completion. That file will be opened with Info. +**** Account for =minibuffer-depth-indication-mode= +If you have =minibuffer-depth-indication-mode= on, the minibuffer +prompt will indicate the current depth. +See [[https://github.com/abo-abo/swiper/pull/134][#134]]. +**** Add fuzzy matching function +To enable fuzzy matching, set your =ivy-re-builders-alist= accordingly: +#+begin_src elisp +(setq ivy-re-builders-alist + '((t . ivy--regex-fuzzy))) +#+end_src +See [[https://github.com/abo-abo/swiper/pull/136][#136]]. + +See also [[https://github.com/abo-abo/swiper/pull/142][#142]] for toggling fuzzy matching with ~C-o m~. +**** =case-fold-search= optimization +Bind case-fold-search to t when the input is all lower-case: + +- input "the" matches both "the" and "The". +- input "The" matches only "The". + +See [[https://github.com/abo-abo/swiper/pull/166][#166]]. +**** Allow to see the candidate index a la =anzu= via =ivy-count-format= +To have this feature, use something like this: +#+begin_src elisp +(setq ivy-count-format "(%d/%d) ") +#+end_src +See [[https://github.com/abo-abo/swiper/pull/167][#167]]. + +You can also set this to nil, if you don't want any count, see [[https://github.com/abo-abo/swiper/pull/188][#188]]. +**** Allow to add additional exit points for any command +Example for =ivy-switch-to-buffer=: +#+begin_src elisp +(ivy-set-actions + 'ivy-switch-buffer + '(("k" + (lambda (x) + (kill-buffer x) + (ivy--reset-state ivy-last)) + "kill") + ("j" + ivy--switch-buffer-other-window-action + "other"))) +#+end_src + +After this: + +- use ~M-o k~ to kill a buffer +- use ~M-o j~ to switch to a buffer in other window + +You can always use ~M-o o~ to access the default action. When there is +only one action, ~M-o~ does the same as ~C-m~. + +See [[https://github.com/abo-abo/swiper/pull/164][#164]]. + + + + + + + + +*** =counsel-describe-function= and =counsel-decribe-variable= +**** Add a binding to look up the symbol in info +Press ~C-,~ to look up the symbol in info, instead of the default +describe action. +See [[https://github.com/abo-abo/swiper/pull/121][#121]]. +**** Handle symbol-at-point better in non-Elisp buffers +See [[https://github.com/abo-abo/swiper/pull/126][#126]]. +*** =ivy-switch-buffer= +**** New face =ivy-virtual= +See [[https://github.com/abo-abo/swiper/pull/129][#129]]. +**** Deal better with invisible buffers +See [[https://github.com/abo-abo/swiper/pull/135][#135]]. +**** Add custom keymap +You can customize =ivy-switch-buffer-map=. + +See [[https://github.com/abo-abo/swiper/pull/164][#164]]. +**** Add extra actions +Add a =kill-buffer= action, and =switch-to-buffer-other-window= action. +*** =counsel-git-grep= +**** Add Async +Make it fully async: the process =git grep= will be killed and +restarted on new input. This results in almost no keyboard delay. +**** Own history variable +*** =swiper= +**** Own history variable +Having own history variable allows to get more use of ~M-p~, ~M-n~ and ~C-r~. +*** =counsel-el= +**** Switch to action-style call +This allows to make use of ~C-M-n~ and ~C-M-p~. +*** =counsel-locate= +**** Add Async +**** Add extra actions +In addition to the default action of opening a file add: + +- =xdg-open= action +- =dired= action + +Press ~M-o~ or ~C-o~ to access these actions. +**** Add own history + +*** API +**** Add :matcher +A matcher is a function that accepts a regexp and a list of candidates +and returns the filtered list of candidates. + +The default matcher is basically =cl-remove-if-not= + =string-match=. +If you'd like to customize this, pass your own matcher. + +See =counsel-git-grep-matcher= for an example. +**** Allow to customize the initial input for all commands +Customize =ivy-initial-inputs-alist= for this. +See [[https://github.com/abo-abo/swiper/pull/140][#140]]. +**** =ivy-sort-functions-alist= should also examine =this-command= +**** :dynamic-collection is now a boolean +Pass the collection function as the second var instead. + +** New Commands +*** =ivy-call= +Execute the current action for the current candidate without exiting +the minibuffer. Bound to ~C-M-m~ or ~M-RET~ or ~C-o g~. + + +*** =counsel-find-file= +Forward to =find-file= with Ivy completion. + +=ivy-next-line-and-call= as well as =ivy-resume= should work for this command. + +The variable =counsel-find-file-ignore-regexp= allows to ignore +certain files, like dot files. Input a leading dot to see all files. + +The variable =counsel-find-file-at-point= allows to automatically use +=ffap=. You also can do it manually with ~M-n~ when the point is on a file name. + +The variable =counsel-find-file-map= allows to customize the +minibuffer key bindings for this command. + +Recommended binding: + +#+begin_src elisp +(global-set-key (kbd "C-x C-f") 'counsel-find-file) +#+end_src + +You can peek at files with ~C-M-n~ and ~C-M-p~. + +See [[https://github.com/abo-abo/swiper/issues/122][#122]] and [[https://github.com/abo-abo/swiper/issues/123][#123]]. + +See [[https://github.com/abo-abo/swiper/pull/152][#152]] about ~M-n~, ~M-p~ and ~M-i~ switching directories when necessary. + +*** =ivy-recentf= +Find a file on =recentf-list=. + +Note that if your set =ivy-use-virtual-buffers=, =recentf-list= is +merged into candidates list for =ivy-switch-buffer=. But if you want +it separately, you can use this command. + +See [[https://github.com/abo-abo/swiper/issues/124][#124]]. +*** =ivy-yank-word= +Add word at point to minibuffer input. + +This is similar to what ~C-w~ does for =isearch=. However it's bound +to ~M-j~ instead of ~C-w~, since ~C-w~ is bound to =kill-region= - a +useful command. + +See [[https://github.com/abo-abo/swiper/issues/125][#125]]. +*** =counsel-M-x= +Forward to =execute-extended-command= with Ivy completion. +The candidate list will also display the key binding for each bound command. + +This command will piggyback on =smex= for sorting, if =smex= is installed. + +Use =counsel-M-x-initial-input= to customize the initial input for +this command. By default, it's "^" - the regex character that +indicates beginning of string. This results in much faster matching, +since you usually type the command name from the start. + +See [[https://github.com/abo-abo/swiper/pull/136][#136]] and [[https://github.com/abo-abo/swiper/pull/138][#138]]. + +*** =hydra-ivy= +Press ~C-o~ to toggle the Hydra for Ivy. +It gives access to shorter bindings and many customizable options. + +Use ~C-o >~ to grow the minibuffer. +Use ~C-o <~ to shrink the minibuffer. + +See [[https://github.com/abo-abo/swiper/pull/151][#151]]. + +*** =ivy-toggle-calling= +Toggle executing the current action each time a new candidate is selected. + +This command is bound to ~C-o c~. + +To explain how this is useful: ~C-M-m C-M-f C-M-f C-M-f~ is equivalent to ~C-o cjjj~. + +*** =ivy-insert-current= +Inserts the current candidate into the minibuffer. + +Press ~M-i~ if you want something close to the current candidate. You +can follow up with an edit and select. + +I find this very useful when creating new files with a similar name to +the existing file: ~C-x C-f M-i~ + a bit of editing is very fast. + +See [[https://github.com/abo-abo/swiper/pull/141][#141]]. + +*** =counsel-load-theme= +Forward to =load-theme= with Ivy completion. Allows to rapidly try themes (e.g. with ~C-M-n~). + +*** =ivy-reverse-i-search= +Allow to recursively match history with ~C-r~. + +I like this command from bash shell. The usual way to search through +history is with ~M-p~ and ~M-n~. Using =ivy-reverse-i-search= will +open a recursive completion session with the current history as the +candidates. +*** =counsel-rhythmbox= +[[http://oremacs.com/2015/07/09/counsel-rhythmbox/][Control Rhythmbox from Emacs.]] +*** =ivy-dispatching-done= +Select an action for the current candidate and execute it. Bound to ~M-o~. + +Some commands that support ~M-o~: + +- =counsel-rhythmbox= +- =counsel-describe-function= +- =counsel-describe-variable= +- =ivy-switch-buffer= +- =counsel-locate= + +*** =counsel-org-tag= +Forward to =org-set-tags= with Ivy completion. + +Selecting any tag each time will toggle it on/off. +The current list of selected tags will be displayed in the prompt. + +See [[https://github.com/abo-abo/swiper/pull/177][#177]] and [[https://github.com/abo-abo/swiper/pull/91][#91]]. + +*** =counsel-org-tag-agenda= +Forward to =org-agenda-set-tags= with Ivy completion. +See [[https://github.com/abo-abo/swiper/pull/177][#177]]. + +*** =counsel-ag= +Interactively =ag= using Ivy completion. + +*** =counsel-recoll= +Use =recoll= with Ivy completion. +See [[http://oremacs.com/2015/07/27/counsel-recoll/][Using Recoll desktop search database with Emacs]]. + +Install recoll with =sudo apt-get install recoll=. + +*** =swiper-from-isearch= +Start =swiper= from the current =isearch= input. + +*** =ivy-immediate-done= +Use this command to exit the minibuffer choosing not the current +candidate, but the current text. Bound to ~C-M-j~ or ~C-u C-j~. + +See [[https://github.com/abo-abo/swiper/pull/183][#183]]. diff --git a/packages/swiper/ivy-hydra.el b/packages/swiper/ivy-hydra.el index 03e4d20..6ab1f9a 100644 --- a/packages/swiper/ivy-hydra.el +++ b/packages/swiper/ivy-hydra.el @@ -30,7 +30,7 @@ (require 'ivy) (eval-when-compile - (unless (package-installed-p 'hydra) + (unless (or (featurep 'hydra) (package-installed-p 'hydra)) (defmacro defhydra (name &rest _) "This is a stub for the uninstalled `hydra' package." `(defun ,(intern (format "%S/body" name)) () @@ -47,11 +47,11 @@ (defhydra hydra-ivy (:hint nil :color pink) " -^^^^^^ ^Yes^ ^No^ ^Maybe^ -^^^^^^^^^^^^^^--------------------------------------- -^ ^ _k_ ^ ^ _f_ollow _i_nsert _c_: calling %s(if ivy-calling \"on\" \"off\") +^^^^^^ ^Yes^ ^No^ ^Maybe^ ^Action^ +^^^^^^^^^^^^^^--------------------------------------------------- +^ ^ _k_ ^ ^ _f_ollow _i_nsert _c_: calling %s(if ivy-calling \"on\" \"off\") _w_/_s_: %s(ivy-action-name) _h_ ^+^ _l_ _d_one _o_ops _m_: matcher %s(if (eq ivy--regex-function 'ivy--regex-fuzzy) \"fuzzy\" \"ivy\") -^ ^ _j_ ^ ^ ^ ^ ^ ^ _<_/_>_: shrink/grow window +^ ^ _j_ ^ ^ _g_o ^ ^ _<_/_>_: shrink/grow window " ;; arrows ("h" ivy-beginning-of-buffer) @@ -66,11 +66,14 @@ _h_ ^+^ _l_ _d_one _o_ops _m_: matcher %s(if (eq ivy--regex-function 'i ("f" ivy-alt-done :exit nil) ("C-j" ivy-alt-done :exit nil) ("d" ivy-done :exit t) + ("g" ivy-call) ("C-m" ivy-done :exit t) ("c" ivy-toggle-calling) ("m" ivy-toggle-fuzzy) (">" ivy-minibuffer-grow) - ("<" ivy-minibuffer-shrink)) + ("<" ivy-minibuffer-shrink) + ("w" ivy-prev-action) + ("s" ivy-next-action)) (provide 'ivy-hydra) diff --git a/packages/swiper/ivy-test.el b/packages/swiper/ivy-test.el index af0fc60..10dd8f9 100644 --- a/packages/swiper/ivy-test.el +++ b/packages/swiper/ivy-test.el @@ -114,3 +114,11 @@ #("\nDESCRIPTION\nFUNCTION LETTERS\nSWITCHES\nDIAGNOSTICS\nEXAMPLE 1\nEXAMPLE 2\nEXAMPLE 3\nSEE ALSO\nAUTHOR" 0 90 (read-only nil) 90 96 (face ivy-current-match read-only nil))))) + +(ert-deftest ivy--filter () + (setq ivy-last (make-ivy-state)) + (should (equal (ivy--filter "the" '("foo" "the" "The")) + '("the" "The"))) + (should (equal (ivy--filter "The" '("foo" "the" "The")) + '("The")))) + diff --git a/packages/swiper/ivy.el b/packages/swiper/ivy.el index f64782c..21197c3 100644 --- a/packages/swiper/ivy.el +++ b/packages/swiper/ivy.el @@ -69,8 +69,10 @@ (defcustom ivy-count-format "%-4d " "The style of showing the current candidate count for `ivy-read'. -Set this to nil if you don't want the count." - :type 'string) +Set this to nil if you don't want the count. You can also set it +to e.g. \"(%d/%d) \" if you want to see both the candidate index +and the candidate count." + :type '(choice (const :tag "Count disabled" nil) string)) (defcustom ivy-wrap nil "Whether to wrap around after the first and last candidate." @@ -90,12 +92,22 @@ Only \"./\" and \"../\" apply here. They appear in reverse order." "When non-nil, add `recentf-mode' and bookmarks to the list of buffers." :type 'boolean) +(defvar ivy--actions-list nil + "A list of extra actions per command.") + +(defun ivy-set-actions (cmd actions) + "Set CMD extra exit points to ACTIONS." + (setq ivy--actions-list + (plist-put ivy--actions-list cmd actions))) + ;;* Keymap (require 'delsel) (defvar ivy-minibuffer-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-m") 'ivy-done) + (define-key map (kbd "C-M-m") 'ivy-call) (define-key map (kbd "C-j") 'ivy-alt-done) + (define-key map (kbd "C-M-j") 'ivy-immediate-done) (define-key map (kbd "TAB") 'ivy-partial-or-done) (define-key map (kbd "C-n") 'ivy-next-line) (define-key map (kbd "C-p") 'ivy-previous-line) @@ -124,7 +136,9 @@ Only \"./\" and \"../\" apply here. They appear in reverse order." (define-key map (kbd "M-j") 'ivy-yank-word) (define-key map (kbd "M-i") 'ivy-insert-current) (define-key map (kbd "C-o") 'hydra-ivy/body) + (define-key map (kbd "M-o") 'ivy-dispatching-done) (define-key map (kbd "C-k") 'ivy-kill-line) + (define-key map (kbd "S-SPC") 'ivy-restrict-to-matches) map) "Keymap used in the minibuffer.") (autoload 'hydra-ivy/body "ivy-hydra" "" t) @@ -223,6 +237,13 @@ When non-nil, it should contain one %d.") ,@body)) (minibuffer-keyboard-quit))) +(defmacro with-ivy-window (&rest body) + "Execute BODY in the window from which `ivy-read' was called." + (declare (indent 0) + (debug t)) + `(with-selected-window (ivy-state-window ivy-last) + ,@body)) + (defun ivy--done (text) "Insert TEXT and exit minibuffer." (if (and ivy--directory @@ -256,6 +277,34 @@ When non-nil, it should contain one %d.") (insert ivy-text) (ivy--exhibit)))) +(defun ivy-dispatching-done () + "Select one of the available actions and call `ivy-done'." + (interactive) + (let ((actions (ivy-state-action ivy-last))) + (if (null (ivy--actionp actions)) + (ivy-done) + (let* ((hint (concat ivy--current + "\n" + (mapconcat + (lambda (x) + (format "%s: %s" + (propertize + (car x) + 'face 'font-lock-builtin-face) + (nth 2 x))) + (cdr actions) + "\n") + "\n")) + (key (string (read-key hint))) + (action (assoc key (cdr actions)))) + (cond ((string= key "")) + ((null action) + (error "%s is not bound" key)) + (t + (message "") + (ivy-set-action (nth 1 action)) + (ivy-done))))))) + (defun ivy-build-tramp-name (x) "Reconstruct X into a path. Is is a cons cell, related to `tramp-get-completion-function'." @@ -433,7 +482,7 @@ If the text hasn't changed as a result, forward to `ivy-alt-done'." (ivy-set-index (max (- ivy--index ivy-height) 0))) (defun ivy-minibuffer-grow () - "Grow the minibuffer window by 1 line" + "Grow the minibuffer window by 1 line." (interactive) (setq-local max-mini-window-height (cl-incf ivy-height))) @@ -485,16 +534,62 @@ If the input is empty, select the previous history element instead." (ivy-previous-line arg)) (defun ivy-toggle-calling () - "Flip `ivy-calling'" + "Flip `ivy-calling'." (interactive) (when (setq ivy-calling (not ivy-calling)) (ivy-call))) +(defun ivy--get-action (state) + "Get the action function from STATE." + (let ((action (ivy-state-action state))) + (when action + (if (functionp action) + action + (cadr (nth (car action) action)))))) + +(defun ivy--actionp (x) + "Return non-nil when X is a list of actions." + (and x (listp x) (not (eq (car x) 'closure)))) + +(defun ivy-next-action () + "When the current action is a list, scroll it forwards." + (interactive) + (let ((action (ivy-state-action ivy-last))) + (when (ivy--actionp action) + (unless (>= (car action) (1- (length action))) + (cl-incf (car action)))))) + +(defun ivy-prev-action () + "When the current action is a list, scroll it backwards." + (interactive) + (let ((action (ivy-state-action ivy-last))) + (when (ivy--actionp action) + (unless (<= (car action) 1) + (cl-decf (car action)))))) + +(defun ivy-action-name () + "Return the name associated with the current action." + (let ((action (ivy-state-action ivy-last))) + (if (ivy--actionp action) + (format "[%d/%d] %s" + (car action) + (1- (length action)) + (nth 2 (nth (car action) action))) + "[1/1] default"))) + (defun ivy-call () "Call the current action without exiting completion." - (when (ivy-state-action ivy-last) - (with-selected-window (ivy-state-window ivy-last) - (funcall (ivy-state-action ivy-last) ivy--current)))) + (interactive) + (let ((action (ivy--get-action ivy-last))) + (when action + (let* ((collection (ivy-state-collection ivy-last)) + (x (if (and (consp collection) + (consp (car collection))) + (cdr (assoc ivy--current collection)) + (if (equal ivy--current "") + ivy-text + ivy--current)))) + (funcall action x))))) (defun ivy-next-line-and-call (&optional arg) "Move cursor vertically down ARG candidates. @@ -617,7 +712,9 @@ On error (read-only), call `ivy-on-del-error-function'." ivy--directory)))) (ivy--exhibit)) (ignore-errors - (backward-kill-word 1)))) + (let ((pt (point))) + (forward-word -1) + (delete-region (point) pt))))) (defvar ivy--regexp-quote 'regexp-quote "Store the regexp quoting state.") @@ -649,7 +746,7 @@ Prioritize directories." "An alist of sorting functions for each collection function. Interactive functions that call completion fit in here as well. -For each entry, nil means no sorting. It's very useful to turn +For each entry, nil means no sorting. It's very useful to turn off the sorting for functions that have candidates in the natural buffer order, like `org-refile' or `Man-goto-section'. @@ -716,7 +813,8 @@ Directories come first." PROMPT is a string to prompt with; normally it ends in a colon and a space. When PROMPT contains %d, it will be updated with -the current number of matching candidates. +the current number of matching candidates. If % appears elsewhere +in the PROMPT it should be quoted as %%. See also `ivy-count-format'. COLLECTION is a list of strings. @@ -743,6 +841,14 @@ MATCHER can completely override matching. DYNAMIC-COLLECTION is a function to call to update the list of candidates with each input." + (let ((extra-actions (plist-get ivy--actions-list this-command))) + (when extra-actions + (setq action + (if (functionp action) + `(1 + ("o" ,action "default") + ,@extra-actions) + (delete-dups (append action extra-actions)))))) (setq ivy-last (make-ivy-state :prompt prompt @@ -779,15 +885,15 @@ candidates with each input." (let ((item (if ivy--directory ivy--current ivy-text))) - (set hist (cons (propertize item 'ivy-index ivy--index) - (delete item - (cdr (symbol-value hist)))))) + (unless (equal item "") + (set hist (cons (propertize item 'ivy-index ivy--index) + (delete item + (cdr (symbol-value hist))))))) res))) (remove-hook 'post-command-hook #'ivy--exhibit) (when (setq unwind (ivy-state-unwind ivy-last)) (funcall unwind))) - (when (setq action (ivy-state-action ivy-last)) - (funcall action ivy--current)))) + (ivy-call))) (defun ivy--reset-state (state) "Reset the ivy to STATE. @@ -801,7 +907,8 @@ This is useful for recursive `ivy-read'." (re-builder (ivy-state-re-builder state)) (dynamic-collection (ivy-state-dynamic-collection state)) (initial-input (ivy-state-initial-input state)) - (require-match (ivy-state-require-match state))) + (require-match (ivy-state-require-match state)) + (matcher (ivy-state-matcher state))) (unless initial-input (setq initial-input (cdr (assoc this-command ivy-initial-inputs-alist)))) @@ -830,8 +937,14 @@ This is useful for recursive `ivy-read'." ((eq collection 'read-file-name-internal) (setq ivy--directory default-directory) (require 'dired) - (setq coll - (ivy--sorted-files default-directory)) + (when preselect + (let ((preselect-directory (file-name-directory preselect))) + (unless (or (null preselect-directory) + (string= preselect-directory + default-directory)) + (setq ivy--directory preselect-directory)) + (setq preselect (file-name-nondirectory preselect)))) + (setq coll (ivy--sorted-files ivy--directory)) (when initial-input (unless (or require-match (equal initial-input default-directory) @@ -870,7 +983,7 @@ This is useful for recursive `ivy-read'." ivy--index) (and preselect (ivy--preselect-index - coll initial-input preselect)) + coll initial-input preselect matcher)) 0)) (setq ivy--old-re nil) (setq ivy--old-cands nil) @@ -880,6 +993,19 @@ This is useful for recursive `ivy-read'." (setq ivy--prompt (cond ((string-match "%.*d" prompt) prompt) + ((null ivy-count-format) + nil) + ((string-match "%d.*%d" ivy-count-format) + (let ((w (length (number-to-string + (length ivy--all-candidates)))) + (s (copy-sequence ivy-count-format))) + (string-match "%d" s) + (match-end 0) + (string-match "%d" s (match-end 0)) + (setq s (replace-match (format "%%-%dd" w) nil nil s)) + (string-match "%d" s) + (concat (replace-match (format "%%%dd" w) nil nil s) + prompt))) ((string-match "%.*d" ivy-count-format) (concat ivy-count-format prompt)) (ivy--directory @@ -906,7 +1032,8 @@ DEF is the default value. _INHERIT-INPUT-METHOD is ignored for now. The history, defaults and input-method arguments are ignored for now." - (ivy-read prompt collection + (ivy-read (replace-regexp-in-string "%" "%%" prompt) + collection :predicate predicate :require-match require-match :initial-input (if (consp initial-input) @@ -941,15 +1068,21 @@ Minibuffer bindings: (setq completing-read-function 'ivy-completing-read) (setq completing-read-function 'completing-read-default))) -(defun ivy--preselect-index (candidates initial-input preselect) - "Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT." - (when initial-input - (setq initial-input (ivy--regex-plus initial-input)) - (setq candidates - (cl-remove-if-not - (lambda (x) - (string-match initial-input x)) - candidates))) +(defun ivy--preselect-index (candidates initial-input preselect matcher) + "Return the index in CANDIDATES filtered by INITIAL-INPUT for PRESELECT. +When MATCHER is non-nil it's used instead of `cl-remove-if-not'." + (if initial-input + (progn + (setq initial-input (ivy--regex-plus initial-input)) + (setq candidates + (if matcher + (funcall matcher initial-input candidates) + (cl-remove-if-not + (lambda (x) + (string-match initial-input x)) + candidates)))) + (when matcher + (setq candidates (funcall matcher "" candidates)))) (or (cl-position preselect candidates :test #'equal) (cl-position-if (lambda (x) @@ -1001,6 +1134,8 @@ When GREEDY is non-nil, join words in a greedy way." (if hashed (prog1 (cdr hashed) (setq ivy--subexps (car hashed))) + (when (string-match "\\([^\\]\\|^\\)\\\\$" str) + (setq str (substring str 0 -1))) (cdr (puthash str (let ((subs (ivy--split str))) (if (= (length subs) 1) @@ -1108,21 +1243,27 @@ Insert .* between each char." (let ((inhibit-read-only t) (std-props '(front-sticky t rear-nonsticky t field t read-only t)) (n-str - (format + (concat + (if (and (bound-and-true-p minibuffer-depth-indicate-mode) + (> (minibuffer-depth) 1)) + (format "[%d] " (minibuffer-depth)) + "") (concat - (if (and (bound-and-true-p minibuffer-depth-indicate-mode) - (> (minibuffer-depth) 1)) - (format "[%d] " (minibuffer-depth)) - "") - head + (if (string-match "%d.*%d" ivy-count-format) + (format head + (1+ ivy--index) + (or (and (ivy-state-dynamic-collection ivy-last) + ivy--full-length) + ivy--length)) + (format head + (or (and (ivy-state-dynamic-collection ivy-last) + ivy--full-length) + ivy--length))) ivy--prompt-extra - tail - (if ivy--directory - (abbreviate-file-name ivy--directory) - "")) - (or (and (ivy-state-dynamic-collection ivy-last) - ivy--full-length) - ivy--length)))) + tail) + (if ivy--directory + (abbreviate-file-name ivy--directory) + "")))) (save-excursion (goto-char (point-min)) (delete-region (point-min) (minibuffer-prompt-end)) @@ -1151,50 +1292,51 @@ Insert .* between each char." (defun ivy--exhibit () "Insert Ivy completions display. Should be run via minibuffer `post-command-hook'." - (setq ivy-text (ivy--input)) - (if (ivy-state-dynamic-collection ivy-last) - ;; while-no-input would cause annoying - ;; "Waiting for process to die...done" message interruptions - (let ((inhibit-message t)) - (unless (equal ivy--old-text ivy-text) - (while-no-input - ;; dynamic collection should take care of everything - (funcall (ivy-state-dynamic-collection ivy-last) ivy-text) - (setq ivy--old-text ivy-text))) - (unless (eq ivy--full-length -1) - (ivy--insert-minibuffer - (ivy--format ivy--all-candidates)))) - (cond (ivy--directory - (if (string-match "/\\'" ivy-text) - (if (member ivy-text ivy--all-candidates) - (ivy--cd (expand-file-name ivy-text ivy--directory)) - (when (string-match "//\\'" ivy-text) - (if (and default-directory - (string-match "\\`[[:alpha:]]:/" default-directory)) - (ivy--cd (match-string 0 default-directory)) - (ivy--cd "/"))) - (when (string-match "[[:alpha:]]:/" ivy-text) - (let ((drive-root (match-string 0 ivy-text))) - (when (file-exists-p drive-root) - (ivy--cd drive-root))))) - (if (string-match "\\`~\\'" ivy-text) - (ivy--cd (expand-file-name "~/"))))) - ((eq (ivy-state-collection ivy-last) 'internal-complete-buffer) - (when (or (and (string-match "\\` " ivy-text) - (not (string-match "\\` " ivy--old-text))) - (and (string-match "\\` " ivy--old-text) - (not (string-match "\\` " ivy-text)))) - (setq ivy--all-candidates - (if (and (> (length ivy-text) 0) - (eq (aref ivy-text 0) - ?\ )) - (ivy--buffer-list " ") - (ivy--buffer-list "" ivy-use-virtual-buffers))) - (setq ivy--old-re nil)))) - (ivy--insert-minibuffer - (ivy--format - (ivy--filter ivy-text ivy--all-candidates))) - (setq ivy--old-text ivy-text))) + (when (memq 'ivy--exhibit post-command-hook) + (setq ivy-text (ivy--input)) + (if (ivy-state-dynamic-collection ivy-last) + ;; while-no-input would cause annoying + ;; "Waiting for process to die...done" message interruptions + (let ((inhibit-message t)) + (unless (equal ivy--old-text ivy-text) + (while-no-input + (setq ivy--all-candidates + (funcall (ivy-state-collection ivy-last) ivy-text)) + (setq ivy--old-text ivy-text))) + (when ivy--all-candidates + (ivy--insert-minibuffer + (ivy--format ivy--all-candidates)))) + (cond (ivy--directory + (if (string-match "/\\'" ivy-text) + (if (member ivy-text ivy--all-candidates) + (ivy--cd (expand-file-name ivy-text ivy--directory)) + (when (string-match "//\\'" ivy-text) + (if (and default-directory + (string-match "\\`[[:alpha:]]:/" default-directory)) + (ivy--cd (match-string 0 default-directory)) + (ivy--cd "/"))) + (when (string-match "[[:alpha:]]:/" ivy-text) + (let ((drive-root (match-string 0 ivy-text))) + (when (file-exists-p drive-root) + (ivy--cd drive-root))))) + (if (string-match "\\`~\\'" ivy-text) + (ivy--cd (expand-file-name "~/"))))) + ((eq (ivy-state-collection ivy-last) 'internal-complete-buffer) + (when (or (and (string-match "\\` " ivy-text) + (not (string-match "\\` " ivy--old-text))) + (and (string-match "\\` " ivy--old-text) + (not (string-match "\\` " ivy-text)))) + (setq ivy--all-candidates + (if (and (> (length ivy-text) 0) + (eq (aref ivy-text 0) + ?\ )) + (ivy--buffer-list " ") + (ivy--buffer-list "" ivy-use-virtual-buffers))) + (setq ivy--old-re nil)))) + (ivy--insert-minibuffer + (ivy--format + (ivy--filter ivy-text ivy--all-candidates))) + (setq ivy--old-text ivy-text)))) (defun ivy--insert-minibuffer (text) "Insert TEXT into minibuffer with appropriate cleanup." @@ -1231,6 +1373,7 @@ Should be run via minibuffer `post-command-hook'." CANDIDATES are assumed to be static." (let* ((re (funcall ivy--regex-function name)) (matcher (ivy-state-matcher ivy-last)) + (case-fold-search (string= name (downcase name))) (cands (cond (matcher (funcall matcher re candidates)) @@ -1272,8 +1415,11 @@ CANDIDATES are assumed to be static." (unless (and (not (equal re ivy--old-re)) (or (setq ivy--index (or - (cl-position re cands - :test #'equal) + (cl-position (if (and (> (length re) 0) + (eq ?^ (aref re 0))) + (substring re 1) + re) cands + :test #'equal) (and ivy--directory (cl-position (concat re "/") cands @@ -1420,14 +1566,40 @@ BUFFER may be a string or nil." (switch-to-buffer buffer nil 'force-same-window))))) +(defun ivy--switch-buffer-other-window-action (buffer) + "Switch to BUFFER in other window. +BUFFER may be a string or nil." + (if (zerop (length buffer)) + (switch-to-buffer-other-window ivy-text) + (let ((virtual (assoc buffer ivy--virtual-buffers))) + (if (and virtual + (not (get-buffer buffer))) + (find-file-other-window (cdr virtual)) + (switch-to-buffer-other-window buffer))))) + +(defvar ivy-switch-buffer-map (make-sparse-keymap)) + +(ivy-set-actions + 'ivy-switch-buffer + '(("k" + (lambda (x) + (kill-buffer x) + (ivy--reset-state ivy-last)) + "kill") + ("j" + ivy--switch-buffer-other-window-action + "other"))) + (defun ivy-switch-buffer () "Switch to another buffer." (interactive) (if (not ivy-mode) (call-interactively 'switch-to-buffer) - (ivy-read "Switch to buffer: " 'internal-complete-buffer - :preselect (buffer-name (other-buffer (current-buffer))) - :action #'ivy--switch-buffer-action))) + (let ((this-command 'ivy-switch-buffer)) + (ivy-read "Switch to buffer: " 'internal-complete-buffer + :preselect (buffer-name (other-buffer (current-buffer))) + :action #'ivy--switch-buffer-action + :keymap ivy-switch-buffer-map)))) (defun ivy-recentf () "Find a file on `recentf-list'." @@ -1439,7 +1611,7 @@ BUFFER may be a string or nil." "Pull next word from buffer into search string." (interactive) (let (amend) - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (let ((pt (point)) (le (line-end-position))) (forward-word 1) @@ -1483,6 +1655,13 @@ The selected history element will be inserted into the minibufer." (insert (substring-no-properties x)) (ivy--cd-maybe))))) +(defun ivy-restrict-to-matches () + "Restrict candidates to current matches and erase input." + (interactive) + (delete-minibuffer-contents) + (setq ivy--all-candidates + (ivy--filter ivy-text ivy--all-candidates))) + (provide 'ivy) ;;; ivy.el ends here diff --git a/packages/swiper/swiper.el b/packages/swiper/swiper.el index 1975ae2..1032f04 100644 --- a/packages/swiper/swiper.el +++ b/packages/swiper/swiper.el @@ -83,9 +83,6 @@ map) "Keymap for swiper.") -(defvar swiper--window nil - "Store the current window.") - (defun swiper-query-replace () "Start `query-replace' with string to replace from last search string." (interactive) @@ -96,7 +93,7 @@ (to (query-replace-read-to from "Query replace" t))) (delete-minibuffer-contents) (ivy-set-action (lambda (_) - (with-selected-window swiper--window + (with-ivy-window (move-beginning-of-line 1) (perform-replace from to t t nil)))) @@ -108,14 +105,14 @@ (declare-function avy--regex-candidates "ext:avy") (declare-function avy--process "ext:avy") (declare-function avy--overlay-post "ext:avy") -(declare-function avy--goto "ext:avy") +(declare-function avy-action-goto "ext:avy") ;;;###autoload (defun swiper-avy () "Jump to one of the current swiper candidates." (interactive) (unless (string= ivy-text "") - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (let* ((avy-all-windows nil) (candidates (avy--regex-candidates @@ -124,12 +121,12 @@ (candidate (avy--process candidates #'avy--overlay-post))) (ivy-quit-and-run - (avy--goto candidate)))))) + (avy-action-goto candidate)))))) (defun swiper-recenter-top-bottom (&optional arg) - "Call (`recenter-top-bottom' ARG) in `swiper--window'." + "Call (`recenter-top-bottom' ARG)." (interactive "P") - (with-selected-window swiper--window + (with-ivy-window (recenter-top-bottom arg))) (defun swiper-font-lock-ensure () @@ -145,7 +142,9 @@ dired-mode jabber-chat-mode elfeed-search-mode - fundamental-mode))) + fundamental-mode + Man-mode + woman-mode))) (unless (> (buffer-size) 100000) (if (fboundp 'font-lock-ensure) (font-lock-ensure) @@ -199,8 +198,7 @@ When non-nil, INITIAL-INPUT is the initial search pattern." "Perform initialization common to both completion methods." (setq swiper--opoint (point)) (setq swiper--len 0) - (setq swiper--anchor (line-number-at-pos)) - (setq swiper--window (selected-window))) + (setq swiper--anchor (line-number-at-pos))) (defun swiper--re-builder (str) "Transform STR into a swiper regex. @@ -244,8 +242,7 @@ Please remove it and update the \"swiper\" package.")) res) (unwind-protect (setq res (ivy-read - (replace-regexp-in-string - "%s" "pattern: " swiper--format-spec) + "Swiper: " candidates :initial-input initial-input :keymap swiper-map @@ -281,13 +278,13 @@ Please remove it and update the \"swiper\" package.")) (defun swiper--update-input-ivy () "Called when `ivy' input is updated." - (swiper--cleanup) - (let* ((re (ivy--regex ivy-text)) - (str ivy--current) - (num (if (string-match "^[0-9]+" str) - (string-to-number (match-string 0 str)) - 0))) - (with-selected-window swiper--window + (with-ivy-window + (swiper--cleanup) + (let* ((re (ivy--regex ivy-text)) + (str ivy--current) + (num (if (string-match "^[0-9]+" str) + (string-to-number (match-string 0 str)) + 0))) (goto-char (point-min)) (when (cl-plusp num) (goto-char (point-min)) @@ -300,7 +297,7 @@ Please remove it and update the \"swiper\" package.")) (isearch-range-invisible (line-beginning-position) (line-end-position)) (unless (and (>= (point) (window-start)) - (<= (point) (window-end swiper--window t))) + (<= (point) (window-end (ivy-state-window ivy-last) t))) (recenter))) (swiper--add-overlays re)))) @@ -311,39 +308,39 @@ BEG and END, when specified, are the point bounds." (line-beginning-position) (1+ (line-end-position))))) (overlay-put ov 'face 'swiper-line-face) - (overlay-put ov 'window swiper--window) - (push ov swiper--overlays)) - (let* ((wh (window-height)) - (beg (or beg (save-excursion - (forward-line (- wh)) - (point)))) - (end (or end (save-excursion - (forward-line wh) - (point))))) - (when (>= (length re) swiper-min-highlight) - (save-excursion - (goto-char beg) - ;; RE can become an invalid regexp - (while (and (ignore-errors (re-search-forward re end t)) - (> (- (match-end 0) (match-beginning 0)) 0)) - (let ((i 0)) - (while (<= i ivy--subexps) - (when (match-beginning i) - (let ((overlay (make-overlay (match-beginning i) - (match-end i))) - (face - (cond ((zerop ivy--subexps) - (cadr swiper-faces)) - ((zerop i) - (car swiper-faces)) - (t - (nth (1+ (mod (+ i 2) (1- (length swiper-faces)))) - swiper-faces))))) - (push overlay swiper--overlays) - (overlay-put overlay 'face face) - (overlay-put overlay 'window swiper--window) - (overlay-put overlay 'priority i))) - (cl-incf i)))))))) + (overlay-put ov 'window (ivy-state-window ivy-last)) + (push ov swiper--overlays) + (let* ((wh (window-height)) + (beg (or beg (save-excursion + (forward-line (- wh)) + (point)))) + (end (or end (save-excursion + (forward-line wh) + (point))))) + (when (>= (length re) swiper-min-highlight) + (save-excursion + (goto-char beg) + ;; RE can become an invalid regexp + (while (and (ignore-errors (re-search-forward re end t)) + (> (- (match-end 0) (match-beginning 0)) 0)) + (let ((i 0)) + (while (<= i ivy--subexps) + (when (match-beginning i) + (let ((overlay (make-overlay (match-beginning i) + (match-end i))) + (face + (cond ((zerop ivy--subexps) + (cadr swiper-faces)) + ((zerop i) + (car swiper-faces)) + (t + (nth (1+ (mod (+ i 2) (1- (length swiper-faces)))) + swiper-faces))))) + (push overlay swiper--overlays) + (overlay-put overlay 'face face) + (overlay-put overlay 'window (ivy-state-window ivy-last)) + (overlay-put overlay 'priority i))) + (cl-incf i))))))))) (defun swiper--action (x input) "Goto line X and search for INPUT." @@ -359,6 +356,16 @@ BEG and END, when specified, are the point bounds." (push-mark swiper--opoint t) (message "Mark saved where search started"))))) +;; (define-key isearch-mode-map (kbd "C-o") 'swiper-from-isearch) +(defun swiper-from-isearch () + "Invoke `swiper' from isearch." + (interactive) + (let ((query (if isearch-regexp + isearch-string + (regexp-quote isearch-string)))) + (isearch-exit) + (swiper query))) + (provide 'swiper) ;;; swiper.el ends here