1003 lines
39 KiB
EmacsLisp
1003 lines
39 KiB
EmacsLisp
|
|
;;; rg.el --- A search tool based on ripgrep -*- lexical-binding: t; -*-
|
|||
|
|
|
|||
|
|
;; Copyright (C) 1985-1987, 1993-1999, 2001-2015 Free Software
|
|||
|
|
;; Foundation, Inc.
|
|||
|
|
;; Copyright (C) 2016-2018 David Landell <david.landell@sunnyhill.email>
|
|||
|
|
;;
|
|||
|
|
;; Author: David Landell <david.landell@sunnyhill.email>
|
|||
|
|
;; Roland McGrath <roland@gnu.org>
|
|||
|
|
;; Package-Version: 20260823.1828
|
|||
|
|
;; Package-Revision: 6b00e2ae98c4
|
|||
|
|
;; URL: https://github.com/dajva/rg.el
|
|||
|
|
;; Package-Requires: ((emacs "28.1") (transient "0.9.2") (wgrep "2.1.10"))
|
|||
|
|
;; Keywords: matching, tools
|
|||
|
|
|
|||
|
|
;; This file is not part of GNU Emacs.
|
|||
|
|
|
|||
|
|
;; This program 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
|
|||
|
|
;; of the License, or (at your option) any later version.
|
|||
|
|
|
|||
|
|
;; This program 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, write to the Free Software
|
|||
|
|
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|||
|
|
;; 02110-1301, USA.
|
|||
|
|
|
|||
|
|
;;; Commentary:
|
|||
|
|
|
|||
|
|
;; A search package based on the ripgrep command line tool.
|
|||
|
|
;; It allows you to interactively create searches, doing automatic
|
|||
|
|
;; searches based on the editing context, refining and modifying
|
|||
|
|
;; search results and much more. It is also highly configurable to be
|
|||
|
|
;; able to fit different users' needs.
|
|||
|
|
|
|||
|
|
;; If you are used to built-in Emacs rgrep command, transitioning to
|
|||
|
|
;; rg should be simple. rg provides a lot of extra features
|
|||
|
|
;; but the basics are similar.
|
|||
|
|
;;
|
|||
|
|
;; The big benefit of using ripgrep instead of grep as a backend is
|
|||
|
|
;; speed. Especially when searching large source code repositories
|
|||
|
|
;; where ripgrep really shines.
|
|||
|
|
|
|||
|
|
;; See info node `(rgel)Top' for documentation or online at https://rgel.readthedocs.io.
|
|||
|
|
|
|||
|
|
|
|||
|
|
;;; Code:
|
|||
|
|
|
|||
|
|
(require 'cl-lib)
|
|||
|
|
(require 'cus-edit)
|
|||
|
|
(require 'grep)
|
|||
|
|
(require 'rg-ibuffer)
|
|||
|
|
(require 'rg-menu)
|
|||
|
|
(require 'rg-result)
|
|||
|
|
(require 'rg-info-hack)
|
|||
|
|
(require 'subr-x)
|
|||
|
|
(require 'vc)
|
|||
|
|
(require 'files-x)
|
|||
|
|
|
|||
|
|
|
|||
|
|
;; Customizations/public vars
|
|||
|
|
(defgroup rg nil
|
|||
|
|
"Settings for rg."
|
|||
|
|
:group 'tools
|
|||
|
|
:group 'external)
|
|||
|
|
|
|||
|
|
(defcustom rg-custom-type-aliases
|
|||
|
|
'()
|
|||
|
|
"A list of file type aliases that are added to the ripgrep built in aliases.
|
|||
|
|
Each list element may be a (string . string) cons containing the name of the
|
|||
|
|
type alias and the file patterns, or a lambda returning a similar cons cell.
|
|||
|
|
A lambda should return nil if it currently has no type aliases to contribute."
|
|||
|
|
:type '(repeat (choice (cons string string) function))
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-prioritized-type-aliases '()
|
|||
|
|
"A list of file type aliases that are prioritized.
|
|||
|
|
When detecting the file type from the current buffer these aliases are selected
|
|||
|
|
if there are conflicting aliases for a file type. Contains only the alias names
|
|||
|
|
and need to match alias names of ripgrep's built in aliases. The order of the
|
|||
|
|
list decides the priority among the types in the list."
|
|||
|
|
:type '(repeat string)
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-executable (executable-find "rg")
|
|||
|
|
"Ripgrep executable."
|
|||
|
|
:type 'string
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-executable-per-connection t
|
|||
|
|
"Invoke `executable-find' per host and store as connection local variable.
|
|||
|
|
Only works in Emacs 27.1 or later."
|
|||
|
|
:type 'boolean
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-command-line-flags nil
|
|||
|
|
"List of command line flags for rg.
|
|||
|
|
Alternatively a function returning a list of flags."
|
|||
|
|
:type '(choice function (repeat string))
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-ignore-case 'case-fold-search
|
|||
|
|
"Decides which mode of case insensitive search that is enabled.
|
|||
|
|
CASE-FOLD-SEARCH means that the variable `case-fold-search' will
|
|||
|
|
trigger smart-case functionality if non nil.
|
|||
|
|
SMART means that case insensitive search will be triggered if the
|
|||
|
|
search pattern contains only lower case. If the pattern contains upper
|
|||
|
|
case letters, case sensitive search will be performed. This is similar
|
|||
|
|
to the rg '--smart-case' flag.
|
|||
|
|
FORCE will force case insensitive search regardless of the content of
|
|||
|
|
the search pattern.
|
|||
|
|
NIL means case sensitive search will be forced."
|
|||
|
|
:type '(choice (const :tag "Case Fold Search" case-fold-search)
|
|||
|
|
(const :tag "Smart" smart)
|
|||
|
|
(const :tag "Force" force)
|
|||
|
|
(const :tag "Off" nil))
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defcustom rg-keymap-prefix "\C-cs"
|
|||
|
|
"Prefix for global `rg' keymap."
|
|||
|
|
:type 'key-sequence
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-default-alias-fallback "everything"
|
|||
|
|
"The default file alias to use when no alias can be determined.
|
|||
|
|
This must be a string that can be match against the types returned
|
|||
|
|
from `rg-get-type-aliases'."
|
|||
|
|
:type 'string
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-buffer-name "rg"
|
|||
|
|
"Search results buffer name.
|
|||
|
|
Can be string or function."
|
|||
|
|
:type '(choice string function)
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-ignore-ripgreprc t
|
|||
|
|
"Ignore the ripgrep config file.
|
|||
|
|
Disabling this setting can break functionality of this package."
|
|||
|
|
:type 'boolean
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-w32-unicode nil
|
|||
|
|
"Enable Unicode support on Windows.
|
|||
|
|
A workaround for NTEmacs subprocess not supporting Unicode arguments."
|
|||
|
|
:type 'boolean
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
(defcustom rg-w32-ripgrep-proxy
|
|||
|
|
(expand-file-name "rg-w32-ripgrep-proxy.bat" user-emacs-directory)
|
|||
|
|
"An automatically generated temporary batch file.
|
|||
|
|
Used to proxy ripgrep Unicode arguments."
|
|||
|
|
:type 'string
|
|||
|
|
:group 'rg)
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defvar rg-command-line-flags-function 'identity
|
|||
|
|
"Function to modify command line flags of a search.
|
|||
|
|
The argument of the function is an optional list of search specific
|
|||
|
|
command line flags and the function shall return a list of command
|
|||
|
|
line flags to use.")
|
|||
|
|
|
|||
|
|
|
|||
|
|
;; Internal vars and structs
|
|||
|
|
(defvar rg-builtin-type-aliases-cache nil
|
|||
|
|
"Cache for \"rg --type-list\".")
|
|||
|
|
|
|||
|
|
(defvar rg-prioritized-type-aliases-cache nil
|
|||
|
|
"Cache for `rg-prioritized-type-aliases'.")
|
|||
|
|
|
|||
|
|
(defvar rg-initial-toggle-flags nil
|
|||
|
|
"List of command line flags set by default by `rg-define-toggle' macro.")
|
|||
|
|
|
|||
|
|
(defvar rg-history nil "History for full rg commands.")
|
|||
|
|
(defvar rg-files-history nil "History for files args.")
|
|||
|
|
(defvar rg-pattern-history nil "History for search patterns.")
|
|||
|
|
|
|||
|
|
(defvar-local rg--executable-local 'unknown)
|
|||
|
|
|
|||
|
|
(defvar rg-required-command-line-flags
|
|||
|
|
'("--color=always"
|
|||
|
|
"--colors=match:fg:red"
|
|||
|
|
"--colors=path:fg:magenta"
|
|||
|
|
"--colors=line:fg:green"
|
|||
|
|
"--colors=column:none"
|
|||
|
|
"-n"))
|
|||
|
|
|
|||
|
|
(defconst rg-internal-type-aliases
|
|||
|
|
'(("all" . "all defined type aliases") ; rg --type=all
|
|||
|
|
("everything" . "*")) ; rg without '--type' arg
|
|||
|
|
"Internal type aliases for special purposes.
|
|||
|
|
These are not produced by \"rg --type-list\" but we need them anyway.")
|
|||
|
|
|
|||
|
|
(defvar rg-global-map
|
|||
|
|
(let ((map (make-sparse-keymap)))
|
|||
|
|
(define-key map "d" 'rg-dwim)
|
|||
|
|
(define-key map "k" 'rg-kill-saved-searches)
|
|||
|
|
(define-key map "l" 'rg-list-searches)
|
|||
|
|
(define-key map "p" 'rg-project)
|
|||
|
|
(define-key map "r" 'rg)
|
|||
|
|
(define-key map "s" 'rg-save-search)
|
|||
|
|
(define-key map "S" 'rg-save-search-as-name)
|
|||
|
|
(define-key map "t" 'rg-literal)
|
|||
|
|
map)
|
|||
|
|
"The global keymap for `rg'.")
|
|||
|
|
|
|||
|
|
|
|||
|
|
;; Defuns
|
|||
|
|
(defun rg-has-connection-local-executable (criteria)
|
|||
|
|
"Return non nil if there is a connection local executable for HOST."
|
|||
|
|
(when (and (fboundp 'hack-connection-local-variables)
|
|||
|
|
(boundp 'connection-local-variables-alist))
|
|||
|
|
(hack-connection-local-variables criteria)
|
|||
|
|
(assoc 'rg-executable connection-local-variables-alist)))
|
|||
|
|
|
|||
|
|
(defun rg-find-executable ()
|
|||
|
|
"Determine which rg executable to use."
|
|||
|
|
(let* ((remote-host (file-remote-p default-directory 'host))
|
|||
|
|
(criteria `(:application rg :machine ,remote-host)))
|
|||
|
|
(if (and remote-host
|
|||
|
|
rg-executable-per-connection
|
|||
|
|
;; Below are just to make byte compiler happy
|
|||
|
|
(fboundp 'connection-local-set-profile-variables)
|
|||
|
|
(fboundp 'connection-local-set-profiles)
|
|||
|
|
(fboundp 'hack-connection-local-variables-apply)
|
|||
|
|
(fboundp 'with-connection-local-variables))
|
|||
|
|
(progn
|
|||
|
|
;; Find executable on remote host once
|
|||
|
|
(when (not (rg-has-connection-local-executable criteria))
|
|||
|
|
(let ((rg-vars-symbol (intern (concat "rg-vars-" remote-host)))
|
|||
|
|
(rg-exec (with-no-warnings (executable-find "rg" t))))
|
|||
|
|
(connection-local-set-profile-variables
|
|||
|
|
rg-vars-symbol
|
|||
|
|
`((rg-executable . ,rg-exec)))
|
|||
|
|
(connection-local-set-profiles criteria rg-vars-symbol)))
|
|||
|
|
;; Here there should be a remote executable available if found
|
|||
|
|
(hack-connection-local-variables-apply criteria)
|
|||
|
|
rg-executable)
|
|||
|
|
;; Use local executable for local buffer
|
|||
|
|
(default-value 'rg-executable))))
|
|||
|
|
|
|||
|
|
(defun rg-executable ()
|
|||
|
|
"Return the \"rg\" executable.
|
|||
|
|
Raises an error if it can not be found."
|
|||
|
|
(let ((executable (rg-find-executable)))
|
|||
|
|
(if executable
|
|||
|
|
(shell-quote-argument executable)
|
|||
|
|
(user-error "No 'rg' executable found in host %s"
|
|||
|
|
(or (file-remote-p default-directory 'host) "localhost")))))
|
|||
|
|
|
|||
|
|
(defun rg--buffer-name ()
|
|||
|
|
"Wrapper for variable `rg-buffer-name'. Return string or call function."
|
|||
|
|
(if (functionp rg-buffer-name)
|
|||
|
|
(funcall rg-buffer-name)
|
|||
|
|
rg-buffer-name))
|
|||
|
|
|
|||
|
|
(defun rg-buffer-name (&optional name-of-mode)
|
|||
|
|
"Return search results buffer name.
|
|||
|
|
NAME-OF-MODE is needed to pass this function to `compilation-start'."
|
|||
|
|
(ignore name-of-mode)
|
|||
|
|
(if rg-recompile
|
|||
|
|
(buffer-name)
|
|||
|
|
(format "*%s*" (rg--buffer-name))))
|
|||
|
|
|
|||
|
|
(defun rg-build-type-add-args ()
|
|||
|
|
"Build a list of --type-add: \"foo:*.foo\" flags.
|
|||
|
|
Do this for each type in `rg-custom-type-aliases'."
|
|||
|
|
(mapcar
|
|||
|
|
(lambda (typedef)
|
|||
|
|
(let ((name (car typedef))
|
|||
|
|
(globs (cdr typedef)))
|
|||
|
|
(mapconcat
|
|||
|
|
(lambda (glob)
|
|||
|
|
(concat "--type-add="
|
|||
|
|
(shell-quote-argument (concat name ":" glob))))
|
|||
|
|
(split-string globs) " ")))
|
|||
|
|
(rg-get-custom-type-aliases)))
|
|||
|
|
|
|||
|
|
(defun rg-is-custom-file-pattern (files)
|
|||
|
|
"Return non nil if FILES is a custom file pattern."
|
|||
|
|
(not (assoc files (rg-get-type-aliases))))
|
|||
|
|
|
|||
|
|
(defun rg-quote-flag (flag)
|
|||
|
|
"If FLAG is an option with a value, shell quote the value if not already quoted.
|
|||
|
|
Options are expected to be in the format '--option=value'."
|
|||
|
|
(if (and (string-match "\\`\\(--[^=]+=\\)\\(.*\\)\\'" flag)
|
|||
|
|
(let ((val (match-string 2 flag)))
|
|||
|
|
(not (or (string-prefix-p "'" val)
|
|||
|
|
(string-prefix-p "\"" val)))))
|
|||
|
|
(concat (match-string 1 flag)
|
|||
|
|
(shell-quote-argument (match-string 2 flag)))
|
|||
|
|
flag))
|
|||
|
|
|
|||
|
|
(defun rg-build-command (pattern files literal flags)
|
|||
|
|
"Create the command line for PATTERN and FILES.
|
|||
|
|
LITERAL determines if search will be literal or regexp based and FLAGS
|
|||
|
|
are command line flags to use for the search."
|
|||
|
|
(let ((command-line
|
|||
|
|
(append
|
|||
|
|
rg-required-command-line-flags
|
|||
|
|
(when (or rg-show-columns rg-group-result)
|
|||
|
|
(list "--column"))
|
|||
|
|
(rg-build-type-add-args)
|
|||
|
|
(mapcar #'rg-quote-flag
|
|||
|
|
(if (functionp rg-command-line-flags)
|
|||
|
|
(funcall rg-command-line-flags)
|
|||
|
|
rg-command-line-flags))
|
|||
|
|
(mapcar #'rg-quote-flag flags)
|
|||
|
|
|
|||
|
|
|
|||
|
|
(list
|
|||
|
|
(if rg-group-result "--heading" "--no-heading"))
|
|||
|
|
(when rg-ignore-ripgreprc
|
|||
|
|
(list "--no-config"))
|
|||
|
|
(when (rg-is-custom-file-pattern files)
|
|||
|
|
(list (concat "--type-add=" (shell-quote-argument (concat "custom:" files)))))
|
|||
|
|
(when literal
|
|||
|
|
(list "--fixed-strings"))
|
|||
|
|
(when (not (equal files "everything"))
|
|||
|
|
(list "--type=<F>"))
|
|||
|
|
(list "-e <R>")
|
|||
|
|
(when (member system-type '(darwin windows-nt))
|
|||
|
|
(list ".")))))
|
|||
|
|
|
|||
|
|
(let ((command (grep-expand-template
|
|||
|
|
(mapconcat 'identity (cons (rg-executable) (delete-dups command-line)) " ")
|
|||
|
|
pattern
|
|||
|
|
(if (rg-is-custom-file-pattern files) "custom" files))))
|
|||
|
|
(cond ((and (eq system-type 'windows-nt) rg-w32-unicode)
|
|||
|
|
(with-temp-file rg-w32-ripgrep-proxy
|
|||
|
|
(set-buffer-multibyte t)
|
|||
|
|
(setq buffer-file-coding-system 'utf-8-dos)
|
|||
|
|
(insert (format "@echo off\n"))
|
|||
|
|
(insert (format "chcp 65001 > nul\n"))
|
|||
|
|
(insert (format "%s\n" command)))
|
|||
|
|
rg-w32-ripgrep-proxy)
|
|||
|
|
(t command)))))
|
|||
|
|
|
|||
|
|
(defun rg-invoke-rg-type-list ()
|
|||
|
|
"Invokes rg --type-list and return the result."
|
|||
|
|
(shell-command-to-string (concat (rg-executable) " --type-list")))
|
|||
|
|
|
|||
|
|
(defun rg-list-builtin-type-aliases ()
|
|||
|
|
"Invokes rg --type-list and puts the result in an alist."
|
|||
|
|
(let ((type-list (delete "" (split-string (rg-invoke-rg-type-list) "\n"))))
|
|||
|
|
(mapcar
|
|||
|
|
(lambda (type-alias)
|
|||
|
|
(setq type-alias (split-string type-alias ":" t))
|
|||
|
|
(cons (string-trim (car type-alias))
|
|||
|
|
(string-trim
|
|||
|
|
(mapconcat 'identity
|
|||
|
|
(split-string (cadr type-alias) "," t )
|
|||
|
|
" "))))
|
|||
|
|
type-list)))
|
|||
|
|
|
|||
|
|
|
|||
|
|
(defun rg-get-custom-type-aliases ()
|
|||
|
|
"Get alist of custom type aliases.
|
|||
|
|
Any lambda elements will be evaluated, and nil results will be
|
|||
|
|
filtered out."
|
|||
|
|
(delq nil (mapcar
|
|||
|
|
(lambda (ct) (if (functionp ct) (funcall ct) ct))
|
|||
|
|
rg-custom-type-aliases)))
|
|||
|
|
|
|||
|
|
(defun rg-get-type-aliases (&optional skip-internal)
|
|||
|
|
"Return supported type aliases.
|
|||
|
|
If SKIP-INTERNAL is non nil the `rg-internal-type-aliases' will be
|
|||
|
|
excluded."
|
|||
|
|
;; Read from ripgrep once or if prioritized aliases changed.
|
|||
|
|
(unless (and rg-builtin-type-aliases-cache
|
|||
|
|
(equal rg-prioritized-type-aliases
|
|||
|
|
(mapcar #'car rg-prioritized-type-aliases-cache)))
|
|||
|
|
(let ((builtin-aliases (rg-list-builtin-type-aliases)))
|
|||
|
|
(if rg-prioritized-type-aliases
|
|||
|
|
(progn
|
|||
|
|
(let ((prioritized-type-aliases
|
|||
|
|
(seq-filter (lambda (item)
|
|||
|
|
(member (car item) rg-prioritized-type-aliases))
|
|||
|
|
builtin-aliases)))
|
|||
|
|
;; Reorder the the found aliases to the same order as rg-prioritized-type-aliases
|
|||
|
|
(setq rg-prioritized-type-aliases-cache
|
|||
|
|
(seq-map (lambda (item)
|
|||
|
|
(assoc item prioritized-type-aliases))
|
|||
|
|
rg-prioritized-type-aliases))
|
|||
|
|
;; Remove the prioritzed aliases from the builtin cache
|
|||
|
|
;; to avoid duplicates.
|
|||
|
|
(setq rg-builtin-type-aliases-cache
|
|||
|
|
(seq-remove (lambda (item)
|
|||
|
|
(member (car item) rg-prioritized-type-aliases))
|
|||
|
|
builtin-aliases))))
|
|||
|
|
;; Simple case, just assign
|
|||
|
|
(setq rg-builtin-type-aliases-cache builtin-aliases)
|
|||
|
|
(setq rg-prioritized-type-aliases-cache nil))))
|
|||
|
|
(append (rg-get-custom-type-aliases)
|
|||
|
|
rg-prioritized-type-aliases-cache
|
|||
|
|
rg-builtin-type-aliases-cache
|
|||
|
|
(unless skip-internal rg-internal-type-aliases)))
|
|||
|
|
|
|||
|
|
(defun rg-default-alias ()
|
|||
|
|
"Return the default alias by matching alias globs with the buffer file name."
|
|||
|
|
(let* ((bufname (or (buffer-file-name)
|
|||
|
|
(replace-regexp-in-string "<[0-9]+>\\'" "" (buffer-name))))
|
|||
|
|
(filename (and bufname
|
|||
|
|
(stringp bufname)
|
|||
|
|
(file-name-nondirectory bufname))))
|
|||
|
|
(or
|
|||
|
|
(when filename
|
|||
|
|
(cl-find-if
|
|||
|
|
(lambda (alias)
|
|||
|
|
(string-match (mapconcat 'wildcard-to-regexp
|
|||
|
|
(split-string (cdr alias) nil t)
|
|||
|
|
"\\|")
|
|||
|
|
filename))
|
|||
|
|
(rg-get-type-aliases t)))
|
|||
|
|
;; Default when an alias for the file can't be determined
|
|||
|
|
(or
|
|||
|
|
(cl-find-if
|
|||
|
|
(lambda (alias)
|
|||
|
|
(string= rg-default-alias-fallback (car alias)))
|
|||
|
|
(rg-get-type-aliases))
|
|||
|
|
(progn
|
|||
|
|
(message "Warning: rg-default-alias-fallback customization does not match any alias. Using \"all\".")
|
|||
|
|
(car rg-internal-type-aliases))))))
|
|||
|
|
|
|||
|
|
(defun rg-tag-default ()
|
|||
|
|
"Get the marked area or thing at point.
|
|||
|
|
Returns nil if nothing at point."
|
|||
|
|
(or (and transient-mark-mode mark-active
|
|||
|
|
(/= (point) (mark))
|
|||
|
|
(buffer-substring-no-properties (point) (mark)))
|
|||
|
|
(funcall (or find-tag-default-function
|
|||
|
|
(get major-mode 'find-tag-default-function)
|
|||
|
|
'find-tag-default))))
|
|||
|
|
|
|||
|
|
(defun rg-read-files ()
|
|||
|
|
"Read files argument for interactive rg."
|
|||
|
|
(let ((default-alias (rg-default-alias)))
|
|||
|
|
(completing-read
|
|||
|
|
(concat "Search in files"
|
|||
|
|
(if default-alias
|
|||
|
|
(concat
|
|||
|
|
" (default: [" (car default-alias) "] = "
|
|||
|
|
(cdr default-alias) ")"))
|
|||
|
|
": ")
|
|||
|
|
(rg-get-type-aliases)
|
|||
|
|
nil nil nil 'rg-files-history
|
|||
|
|
(car default-alias))))
|
|||
|
|
|
|||
|
|
(defun rg-read-pattern (literal &optional default)
|
|||
|
|
"Read search pattern argument from user.
|
|||
|
|
If LITERAL is non nil prompt for literal string.
|
|||
|
|
DEFAULT is the default pattern to use at the prompt."
|
|||
|
|
(let ((default (or default (rg-tag-default)))
|
|||
|
|
(prompt (concat (if literal "Literal" "Regexp")
|
|||
|
|
" search for")))
|
|||
|
|
(read-regexp prompt default 'rg-pattern-history)))
|
|||
|
|
|
|||
|
|
(defun rg-project-root (file)
|
|||
|
|
"Find the project root of the given FILE."
|
|||
|
|
(or
|
|||
|
|
(when (and (require 'projectile nil t)
|
|||
|
|
(fboundp 'projectile-project-root))
|
|||
|
|
(projectile-project-root))
|
|||
|
|
(when (and (require 'find-file-in-project nil t)
|
|||
|
|
(fboundp 'ffip-project-root))
|
|||
|
|
(ffip-project-root))
|
|||
|
|
(when (and (require 'project nil t)
|
|||
|
|
(fboundp 'project-current))
|
|||
|
|
(if-let* ((project (project-current)))
|
|||
|
|
(cond
|
|||
|
|
((fboundp 'project-root)
|
|||
|
|
(project-root project))
|
|||
|
|
((fboundp 'project-roots)
|
|||
|
|
(car (project-roots project))))))
|
|||
|
|
(let ((file (expand-file-name (or file default-directory))))
|
|||
|
|
(condition-case nil
|
|||
|
|
(vc-call-backend (vc-responsible-backend file) 'root file)
|
|||
|
|
(error (file-name-directory file))))))
|
|||
|
|
|
|||
|
|
(defun rg-run (pattern files dir &optional literal confirm flags)
|
|||
|
|
"Execute rg command with supplied PATTERN, FILES and DIR.
|
|||
|
|
If LITERAL is nil interpret PATTERN as regexp, otherwise as a literal.
|
|||
|
|
CONFIRM allows the user to confirm and modify the command before
|
|||
|
|
executing. FLAGS is additional command line flags to use in the search."
|
|||
|
|
(unless (and (stringp pattern) (> (length pattern) 0))
|
|||
|
|
(signal 'user-error '("Empty string: No search done")))
|
|||
|
|
(wgrep-rg-warn-ag-setup)
|
|||
|
|
(unless (and (file-directory-p dir) (file-readable-p dir))
|
|||
|
|
(setq dir default-directory))
|
|||
|
|
(rg-apply-case-flag pattern)
|
|||
|
|
(setq dir (file-name-as-directory (expand-file-name dir)))
|
|||
|
|
(let* ((flags (append rg-initial-toggle-flags flags))
|
|||
|
|
(default-directory dir)
|
|||
|
|
(command (rg-build-command
|
|||
|
|
pattern files literal
|
|||
|
|
flags))
|
|||
|
|
confirmed)
|
|||
|
|
(if confirm
|
|||
|
|
(setq confirmed
|
|||
|
|
(read-from-minibuffer "Confirm: "
|
|||
|
|
command nil nil 'rg-history))
|
|||
|
|
(add-to-history 'rg-history command))
|
|||
|
|
(let ((search (rg-search-create
|
|||
|
|
:pattern pattern
|
|||
|
|
:files files
|
|||
|
|
:dir dir
|
|||
|
|
:literal literal
|
|||
|
|
:flags flags)))
|
|||
|
|
(when (and confirmed
|
|||
|
|
(not (string= confirmed command)))
|
|||
|
|
(setq command confirmed)
|
|||
|
|
;; If user changed command we can't know the parts of the
|
|||
|
|
;; search and needs to disable result buffer modifications.
|
|||
|
|
(setf (rg-search-full-command search) command))
|
|||
|
|
;; Since `rg-buffer-name' can be changed through dir-locals file we
|
|||
|
|
;; need to apply variables from dir-locals that resides in search dir.
|
|||
|
|
;; This way the results buffer will receive correct name on `compilation-start'.
|
|||
|
|
;; `hack-dir-local-variables' is searching dir-locals file in
|
|||
|
|
;; `buffer-file-name' or `default-directory' directory.
|
|||
|
|
;; Temporary buffer must be created here to make sure that dir-locals
|
|||
|
|
;; file is loaded from `default-directory' defined above and
|
|||
|
|
;; not from `buffer-file-name' in case search is started from file buffer.
|
|||
|
|
(with-temp-buffer
|
|||
|
|
(hack-dir-local-variables-non-file-buffer)
|
|||
|
|
;; Setting process-setup-function makes exit-message-function work
|
|||
|
|
;; even when async processes aren't supported.
|
|||
|
|
(with-current-buffer (compilation-start command 'rg-mode #'rg-buffer-name)
|
|||
|
|
(rg-mode-init search)))))
|
|||
|
|
(if (eq next-error-last-buffer (current-buffer))
|
|||
|
|
(setq default-directory dir)))
|
|||
|
|
|
|||
|
|
(defun rg-apply-case-flag (pattern)
|
|||
|
|
"Make sure -i is added to the command if needed.
|
|||
|
|
The value of the `rg-ignore-case' variable and the case of the
|
|||
|
|
supplied PATTERN influences the result. See `rg-ignore-case' for more
|
|||
|
|
detailed info."
|
|||
|
|
(if (or (eq rg-ignore-case 'force)
|
|||
|
|
(and (or (eq rg-ignore-case 'smart)
|
|||
|
|
(and (eq rg-ignore-case 'case-fold-search) case-fold-search))
|
|||
|
|
(isearch-no-upper-case-p pattern t)))
|
|||
|
|
(setq rg-initial-toggle-flags
|
|||
|
|
(add-to-list 'rg-initial-toggle-flags "-i" ))
|
|||
|
|
(setq rg-initial-toggle-flags
|
|||
|
|
(delete "-i" rg-initial-toggle-flags))))
|
|||
|
|
|
|||
|
|
(defun rg-get-rename-target ()
|
|||
|
|
"Return the buffer that will be target for renaming."
|
|||
|
|
(if (eq major-mode 'rg-mode)
|
|||
|
|
(current-buffer)
|
|||
|
|
(get-buffer (rg-buffer-name))))
|
|||
|
|
|
|||
|
|
(defun rg-get-buffer-file-name ()
|
|||
|
|
"Wrapper for function `buffer-file-name'.
|
|||
|
|
Return the result of function `buffer-file-name' if buffer has an
|
|||
|
|
associated file, otherwise raise a user error."
|
|||
|
|
(if (buffer-file-name)
|
|||
|
|
(file-name-nondirectory (buffer-file-name))
|
|||
|
|
(user-error "Buffer does not have an associated file")))
|
|||
|
|
|
|||
|
|
(defalias 'kill-rg 'kill-compilation)
|
|||
|
|
(defalias 'rg-kill-current 'kill-compilation "Kill the ongoing ripgrep search.")
|
|||
|
|
(make-obsolete 'kill-rg 'rg-kill-current "1.7.1")
|
|||
|
|
|
|||
|
|
(defun rg-print-environment ()
|
|||
|
|
"Print the environmet in which this package is running.
|
|||
|
|
The result is also copied to the `kill-ring'. Should be attached to bug
|
|||
|
|
reports."
|
|||
|
|
(interactive)
|
|||
|
|
(let* ((settings
|
|||
|
|
(thread-last (custom-group-members 'rg nil)
|
|||
|
|
(seq-filter (lambda (item)
|
|||
|
|
(eq (cadr item) 'custom-variable)))
|
|||
|
|
(mapcar (lambda (member)
|
|||
|
|
(cons (car member) (symbol-value (car member)))))))
|
|||
|
|
(rg-version (car
|
|||
|
|
(split-string
|
|||
|
|
(shell-command-to-string (format "%s --version" (rg-executable)))
|
|||
|
|
"\n")))
|
|||
|
|
(compilation-filter-advised
|
|||
|
|
(when-let* ((advice-alist (get 'compilation-filter 'ad-advice-info)))
|
|||
|
|
(cdr (assoc 'active advice-alist))))
|
|||
|
|
(environment (concat
|
|||
|
|
"--------- RG environment ---------\n"
|
|||
|
|
(format "emacs-version: %s\n" (car (split-string (emacs-version) "\n")))
|
|||
|
|
(format "system: %s\n" system-type)
|
|||
|
|
(format "ripgrep-version: %s\n" rg-version)
|
|||
|
|
(format "compilation-filter-hook: %S\n" compilation-filter-hook)
|
|||
|
|
(format "compilation-filter-advised: %S\n" compilation-filter-advised)
|
|||
|
|
(mapconcat
|
|||
|
|
(lambda (setting) (format "%S: %S" (car setting) (cdr setting)))
|
|||
|
|
settings "\n")
|
|||
|
|
"\n------------------ END ------------------")))
|
|||
|
|
(message environment)
|
|||
|
|
(kill-new environment)))
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defmacro rg-define-toggle (flag &optional key default)
|
|||
|
|
"Define a command line flag that can be toggled from the rg result buffer.
|
|||
|
|
|
|||
|
|
This will create a function with prefix \"rg-custom-toggle-flag-\"
|
|||
|
|
concatenated with the FLAG name, stripped of any leading dashes. Flag
|
|||
|
|
must be a form that will be evaluated to a string at macro expansion
|
|||
|
|
time. For instance, if FLAG is \"--invert-match\" the function name
|
|||
|
|
will be `rg-custom-toggle-flag-invert-match'. If the flag contains a
|
|||
|
|
value that will be excluded from the function name.
|
|||
|
|
|
|||
|
|
Optional KEY is a key binding that is added to `rg-mode-map'. If the
|
|||
|
|
optional DEFAULT parameter is non nil the flag will be enabled by default."
|
|||
|
|
(let* ((flagvalue (eval flag))
|
|||
|
|
(flagname (thread-last (car (split-string flagvalue " " t))
|
|||
|
|
(string-remove-prefix "-")
|
|||
|
|
(string-remove-prefix "-")))
|
|||
|
|
(funname (concat "rg-custom-toggle-flag-" flagname)))
|
|||
|
|
`(progn
|
|||
|
|
,(if default
|
|||
|
|
`(setq rg-initial-toggle-flags
|
|||
|
|
(add-to-list 'rg-initial-toggle-flags ,flagvalue))
|
|||
|
|
`(setq rg-initial-toggle-flags
|
|||
|
|
(delete ,flagvalue rg-initial-toggle-flags)))
|
|||
|
|
,(when key
|
|||
|
|
`(define-key rg-mode-map ,key (quote ,(intern funname))))
|
|||
|
|
(defun ,(intern funname) ()
|
|||
|
|
,(format "Rerun last search with flag '%s' toggled." flagvalue)
|
|||
|
|
(interactive)
|
|||
|
|
(rg-rerun-toggle-flag ,flagvalue)))))
|
|||
|
|
|
|||
|
|
(defun rg-save-search-as-name (newname)
|
|||
|
|
"Save the search result in current result buffer.
|
|||
|
|
NEWNAME will be added to the result buffer name. New searches will use the
|
|||
|
|
standard buffer unless the search is done from a saved buffer in
|
|||
|
|
which case the saved buffer will be reused."
|
|||
|
|
(interactive "sSave search as name: ")
|
|||
|
|
(when-let* ((buffer (rg-get-rename-target)))
|
|||
|
|
(with-current-buffer buffer
|
|||
|
|
(rename-buffer (format "*%s %s*" (rg--buffer-name) newname)))))
|
|||
|
|
|
|||
|
|
(defun rg-save-search ()
|
|||
|
|
"Save the search result in current result buffer.
|
|||
|
|
The result buffer will be renamed by the `rename-uniquify' function.
|
|||
|
|
To choose a custom name, use `rg-save-search-as-name' instead. New
|
|||
|
|
searches will use the standard buffer unless the search is done from
|
|||
|
|
a saved buffer in which case the saved buffer will be reused."
|
|||
|
|
(interactive)
|
|||
|
|
(when-let* ((buffer (rg-get-rename-target)))
|
|||
|
|
(with-current-buffer buffer
|
|||
|
|
(rename-uniquely)
|
|||
|
|
;; If the new buffer name became default result buffer name, just rename
|
|||
|
|
;; again to make sure the result is saved.
|
|||
|
|
(when (equal (buffer-name) (rg-buffer-name))
|
|||
|
|
(rename-uniquely)))))
|
|||
|
|
|
|||
|
|
(defun rg-kill-saved-searches ()
|
|||
|
|
"Kill all saved rg buffers. The default result buffer will be kept."
|
|||
|
|
(interactive)
|
|||
|
|
(when (y-or-n-p "Confirm kill all saved rg searches? ")
|
|||
|
|
(dolist (buf (buffer-list))
|
|||
|
|
(when (with-current-buffer buf
|
|||
|
|
(and (eq major-mode 'rg-mode)
|
|||
|
|
(not (equal (buffer-name) (rg-buffer-name)))))
|
|||
|
|
(kill-buffer buf)))))
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defun rg-enable-default-bindings (&optional prefix)
|
|||
|
|
"Enable the global `rg' default key bindings under PREFIX key.
|
|||
|
|
If prefix is not supplied `rg-keymap-prefix' is used."
|
|||
|
|
(interactive)
|
|||
|
|
(when-let* ((prefix (or prefix rg-keymap-prefix)))
|
|||
|
|
(if rg-use-transient-menu
|
|||
|
|
(rg-enable-menu prefix)
|
|||
|
|
(global-set-key prefix rg-global-map))))
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defun rg-use-old-defaults ()
|
|||
|
|
"Restore default settings pre version 2.0.0."
|
|||
|
|
(define-key rg-mode-map "\C-f" 'rg-forward-history)
|
|||
|
|
(define-key rg-mode-map "\C-c>" nil)
|
|||
|
|
(define-key rg-mode-map "\C-b" 'rg-back-history)
|
|||
|
|
(define-key rg-mode-map "\C-c<" nil)
|
|||
|
|
(define-key rg-mode-map "\C-n" 'rg-next-file)
|
|||
|
|
(define-key rg-mode-map "\M-N" nil)
|
|||
|
|
(define-key rg-mode-map "\C-p" 'rg-prev-file)
|
|||
|
|
(define-key rg-mode-map "\M-P" nil)
|
|||
|
|
(define-key rg-mode-map "l" 'rg-list-searches)
|
|||
|
|
(define-key rg-mode-map "L" nil)
|
|||
|
|
(define-key rg-mode-map "w" 'wgrep-change-to-wgrep-mode)
|
|||
|
|
(define-key rg-mode-map "e" nil)
|
|||
|
|
(setf rg-group-result nil)
|
|||
|
|
(setf rg-align-position-numbers nil)
|
|||
|
|
(setf rg-align-line-column-separator nil)
|
|||
|
|
(setf rg-align-position-content-separator nil)
|
|||
|
|
(setf rg-use-transient-menu nil)
|
|||
|
|
(setf rg-default-alias-fallback "all"))
|
|||
|
|
|
|||
|
|
|
|||
|
|
(eval-and-compile
|
|||
|
|
(defun rg-set-search-defaults (args)
|
|||
|
|
"Set defaults for required search options missing from ARGS.
|
|||
|
|
If the :confirm option is missing, set it to NEVER, if
|
|||
|
|
the :format option is missing, set it to REGEXP, and if
|
|||
|
|
the :query option is missing, set it to ASK"
|
|||
|
|
(unless (plist-get args :confirm)
|
|||
|
|
(setq args (plist-put args :confirm 'never)))
|
|||
|
|
|
|||
|
|
(unless (plist-get args :format)
|
|||
|
|
(setq args (plist-put args :format 'regexp)))
|
|||
|
|
|
|||
|
|
(unless (plist-get args :query)
|
|||
|
|
(setq args (plist-put args :query 'ask)))
|
|||
|
|
|
|||
|
|
(unless (plist-get args :files)
|
|||
|
|
(setq args (plist-put args :files 'ask)))
|
|||
|
|
|
|||
|
|
(unless (plist-get args :dir)
|
|||
|
|
(setq args (plist-put args :dir 'ask)))
|
|||
|
|
args))
|
|||
|
|
|
|||
|
|
(eval-and-compile
|
|||
|
|
(defun rg-ensure-quoted (arg)
|
|||
|
|
"Ensure that ARG is quoted."
|
|||
|
|
(if (and (consp arg)
|
|||
|
|
(eq (car arg) 'quote))
|
|||
|
|
arg
|
|||
|
|
`(quote ,arg)))
|
|||
|
|
|
|||
|
|
(defun rg-ensure-unquoted (arg)
|
|||
|
|
"Ensure that ARG is quoted."
|
|||
|
|
(if (and (consp arg)
|
|||
|
|
(eq (car arg) 'quote))
|
|||
|
|
(cadr arg)
|
|||
|
|
arg)))
|
|||
|
|
|
|||
|
|
(eval-when-compile
|
|||
|
|
;; parse :format arg, default (non-nil) means to use regexp, otherwise
|
|||
|
|
;; do a literal search
|
|||
|
|
(defsubst rg-parse-format-literal (format-opt)
|
|||
|
|
(pcase format-opt
|
|||
|
|
('regexp nil) ; default to regexp search
|
|||
|
|
('literal t)
|
|||
|
|
(_ format-opt)))) ; otherwise evaluate form
|
|||
|
|
|
|||
|
|
(eval-and-compile
|
|||
|
|
(defun rg-search-parse-local-bindings (search-cfg)
|
|||
|
|
"Parse local bindings for search functions from SEARCH-CFG."
|
|||
|
|
(let* ((confirm-opt (plist-get search-cfg :confirm))
|
|||
|
|
(format-opt (plist-get search-cfg :format))
|
|||
|
|
(query-opt (plist-get search-cfg :query))
|
|||
|
|
(alias-opt (plist-get search-cfg :files))
|
|||
|
|
(dir-opt (plist-get search-cfg :dir))
|
|||
|
|
(flags-opt (plist-get search-cfg :flags))
|
|||
|
|
(literal-opt (rg-parse-format-literal format-opt))
|
|||
|
|
(binding-list `((literal ,literal-opt))))
|
|||
|
|
|
|||
|
|
;; confirm binding
|
|||
|
|
(cond ((eq confirm-opt 'never)
|
|||
|
|
(setq binding-list (append binding-list `((confirm nil)))))
|
|||
|
|
|
|||
|
|
((eq confirm-opt 'always)
|
|||
|
|
(setq binding-list (append binding-list `((confirm t)))))
|
|||
|
|
|
|||
|
|
((eq confirm-opt 'prefix)
|
|||
|
|
(setq binding-list (append binding-list
|
|||
|
|
'((confirm (equal current-prefix-arg
|
|||
|
|
'(4))))))))
|
|||
|
|
|
|||
|
|
;; query binding
|
|||
|
|
(unless (eq query-opt 'ask)
|
|||
|
|
(let ((query (cond ((eq query-opt 'point) '(or (rg-tag-default)
|
|||
|
|
(rg-read-pattern literal)))
|
|||
|
|
(t query-opt))))
|
|||
|
|
(setq binding-list (append binding-list `((query ,query))))))
|
|||
|
|
|
|||
|
|
;; dir binding
|
|||
|
|
(unless (eq dir-opt 'ask)
|
|||
|
|
(let ((dirs (cond ((eq dir-opt 'project) '(rg-project-root
|
|||
|
|
buffer-file-name))
|
|||
|
|
((eq dir-opt 'current) 'default-directory)
|
|||
|
|
(t dir-opt))))
|
|||
|
|
(setq binding-list (append binding-list `((dir ,dirs))))))
|
|||
|
|
|
|||
|
|
;; file alias binding
|
|||
|
|
(unless (eq alias-opt 'ask)
|
|||
|
|
(let ((files (if (eq alias-opt 'current)
|
|||
|
|
'(car (rg-default-alias))
|
|||
|
|
alias-opt)))
|
|||
|
|
(setq binding-list (append binding-list `((files ,files))))))
|
|||
|
|
|
|||
|
|
(when (eq flags-opt 'ask)
|
|||
|
|
(setq flags-opt 'flags))
|
|||
|
|
|
|||
|
|
(setq flags-opt (rg-ensure-quoted flags-opt))
|
|||
|
|
(setq binding-list
|
|||
|
|
(append binding-list
|
|||
|
|
`((flags (funcall rg-command-line-flags-function ,flags-opt)))))
|
|||
|
|
binding-list)))
|
|||
|
|
|
|||
|
|
(eval-and-compile
|
|||
|
|
(defun rg-search-parse-interactive-args (search-cfg)
|
|||
|
|
"Parse interactive args from SEARCH-CFG for search functions."
|
|||
|
|
(let* ((query-opt (plist-get search-cfg :query))
|
|||
|
|
(format-opt (plist-get search-cfg :format))
|
|||
|
|
(literal (rg-parse-format-literal format-opt))
|
|||
|
|
(dir-opt (plist-get search-cfg :dir))
|
|||
|
|
(files-opt (plist-get search-cfg :files))
|
|||
|
|
(flags-opt (plist-get search-cfg :flags))
|
|||
|
|
(iargs '()))
|
|||
|
|
|
|||
|
|
(when (eq query-opt 'ask)
|
|||
|
|
(setq iargs
|
|||
|
|
(append iargs `((query . (rg-read-pattern ,literal))))))
|
|||
|
|
|
|||
|
|
(when (eq files-opt 'ask)
|
|||
|
|
(setq iargs
|
|||
|
|
(append iargs '((files . (rg-read-files))))))
|
|||
|
|
|
|||
|
|
(when (eq dir-opt 'ask)
|
|||
|
|
(setq iargs
|
|||
|
|
(append iargs
|
|||
|
|
'((dir . (read-directory-name
|
|||
|
|
"In directory: " nil default-directory t))))))
|
|||
|
|
|
|||
|
|
(when (eq flags-opt 'ask)
|
|||
|
|
(setq iargs
|
|||
|
|
(append iargs '((flags . (split-string
|
|||
|
|
(read-string "Command line flags: ")))))))
|
|||
|
|
iargs)))
|
|||
|
|
|
|||
|
|
(eval-and-compile
|
|||
|
|
(defun rg-search-parse-menu-arg (search-cfg name)
|
|||
|
|
"Parse :menu option in SEARCH-CFG.
|
|||
|
|
Returns forms for binding function with NAME into rg-menu."
|
|||
|
|
(when-let* ((menu-opt (rg-ensure-unquoted
|
|||
|
|
(plist-get search-cfg :menu))))
|
|||
|
|
(unless (and (consp menu-opt)
|
|||
|
|
(= (length menu-opt) 3))
|
|||
|
|
(user-error "'%S' should be a list of length 3" menu-opt))
|
|||
|
|
`((rg-menu-transient-insert
|
|||
|
|
,@menu-opt
|
|||
|
|
',(intern (concat (symbol-name name) "--transient")))))))
|
|||
|
|
|
|||
|
|
(defconst rg-elisp-font-lock-keywords
|
|||
|
|
'(("(\\(rg-define-search\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
|
|||
|
|
(1 font-lock-keyword-face)
|
|||
|
|
(2 font-lock-function-name-face nil t))))
|
|||
|
|
|
|||
|
|
(font-lock-add-keywords 'emacs-lisp-mode rg-elisp-font-lock-keywords)
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defmacro rg-define-search (name &rest args)
|
|||
|
|
"Define an rg search functions named NAME.
|
|||
|
|
ARGS is a search specification that defines parameters of a search.
|
|||
|
|
It optionally starts with a string that is used as the docstring for
|
|||
|
|
the defined function. The rest of ARGS contains key value pairs
|
|||
|
|
according to the specification below. All keys are optional with
|
|||
|
|
specified default if left out.
|
|||
|
|
|
|||
|
|
:query Method for retrieving the search string. Allowed values
|
|||
|
|
are `point' which means extract thing at point and `ask'
|
|||
|
|
which means prompt the user for a string. Any form that
|
|||
|
|
evaluates to a string is allowed.
|
|||
|
|
Default is `ask'.
|
|||
|
|
:format Specifies if :query is interpreted literally (`literal')
|
|||
|
|
or as a regexp (`regexp'). If it is a form, eg.
|
|||
|
|
(not `current-prefix-arg'), and is non-nil the :query is
|
|||
|
|
interpreted literally, otherwise as a regexp.
|
|||
|
|
Default is `regexp'.
|
|||
|
|
:files Form that evaluates to a file alias or custom file glob.
|
|||
|
|
`current' means extract alias from current buffer file name,
|
|||
|
|
`ask' will prompt the user.
|
|||
|
|
Default is `ask'.
|
|||
|
|
:dir Root search directory. Allowed values are `ask' for user
|
|||
|
|
prompt, `current' for current dir and `project' for project
|
|||
|
|
root. Any form that evaluates to a directory string is
|
|||
|
|
also allowed.
|
|||
|
|
Default is `ask'.
|
|||
|
|
:confirm `never', `always', or `prefix' are allowed values. Specifies
|
|||
|
|
if the the final search command line string can be modified
|
|||
|
|
and confirmed by the user.
|
|||
|
|
Default is `never'.
|
|||
|
|
:flags `ask' or a list of command line flags that will be used when
|
|||
|
|
invoking the search.
|
|||
|
|
:menu Bind the command into `rg-menu'. Must be a list with three
|
|||
|
|
items in it. The first item is the description of the
|
|||
|
|
group in which the new command will appear. If the group
|
|||
|
|
does not exist a new will be created. The second item is
|
|||
|
|
the key binding for this new command (ether a key vector
|
|||
|
|
or a key description string) and the third item is the
|
|||
|
|
description of the command that will appear in the menu.
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
\(rg-define-search search-home-dir-in-elisp
|
|||
|
|
\"Doc string.\"
|
|||
|
|
:query ask
|
|||
|
|
:format literal
|
|||
|
|
:files \"elisp\"
|
|||
|
|
:dir (getenv \"HOME\"\)\)
|
|||
|
|
:menu (\"Custom\" \"H\" \"Home dir\")"
|
|||
|
|
(declare (indent defun))
|
|||
|
|
(let* ((body (macroexp-parse-body args))
|
|||
|
|
(decls (car body))
|
|||
|
|
(search-cfg (rg-set-search-defaults (cdr body)))
|
|||
|
|
(local-bindings (rg-search-parse-local-bindings search-cfg))
|
|||
|
|
(iargs (rg-search-parse-interactive-args search-cfg))
|
|||
|
|
(menu-forms (rg-search-parse-menu-arg search-cfg name)))
|
|||
|
|
`(progn
|
|||
|
|
(defun ,name ,(mapcar 'car iargs)
|
|||
|
|
,@decls
|
|||
|
|
(interactive
|
|||
|
|
(list ,@(mapcar 'cdr iargs)))
|
|||
|
|
(let* ,local-bindings
|
|||
|
|
(rg-run query files dir literal confirm flags)))
|
|||
|
|
(rg-menu-wrap-transient-search ,name)
|
|||
|
|
,@menu-forms)))
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg-project "rg.el" "" t)
|
|||
|
|
(rg-define-search rg-project
|
|||
|
|
"Run ripgrep in current project searching for REGEXP in FILES.
|
|||
|
|
The project root will will be determined by either common project
|
|||
|
|
packages like projectile and `find-file-in-project' or the source
|
|||
|
|
version control system."
|
|||
|
|
:dir project)
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg-dwim-project-dir "rg.el" "" t)
|
|||
|
|
(rg-define-search rg-dwim-project-dir
|
|||
|
|
"Search for thing at point in files matching the current file
|
|||
|
|
under the project root directory."
|
|||
|
|
:query point
|
|||
|
|
:format literal
|
|||
|
|
:files current
|
|||
|
|
:dir project)
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg-dwim-current-dir "rg.el" "" t)
|
|||
|
|
(rg-define-search rg-dwim-current-dir
|
|||
|
|
"Search for thing at point in files matching the current file
|
|||
|
|
under the current directory."
|
|||
|
|
:query point
|
|||
|
|
:format literal
|
|||
|
|
:files current
|
|||
|
|
:dir current)
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg-dwim-current-file "rg.el" "" t)
|
|||
|
|
(rg-define-search rg-dwim-current-file
|
|||
|
|
"Search for thing at point in files matching the current file
|
|||
|
|
name (as a pattern) under the current directory."
|
|||
|
|
:query point
|
|||
|
|
:format literal
|
|||
|
|
:files (rg-get-buffer-file-name)
|
|||
|
|
:dir current)
|
|||
|
|
|
|||
|
|
;;;###autoload
|
|||
|
|
(defun rg-dwim (&optional curdir)
|
|||
|
|
"Run ripgrep without user interaction figuring out the intention by magic(!).
|
|||
|
|
The default magic searches for thing at point in files matching
|
|||
|
|
current file under project root directory.
|
|||
|
|
|
|||
|
|
With \\[universal-argument] prefix (CURDIR), search is done in
|
|||
|
|
current dir instead of project root.
|
|||
|
|
|
|||
|
|
With repeated \\[universal-argument] prefix, search is done in
|
|||
|
|
the current dir and using the current variable `buffer-file-name'
|
|||
|
|
as a pattern. Subdirectories are still searched, so different
|
|||
|
|
files with the same name pattern still will be searched."
|
|||
|
|
(interactive "P")
|
|||
|
|
(cond
|
|||
|
|
((eq 4 (and (consp curdir) (car curdir))) (rg-dwim-current-dir))
|
|||
|
|
((eq 16 (and (consp curdir) (car curdir))) (rg-dwim-current-file))
|
|||
|
|
(t (rg-dwim-project-dir))))
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg-literal "rg.el" "" t)
|
|||
|
|
(rg-define-search rg-literal
|
|||
|
|
"Run ripgrep, searching for literal PATTERN in FILES in directory DIR.
|
|||
|
|
With \\[universal-argument] prefix (CONFIRM), you can edit the
|
|||
|
|
constructed shell command line before it is executed."
|
|||
|
|
:format literal
|
|||
|
|
:confirm prefix)
|
|||
|
|
|
|||
|
|
;;;###autoload (autoload 'rg "rg.el" "" t)
|
|||
|
|
(rg-define-search rg
|
|||
|
|
"Run ripgrep, searching for REGEXP in FILES in directory DIR.
|
|||
|
|
The search is limited to file names matching shell pattern FILES.
|
|||
|
|
FILES may use abbreviations defined in `rg-custom-type-aliases'
|
|||
|
|
or ripgrep builtin type aliases, e.g. entering `elisp' is
|
|||
|
|
equivalent to `*.el'. REGEXP is a regexp as defined by the
|
|||
|
|
ripgrep executable. With \\[universal-argument] prefix (CONFIRM),
|
|||
|
|
you can edit the constructed shell command line before it is
|
|||
|
|
executed. Collect output in a buffer. While ripgrep runs
|
|||
|
|
asynchronously, you can use \\[next-error] (M-x `next-error'), or
|
|||
|
|
\\<grep-mode-map>\\[compile-goto-error] \ in the rg output
|
|||
|
|
buffer, to go to the lines where rg found matches."
|
|||
|
|
:confirm prefix)
|
|||
|
|
|
|||
|
|
(provide 'rg)
|
|||
|
|
|
|||
|
|
;; Local Variables:
|
|||
|
|
;; byte-compile-warnings: (not cl-functions)
|
|||
|
|
;; End:
|
|||
|
|
|
|||
|
|
;;; rg.el ends here
|