Created
May 23, 2017 21:46
-
-
Save nikomatsakis/e5fb99718db9ad2de50191d1a0fd5b34 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; ripgrep.el --- from end for ripgrep | |
;; | |
;; Copyright (C) 2016 Nicholas Matsakis | |
;; | |
;; Adapted from ack-and-a-half.el, which is licensed as follows: | |
;; | |
;; Copyright (C) 2011 Jacob Helwig | |
;; | |
;; Author: Jacob Helwig <jacob+ack * technosorcery.net> | |
;; Version: 0.0.1 | |
;; Homepage: http://technosorcery.net | |
;; | |
;; 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 2, 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 ; see the file COPYING. If not, write to | |
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
;; Boston, MA 02111-1307, USA. | |
;; | |
;;; Commentary: | |
;; | |
;; ripgrep.el provides a simple compilation mode for the tool ripgrep | |
;; | |
;; Add the following to your .emacs: | |
;; | |
;; (add-to-list 'load-path "/path/to/ripgrep") | |
;; (autoload 'ripgrep-same "ripgrep" nil t) | |
;; (autoload 'ripgrep "ripgrep" nil t) | |
;; (autoload 'ripgrep-find-file-samee "ripgrep" nil t) | |
;; (autoload 'ripgrep-find-file "ripgrep" nil t) | |
;; | |
;; Run `ripgrep' to search for all files and `ripgrep-same' to search for | |
;; files of the same type as the current buffer. | |
;; | |
;; `next-error' and `previous-error' can be used to jump to the | |
;; matches. | |
;; | |
;; `ripgrep-find-file' and `ripgrep-find-same-file' use ripgrep to | |
;; list the files in the current project. It's a convenient, though | |
;; slow, way of finding files. | |
;; | |
(eval-when-compile (require 'cl)) | |
(require 'compile) | |
(require 'grep) | |
(add-to-list 'debug-ignored-errors | |
"^Moved \\(back before fir\\|past la\\)st match$") | |
(add-to-list 'debug-ignored-errors "^File .* not found$") | |
(define-compilation-mode ripgrep-mode "ripgrep" | |
"ripgrep results compilation mode." | |
(set (make-local-variable 'compilation-disable-input) t) | |
(set (make-local-variable 'compilation-error-face) grep-hit-face)) | |
(defgroup ripgrep nil "Front end for ripgrep." | |
:group 'tools | |
:group 'matching) | |
(defcustom ripgrep-executable (executable-find "rg") | |
"*The location of the ripgrep executable" | |
:group 'ripgrep | |
:type 'file) | |
(defcustom ripgrep-arguments nil | |
"*Extra arguments to pass to ripgrep." | |
:group 'ripgrep | |
:type '(repeat (string))) | |
(defcustom ripgrep-mode-type-alist nil | |
"*File type(s) to search per major mode. (ripgrep-same) | |
This overrides values in `ripgrep-mode-type-default-alist'. | |
The car in each list element is a major mode, and the rest | |
is a list of strings passed to the --type flag of ripgrep when running | |
`ripgrep-same'." | |
:group 'ripgrep | |
:type '(repeat (cons (symbol :tag "Major mode") | |
(repeat (string :tag "rg --type"))))) | |
(defcustom ripgrep-mode-extension-alist nil | |
"*File extensions to search per major mode. (ripgrep-same) | |
This overrides values in `ripgrep-mode-extension-default-alist'. | |
The car in each list element is a major mode, and the rest | |
is a list of file extensions to be searched in addition to | |
the type defined in `ripgrep-mode-type-alist' when | |
running `ripgrep-same'." | |
:group 'ripgrep | |
:type '(repeat (cons (symbol :tag "Major mode") | |
(repeat :tag "File extensions" (string))))) | |
(defcustom ripgrep-ignore-case 'smart | |
"*Ignore case when searching | |
The special value 'smart enables the ack option \"smart-case\"." | |
:group 'ripgrep | |
:type '(choice (const :tag "Case sensitive" nil) | |
(const :tag "Smart case" 'smart) | |
(const :tag "Case insensitive" t))) | |
(defcustom ripgrep-regexp-search t | |
"*Default to regular expression searching. | |
Giving a prefix argument to `ripgrep' toggles this option." | |
:group 'ripgrep | |
:type '(choice (const :tag "Literal searching" nil) | |
(const :tag "Regular expression searching" t))) | |
(defcustom ripgrep-use-environment t | |
"*Use .ackrc and ACK_OPTIONS when searching." | |
:group 'ripgrep | |
:type '(choice (const :tag "Ignore environment" nil) | |
(const :tag "Use environment" t))) | |
(defcustom ripgrep-root-directory-functions '(ripgrep-guess-project-root) | |
"*List of functions used to find the base directory to ack from. | |
These functions are called until one returns a directory. If successful, | |
`ripgrep' is run from that directory instead of from `default-directory'. | |
The directory is verified by the user depending on `ripgrep-prompt-for-directory'." | |
:group 'ripgrep | |
:type '(repeat function)) | |
(defcustom ripgrep-project-root-file-patterns | |
'(".project\\'" | |
".xcodeproj\\'" | |
".sln\\'" | |
"\\`Project.ede\\'" | |
"\\`.git\\'" | |
"\\`.bzr\\'" | |
"\\`_darcs\\'" | |
"\\`.hg\\'") | |
"*List of file patterns for the project root (used by `ripgrep-guess-project-root'. | |
Each element is a regular expression. If a file matching any element is | |
found in a directory, then that directory is assumed to be the project | |
root by `ripgrep-guess-project-root'." | |
:group 'ripgrep | |
:type '(repeat (string :tag "Regular expression"))) | |
(defcustom ripgrep-prompt-for-directory 'unless-guessed | |
"*Prompt for directory in which to run ack. | |
If this is 'unless-guessed, then the value determined by `ripgrep-root-directory-functions' | |
is used without confirmation. If it is nil, then the directory is never | |
confirmed. If t, then always prompt for the directory to use." | |
:group 'ripgrep | |
:type '(choice (const :tag "Don't prompt" nil) | |
(const :tag "Don't prompt when guessed" 'unless-guessed) | |
(const :tag "Always prompt" t))) | |
;;; Default setting lists ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
(defconst ripgrep-mode-type-default-alist | |
'((actionscript-mode "actionscript") | |
(LaTeX-mode "tex") | |
(TeX-mode "tex") | |
(asm-mode "asm") | |
(batch-file-mode "batch") | |
(c++-mode "cpp") | |
(c-mode "cc") | |
(cfmx-mode "cfmx") | |
(cperl-mode "perl") | |
(csharp-mode "csharp") | |
(css-mode "css") | |
(emacs-lisp-mode "elisp") | |
(erlang-mode "erlang") | |
(espresso-mode "java") | |
(fortran-mode "fortran") | |
(haskell-mode "haskell") | |
(hexl-mode "binary") | |
(html-mode "html") | |
(java-mode "java") | |
(javascript-mode "js") | |
(jde-mode "java") | |
(js2-mode "js") | |
(jsp-mode "jsp") | |
(latex-mode "tex") | |
(lisp-mode "lisp") | |
(lua-mode "lua") | |
(makefile-mode "make") | |
(mason-mode "mason") | |
(nxml-mode "xml") | |
(objc-mode "objc" "objcpp") | |
(ocaml-mode "ocaml") | |
(parrot-mode "parrot") | |
(perl-mode "perl") | |
(php-mode "php") | |
(plone-mode "plone") | |
(python-mode "python") | |
(ruby-mode "ruby") | |
(scheme-mode "scheme") | |
(shell-script-mode "shell") | |
(skipped-mode "skipped") | |
(smalltalk-mode "smalltalk") | |
(sql-mode "sql") | |
(tcl-mode "tcl") | |
(tex-mode "tex") | |
(tt-mode "tt") | |
(vb-mode "vb") | |
(vim-mode "vim") | |
(xml-mode "xml") | |
(yaml-mode "yaml")) | |
"Default values for `ripgrep-mode-type-alist'.") | |
(defconst ripgrep-mode-extension-default-alist | |
'((d-mode "d")) | |
"Default values for `ripgrep-mode-extension-alist'.") | |
(defun ripgrep-create-type (extensions) | |
(list "--type-set" | |
(concat "ripgrep-custom-type=" (mapconcat 'identity extensions ",")) | |
"--type" "ripgrep-custom-type")) | |
(defun ripgrep-type-for-major-mode (mode) | |
"Return the --type and --type-set arguments to use with ack for major mode MODE." | |
(let ((types (cdr (or (assoc mode ripgrep-mode-type-alist) | |
(assoc mode ripgrep-mode-type-default-alist)))) | |
(ext (cdr (or (assoc mode ripgrep-mode-extension-alist) | |
(assoc mode ripgrep-mode-extension-default-alist)))) | |
result) | |
(dolist (type types) | |
(push type result) | |
(push "--type" result)) | |
(if ext | |
(if types | |
`("--type-add" ,(concat (car types) | |
"=" (mapconcat 'identity ext ",")) | |
. ,result) | |
(ripgrep-create-type ext)) | |
result))) | |
;;; Project root ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
(defun ripgrep-guess-project-root () | |
"Guess the project root directory. | |
This is intended to be used in `ripgrep-root-directory-functions'." | |
(catch 'root | |
(let ((dir (expand-file-name (if buffer-file-name | |
(file-name-directory buffer-file-name) | |
default-directory))) | |
(pattern (mapconcat 'identity ripgrep-project-root-file-patterns "\\|"))) | |
(while (not (equal dir "/")) | |
(when (directory-files dir nil pattern t) | |
(throw 'root dir)) | |
(setq dir (file-name-directory (directory-file-name dir))))))) | |
;;; Commands ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
(defvar ripgrep-directory-history nil | |
"Directories recently searched with `ripgrep'.") | |
(defvar ripgrep-literal-history nil | |
"Strings recently searched for with `ripgrep'.") | |
(defvar ripgrep-regexp-history nil | |
"Regular expressions recently searched for with `ripgrep'.") | |
(defsubst ripgrep-read (regexp) | |
(read-from-minibuffer (if regexp "ripgrep pattern: " "ripgrep literal search: ") | |
nil nil nil | |
(if regexp 'ripgrep-regexp-history 'ripgrep-literal-history))) | |
(defun ripgrep-read-dir () | |
(let ((dir (run-hook-with-args-until-success 'ripgrep-root-directory-functions))) | |
(if ripgrep-prompt-for-directory | |
(if (and dir (eq ripgrep-prompt-for-directory 'unless-guessed)) | |
dir | |
(read-directory-name "Directory: " dir dir t)) | |
(or dir | |
(and buffer-file-name (file-name-and-directory buffer-file-name)) | |
default-directory)))) | |
(defsubst ripgrep-xor (a b) | |
(if a (not b) b)) | |
(defun ripgrep-interactive () | |
"Return the (interactive) arguments for `ripgrep' and `ripgrep-same'." | |
(let ((regexp (ripgrep-xor current-prefix-arg ripgrep-regexp-search))) | |
(list (ripgrep-read regexp) | |
regexp | |
(ripgrep-read-dir)))) | |
(defun ripgrep-type () | |
(or (ripgrep-type-for-major-mode major-mode) | |
(when buffer-file-name | |
(ripgrep-create-type (list (file-name-extension buffer-file-name)))))) | |
(defun ripgrep-option (name enabled) | |
(format "--%s%s" (if enabled "" "no") name)) | |
(defun ripgrep-arguments-from-options (regexp) | |
(let ((arguments (list "--no-heading" "--color" "never"))) | |
;(ripgrep-option "env" ripgrep-use-environment) | |
(if (eq ripgrep-ignore-case 'smart-case) | |
(push "--smart-case" arguments)) | |
(unless ripgrep-ignore-case | |
(push "-i" arguments)) | |
(unless regexp | |
(push "--literal" arguments)) | |
arguments)) | |
(defun ripgrep-string-replace (from to string &optional re) | |
"Replace all occurrences of FROM with TO in STRING. | |
All arguments are strings. | |
When optional fourth argument is non-nil, treat the from as a regular expression." | |
(let ((pos 0) | |
(res "") | |
(from (if re from (regexp-quote from)))) | |
(while (< pos (length string)) | |
(if (setq beg (string-match from string pos)) | |
(progn | |
(setq res (concat res | |
(substring string pos (match-beginning 0)) | |
to)) | |
(setq pos (match-end 0))) | |
(progn | |
(setq res (concat res (substring string pos (length string)))) | |
(setq pos (length string))))) | |
res)) | |
(defun ripgrep-shell-quote (string) | |
"Wrap in single quotes, and quote existing single quotes to make shell safe." | |
(concat "'" (ripgrep-string-replace "'" "'\\''" string) "'")) | |
(defun ripgrep-run (directory regexp &rest arguments) | |
"Run ack in DIRECTORY with ARGUMENTS." | |
(setq default-directory | |
(if directory | |
(file-name-as-directory (expand-file-name directory)) | |
default-directory)) | |
(setq arguments (append ripgrep-arguments | |
(nconc (ripgrep-arguments-from-options regexp) | |
arguments))) | |
(compilation-start (mapconcat 'identity (nconc (list ripgrep-executable) arguments) " ") | |
'ripgrep-mode)) | |
(defun ripgrep-read-file (prompt choices) | |
(if ido-mode | |
(ido-completing-read prompt choices nil t) | |
(require 'iswitchb) | |
(with-no-warnings | |
(let ((iswitchb-make-buflist-hook | |
(lambda () (setq iswitchb-temp-buflist choices)))) | |
(iswitchb-read-buffer prompt nil t))))) | |
(defun ripgrep-list-files (directory &rest arguments) | |
(with-temp-buffer | |
(let ((default-directory directory)) | |
(when (eq 0 (apply 'call-process ripgrep-executable nil t nil "-f" "--print0" | |
arguments)) | |
(goto-char (point-min)) | |
(let ((beg (point-min)) | |
files) | |
(while (re-search-forward "\0" nil t) | |
(push (buffer-substring beg (match-beginning 0)) files) | |
(setq beg (match-end 0))) | |
files))))) | |
(defun ripgrep-version-string () | |
"Return the ack version string." | |
(with-temp-buffer | |
(call-process ack-executable nil t nil "--version") | |
(goto-char (point-min)) | |
(re-search-forward " +") | |
(buffer-substring (point) (point-at-eol)))) | |
;;; Public interface ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;;;###autoload | |
(defun ripgrep (pattern &optional regexp directory) | |
"Run ack. | |
PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If | |
called interactively, the value of REGEXP is determined by `ripgrep-regexp-search'. | |
A prefix arg toggles the behavior. | |
DIRECTORY is the root directory. If called interactively, it is determined by | |
`ripgrep-project-root-file-patterns'. The user is only prompted, if | |
`ripgrep-prompt-for-directory' is set." | |
(interactive (ripgrep-interactive)) | |
(ripgrep-run directory regexp (ripgrep-shell-quote pattern))) | |
;;;###autoload | |
(defun ripgrep-same (pattern &optional regexp directory) | |
"Run ack with --type matching the current `major-mode'. | |
The types of files searched are determined by `ripgrep-mode-type-alist' and | |
`ripgrep-mode-extension-alist'. If no type is configured, the buffer's | |
file extension is used for the search. | |
PATTERN is interpreted as a regular expression, iff REGEXP is non-nil. If | |
called interactively, the value of REGEXP is determined by `ripgrep-regexp-search'. | |
A prefix arg toggles that value. | |
DIRECTORY is the directory in which to start searching. If called | |
interactively, it is determined by `ripgrep-project-root-file-patterns`. | |
The user is only prompted, if `ripgrep-prompt-for-directory' is set.`" | |
(interactive (ripgrep-interactive)) | |
(let ((type (ack-type))) | |
(if type | |
(apply 'ripgrep-run directory regexp (append type (list (ripgrep-shell-quote pattern)))) | |
(ripgrep pattern regexp directory)))) | |
;;;###autoload | |
(defun ripgrep-find-file (&optional directory) | |
"Prompt to find a file found by ack in DIRECTORY." | |
(interactive (list (ripgrep-read-dir))) | |
(find-file (expand-file-name (ack-read-file "Find file: " | |
(ripgrep-list-files directory)) | |
directory))) | |
;;;###autoload | |
(defun ripgrep-find-file-same (&optional directory) | |
"Prompt to find a file found by ack in DIRECTORY." | |
(interactive (list (ripgrep-read-dir))) | |
(find-file (expand-file-name | |
(ripgrep-read-file "Find file: " | |
(apply 'ripgrep-list-files directory (ripgrep-type))) | |
directory))) | |
;;; End ripgrep.el ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
(provide 'ripgrep) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment