From 877ae26a9689aac96c0c6df375f40c17a487c3ab Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Fri, 12 May 2017 11:50:05 +0200 Subject: [PATCH] completion/ivy: rewrite file search (:find => :ag/:rg) --- modules/completion/ivy/README.org | 45 ++++----- modules/completion/ivy/autoload/evil.el | 119 +++++++++++++++++------- modules/completion/ivy/config.el | 14 +-- modules/private/hlissner/+commands.el | 6 +- 4 files changed, 119 insertions(+), 65 deletions(-) diff --git a/modules/completion/ivy/README.org b/modules/completion/ivy/README.org index 910dd5738..2d728f9c7 100644 --- a/modules/completion/ivy/README.org +++ b/modules/completion/ivy/README.org @@ -24,8 +24,9 @@ sudo pacman --needed --noconfirm -S ripgrep ** Highlights *** Search & Replace -A project-wide search can be performed by invoking ~counsel-rg~ or via the ex -command (evil-mode): ~:find~. +A project-wide search can be performed with Ag (the silver searcher) or Rg +(ripgrep) via their ex commands: ~:ag[!]~ and ~:rg[!]~ (or their +current-directory counterparts ~:agcwd[!]~ and ~:rgcwd[!]~) [[/../screenshots/modules/completion/ivy/ivy-search.gif]] @@ -40,7 +41,7 @@ abort. *** Jump-to-file project navigation Inspired by Sublime Text's jump-to-anywhere, Vim's CtrlP or Unite plugins, and Textmate's Command-T, a marriage of ~projectile~ and ~ivy~ makes this available -to you in Emacs. Invoke it with =, /= or ~counsel-projectile-find-file~. +to you in Emacs. Invoke it with = /= or ~counsel-projectile-find-file~. [[/../screenshots/modules/completion/ivy/ivy-projectile.gif]] @@ -65,23 +66,25 @@ Here is a list of my commonly used commands, their default keybinds (defined in [[../../private/hlissner/+bindings.el][private/hlissner/+bindings.el]]), and their corresponding ex command (defined in [[../../private/hlissner/+commands.el][private/hlissner/+commands.el]]). -| command | key / ex command | description | -|-------------------------------------+-------------------------+-----------------------------------------------------------| -| ~counsel-M-x~ | =M-x= | Smarter, smex-powered M-x | -| ~counsel-bookmark~ | = b= | Find bookmark | -| ~counsel-find-file~ | = .= | Browse from current directory | -| ~counsel-projectile-find-file~ | = /= | Find file in project | -| ~counsel-projectile-switch-project~ | = p= | Open another project | -| ~counsel-recentf~ | = r= | Find recently opened file | -| ~+ivy/switch-buffer~ | = ,= | Jump to buffer in current workspace | -| ~+ivy/switch-workspace-buffer~ | = <= | Jump to buffer across workspaces | -| ~+ivy:file-search~ | ~:f[in]d[!] [QUERY]~ | Search project (if BANG, ignore gitignore) | -| ~+ivy:file-search-cwd~ | ~:f[in]dcwd[!] [QUERY]~ | Search project in cwd (if BANG, ignore gitignore) | -| ~+ivy:swiper~ | ~:sw[iper] [QUERY]~ | Search current buffer | -| ~+ivy:todo~ | ~:todo[!]~ | List all TODO/FIXMEs in project (or current file if BANG) | +| command | key / ex command | description | +|-------------------------------------+---------------------+------------------------------------------------------------------| +| ~counsel-M-x~ | =M-x= | Smarter, smex-powered M-x | +| ~counsel-bookmark~ | = b= | Find bookmark | +| ~counsel-find-file~ | = .= | Browse from current directory | +| ~counsel-projectile-find-file~ | = /= | Find file in project | +| ~counsel-projectile-switch-project~ | = p= | Open another project | +| ~counsel-recentf~ | = r= | Find recently opened file | +| ~+ivy/switch-buffer~ | = ,= | Jump to buffer in current workspace | +| ~+ivy/switch-workspace-buffer~ | = <= | Jump to buffer across workspaces | +| ~+ivy:ag~ | ~:ag[!] [QUERY]~ | Search project (BANG = ignore gitignore) | +| ~+ivy:ag-cwd~ | ~:agcwd[!] [QUERY]~ | Search this directory (BANG = don't recurse into subdirectories) | +| ~+ivy:rg~ | ~:rg[!] [QUERY]~ | Search project (if BANG, ignore gitignore) | +| ~+ivy:rg-cwd~ | ~:rgcwd[!] [QUERY]~ | Search this directory (BANG = don't recurse into subdirectories) | +| ~+ivy:swiper~ | ~:sw[iper] [QUERY]~ | Search current buffer | +| ~+ivy:todo~ | ~:todo[!]~ | List all TODO/FIXMEs in project (or current file if BANG) | -While in an ~counsel-rg~ search (e.g. invoked from ~+ivy:file-search~), these -new keybindings are available to you: +While in a search (e.g. invoked from ~+ivy:ag~ or ~+ivy:rg~), these new +keybindings are available to you: | key | description | |-------------+--------------------------------------------------------------------------------| @@ -93,8 +96,8 @@ new keybindings are available to you: + Where possible, functions with ivy/counsel equivalents have been remapped (like ~find-file~ => ~counsel-find-file~). So a keybinding to ~find-file~ will invoke ~counsel-find-file~ instead. -+ ~counsel-rg~'s 3-character limit was reduced to 1 (mainly for the ex command) -+ ~counsel-rg~'s parentheses quoting behavior was reversed. Now, if you ++ ~counsel-[arp]g~'s 3-character limit was reduced to 1 (mainly for the ex command) ++ ~counsel-[arp]g~'s parentheses quoting behavior was reversed. Now, if you want literal parentheses, you must escape them: e.g. ~\(match\)~ is literal, ~(match)~ is a regexp group. diff --git a/modules/completion/ivy/autoload/evil.el b/modules/completion/ivy/autoload/evil.el index e3a9a47b1..224d8cfd3 100644 --- a/modules/completion/ivy/autoload/evil.el +++ b/modules/completion/ivy/autoload/evil.el @@ -1,40 +1,5 @@ ;;; completion/ivy/autoload/evil.el -(defvar +ivy--file-last-search nil) - -;;;###autoload (autoload '+ivy:file-search "completion/ivy/autoload/evil" nil t) -(evil-define-operator +ivy:file-search (beg end query all-files-p &optional dir) - "Preform a `counsel-rg' search with QUERY. If QUERY is nil and in visual mode, -use the selection, otherwise activate live rg searching in ivy. - -If ALL-FILES-P is non-nil, don't respect .gitignore files and search everything. - -If there is no selection and QUERY is empty, then relaunch the previous search -session." - :type inclusive :repeat nil - (interactive "") - (let ((query (or query - (and (evil-visual-state-p) - (and beg end - (rxt-quote-pcre (buffer-substring-no-properties beg end)))) - +ivy--file-last-search)) - ;; smart-case instead of case-insensitive flag - (counsel-rg-base-command - (replace-regexp-in-string " -i " " -S " counsel-rg-base-command))) - (setq +ivy--file-last-search query) - (counsel-rg query - (or dir (doom-project-root)) - (when all-files-p " -u") - (format "File search%s" (if all-files-p " (all)" ""))))) - -;;;###autoload (autoload '+ivy:file-search-cwd "completion/ivy/autoload/evil" nil t) -(evil-define-operator +ivy:file-search-cwd (beg end search all-files-p) - "Perform a `counsel-rg' search for SEARCH (or the current selection) in -`default-directory'." - :type inclusive :repeat nil - (interactive "") - (+ivy:file-search beg end search all-files-p default-directory)) - ;;;###autoload (autoload '+ivy:swiper "completion/ivy/autoload/evil" nil t) (evil-define-command +ivy:swiper (&optional search) "Invoke `swiper' with SEARCH, otherwise with the symbol at point." @@ -46,3 +11,87 @@ session." "An ex wrapper around `+ivy/tasks'." (interactive "") (+ivy/tasks bang)) + + +;; --- file searching --------------------- + +(defvar +ivy--file-last-search nil) +(defvar +ivy--file-search-recursion-p t) +(defvar +ivy--file-search-all-files-p nil) + +(defun +ivy--file-search (engine beg end query &optional directory prompt) + (let* ((directory (or directory (doom-project-root))) + (recursion-p +ivy--file-search-recursion-p) + (all-files-p +ivy--file-search-all-files-p) + (project-root (doom-project-root)) + (query + (or query + (and (evil-visual-state-p) + beg end + (rxt-quote-pcre (buffer-substring-no-properties beg end))) + +ivy--file-last-search)) + (prompt + (format "%s%%s %s" + (symbol-name engine) + (cond ((equal directory default-directory) + "./") + ((equal directory project-root) + (projectile-project-name)) + (t + (file-relative-name directory project-root)))))) + (setq +ivy--file-last-search query) + (cond ((eq engine 'ag) + (let ((args (concat + (if all-files-p " -a") + (unless recursion-p " -n")))) + (counsel-ag query directory args (format prompt args)))) + + ((eq engine 'rg) + ;; smart-case instead of case-insensitive flag + (let ((counsel-rg-base-command + (replace-regexp-in-string " -i " " -S " counsel-rg-base-command)) + (args (concat + (if all-files-p " -uu") + (unless recursion-p " --maxdepth 0")))) + (counsel-rg query directory args (format prompt args)))) + + ((eq engine 'pt)) ; TODO pt search engine (necessary?) + + (t (error "No search engine specified"))))) + +;;;###autoload (autoload '+ivy:ag "completion/ivy/autoload/evil" nil t) +(evil-define-operator +ivy:ag (beg end query &optional all-files-p directory) + "Perform a project file search using the silver search. QUERY is a pcre +regexp. If omitted, will perform the last known search. + +If ALL-FILES-P, don't respect .gitignore files and search everything." + (interactive "") + (let ((+ivy--file-search-all-files-p all-files-p)) + (+ivy--file-search 'ag beg end query directory))) + +;;;###autoload (autoload '+ivy:rg "completion/ivy/autoload/evil" nil t) +(evil-define-operator +ivy:rg (beg end query &optional all-files-p directory) + "Perform a project file search using ripgrep. QUERY is a regexp. If omitted, +will perform the last known search. + +If ALL-FILES-P, don't respect .gitignore files and search everything." + (interactive "") + (let ((+ivy--file-search-all-files-p all-files-p)) + (+ivy--file-search 'rg beg end query directory))) + + +;;;###autoload (autoload '+ivy:ag-cwd "completion/ivy/autoload/evil" nil t) +(evil-define-operator +ivy:ag-cwd (beg end query &optional inhibit-recursion-p) + "The same as :ag, but only searches the current directory. If +INHIBIT-RECURSION-P, don't recurse into sub-directories." + (interactive "") + (let ((+ivy--file-search-recursion-p (not inhibit-recursion-p))) + (+ivy:ag beg end query t default-directory))) + +;;;###autoload (autoload '+ivy:rg-cwd "completion/ivy/autoload/evil" nil t) +(evil-define-operator +ivy:rg-cwd (beg end query &optional inhibit-recursion-p) + "The same as :rg, but only searches the current directory. If +INHIBIT-RECURSION-P, don't recurse into sub-directories." + (interactive "") + (let ((+ivy--file-search-recursion-p (not inhibit-recursion-p))) + (+ivy:rg beg end query t default-directory))) diff --git a/modules/completion/ivy/config.el b/modules/completion/ivy/config.el index 2575b836e..c2f7ed2a1 100644 --- a/modules/completion/ivy/config.el +++ b/modules/completion/ivy/config.el @@ -89,19 +89,19 @@ session)." (setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)") - ;; Configure `counsel-rg'/`counsel-ag' - (set! :popup "^\\*ivy-occur counsel-[ar]g" :size (+ 2 ivy-height) :regexp t :autokill t) + ;; Configure `counsel-rg', `counsel-ag' & `counsel-pt' + (set! :popup "^\\*ivy-occur counsel-[arp]g" :size (+ 2 ivy-height) :regexp t :autokill t) - (ivy-add-actions - 'counsel-rg - '(("O" +ivy-git-grep-other-window-action "open in other window"))) + (dolist (cmd '(counsel-ag counsel-rg counsel-pt)) + (ivy-add-actions + cmd + '(("O" +ivy-git-grep-other-window-action "open in other window")))) - (map! :map counsel-ag-map ; applies to counsel-rg too + (map! :map counsel-ag-map [backtab] #'+ivy/wgrep-occur ; search/replace on results "C-SPC" #'counsel-git-grep-recenter ; preview "M-RET" (+ivy-do-action! #'+ivy-git-grep-other-window-action)) - ;; NOTE Both counsel-rg and counsel-ag use this function (advice-add #'counsel-ag-function :override #'+ivy*counsel-ag-function)) diff --git a/modules/private/hlissner/+commands.el b/modules/private/hlissner/+commands.el index e315a974d..be2f7eea1 100644 --- a/modules/private/hlissner/+commands.el +++ b/modules/private/hlissner/+commands.el @@ -52,8 +52,10 @@ ;; Project navigation (ex! "a" 'projectile-find-other-file) -(ex! "f[in]d" '+ivy:file-search) -(ex! "f[in]dcwd" '+ivy:file-search-cwd) +(ex! "ag" '+ivy:ag) +(ex! "agc[wd]" '+ivy:ag-cwd) +(ex! "rg" '+ivy:rg) +(ex! "rgc[wd]" '+ivy:rg-cwd) (ex! "cd" '+hlissner:cd) (ex! "sw[iper]" '+ivy:swiper) ; in-file search