/elpa-local/28.1/helm-rg-20200721.725/helm-rg.el
Emacs Lisp | 1222 lines | 857 code | 170 blank | 195 comment | 77 complexity | 4eff24311d28b38d6ac4a6736f276fea MD5 | raw file
Possible License(s): GPL-3.0, CC-BY-4.0, BSD-3-Clause
- ;;; helm-rg.el --- a helm interface to ripgrep -*- lexical-binding: t -*-
- ;; Author: Danny McClanahan
- ;; Version: 0.1
- ;; Package-Version: 20200721.725
- ;; Package-Commit: ee0a3c09da0c843715344919400ab0a0190cc9dc
- ;; URL: https://github.com/cosmicexplorer/helm-rg
- ;; Package-Requires: ((emacs "25") (cl-lib "0.5") (dash "2.13.0") (helm "2.8.8"))
- ;; Keywords: find, file, files, helm, fast, rg, ripgrep, grep, search, match
- ;; This file is not part of GNU Emacs.
- ;; This file is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
- ;; This file is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
- ;;; Commentary:
- ;; The below is generated from a README at
- ;; https://github.com/cosmicexplorer/helm-rg.
- ;; MELPA: https://melpa.org/#/helm-rg
- ;; !`helm-rg' example usage (./emacs-helm-rg.png)
- ;; Search massive codebases extremely fast, using `ripgrep'
- ;; (https://github.com/BurntSushi/ripgrep) and `helm'
- ;; (https://github.com/emacs-helm/helm). Inspired by `helm-ag'
- ;; (https://github.com/syohex/emacs-helm-ag) and `f3'
- ;; (https://github.com/cosmicexplorer/f3).
- ;; Also check out rg.el (https://github.com/dajva/rg.el), which I haven't used
- ;; much but seems pretty cool.
- ;; Usage:
- ;; *See the `ripgrep' whirlwind tour
- ;; (https://github.com/BurntSushi/ripgrep#whirlwind-tour) for further
- ;; information on invoking `ripgrep'.*
- ;; - Invoke the interactive function `helm-rg' to start a search with `ripgrep'
- ;; in the current directory.
- ;; - `helm' is used to browse the results and update the output as you
- ;; type.
- ;; - Each line has the file path, the line number, and the column number of
- ;; the start of the match, and each part is highlighted differently.
- ;; - Use `TAB' to invoke the helm persistent action, which previews the
- ;; result and highlights the matched text in the preview.
- ;; - Use `RET' to visit the file containing the result, move point to the
- ;; start of the match, and recenter.
- ;; - The result's buffer is displayed with
- ;; `helm-rg-display-buffer-normal-method' (which defaults to
- ;; `switch-to-buffer').
- ;; - Use a prefix argument (`C-u RET') to open the buffer with
- ;; `helm-rg-display-buffer-alternate-method' (which defaults to
- ;; `pop-to-buffer').
- ;; - The text entered into the minibuffer is interpreted into a PCRE
- ;; (https://pcre.org) regexp to pass to `ripgrep'.
- ;; - `helm-rg''s pattern syntax is basically PCRE, but single spaces
- ;; basically act as a more powerful conjunction operator.
- ;; - For example, the pattern `a b' in the minibuffer is transformed
- ;; into `a.*b|b.*a'.
- ;; - The single space can be used to find lines with any
- ;; permutation of the regexps on either side of the space.
- ;; - Two spaces in a row will search for a literal single space.
- ;; - `ripgrep''s `--smart-case' option is used so that case-sensitive
- ;; search is only on if any of the characters in the pattern are capitalized.
- ;; - For example, `ab' (conceptually) searches `[Aa][bB]', but `Ab'
- ;; in the minibuffer will only search for the pattern `Ab' with `ripgrep',
- ;; because it has at least one uppercase letter.
- ;; - Use `M-d' to select a new directory to search from.
- ;; - Use `M-g' to input a glob pattern to filter files by, e.g. `*.py'.
- ;; - The glob pattern defaults to the value of
- ;; `helm-rg-default-glob-string', which is an empty string (matches every file)
- ;; unless you customize it.
- ;; - Pressing `M-g' again shows the same minibuffer prompt for the glob
- ;; pattern, with the string that was previously input.
- ;; - Use `<left>' and `<right>' to go up and down by files in the results.
- ;; - `<up>' and `<down>' simply go up and down by match result, and there
- ;; may be many matches for your pattern in a single file, even multiple on a
- ;; single line (which `ripgrep' reports as multiple separate results).
- ;; - The `<left>' and `<right>' keys will move up or down until it lands on
- ;; a result from a different file than it started on.
- ;; - When moving by file, `helm-rg' will cycle around the results list,
- ;; but it will print a harmless error message instead of looping infinitely if
- ;; all results are from the same file.
- ;; - Use the interactive autoloaded function `helm-rg-display-help' to see the
- ;; ripgrep command's usage info.
- ;; TODO:
- ;; *items checked completed here are ready to be added to the docs above*
- ;; - [x] make a keybinding to drop into an "edit mode" and edit file content
- ;; inline in results like `helm-ag' (https://github.com/syohex/emacs-helm-ag)
- ;; - *currently called "bounce mode"* in the alpha stage
- ;; - [x] needs to dedup results from the same line
- ;; - [x] should also merge the colorations
- ;; - [x] this might be easier without using the `--vimgrep' flag (!!!)
- ;; - [x] can insert markers on either side of each line to find the text
- ;; added or removed
- ;; - [x] can change the filename by editing the file line
- ;; - [x] needs to reset all the file data for each entry if the file
- ;; name is being changed!!!
- ;; - [x] can expand the windows of text beyond single lines at a time
- ;; - using `helm-rg--expand-match-context' and/or
- ;; `helm-rg--spread-match-context'
- ;; - [x] and pop into another buffer for a quick view if you want
- ;; - can use `helm-rg--visit-current-file-for-bounce'
- ;; - [ ] can expand up and down from file header lines to add lines
- ;; from the top or bottom of the file!
- ;; - [ ] can use newlines in inserted text
- ;; - not for file names -- newlines are still removed there
- ;; - would need to use text properties to move by match results
- ;; then, for everything that uses `helm-rg--apply-matches-with-file-for-bounce'
- ;; basically
- ;; - [x] visiting the file should go to the appropriate line of the file!
- ;; - [x] color all results in the file in the async action!
- ;; - [x] don't recolor when switching to a different result in the same
- ;; file!
- ;; - [x] don't color matches whenever file path matches
- ;; `helm-rg-shallow-highlight-files-regexp'
- ;; - [ ] use `ripgrep' file types instead of flattening globbing out into
- ;; `helm-rg-default-glob-string'
- ;; - user defines file types in a `defcustom', and can interactively toggle
- ;; the accepted file types
- ;; - user can also set the default set of file types
- ;; - as a dir-local variable!!
- ;; - [ ] add testing
- ;; - [ ] should be testing all of our interactive functions
- ;; - in all configurations (for all permutations of `defcustom' values)
- ;; - [ ] also everything that's called by helm
- ;; - does helm have any frameworks to make integration testing easier?
- ;; - [ ] publish `update-commentary.el' and the associated machinery
- ;; - as an npm package, MELPA package, pandoc writer, *???*
- ;; - [ ] make a keybinding for running `helm-rg' on dired marked files
- ;; - then you could do an `f3' search, bounce to dired, then immediately
- ;; `helm-rg' on just the file paths from the `f3' search, *which would be
- ;; sick*
- ;; License:
- ;; GPL 3.0+ (./LICENSE)
- ;; End Commentary
- ;;; Code:
- (require 'ansi-color)
- (require 'cl-lib)
- (require 'dash)
- (require 'font-lock)
- (require 'helm)
- (require 'helm-files)
- (require 'helm-grep)
- (require 'helm-lib)
- (require 'pcase)
- (require 'rx)
- (require 'subr-x)
- ;; Customization Helpers
- (defun helm-rg--always-safe-local (_)
- "Use as a :safe predicate in a `defcustom' form to accept any local override."
- t)
- (defun helm-rg--gen-defcustom-form-from-alist (name alist doc args)
- ;; TODO: get all the pcase macros at the very top of the file!
- (let ((alist-resolved (pcase-exhaustive alist
- ((and (pred symbolp) x) (symbol-value x))
- ((and (pred listp) x) x))))
- `(defcustom ,name ',(car (helm-rg--alist-keys alist-resolved))
- ,doc
- :type `(radio ,@(--map `(const ,it) (helm-rg--alist-keys ',alist-resolved)))
- :group 'helm-rg
- ,@args)))
- (defmacro helm-rg--defcustom-from-alist (name alist doc &rest args)
- "Create a `defcustom' named NAME which can take the keys of ALIST as values.
- The DOC and ARGS are passed on to the generated `defcustom' form. The default value for the
- `defcustom' is the `car' of the first element of ALIST. ALIST must be the unquoted name of a
- variable containing an alist."
- (declare (indent 2))
- (helm-rg--gen-defcustom-form-from-alist name alist doc args))
- ;; CL deftypes
- (cl-deftype helm-rg-existing-file ()
- `(and string
- (satisfies file-exists-p)))
- (cl-deftype helm-rg-existing-directory ()
- `(and helm-rg-existing-file
- (satisfies file-directory-p)))
- ;; Interesting macros
- (cl-defmacro helm-rg--with-gensyms ((&rest syms) &rest body)
- (declare (indent 1))
- `(let ,(--map `(,it (cl-gensym)) syms)
- ,@body))
- (defmacro helm-rg--_ (expr)
- "Replace all instances of `_' in EXPR with an anonymous argument.
- Return a lambda accepting that argument."
- (declare (debug (sexp body)))
- (helm-rg--with-gensyms (arg)
- `(lambda (,arg)
- ,(cl-subst arg '_ expr :test #'eq))))
- (cl-defun helm-rg--join-conditions (conditions &key (joiner 'or))
- "If CONDITIONS has one element, return it, otherwise wrap them with JOINER.
- This is used because `pcase' doesn't accept conditions with a single element (e.g. `(or 3)')."
- (pcase-exhaustive conditions
- (`nil (error "The list of conditions may not be nil (with joiner '%S')" joiner))
- (`(,single-sexp) single-sexp)
- (x `(,joiner ,@x))))
- (pcase-defmacro helm-rg-cl-typep (&rest types)
- "Matches when the subject is any of TYPES, using `cl-typep'."
- (helm-rg--with-gensyms (val)
- `(and ,val
- ,(helm-rg--join-conditions
- (--map `(guard (cl-typep ,val ',it)) types)))))
- (pcase-defmacro helm-rg-deref-sym (sym)
- "???"
- (list 'quote (eval sym)))
- (defconst helm-rg--keyword-symbol-rx-expr `(: bos ":"))
- (cl-deftype helm-rg-non-keyword-symbol ()
- `(and symbol
- (not keyword)))
- (defun helm-rg--make-non-keyword-sym-from-keyword-sym (kw-sym)
- (cl-check-type kw-sym keyword)
- (->> kw-sym
- (symbol-name)
- (replace-regexp-in-string (rx-to-string helm-rg--keyword-symbol-rx-expr) "")
- (intern)))
- (defun helm-rg--make-keyword-from-non-keyword-sym (non-kw-sym)
- (cl-check-type non-kw-sym helm-rg-non-keyword-symbol)
- (->> non-kw-sym
- (symbol-name)
- (format ":%s")
- (intern)))
- (defun helm-rg--parse-plist-spec (plist-spec)
- (pcase-exhaustive plist-spec
- (`(,(and (helm-rg-cl-typep keyword) kw-sym)
- ,value)
- `(,kw-sym ,value))
- ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol)
- sym)
- `(,(helm-rg--make-keyword-from-non-keyword-sym sym)
- ,sym))))
- (defmacro helm-rg-construct-plist (&rest plist-specs)
- (->> plist-specs
- (-map #'helm-rg--parse-plist-spec)
- (apply #'append '(list))))
- (defun helm-rg--parse-&optional-spec (optional-spec)
- (pcase-exhaustive optional-spec
- (`(,upat ,initform ,svar)
- (helm-rg-construct-plist upat initform svar))
- (`(,upat ,initform)
- (helm-rg-construct-plist upat initform))
- ((or `(,upat) upat)
- (helm-rg-construct-plist upat))))
- (defun helm-rg--read-&optional-specs (parsed-optional-spec-list)
- (pcase-exhaustive parsed-optional-spec-list
- (`(,cur . ,rest)
- `(or (and `nil
- ,@(->> (cons cur rest)
- (--map (cl-destructuring-bind (&key upat initform svar) it
- `(,@(and svar `((let ,svar nil)))
- (let ,upat ,initform))))
- (funcall #'append)
- (-flatten-n 1)))
- ,(cl-destructuring-bind (&key upat _initform svar) cur
- (helm-rg--join-conditions
- ;; FIXME: put the below comment in the docstrings for optional and keyword pcase
- ;; macros!
- ;; NB: SVAR is bound before INITFORM is evaluated, which means you can refer to SVAR
- ;; within INITFORM (and more importantly, within UPAT)!
- `(,@(and svar `((let ,svar t)))
- ,(->> (and rest
- (->> rest
- (helm-rg--read-&optional-specs)
- (list '\,)))
- (cons (list '\, upat))
- (list '\`)))
- :joiner 'and))))))
- (pcase-defmacro helm-rg-&optional (&rest all-optional-specs)
- (->> all-optional-specs
- (-map #'helm-rg--parse-&optional-spec)
- (helm-rg--read-&optional-specs)))
- (defun helm-rg--parse-&key-spec (key-spec)
- (pcase-exhaustive key-spec
- ((and (or :exhaustive :required) special-sym)
- special-sym)
- (`(,(or `(,(and (helm-rg-cl-typep keyword)
- kw-sym)
- ,upat)
- (and (or `(,upat) upat)
- (let kw-sym (helm-rg--make-keyword-from-non-keyword-sym upat))))
- . ,(or
- (and :required
- (let required t)
- (let initform nil)
- (let svar nil))
- (and (helm-rg-&optional initform svar)
- (let required nil))))
- (helm-rg-construct-plist kw-sym upat required initform svar))
- ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol)
- upat)
- (helm-rg-construct-plist
- (:kw-sym (helm-rg--make-keyword-from-non-keyword-sym upat))
- upat
- (:required nil)
- (:initform nil)
- (:svar nil)))))
- (defun helm-rg--flipped-plist-member (prop plist)
- (plist-member plist prop))
- (defun helm-rg--plist-parse-pairs (plist)
- (cl-loop
- with prev-keyword = nil
- for el in plist
- for is-keyword-posn = t then (not is-keyword-posn)
- when is-keyword-posn
- do (progn
- (cl-check-type el keyword)
- (setq prev-keyword el))
- else
- collect (list prev-keyword el)
- into pairs
- finally return (progn
- (cl-assert (not is-keyword-posn) t
- (format "Invalid plist %S ends on keyword '%S'"
- plist prev-keyword))
- pairs)))
- (defun helm-rg--plist-keys (plist)
- (->> plist
- (helm-rg--plist-parse-pairs)
- (-map #'car)))
- (defun helm-rg--force-required-parsed-&key-spec (spec)
- (cl-destructuring-bind (&key kw-sym upat required initform svar) spec
- ;; TODO: better error messaging here!
- (cl-assert (not initform))
- (cl-assert (not svar))
- (cl-assert (not required))
- (helm-rg-construct-plist kw-sym upat (:required t) (:initform nil) (:svar nil))))
- (cl-defun helm-rg--find-first-duplicate (seq &key (test #'eq))
- (cl-loop
- with tbl = (make-hash-table :test test)
- for el in seq
- when (gethash el tbl)
- return el
- else do (puthash el t tbl)
- finally return nil))
- (cl-defun helm-rg--read-&key-specs (parsed-key-spec-list &key exhaustive)
- (let* ((all-keys (->> parsed-key-spec-list
- (--keep (pcase-exhaustive it
- (:required nil)
- (x (plist-get x :kw-sym))))))
- (first-duplicate-key (helm-rg--find-first-duplicate all-keys)))
- (when first-duplicate-key
- (error "Keyword '%S' provided more than once for keyword set %S"
- first-duplicate-key all-keys))
- (let ((pcase-expr
- (pcase-exhaustive parsed-key-spec-list
- (`(:required . ,rest)
- (--> rest
- (-map #'helm-rg--force-required-parsed-&key-spec it)
- (helm-rg--read-&key-specs it)))
- (`(,cur . ,rest)
- (helm-rg--join-conditions
- `(,(helm-rg--join-conditions
- (cl-destructuring-bind
- (&key kw-sym upat required initform svar) cur
- `((app (helm-rg--flipped-plist-member ,kw-sym)
- ,(helm-rg--join-conditions
- `(,@(unless required
- `((and `nil
- ,@(and svar `((let ,svar nil)))
- (let ,upat ,initform))))
- ,(helm-rg--join-conditions
- `(,@(and svar `((let ,svar t)))
- ;; `plist-member' gives us the rest of the list too -- discard
- ;; by matching it to `_'.
- ,(->> (list kw-sym (list '\, upat) '\, '_)
- (list '\`)))
- :joiner 'and))
- :joiner 'or))))
- :joiner 'and)
- ,@(and rest (list (helm-rg--read-&key-specs rest))))
- :joiner 'and)))))
- (if exhaustive
- (helm-rg--with-gensyms (exp-plist-keys)
- `(and
- ;; NB: we do not attempt to parse the `pcase' subject as a plist (done with
- ;; `helm-rg--plist-keys') unless `:exhaustive' is provided (we just use `plist-get')
- ;; -- this is intentional.
- (and (app (helm-rg--plist-keys) ,exp-plist-keys)
- (guard (not (-difference ,exp-plist-keys ',all-keys))))
- ,pcase-expr))
- pcase-expr))))
- (pcase-defmacro helm-rg-&key (&rest all-key-specs)
- ;;; TODO: add alist matching -- this should be trivial, just allowing
- ;;; non-keyword syms in the argument spec.
- (pcase all-key-specs
- (`(:exhaustive . ,rest)
- (--> rest
- (-map #'helm-rg--parse-&key-spec it)
- (helm-rg--read-&key-specs it :exhaustive t)))
- (specs (->> specs
- (-map #'helm-rg--parse-&key-spec)
- (helm-rg--read-&key-specs)))))
- (pcase-defmacro helm-rg-&key-complete (&rest all-key-specs)
- "`helm-rg-&key', but there must be no other keys, and all the keys in ALL-KEY-SPECS must exist."
- `(helm-rg-&key :exhaustive :required ,@all-key-specs))
- (defun helm-rg--parse-format-spec (format-spec)
- "Convert a list FORMAT-SPEC into some result for `helm-rg--make-formatter'."
- (pcase-exhaustive format-spec
- ((and (helm-rg-cl-typep string) x)
- (helm-rg-construct-plist
- (:fmt x) (:expr nil) (:argument nil)))
- ((and (helm-rg-cl-typep helm-rg-non-keyword-symbol) sym)
- (helm-rg-construct-plist (:fmt "%s") (:expr sym) (:argument nil)))
- ((and (helm-rg-cl-typep keyword)
- (app (helm-rg--make-non-keyword-sym-from-keyword-sym)
- non-kw-sym))
- (helm-rg-construct-plist (:fmt "%s") (:expr non-kw-sym) (:argument non-kw-sym)))
- (`(,(or (and (helm-rg-cl-typep keyword)
- (app (helm-rg--make-non-keyword-sym-from-keyword-sym)
- argument)
- (let expr argument))
- (and expr (let argument nil)))
- . ,(helm-rg-&key (fmt "%s")))
- (helm-rg-construct-plist fmt expr argument))))
- (defun helm-rg--read-format-specs (format-spec-list)
- (cl-loop
- with fmts = nil
- with exprs = nil
- with arguments = nil
- for parsed-spec in (-map #'helm-rg--parse-format-spec format-spec-list)
- ;; TODO: turn this into an unzip-plists method/macro or something!
- do (cl-destructuring-bind (&key fmt expr argument) parsed-spec
- (push fmt fmts)
- (when expr (push expr exprs))
- (when argument (push argument arguments)))
- finally return (helm-rg-construct-plist
- (:fmts (reverse fmts))
- (:exprs (reverse exprs))
- (:arguments (-> arguments (-uniq) (reverse))))))
- (cl-defmacro helm-rg-format ((format-specs &rest kwargs) &key (sep " "))
- (cl-destructuring-bind (&key fmts exprs arguments)
- (helm-rg--read-format-specs format-specs)
- (cond
- (arguments
- `(cl-destructuring-bind (&key ,@arguments) ',kwargs
- ;; TODO: a "once-only" macro that's just sugar for gensyms
- (format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs)))
- (kwargs
- (error "No arguments were declared, but keyword arguments %S were provided" kwargs))
- (t
- `(format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs)))))
- (cl-defmacro helm-rg-make-formatter (format-specs &key (sep " "))
- (cl-destructuring-bind (&key fmts exprs arguments)
- (helm-rg--read-format-specs format-specs)
- (unless arguments
- (error "No arguments were declared in the specs %S" format-specs))
- ;; TODO: make a macro that can create a lambda with visible keyword arguments (a "cl-lambda"
- ;; type thing)
- (helm-rg--with-gensyms (args)
- `(lambda (&rest ,args)
- (cl-destructuring-bind (&key ,@arguments) ,args
- (format (mapconcat #'identity (list ,@fmts) ,sep) ,@exprs))))))
- (defun helm-rg--validate-rx-kwarg (keyword-sym-for-binding)
- (pcase-exhaustive keyword-sym-for-binding
- ((and (helm-rg-cl-typep keyword)
- (app (helm-rg--make-non-keyword-sym-from-keyword-sym)
- non-kw-sym))
- non-kw-sym)
- ((and (helm-rg-cl-typep symbol)
- non-kw-sym
- (app (helm-rg--make-keyword-from-non-keyword-sym)
- kw-sym))
- (error (helm-rg-format
- (("symbol" (non-kw-sym :fmt "%S")
- "must be a keyword arg" (kw-sym :fmt "(e.g. %S)."))))))))
- (defun helm-rg--apply-tree-fun (mapper tree)
- "Apply MAPPER to the nodes of TREE using `-tree-map-nodes'.
- This method applies MAPPER, saves the result, and if the result is non-nil, returns the result
- instead of the node of MAPPER, otherwise it continues to recurse down the nodes of TREE."
- (let (intermediate-value-holder)
- (-tree-map-nodes
- (helm-rg--_ (setq intermediate-value-holder (funcall mapper _)))
- (helm-rg--_ intermediate-value-holder)
- tree)))
- (defmacro helm-rg--pcase-tree (tree &rest pcase-exprs)
- "Apply a `pcase' to the nodes of TREE with `helm-rg--apply-tree-fun'.
- PCASE-EXPRS are the cases provided to `pcase'. If the `pcase' cases do not
- match the node (returns nil), it continues to recurse down the tree --
- otherwise, the return value replaces the node of the tree."
- (declare (indent 1))
- `(helm-rg--apply-tree-fun
- (helm-rg--_ (pcase _ ,@pcase-exprs))
- ,tree))
- (defconst helm-rg--named-group-symbol 'named-group)
- (defconst helm-rg--eval-expr-symbol 'eval)
- (defconst helm-rg--duplicate-var-eval-form-error-str
- "'%S' variable name used a second time in evaluation of form '%S'.
- previous vars were: %S")
- (defconst helm-rg--duplicate-var-literal-form-error-str
- "'%S' variable named used a second time in declaration of regexp group '%S'.
- previous vars were: %S")
- (cl-defun helm-rg--transform-rx-sexp (sexp &key (group-num-init 1))
- (let ((all-bind-vars-mappings nil))
- (--> (helm-rg--pcase-tree sexp
- ;; `(eval ,eval-expr) => evaluate the expression!
- ;; NB: this occurs at macro-expansion time, like the equivalent `rx'
- ;; pcase macro, which is before any surrounding let-bindings occur!)
- (`(,(helm-rg-deref-sym helm-rg--eval-expr-symbol) ,eval-expr)
- (cl-destructuring-bind (&key transformed bind-vars)
- (helm-rg--transform-rx-sexp (eval eval-expr t) :group-num-init group-num-init)
- (cl-loop
- for quoted-var in bind-vars
- do (progn
- (cl-incf group-num-init)
- (when (cl-find quoted-var all-bind-vars-mappings)
- (error helm-rg--duplicate-var-eval-form-error-str
- quoted-var eval-expr all-bind-vars-mappings))
- (push quoted-var all-bind-vars-mappings)))
- transformed))
- ;; `(named-group :var-name . ,rx-forms) => create an explicitly-numbered regexp group
- ;; and, if the resulting regexp matches, bind the match string for that numbered group to
- ;; var-name (without the initial ":", which is required)!
- (`(,(helm-rg-deref-sym helm-rg--named-group-symbol)
- ,(app (helm-rg--validate-rx-kwarg) binding-var)
- . ,rx-forms)
- ;; We have bound to this variable -- save the current group number and push this
- ;; variable onto the list of binding variables.
- (let ((cur-group-num group-num-init))
- (push binding-var all-bind-vars-mappings)
- (cl-incf group-num-init)
- (cl-loop
- for sub-rx in rx-forms
- collect (cl-destructuring-bind (&key transformed bind-vars)
- (helm-rg--transform-rx-sexp sub-rx :group-num-init group-num-init)
- (cl-loop
- for quoted-var in bind-vars
- do (progn
- (cl-incf group-num-init)
- (when (cl-find quoted-var all-bind-vars-mappings)
- (error
- helm-rg--duplicate-var-literal-form-error-str
- quoted-var sub-rx all-bind-vars-mappings))
- (push quoted-var all-bind-vars-mappings)))
- transformed)
- into all-transformed-exprs
- finally return `(group-n ,cur-group-num ,@all-transformed-exprs)))))
- (list :transformed it :bind-vars (reverse all-bind-vars-mappings)))))
- (defmacro helm-rg-pcase-cl-defmacro (&rest args)
- "`pcase-defmacro', but the --pcase-macroexpander function is a `cl-defun'.
- \n(fn NAME ARGS [DOC] &rest BODY...)"
- (declare (indent 2) (debug defun) (doc-string 3))
- (->> `(pcase-defmacro ,@args)
- (macroexpand-1)
- (cl-subst 'cl-defun 'defun)))
- (helm-rg-pcase-cl-defmacro helm-rg-rx (rx-sexp)
- ;; FIXME: have some way to get the indices of each bound var (for things like
- ;; `match-data')
- (pcase-exhaustive (helm-rg--transform-rx-sexp rx-sexp)
- ((helm-rg-&key-complete transformed bind-vars)
- (helm-rg--with-gensyms (str-sym)
- `(and ,str-sym
- ,(helm-rg--join-conditions
- ;; We would just delegate to `rx--pcase-macroexpander', but requiring subr errors
- ;; out, extremely mysteriously.
- `((pred (string-match (rx-to-string ',transformed)))
- ,@(cl-loop for symbol-to-bind in bind-vars
- for match-index upfrom 1
- collect `(let ,symbol-to-bind (match-string ,match-index ,str-sym))))
- :joiner 'and))))))
- (defun helm-rg--prefix-symbol-with-underscore (sym)
- (->> sym
- (symbol-name)
- (format "_%s")
- (intern)))
- (defmacro helm-rg-mark-unused (vars &rest body)
- (declare (indent 1))
- `(let (,@(--map `(,(helm-rg--prefix-symbol-with-underscore it) ,it) vars))
- ,@body))
- ;; Public error types
- (define-error 'helm-rg-error "Error invoking `helm-rg'")
- ;; Customization
- (defgroup helm-rg nil
- "Group for `helm-rg' customizations."
- :group 'helm-grep)
- (defcustom helm-rg-ripgrep-executable (executable-find "rg")
- "The location of the ripgrep binary executable."
- :type 'string
- :group 'helm-rg)
- (defcustom helm-rg-default-glob-string ""
- "The glob pattern used for the '-g' argument to ripgrep.
- Set to the empty string to match every file."
- :type 'string
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-default-extra-args nil
- "Extra arguments passed to ripgrep on the command line.
- Note that default filename globbing and case sensitivity can be set with their own defcustoms, and
- can be modified while invoking `helm-rg' -- see the help for that method. If the extra arguments are
- ones you use commonly, consider submitting a pull request to
- https://github.com/cosmicexplorer/helm-rg with a specific `defcustom' and keybinding for that
- particular ripgrep option and set of options."
- :type '(repeat string)
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-default-directory 'default
- "Specification for starting directory to invoke ripgrep in.
- Used in `helm-rg--interpret-starting-dir'. Possible values:
- 'default => Use `default-directory'.
- 'git-root => Use \"git rev-parse --show-toplevel\" (see
- `helm-rg-git-executable').
- <string> => Use the directory at path <string>."
- :type '(choice symbol string)
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-git-executable (executable-find "git")
- "Location of git executable."
- :type 'string
- :group 'helm-rg)
- (defcustom helm-rg-thing-at-point 'symbol
- "Type of object at point to initialize the `helm-rg' minibuffer input with."
- :type 'symbol
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-input-min-search-chars 2
- "Ripgrep will not be invoked unless the input is at least this many chars.
- See `helm-rg--make-process' and `helm-rg--make-dummy-process' if interested."
- ;; FIXME: this should be a *positive* integer!
- :type 'integer
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-display-buffer-normal-method #'switch-to-buffer
- "A function accepting a single argument BUF and displaying the buffer.
- The default function to invoke to display a visited buffer in some window in
- `helm-rg'."
- :type 'function
- :group 'helm-rg)
- (defcustom helm-rg-display-buffer-alternate-method #'pop-to-buffer
- "A function accepting a single argument BUF and displaying the buffer.
- The function will be invoked if a prefix argument is used when visiting a result
- in `helm-rg'."
- :type 'function
- :group 'helm-rg)
- (defcustom helm-rg-shallow-highlight-files-regexp nil
- "Regexp describing file paths to only partially highlight, for performance reasons.
- By default, `helm-rg' will create overlays to highlight all the matches from ripgrep in a file when
- previewing a result. This is done each time a match is selected, even for buffers already
- previewed. Creating these overlays can be slow for files with lots of matches in some search. If
- this variable is set to an elisp regexp and some file path matches it, `helm-rg' will only highlight
- the current line of the file and the matches in that line when previewing that file."
- :type 'regexp
- :safe #'helm-rg--always-safe-local
- :group 'helm-rg)
- (defcustom helm-rg-prepend-file-name-line-at-top-of-matches t
- "Whether to put the file path as a separate line in `helm-rg' output above the file's matches.
- The file can be visited as if it was a match on the first line of the file (without any matched
- text).
- FIXME: if this is nil and `helm-rg-include-file-on-every-match-line' is t, you get a stream of just
- line numbers and content, without any file names. We should unify these two boolean options somehow
- to get all three allowable states."
- :type 'boolean
- :group 'helm-rg)
- (defcustom helm-rg-include-file-on-every-match-line nil
- "Whether to include the file path on every line of `helm-rg' output.
- This is purely an interface change, and does not affect anything else."
- :type 'boolean
- :group 'helm-rg)
- (defcustom helm-rg--default-expand-match-lines-for-bounce 3
- "???"
- ;; FIXME: this should be a *positive* integer!
- :type 'integer
- :group 'helm-rg)
- ;; Faces
- (defface helm-rg-preview-line-highlight
- '((t (:background "green" :foreground "black")))
- "Face for the line of text matched by the ripgrep process."
- :group 'helm-rg)
- (defface helm-rg-base-rg-cmd-face
- '((t (:foreground "gray" :weight normal)))
- "Face for the ripgrep executable in the ripgrep invocation."
- :group 'helm-rg)
- (defface helm-rg-extra-arg-face
- '((t (:foreground "yellow" :weight normal)))
- "Face for any arguments added to the command line through `helm-rg--extra-args'."
- :group 'helm-rg)
- (defface helm-rg-inactive-arg-face
- '((t (:foreground "gray" :weight normal)))
- "Face for non-essential arguments in the ripgrep invocation."
- :group 'helm-rg)
- (defface helm-rg-active-arg-face
- '((t (:foreground "green")))
- "Face for arguments in the ripgrep invocation which affect the results."
- :group 'helm-rg)
- (defface helm-rg-directory-cmd-face
- '((t (:foreground "brown" :background "black" :weight normal)))
- "Face for any directories provided as paths to the ripgrep invocation.")
- (defface helm-rg-error-message
- '((t (:foreground "red")))
- "Face for error text displayed in the `helm-buffer' for `helm-rg'."
- :group 'helm-rg)
- (defface helm-rg-title-face
- '((t (:foreground "purple" :background "black" :weight bold)))
- "Face for the title of the ripgrep async helm source."
- :group 'helm-rg)
- (defface helm-rg-directory-header-face
- '((t (:foreground "white" :background "black" :weight bold)))
- "Face for the current directory in the header of the `helm-buffer' for `helm-rg'."
- :group 'helm-rg)
- (defface helm-rg-file-match-face
- '((t (:foreground "#0ff" :underline t)))
- "Face for the file name when displaying matches in the `helm-buffer' for `helm-rg'."
- :group 'helm-rg)
- (defface helm-rg-colon-separator-ripgrep-output-face
- '((t (:foreground "white")))
- "Face for the separator between file, line, and match text in ripgrep output."
- :group 'helm-rg)
- (defface helm-rg-line-number-match-face
- '((t (:foreground "orange" :underline t)))
- "Face for line numbers when displaying matches in the `helm-buffer' for `helm-rg'."
- :group 'helm-rg)
- (defface helm-rg-match-text-face
- '((t (:foreground "white" :background "purple")))
- "Face for displaying matches in the `helm-buffer' and in file previews for `helm-rg'."
- :group 'helm-rg)
- ;; Constants
- (defconst helm-rg--color-format-argument-alist
- '((red :cmd-line "red" :text-property "red3"))
- "Alist mapping symbols to color descriptions.
- This alist mapps (a symbol named after a color) -> (strings to describe that symbol on the ripgrep
- command line and in an Emacs text property). This allows `helm-rg' to identify matched text using
- ripgrep's highlighted output directly instead of doing it ourselves, by telling ripgrep to highlight
- matches a specific color, then searching for that specific color as a text property in the output.")
- (defconst helm-rg--style-format-argument-alist
- '((bold :cmd-line "bold" :text-property bold))
- "Very similar to `helm-rg--color-format-argument-alist', but for non-color styling.")
- (defconst helm-rg--case-sensitive-argument-alist
- '((smart-case "--smart-case")
- (case-sensitive "--case-sensitive")
- (case-insensitive "--ignore-case"))
- "Alist of methods of treating case-sensitivity when invoking ripgrep.
- The value is the ripgrep command line argument which enforces the specified type of
- case-sensitivity.")
- (defconst helm-rg--ripgrep-argv-format-alist
- `((helm-rg-ripgrep-executable :face helm-rg-base-rg-cmd-face)
- ((->> helm-rg--case-sensitive-argument-alist
- (helm-rg--alist-get-exhaustive helm-rg--case-sensitivity))
- :face helm-rg-active-arg-face)
- ("--color=ansi" :face helm-rg-inactive-arg-face)
- ((helm-rg--construct-match-color-format-arguments)
- :face helm-rg-inactive-arg-face)
- ((unless (helm-rg--empty-glob-p helm-rg--glob-string)
- (list "-g" helm-rg--glob-string))
- :face helm-rg-active-arg-face)
- (helm-rg--extra-args :face helm-rg-extra-arg-face)
- (it
- :face font-lock-string-face)
- ((helm-rg--process-paths-to-search helm-rg--paths-to-search)
- :face helm-rg-directory-cmd-face))
- "Alist mapping (sexp -> face) describing how to generate and propertize the argv for ripgrep.")
- (defconst helm-rg--helm-buffer-name "*helm-rg*")
- (defconst helm-rg--process-name "*helm-rg--rg*")
- (defconst helm-rg--process-buffer-name "*helm-rg--rg-output*")
- (defconst helm-rg--error-process-name "*helm-rg--error-process*")
- (defconst helm-rg--error-buffer-name "*helm-rg--errors*")
- (defconst helm-rg--ripgrep-help-buffer-name "helm-rg-usage-help")
- (defconst helm-rg--bounce-buffer-name "helm-rg-bounce-buf")
- (defconst helm-rg--output-new-file-line-rx-expr
- `(named-group
- :whole-line
- (: bos
- (named-group :file-path (+? (not (any 0))))
- eos))
- "Regexp for ripgrep output which marks the start of results for a new file.
- See `helm-rg--process-transition' for usage.")
- (defconst helm-rg--numbered-text-line-rx-expr
- `(named-group
- :whole-line
- (: bos
- (named-group :line-num-str (+ digit))
- ":"
- (named-group :content (*? anything))
- eos))
- "Regexp for ripgrep output which marks a matched line, with the line number and content.
- See `helm-rg--process-transition' for usage.")
- (defconst helm-rg--persistent-action-display-buffer-method #'switch-to-buffer
- "A function accepting a single argument BUF and displaying the buffer.
- Let-bound to `helm-rg--display-buffer-method' in `helm-rg--async-persistent-action'.")
- (defconst helm-rg--loop-input-pattern-regexp
- (rx
- (:
- (* (char ? ))
- ;; group 1 = single entire element
- (group
- (+
- (|
- (not (in ? ))
- (= 2 ? ))))))
- "Regexp applied iteratively to split the input interpreted by `helm-rg'.")
- (defconst helm-rg--all-whitespace-regexp
- (rx (: bos (zero-or-more space) eos)))
- (defconst helm-rg--jump-location-text-property 'helm-rg-jump-to
- "Name of a text property attached to the colorized ripgrep output.
- This text property contains location and match info. See `helm-rg--process-transition' for usage.")
- (defconst helm-rg--helm-header-property-name 'helm-header
- "Property used for the \"header\" of the `helm-buffer' displayed in `helm-rg'.
- This header is generated by helm, and is separate from the process output.")
- ;; Variables
- (defvar helm-rg--append-persistent-buffers nil
- "Whether to record buffers opened during an `helm-rg' session.")
- (defvar helm-rg--cur-persistent-bufs nil
- "List of buffers opened temporarily during an `helm-rg' session.")
- (defvar helm-rg--matches-in-current-file-overlays nil
- "List of overlays used to highlight matches in `helm-rg'.")
- (defvar helm-rg--current-line-overlay nil
- "Overlay for highlighting the selected matching line in a file in `helm-rg'.")
- (defvar helm-rg--current-dir nil
- "Working directory for the current `helm-rg' session.")
- (defvar helm-rg--last-dir nil
- "Last used working directory for resume.")
- (defvar helm-rg--glob-string nil
- "Glob string used for the current `helm-rg' session.")
- (defvar helm-rg--glob-string-history nil
- "History variable for the selection of `helm-rg--glob-string'.")
- (defvar helm-rg--extra-args nil
- "Arguments not associated with other `helm-rg' options, added to the ripgrep command line.")
- (defvar helm-rg--extra-args-history nil
- "History variable for the selection of `helm-rg--extra-args'.")
- (defvar helm-rg--input-history nil
- "History variable for the pattern input to the ripgrep process.")
- (defvar helm-rg--display-buffer-method nil
- "The method to use to display a buffer visiting a result.
- Should accept one argument BUF, the buffer to display.")
- (defvar helm-rg--paths-to-search nil
- ;; FIXME: we have multiple `defvar's which just mirror `defcustoms' (and can then be toggled while
- ;; searching) -- we should almost definitely have a macro to declare/access these kinds of
- ;; variables uniformly.
- "List of paths to use in the ripgrep command.
- All paths are interpreted relative to the directory ripgrep is invoked from.
- When nil, searches from the directory ripgrep is invoked from.
- See the documentation for `helm-rg-default-directory'.")
- (defvar helm-rg--case-sensitivity nil
- "Key of `helm-rg--case-sensitive-argument-alist' to use in a `helm-rg' session.")
- (defvar helm-rg--previously-highlighted-buffer nil
- "Previous buffer visited in between async actions of a `helm-rg' session.
- Used to cache the overlays drawn for matches within a file when visiting matches in the same file
- using `helm-rg--async-persistent-action'.")
- (defvar helm-rg--last-argv nil
- "Argument list for the most recent ripgrep invocation.
- Used for the command line header in `helm-rg--bounce-mode'.")
- ;; Buffer-local Variables
- (defvar-local helm-rg--process-output-parse-state
- (list :cur-file nil)
- "Contains state which is updated as the ripgrep output is processed.
- This is buffer-local because it is specific to a single process invocation and is manipulated in
- that process's buffer. See `helm-rg--parse-process-output' for usage.")
- (defvar-local helm-rg--beginning-of-bounce-content-mark nil
- "Contains a marker pointing to the beginning of the match results in a `helm-rg--bounce' buffer.")
- (defvar-local helm-rg--do-font-locking nil
- "If t, colorize the file text as it would be in an editor.
- This may be expensive for larger files, so it is turned off if
- `helm-rg-shallow-highlight-files-regexp' is a regexp matching the file's path.")
- ;; Utilities
- (defun helm-rg--alist-get-exhaustive (key alist)
- "Get KEY from ALIST, or throw an error."
- (or (alist-get key alist)
- (error "Key '%s' was not found in alist '%S' during an exhaustiveness check"
- key alist)))
- (defun helm-rg--alist-keys (alist)
- "Get all keys of ALIST."
- (cl-mapcar #'car alist))
- (defmacro helm-rg--get-optional-typed (type-name obj &rest body)
- "If OBJ is non-nil, check its type against TYPE-NAME, then bind it to `it' and execute BODY."
- (declare (indent 2))
- `(let ((it ,obj))
- (when it
- (cl-check-type it ,type-name)
- ,@body)))
- (defmacro helm-rg--into-temp-buffer (to-insert &rest body)
- "Execute BODY at the beginning of a `with-temp-buffer' containing TO-INSERT."
- (declare (indent 1))
- `(with-temp-buffer
- (insert ,to-insert)
- (goto-char (point-min))
- ,@body))
- (defmacro helm-rg--with-named-temp-buffer (name &rest body)
- "Execute BODY after binding the result of a `with-temp-buffer' to NAME.
- BODY is executed in the original buffer, not the new temp buffer."
- (declare (indent 1))
- (let ((cur-buf (cl-gensym "helm-rg--with-named-temp-buffer")))
- `(let ((,cur-buf (current-buffer)))
- (with-temp-buffer
- (let ((,name (current-buffer)))
- (with-current-buffer ,cur-buf
- ,@body))))))
- ;; Logic
- (defun helm-rg--make-dummy-process (input err-msg)
- "Make a process that immediately exits to display just a title.
- Provide INPUT to represent the `helm-pattern', and ERR-MSG as the reasoning for failing to display
- any results."
- (let* ((dummy-proc (make-process
- :name helm-rg--process-name
- :buffer helm-rg--process-buffer-name
- :command '("echo")
- :noquery t))
- (input-repr
- (cond
- ((string= input "")
- "<empty string>")
- ((string-match-p helm-rg--all-whitespace-regexp input)
- "<whitespace>")
- (t input)))
- (helm-src-name
- (format "%s %s: %s"
- (helm-rg--make-face 'helm-rg-error-message "no results for input")
- (helm-rg--make-face 'font-lock-string-face input-repr)
- (helm-rg--make-face 'helm-rg-error-message err-msg))))
- (helm-attrset 'name helm-src-name)
- dummy-proc))
- (defun helm-rg--validate-or-make-dummy-process (input)
- (cond
- ((< (length input) helm-rg-input-min-search-chars)
- (helm-rg--make-dummy-process
- input
- (format "must be at least %d characters" helm-rg-input-min-search-chars)))
- (t t)))
- (defun helm-rg--join (sep seq)
- (mapconcat #'identity seq sep))
- (defun helm-rg--props (props str)
- (apply #'propertize (append (list str) props)))
- (defun helm-rg--make-face (face str)
- (helm-rg--props `(face ,face) str))
- (defun helm-rg--process-paths-to-search (paths)
- (cl-check-type helm-rg--current-dir helm-rg-existing-directory)
- (cl-loop
- for path in paths
- for expanded = (expand-file-name path helm-rg--current-dir)
- unless (file-exists-p expanded)
- do (error (concat "Error: expanded path '%s' does not exist. "
- "The cwd was '%s', and the paths provided were %S.")
- expanded
- helm-rg--current-dir
- paths)
- ;; TODO: a `pcase-defmacro' or `pcase' wrapper which checks that all possible cases of a
- ;; `helm-rg--defcustom-from-alist' are enumerated at compile time!
- ;; TODO: `helm-resume' currently fails on resume in the 'relative case.
- collect (pcase-exhaustive helm-rg-file-paths-in-matches-behavior
- (`relative (file-relative-name expanded helm-rg--current-dir))
- (`absolute expanded))))
- (defun helm-rg--empty-glob-p (glob-str)
- (or (null glob-str)
- (string-blank-p glob-str)))
- (defun helm-rg--construct-argv (pattern)
- "Create an argument list from the `helm-pattern' PATTERN for the ripgrep command.
- This argument list is propertized for display in the `helm-buffer' header when using `helm-rg', and
- is used directly to invoke ripgrep. It uses `defcustom' values, and `defvar' values bound in other
- functions."
- ;; TODO: document these pcase deconstructions in the docstring for
- ;; `helm-rg--ripgrep-argv-format-alist'!
- (cl-loop
- for el in helm-rg--ripgrep-argv-format-alist
- append (pcase-exhaustive el
- (`(,(or (and `it (let expr pattern)) expr) :face ,face-sym)
- (pcase-exhaustive (eval expr)
- ((and (pred listp) args)
- (--map (helm-rg--make-face face-sym it) args))
- (arg
- (list (helm-rg--make-face face-sym arg))))))))
- (defun helm-rg--make-process-from-argv (argv)
- (let* ((real-proc (make-process
- :name helm-rg--process-name
- :buffer helm-rg--process-buffer-name
- :command argv
- :noquery t))
- (helm-src-name
- (format "argv: %s" (helm-rg--join " " argv))))
- (helm-attrset 'name helm-src-name)
- (set-process-query-on-exit-flag real-proc nil)
- real-proc))
- (defun helm-rg--make-process ()
- "Invoke ripgrep in `helm-rg--current-dir' with `helm-pattern'.
- Make a dummy process if the input is empty with a clear message to the user."
- (let* ((default-directory helm-rg--current-dir)
- (input helm-pattern))
- (pcase-exhaustive (helm-rg--validate-or-make-dummy-process input)
- ((and (pred processp) x)
- (setq helm-rg--last-argv nil)
- x)
- (`t
- (let* ((rg-regexp (helm-rg--helm-pattern-to-ripgrep-regexp input))
- (argv (helm-rg--construct-argv rg-regexp))
- (real-proc (helm-rg--make-process-from-argv argv)))
- (setq helm-rg--last-argv argv)
- real-proc)))))
- (defun helm-rg--make-overlay-with-face (beg end face)
- "Generate an overlay in region BEG to END with face FACE."
- (let ((olay (make-overlay beg end)))
- (overlay-put olay 'face face)
- olay))
- (defun helm-rg--delete-match-overlays ()
- "Delete all cached overlays in `helm-rg--matches-in-current-file-overlays', and clear it."
- (mapc #'delete-overlay helm-rg--matches-in-current-file-overlays)
- (setq helm-rg--matches-in-current-file-overlays nil))
- (defun helm-rg--delete-line-overlay ()
- "Delete the cached overlay `helm-rg--current-line-overlay', if it exists, and clear it."
- (helm-rg--get-optional-typed overlay helm-rg--current-line-overlay
- (delete-overlay it))
- (setq helm-rg--current-line-overlay nil))
- (defun helm-rg--collect-lines-matches-current-file (orig-line-parsed)
- "Collect all of the matched text regions from ripgrep's highlighted output from ORIG-LINE-PARSED."
- ;; If we are on a file's line, stay where we are, otherwise back up to the closest file line above
- ;; the current line (this is the file that "owns" the entry).
- (cl-destructuring-bind (&key
- ((:file orig-file))
- ((:line-num _orig-line-num))
- ((:match-results _orig-match-results)))
- orig-line-parsed
- ;; Collect all the results on all matching lines of the file.
- (with-helm-window
- (helm-rg--file-backward t)
- (let ((all-match-results nil))
- ;; Process the first line (`helm-rg--iterate-results' will advance
- ;; past the initial element).
- (cl-destructuring-bind (&key _file line-num match-results) (helm-rg--current-jump-location)
- (when (and line-num match-results)
- (push (list :match-line-num line-num
- :line-match-results match-results)
- all-match-results)))
- (helm-rg--iterate-results
- 'forward
- :success-fn (lambda (cur-line-parsed)
- (cl-destructuring-bind (&key file line-num match-results)
- cur-line-parsed
- (cl-check-type orig-file string)
- (cl-check-type file string)
- (if (not (string= orig-file file))
- ;; We have reached the results from a different file, so done.
- t
- (progn
- ;; In filename lines, these are nil.
- (when (and line-num match-results)
- (push (list :match-line-num line-num
- :line-match-results match-results)
- all-match-results))
- ;; We loop forever if there's only one file in
- ;; the results unless we return this as success.
- (helm-end-of-source-p)))))
- :failure-fn (lambda (cur-line-parsed)
- (helm-rg--different-file-line orig-line-parsed cur-line-parsed)))
- (helm-rg--iterate-results
- 'backward
- :success-fn (lambda (cur-line-pars