;;; ripgrep.el --- Front-end for ripgrep, a command line search tool ;; Copyright (C) 2016 Nicolas Lamirault ;; ;; Author: Nicolas Lamirault ;; Package-Version: 20220520.1410 ;; Package-Revision: 872e250e8f93 ;; Keywords : ripgrep ack pt ag sift grep search ;; Homepage: https://github.com/nlamirault/ripgrep.el ;;; Commentary: ;; Emacs front-end for ripgrep, a command line search tool ;; Installation: ;; ripgrep.el is available on the two major community maintained repositories ;; Melpa stable (https://stable.melpa.org), and Melpa (https://melpa.org) ;; ;; (add-to-list 'package-archives ;; '("melpa" . "https://melpa.org/packages/") t) ;; ;; M-x package-install ripgrep ;;; License: ;; 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 ;; 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: ;; Usage : ;; M-x ripgrep-regexp ;;; Code: (require 'compile) (require 'grep) (require 'thingatpt) (require 'wgrep nil 'noerror) ;; optional ;; Customization ;; -------------------------- (defgroup ripgrep nil "Ripgrep" :group 'tools :group 'matching) (defcustom ripgrep-executable "rg" "Name of the ripgrep executable to use." :type 'string :group 'ripgrep) (defcustom ripgrep-arguments (list "") "Default arguments passed to ripgrep." :type '(repeat (string)) :group 'ripgrep) (defcustom ripgrep-highlight-search t "Non-nil means we highlight the current search term in results. This requires the ripgrep command to support --color-match, which is only in v0.14+" :type 'boolean :group 'ripgrep) ;; Faces ;; -------------------------- (defface ripgrep-hit-face '((t :inherit compilation-info)) "Face name to use for ripgrep hits." :group 'ripgrep) (defface ripgrep-match-face '((t :inherit match)) "Face name to use for ripgrep matches." :group 'ripgrep) (defface ripgrep-error-face '((t :inherit compilation-error)) "Face name to use for ripgrep errors." :group 'ripgrep) (defface ripgrep-context-face '((t :inherit shadow)) "Face name to use for ripgrep errors." :group 'ripgrep) ;; Mode ;; -------------------------- (defvar ripgrep--base-arguments '("--no-heading" "--line-number" "--with-filename") "Options that are always applied to ripgrep commands.") (defvar ripgrep-search-finished-hook nil "Hook run when ripgrep completes a search in a buffer.") (defun ripgrep/run-finished-hook (buffer how-finished) "Run the ripgrep hook to signal that the search has completed." (with-current-buffer buffer (run-hooks 'ripgrep-search-finished-hook))) (defun ripgrep/kill-buffer () "Kill the ripgrep search buffer." (interactive) (let ((kill-buffer-query-functions)) (kill-buffer))) (defvar ripgrep-search-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map compilation-minor-mode-map) (define-key map "p" 'compilation-previous-error) (define-key map "n" 'compilation-next-error) (define-key map "{" 'compilation-previous-file) (define-key map "}" 'compilation-next-file) (define-key map "k" 'ripgrep/kill-buffer) (define-key map "C-c C-p" 'wgrep-change-to-wgrep-mode) map) "Keymap for ripgrep-search buffers. `compilation-minor-mode-map' is a cdr of this.") ;; from grep-mode-font-lock-keywords (defvar ripgrep-search-mode-font-lock-keywords '(;; Command output lines. (": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$" 1 'ripgrep-error-face) ;; remove match from grep-regexp-alist before fontifying ("^Ripgrep[/a-zA-z]* \\(started\\|finished\\).*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)) ("^Ripgrep[/a-zA-z]* \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) (1 'ripgrep-error-face) (2 'ripgrep-error-face nil t)) ;; "filename-linenumber-" format is used for context lines in GNU grep, ;; "filename=linenumber=" for lines with function names in "git grep -p". ("^.+?\\([-=\0]\\)[0-9]+\\([-=]\\).*\n" (0 'ripgrep-context-face) (1 (if (eq (char-after (match-beginning 1)) ?\0) `(face nil display ,(match-string 2)))))) "Additional things to highlight in ripgrep output. This gets tacked on the end of the generated expressions.") (define-compilation-mode ripgrep-search-mode "Ripgrep" "Ripgrep results compilation mode" (set (make-local-variable 'truncate-lines) t) (set (make-local-variable 'compilation-disable-input) t) (set (make-local-variable 'tool-bar-map) grep-mode-tool-bar-map) (let ((symbol 'compilation-ripgrep) (pattern '("^\\(.+\\):\\([1-9][0-9]*\\):" 1 2))) (set (make-local-variable 'compilation-error-regexp-alist) (list symbol)) (set (make-local-variable 'compilation-error-regexp-alist-alist) (list (cons symbol pattern)))) (set (make-local-variable 'compilation-error-face) 'ripgrep-hit-face) (add-hook 'compilation-filter-hook 'ripgrep-filter nil t) (add-hook 'compilation-finish-functions 'ripgrep/run-finished-hook nil t) (when (featurep 'wgrep) (add-hook 'ripgrep-search-mode-hook 'wgrep-remove-all-change nil t) (add-hook 'ripgrep-search-mode-hook 'wgrep-setup))) (defvar ripgrep--match-regexp "\e\\[[0-9]*m\e\\[[0-9]*1m\e\\[[0-9]*1m\\(.*?\\)\e\\[[0-9]*0m" "Used in `ripgrep-filter' to search for the match via ANSI escape codes") ;; Taken from grep-filter, just changed the color regex. (defun ripgrep-filter () "Handle match highlighting escape sequences inserted by the rg process. This function is called from `compilation-filter-hook'." (when ripgrep-highlight-search (save-excursion (forward-line 0) (let ((end (point)) beg) (goto-char compilation-filter-start) (forward-line 0) (setq beg (point)) ;; Only operate on whole lines so we don't get caught with part of an ;; escape sequence in one chunk and the rest in another. (when (< (point) end) (setq end (copy-marker end)) ;; Highlight rg matches and delete marking sequences. (while (re-search-forward ripgrep--match-regexp end 1) (replace-match (propertize (match-string 1) 'face nil 'font-lock-face 'ripgrep-match-face) t t)) ;; Delete all remaining escape sequences (goto-char beg) (while (re-search-forward "\033\\[[0-9;]*[mK]" end 1) (replace-match "" t t))))))) ;; API ;; -------------------------- ;;;###autoload (defun ripgrep-regexp (regexp directory &optional args) "Run a ripgrep search with `REGEXP' rooted at `DIRECTORY'. `ARGS' provides Ripgrep command line arguments." (interactive (list (read-from-minibuffer "Ripgrep search for: " (thing-at-point 'symbol)) (read-directory-name "Directory: "))) (let ((default-directory directory)) (compilation-start (mapconcat 'identity (append (list ripgrep-executable) ripgrep-arguments args ripgrep--base-arguments (when ripgrep-highlight-search '("--color=always")) (when (and case-fold-search (isearch-no-upper-case-p regexp t)) '("--ignore-case")) '("--") (list (shell-quote-argument regexp) ".")) " ") 'ripgrep-search-mode))) (provide 'ripgrep) ;;; ripgrep.el ends here