813 lines
30 KiB
EmacsLisp
813 lines
30 KiB
EmacsLisp
;;; rg-result.el ---- Result buffer implementation for rg.el. -*- lexical-binding: t; -*-
|
||
|
||
;; Copyright (C) 2018 David Landell <david.landell@sunnyhill.email>
|
||
;;
|
||
;; Author: David Landell <david.landell@sunnyhill.email>
|
||
;; URL: https://github.com/dajva/rg.el
|
||
|
||
;; 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:
|
||
|
||
;;; Code:
|
||
|
||
(require 'cl-lib)
|
||
(require 'grep)
|
||
(require 'mouse)
|
||
(require 'rg-header)
|
||
(require 'rg-history)
|
||
(require 'subr-x)
|
||
(require 'wgrep-rg)
|
||
|
||
;; Forward declarations.
|
||
(declare-function rg-build-command "rg.el")
|
||
(declare-function rg-get-type-aliases "rg.el")
|
||
(declare-function rg-list-searches "rg.el")
|
||
(declare-function rg-read-pattern "rg.el")
|
||
(declare-function rg-menu "rg-menu.el")
|
||
|
||
|
||
;; Customizations/public vars
|
||
(defcustom rg-use-transient-menu t
|
||
"Use transient menu instead of a the global keymap."
|
||
:type 'boolean
|
||
:group 'rg
|
||
:package-version '(rg . "2.0.0"))
|
||
|
||
(defcustom rg-show-columns nil
|
||
"If t, show the columns of the matches in the output buffer."
|
||
:type 'boolean
|
||
:group 'rg)
|
||
|
||
(defcustom rg-group-result t
|
||
"Group matches in the same file together.
|
||
If nil, the file name is repeated at the beginning of every match line."
|
||
:type 'boolean
|
||
:group 'rg
|
||
:package-version '(rg . "2.0.0"))
|
||
|
||
(defcustom rg-show-header t
|
||
"Show header in results buffer if non nil."
|
||
:type 'boolean
|
||
:group 'rg)
|
||
|
||
(defcustom rg-hide-command t
|
||
"Hide most of rg command line when non nil."
|
||
:type 'boolean
|
||
:group 'rg)
|
||
|
||
(defcustom rg-align-position-numbers t
|
||
"If non nil, alignment of line and column numbers is turned on."
|
||
:type 'boolean
|
||
:group 'rg
|
||
:package-version '(rg . "2.0.0"))
|
||
|
||
(defcustom rg-align-line-number-field-length 4
|
||
"Field length of aligned line numbers."
|
||
:type 'integer
|
||
:group 'rg)
|
||
|
||
(defcustom rg-align-column-number-field-length 3
|
||
"Field length of aligned column numbers."
|
||
:type 'integer
|
||
:group 'rg)
|
||
|
||
(defcustom rg-align-line-column-separator " "
|
||
"Separator used between line and column numbers.
|
||
Depends on `rg-show-columns'. Default is ':'."
|
||
:type 'string
|
||
:group 'rg
|
||
:package-version '(rg . "2.0.0"))
|
||
|
||
(defcustom rg-align-position-content-separator " "
|
||
"Separator used between position numbers and the matching file content.
|
||
Default is ':'."
|
||
:type 'string
|
||
:group 'rg
|
||
:package-version '(rg . "2.0.0"))
|
||
|
||
(defvar rg-filter-hook nil
|
||
"Hook for new content in the rg buffer.
|
||
This hook is called every time the rg buffer has been updated with
|
||
new content and filtered through the `rg-filter' function.")
|
||
|
||
|
||
;; Faces
|
||
(defgroup rg-face nil
|
||
"Settings for rg faces."
|
||
:group 'rg)
|
||
|
||
(defface rg-match-face
|
||
`((t :inherit ,grep-match-face))
|
||
"Face for match highlight."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-error-face
|
||
`((t :inherit ,grep-error-face))
|
||
"Face for error."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-context-face
|
||
`((t :inherit ,grep-context-face))
|
||
"Face for context lines."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-info-face
|
||
'((t :inherit compilation-info))
|
||
"Face for info."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-warning-face
|
||
'((t :inherit compilation-warning))
|
||
"Face for warning."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-filename-face
|
||
'((t :inherit rg-info-face))
|
||
"Face for filename."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-file-tag-face
|
||
'((t :inherit rg-info-face))
|
||
"Face for file tag in grouped layout."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-line-number-face
|
||
'((t :inherit compilation-line-number))
|
||
"Face for line numbers."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-column-number-face
|
||
'((t :inherit compilation-column-number))
|
||
"Face for column numbers."
|
||
:group 'rg-face)
|
||
|
||
(defface rg-match-position-face
|
||
'((t :inherit default))
|
||
"Face that is being appended to file positions.
|
||
This is the start of each matching line. This includes line number
|
||
and, depending on configuration, column number and file name."
|
||
:group 'rg-face)
|
||
|
||
|
||
;; Internal vars and structs
|
||
|
||
(cl-defstruct (rg-search (:constructor rg-search-create)
|
||
(:constructor rg-search-new (pattern files dir))
|
||
(:copier rg-search-copy))
|
||
pattern ; search pattern
|
||
files ; files to search
|
||
dir ; base directory
|
||
full-command ; full-command (t or nil)
|
||
literal ; literal patterh (t or nil)
|
||
flags) ; search specific flags
|
||
|
||
(defvar-local rg-cur-search nil
|
||
"Stores parameters of last search.
|
||
Becomes buffer local in `rg-mode' buffers.")
|
||
(put 'rg-cur-search 'permanent-local t)
|
||
|
||
(defvar-local rg-search-history nil
|
||
"Stores the search history per rg-mode buffer.")
|
||
(put 'rg-search-history 'permanent-local t)
|
||
|
||
(defvar-local rg-hit-count 0
|
||
"Stores number of hits in a search.")
|
||
|
||
(defvar-local rg-match-positions nil
|
||
"Stores position of matches in a search.
|
||
Each element is consists by (match-beginning-marker . match-string-length).")
|
||
|
||
(defvar-local rg-recompile nil
|
||
"Is `recompile' in progress or `compile-start'.")
|
||
|
||
(defconst rg-mode-font-lock-keywords
|
||
'(;; Command output lines.
|
||
(": \\(.+\\): \\(?:Permission denied\\|No such \\(?:file or directory\\|device or address\\)\\)$"
|
||
1 'rg-error-face)
|
||
("^rg[/a-zA-z]* started.*"
|
||
(0 '(face nil compilation-message nil help-echo nil mouse-face nil) t))
|
||
("^rg[/a-zA-z]* finished \\(?:(\\([0-9]+ matches found\\))\\|with \\(no matches found\\)\\).*"
|
||
(0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)
|
||
(1 'rg-info-face nil t)
|
||
(2 'rg-warning-face nil t))
|
||
("^rg[/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 'rg-error-face)
|
||
(2 'rg-error-face nil t))
|
||
;; "filename-linenumber-" or "linenumber-" format is used for
|
||
;; context lines in rg
|
||
("^ *\\(?:.+?-\\)?[0-9]+-.*\n" (0 'rg-context-face))
|
||
;; Optional " after 'rg' is for windows where shell-quote-argument adds these.
|
||
("^.*rg\"? \\(--color=always .*$\\)"
|
||
(0 rg-command-line-properties)
|
||
(1 (rg-hidden-command-line-properties)))
|
||
("^-\\*- mode: rg; default-directory: \"\\(.*\\)\" -\\*-$"
|
||
(1 rg-directory-properties))
|
||
("\\[\\.\\.\\. [0-9]+ more match\\(es\\)*\\]$"
|
||
(0 'rg-info-face nil t))))
|
||
|
||
(defvar rg-menu-map
|
||
(let ((map (make-sparse-keymap "RipGrep")))
|
||
(define-key map [rg-toggle-command-hiding]
|
||
'(menu-item "Toggle command visibility"
|
||
rg-toggle-command-hiding
|
||
:help "Toggle showing verbose command options"))
|
||
(define-key map [rg-enable-wgrep]
|
||
'(menu-item "Edit buffer" wgrep-change-to-wgrep-mode
|
||
:help "Edit buffer and save changes."))
|
||
(define-key map [rg-save-search]
|
||
'(menu-item "Save search" rg-save-search-as-name
|
||
:help "Save current search."))
|
||
(define-key map [rg-search-list]
|
||
'(menu-item "List searches" rg-list-searches
|
||
:help "List all ripgrep search buffers."))
|
||
(define-key map [rg-change-dir]
|
||
'(menu-item "Change dir" rg-rerun-change-dir
|
||
:help "Rerun search in another directory."))
|
||
(define-key map [rg-change-files]
|
||
'(menu-item "Change file type" rg-rerun-change-files
|
||
:help "Rerun search on other file types."))
|
||
(define-key map [rg-change-regexp]
|
||
'(menu-item "Change regexp" rg-rerun-change-regexp
|
||
:help "Run regexp search with changed pattern."))
|
||
(define-key map [rg-change-literal]
|
||
'(menu-item "Change literal" rg-rerun-change-literal
|
||
:help "Run literal search with changed search string."))
|
||
(define-key map [rg-kill-compilation]
|
||
'(menu-item "Kill Ripgrep" kill-compilation
|
||
:help "Kill the currently running rg process"))
|
||
(define-key map [rg-another]
|
||
'(menu-item "Another search..."
|
||
(lambda ()
|
||
(interactive)
|
||
(rg-save-search)
|
||
(call-interactively 'rg))
|
||
:help "Save current search results and start a new search."))
|
||
(define-key map [rg-recompile]
|
||
'(menu-item "Go..." rg-recompile
|
||
:help "Rerun search"))
|
||
map))
|
||
|
||
(defvar rg-mode-map
|
||
(let ((map (make-sparse-keymap)))
|
||
(set-keymap-parent map compilation-minor-mode-map)
|
||
(define-key map " " 'scroll-up-command)
|
||
(define-key map [?\S-\ ] 'scroll-down-command)
|
||
(define-key map "\^?" 'scroll-down-command)
|
||
(define-key map "\C-c\C-f" 'next-error-follow-minor-mode)
|
||
(define-key map "\r" 'compile-goto-error) ;; ?
|
||
(define-key map "n" 'next-error-no-select)
|
||
(define-key map "p" 'previous-error-no-select)
|
||
(define-key map "{" 'compilation-previous-file)
|
||
(define-key map "}" 'compilation-next-file)
|
||
(define-key map "\t" 'compilation-next-error)
|
||
(define-key map [backtab] 'compilation-previous-error)
|
||
|
||
(define-key map "c" 'rg-rerun-toggle-case)
|
||
(define-key map "d" 'rg-rerun-change-dir)
|
||
(define-key map "f" 'rg-rerun-change-files)
|
||
(define-key map "g" 'rg-recompile)
|
||
(define-key map "i" 'rg-rerun-toggle-ignore)
|
||
(define-key map "L" 'rg-list-searches)
|
||
(define-key map "r" 'rg-rerun-change-regexp)
|
||
(define-key map "s" 'rg-save-search)
|
||
(define-key map "S" 'rg-save-search-as-name)
|
||
(define-key map "t" 'rg-rerun-change-literal)
|
||
(define-key map "e" 'wgrep-change-to-wgrep-mode)
|
||
(define-key map "\M-N" 'rg-next-file)
|
||
(define-key map "\M-P" 'rg-prev-file)
|
||
(define-key map "\C-c>" 'rg-forward-history)
|
||
(define-key map "\C-c<" 'rg-back-history)
|
||
(when rg-use-transient-menu
|
||
(define-key map "m" #'rg-menu))
|
||
|
||
;; Set up the menu-bar
|
||
(define-key map [menu-bar rg]
|
||
(cons "RipGrep" rg-menu-map))
|
||
map)
|
||
"The keymap for `rg-mode'.")
|
||
|
||
(defvar rg-ellipsis (if (char-displayable-p ?…) "[…]" "[...]")
|
||
"Used when hiding command line.")
|
||
|
||
(defvar rg-finish-functions '()
|
||
"Functions to call when a ripgrep process finishes.
|
||
|
||
Each function is called with two arguments: the compilation buffer,
|
||
and a string describing how the process finished.")
|
||
|
||
|
||
;; Defuns
|
||
|
||
(defun rg-create-mouse-map (command)
|
||
"Create a mouse-map for the COMMAND.
|
||
This makes sure point moves to click and that the clicked window is
|
||
selected."
|
||
(let ((map (make-sparse-keymap)))
|
||
(define-key map [down-mouse-2] 'mouse-set-point)
|
||
(define-key map [mouse-2] command)
|
||
(define-key map "\C-m" command)
|
||
map))
|
||
|
||
;; This solution was mostly copied from emacs grep.el and adjusted to
|
||
;; be more usable.
|
||
(defvar rg-command-line-properties
|
||
(let ((map (rg-create-mouse-map 'rg-toggle-command-hiding)))
|
||
`(face rg-context-face mouse-face highlight
|
||
help-echo "RET, mouse-2: show unabbreviated command"
|
||
keymap ,map))
|
||
"Properties for graying out and keymap for hiding command line.")
|
||
|
||
(defun rg-hidden-command-line-properties ()
|
||
"Return properties of button-like ellipsis on part of rg command line."
|
||
(append
|
||
'(face nil rg-command-hidden-part t)
|
||
(when rg-hide-command
|
||
`(display ,rg-ellipsis))))
|
||
|
||
(defun rg-toggle-command-hiding ()
|
||
"Toggle showing the hidden part of rg command line."
|
||
(interactive)
|
||
(with-silent-modifications
|
||
(let* ((beg (next-single-property-change (point-min) 'rg-command-hidden-part))
|
||
(end (when beg
|
||
(next-single-property-change beg 'rg-command-hidden-part))))
|
||
(if end
|
||
(if (get-text-property beg 'display)
|
||
(remove-list-of-text-properties beg end '(display))
|
||
(add-text-properties beg end `(display ,rg-ellipsis)))
|
||
(user-error "No abbreviated part to hide/show")))))
|
||
|
||
(defvar rg-directory-properties
|
||
(let ((map (rg-create-mouse-map 'rg-rerun-change-dir)))
|
||
`(face rg-filename-face mouse-face highlight
|
||
help-echo "RET, mouse-2: Change search directory"
|
||
keymap ,map))
|
||
"Properties for `default-directory' in header.")
|
||
|
||
(defun rg-list-toggle (elem list)
|
||
"Remove ELEM from LIST if present or add it if not present.
|
||
Returns the new list."
|
||
(if (member elem list)
|
||
(delete elem list)
|
||
(push elem list)))
|
||
|
||
(defun rg-process-setup ()
|
||
"Setup compilation variables and buffer for `rg'.
|
||
Set up `compilation-exit-message-function'."
|
||
(set (make-local-variable 'compilation-exit-message-function)
|
||
(lambda (status code msg)
|
||
(if (eq status 'exit)
|
||
;; This relies on the fact that `compilation-start'
|
||
;; sets buffer-modified to nil before running the command,
|
||
;; so the buffer is still unmodified if there is no output.
|
||
(cond ((and (zerop code) (buffer-modified-p))
|
||
`(,(format "finished (%d matches found)\n" rg-hit-count) . "matched"))
|
||
((not (buffer-modified-p))
|
||
'("finished with no matches found\n" . "no match"))
|
||
(t
|
||
(cons msg code)))
|
||
(cons msg code)))))
|
||
|
||
(defun rg-prepend-space (text length)
|
||
"Prepend TEXT with LENGTH number of spaces."
|
||
(let ((space-count (- length (length text))))
|
||
(concat
|
||
(when (> space-count 0) (make-string space-count ?\s))
|
||
text)))
|
||
|
||
(defun rg-perform-position-numbers-alignment (line-number &optional column-number context-marker )
|
||
"Return aligned LINE-NUMBER, COLUMN-NUMBER and CONTEXT-MARKER."
|
||
(let* ((line-col-separator (or rg-align-line-column-separator ":"))
|
||
(pos-content-separator (or rg-align-position-content-separator ":"))
|
||
(pos-content-separator (if rg-show-columns
|
||
pos-content-separator
|
||
(propertize pos-content-separator 'invisible t)))
|
||
(line-number-width
|
||
(if (and rg-show-columns context-marker)
|
||
;; Context lines should be aligned to column numbers
|
||
(+ rg-align-line-number-field-length
|
||
(1+ rg-align-column-number-field-length))
|
||
rg-align-line-number-field-length))
|
||
(column-number (when column-number
|
||
(if rg-show-columns
|
||
column-number
|
||
(propertize column-number 'invisible t)))))
|
||
(cl-assert (if column-number (not context-marker) context-marker))
|
||
(concat (rg-prepend-space line-number line-number-width)
|
||
(if column-number
|
||
(concat (if rg-show-columns
|
||
line-col-separator
|
||
pos-content-separator)
|
||
(rg-prepend-space column-number
|
||
rg-align-column-number-field-length)
|
||
pos-content-separator)
|
||
context-marker))))
|
||
|
||
(defun rg-format-line-and-column-numbers (beg end)
|
||
"Align numbers in region defined by BEG and END."
|
||
(goto-char beg)
|
||
(while (re-search-forward
|
||
"^\033\\[[0]*m\033\\[32m\\([0-9]*?\\)\033\\[[0]*m\\(:\\|-\\)\\(?:\033\\[[0]*m\\([0-9]*?\\)\033\\[[0]*m:\\)?"
|
||
end 1)
|
||
(let* ((line-match (match-string 1))
|
||
(col-separator-match (match-string 2))
|
||
(context-marker (when (equal col-separator-match "-")
|
||
col-separator-match))
|
||
(column-match (match-string 3)))
|
||
(cond
|
||
(rg-align-position-numbers
|
||
(replace-match
|
||
(rg-perform-position-numbers-alignment
|
||
line-match column-match context-marker)
|
||
t t))
|
||
((and column-match (not rg-show-columns))
|
||
(replace-match
|
||
(propertize col-separator-match 'invisible t) t t nil 2)
|
||
(replace-match (propertize column-match 'invisible t) t t nil 3))))))
|
||
|
||
(defun rg-filter ()
|
||
"Handle match highlighting escape sequences inserted by the rg process.
|
||
This function is called from `compilation-filter-hook'."
|
||
(save-excursion
|
||
(forward-line 0)
|
||
(let ((end (point)) beg temp-positions)
|
||
(goto-char compilation-filter-start)
|
||
(forward-line 0)
|
||
(setq beg (point))
|
||
(when (zerop rg-hit-count)
|
||
(newline))
|
||
;; 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))
|
||
;; Add File: in front of filename
|
||
(when rg-group-result
|
||
(while (re-search-forward "^\033\\[[0]*m\033\\[35m\\(.*?\\)\033\\[[0]*m$" end 1)
|
||
(replace-match (concat (propertize "File:"
|
||
'rg-file-message t
|
||
'face nil
|
||
'font-lock-face 'rg-file-tag-face)
|
||
" "
|
||
(propertize (match-string 1)
|
||
'face nil
|
||
'font-lock-face 'rg-filename-face))
|
||
t t))
|
||
(goto-char beg))
|
||
|
||
;; Highlight rg matches and delete marking sequences.
|
||
(while (re-search-forward "\033\\[[0]*m\033\\[[3]*1m\033\\[[3]*1m\\(.*?\\)\033\\[[0]*m" end 1)
|
||
(replace-match (propertize (match-string 1)
|
||
'face nil 'font-lock-face 'rg-match-face)
|
||
t t)
|
||
(push (cons (copy-marker (match-beginning 0))
|
||
(length (match-string 0)))
|
||
temp-positions)
|
||
(cl-incf rg-hit-count))
|
||
(rg-format-line-and-column-numbers beg end)
|
||
|
||
;; Delete all remaining escape sequences
|
||
(goto-char beg)
|
||
(while (re-search-forward "\033\\[[0-9;]*[0mK]" end 1)
|
||
(replace-match "" t t))
|
||
|
||
(goto-char beg)
|
||
|
||
(setq rg-match-positions (nconc rg-match-positions (nreverse temp-positions)))
|
||
|
||
(run-hooks 'rg-filter-hook)))))
|
||
|
||
;; The regexp and filter functions below were taken from ag.el
|
||
;; Kudos to the people from https://github.com/Wilfred/ag.el for these.
|
||
(defconst rg-file-line-column-pattern-nogroup
|
||
"^\\([^\n:]+?\\):\\([1-9][0-9]*\\):\\([1-9][0-9]*\\):"
|
||
"A regexp pattern that groups output.
|
||
Groups into filename,line number and column number.")
|
||
|
||
(defun rg-file-line-column-pattern-group ()
|
||
"A regexp pattern to match line number and column number with grouped output."
|
||
(concat "^ *\\([1-9][0-9]*\\)"
|
||
(regexp-quote (or (and rg-align-position-numbers
|
||
rg-align-line-column-separator) ":"))
|
||
" *\\([1-9][0-9]*\\)"
|
||
(regexp-quote (or (and rg-align-position-numbers
|
||
rg-align-position-content-separator) ":"))))
|
||
|
||
(defconst rg-file-line-pattern-nogroup
|
||
"^\\([^\n:]+?\\):\\([1-9][0-9]*\\):"
|
||
"A regexp pattern that groups output into filename, line number.")
|
||
|
||
(defun rg-file-line-pattern-group ()
|
||
"A regexp pattern to match line number with grouped output."
|
||
(concat "^ *\\([1-9][0-9]*\\)"
|
||
(regexp-quote (or (and rg-align-position-numbers
|
||
rg-align-position-content-separator) ":"))))
|
||
|
||
(defun rg-match-grouped-filename ()
|
||
"Match filename backwards when a line/column match is found."
|
||
(save-match-data
|
||
(save-excursion
|
||
(when (re-search-backward "^File: \\(.*\\)$" (point-min) t)
|
||
(list (match-string 1))))))
|
||
|
||
(defun rg-set-compilation-error-regexps ()
|
||
"Set the compilation mode regexps for errors for rg-mode buffers."
|
||
(set (make-local-variable 'compilation-error-regexp-alist)
|
||
'(rg-group-with-column
|
||
rg-nogroup-with-column
|
||
rg-group-no-column
|
||
rg-nogroup-no-column))
|
||
(set (make-local-variable 'compilation-error-regexp-alist-alist)
|
||
(list (cons 'rg-nogroup-no-column (list rg-file-line-pattern-nogroup 1 2))
|
||
(cons 'rg-nogroup-with-column (list rg-file-line-column-pattern-nogroup 1 2 3))
|
||
(cons 'rg-group-with-column (list (rg-file-line-column-pattern-group) 'rg-match-grouped-filename 1 2))
|
||
(cons 'rg-group-no-column (list (rg-file-line-pattern-group) 'rg-match-grouped-filename 1)))))
|
||
|
||
(define-compilation-mode rg-mode "rg"
|
||
"Major mode for `rg' search results.
|
||
Commands:
|
||
\\<rg-mode-map>
|
||
\\[rg-rerun-change-dir]\t Repeat this search in another directory (`rg-rerun-change-dir').
|
||
\\[rg-rerun-change-files]\t Repeat this search with another file pattern (`rg-rerun-change-files').
|
||
\\[rg-rerun-change-regexp]\t Change the search regexp for the current search (`rg-rerun-change-regexp').
|
||
\\[rg-rerun-change-literal]\t Change the search literal for the current search (`rg-rerun-change-literal').
|
||
\\[rg-rerun-toggle-ignore]\t Repeat search with toggled '--no-ignore' flag (`rg-rerun-toggle-ignore').
|
||
\\[rg-rerun-toggle-case]\t Repeat search with toggled case insensitive setting (`rg-rerun-toggle-case').
|
||
\\[rg-save-search-as-name]\t Save search result, prompt for new name (`rg-save-search-as-name').
|
||
\\[rg-save-search]\t Save search result to some unique name (`rg-save-search').
|
||
\\[wgrep-change-to-wgrep-mode]\t Change mode to `wgrep'.
|
||
|
||
\\{rg-mode-map}"
|
||
(set (make-local-variable 'tool-bar-map) grep-mode-tool-bar-map)
|
||
(set (make-local-variable 'compilation-error-face) 'rg-filename-face)
|
||
(set (make-local-variable 'compilation-message-face) 'rg-match-position-face)
|
||
(set (make-local-variable 'compilation-line-face) 'rg-line-number-face)
|
||
(set (make-local-variable 'compilation-column-face) 'rg-column-number-face)
|
||
|
||
;; compilation-directory-matcher can't be nil, so we set it to a regexp that
|
||
;; can never match.
|
||
(set (make-local-variable 'compilation-directory-matcher) '("\\`a\\`"))
|
||
(set (make-local-variable 'compilation-process-setup-function)
|
||
'rg-process-setup)
|
||
(set (make-local-variable 'compilation-disable-input) t)
|
||
(set (make-local-variable 'compilation-error-screen-columns) nil)
|
||
(unless rg-search-history
|
||
(setq rg-search-history (rg-history-create)))
|
||
(set (make-local-variable 'compilation-filter-hook) '(rg-filter))
|
||
;; Set compilation error regexps as the last compilation-mode-hook to be
|
||
;; able to override these if they were set by other hooks.
|
||
(add-hook 'compilation-mode-hook
|
||
'rg-set-compilation-error-regexps 90 'local))
|
||
|
||
(defun rg-maybe-show-header ()
|
||
"Recreate header if enabled."
|
||
(when rg-show-header
|
||
(rg-create-header-line 'rg-cur-search
|
||
(rg-search-full-command rg-cur-search))))
|
||
|
||
(defun rg-mode-init (search)
|
||
"Initiate `rg-mode' with SEARCH in current buffer."
|
||
(unless (eq major-mode 'rg-mode)
|
||
(error "Function rg-mode-init called in non rg mode buffer"))
|
||
(hack-dir-local-variables-non-file-buffer)
|
||
(setq rg-cur-search search)
|
||
(rg-history-push (rg-search-copy rg-cur-search)
|
||
rg-search-history)
|
||
(rg-maybe-show-header)
|
||
(rg-configure-imenu))
|
||
|
||
(defun rg-recompile ()
|
||
"Rerun the current search."
|
||
(interactive)
|
||
(let ((rg-recompile t))
|
||
(recompile))
|
||
(hack-dir-local-variables-non-file-buffer)
|
||
(rg-maybe-show-header)
|
||
(rg-configure-imenu))
|
||
|
||
(defun rg-rerun (&optional no-history)
|
||
"Run `recompile' with `compilation-arguments' taken from `rg-cur-search'.
|
||
If NO-HISTORY is non nil skip adding the search to the search history."
|
||
(let ((pattern (rg-search-pattern rg-cur-search))
|
||
(files (rg-search-files rg-cur-search))
|
||
(dir (rg-search-dir rg-cur-search))
|
||
(literal (rg-search-literal rg-cur-search))
|
||
(flags (rg-search-flags rg-cur-search)))
|
||
;; compilation-directory is used as search dir and
|
||
;; default-directory is used as the base for file paths.
|
||
(setq compilation-directory dir)
|
||
(setq default-directory compilation-directory)
|
||
(setcar compilation-arguments
|
||
(or (rg-search-full-command rg-cur-search)
|
||
(rg-build-command pattern files literal flags)))
|
||
(unless no-history
|
||
(rg-history-push (rg-search-copy rg-cur-search)
|
||
rg-search-history))
|
||
(rg-recompile)))
|
||
|
||
(defun rg-navigate-file-message (pos limit direction)
|
||
"Return position of next 'rg-file-message text property.
|
||
POS is the start position of the search and LIMIT is the limit of the
|
||
search. If the property is not found within LIMIT, LIMIT is returned. If
|
||
DIRECTION is positive search forward in the buffer, otherwise search
|
||
backward."
|
||
(let ((prop-change-func
|
||
(if (> direction 0)
|
||
'next-single-property-change
|
||
'previous-single-property-change)))
|
||
(while
|
||
(progn
|
||
(setq pos (funcall prop-change-func pos 'rg-file-message nil limit))
|
||
(and (not (eq pos limit))
|
||
(not (get-text-property pos 'rg-file-message)))))
|
||
pos))
|
||
|
||
(defun rg-navigate-file-group (steps)
|
||
"Move point to the a matched result group in the compilation buffer.
|
||
STEPS decides how many groups to move past. Negative value means
|
||
backwards and positive means forwards."
|
||
(let (move-to
|
||
(pos (point))
|
||
(steps-left (abs steps))
|
||
(limit
|
||
(if (< steps 0)
|
||
(point-min)
|
||
(point-max))))
|
||
(while (and (> steps-left 0) (not (equal pos limit)))
|
||
(setq pos (rg-navigate-file-message pos limit steps))
|
||
(unless (eq pos limit)
|
||
(setq move-to pos))
|
||
(setq steps-left (- steps-left 1)))
|
||
(when move-to
|
||
(goto-char move-to))))
|
||
|
||
(defun rg-rerun-toggle-flag (flag)
|
||
"Toggle FLAG in `rg-cur-search`."
|
||
(setf (rg-search-flags rg-cur-search)
|
||
(rg-list-toggle flag (rg-search-flags rg-cur-search)))
|
||
(rg-rerun))
|
||
|
||
(defun rg-rerun-toggle-case ()
|
||
"Rerun last search with toggled case sensitivity setting."
|
||
(interactive)
|
||
(rg-rerun-toggle-flag "-i"))
|
||
|
||
(defun rg-rerun-toggle-ignore ()
|
||
"Rerun last search with toggled '--no-ignore' flag."
|
||
(interactive)
|
||
(rg-rerun-toggle-flag "--no-ignore"))
|
||
|
||
(defun rg-rerun-toggle-rexexp-literal ()
|
||
"Switch between literal and regexp and rerun last search."
|
||
(interactive)
|
||
(setf (rg-search-literal rg-cur-search)
|
||
(not (rg-search-literal rg-cur-search)))
|
||
(rg-rerun))
|
||
|
||
(defun rg-rerun-change-query ()
|
||
"Rerun last search and change search string."
|
||
(interactive)
|
||
(rg-rerun-change-search-string
|
||
(rg-search-literal rg-cur-search)))
|
||
|
||
(defun rg-rerun-change-search-string (literal)
|
||
"Rerun last search but prompt for new search pattern.
|
||
IF LITERAL is non nil this will trigger a literal search,
|
||
otherwise a regexp search."
|
||
(let ((pattern (rg-search-pattern rg-cur-search))
|
||
(read-from-minibuffer-orig (symbol-function 'read-from-minibuffer)))
|
||
;; Override read-from-minibuffer in order to insert the original
|
||
;; pattern in the input area.
|
||
(cl-letf (((symbol-function #'read-from-minibuffer)
|
||
(lambda (prompt &optional _ &rest args)
|
||
(apply read-from-minibuffer-orig prompt pattern args))))
|
||
(setf (rg-search-pattern rg-cur-search) (rg-read-pattern literal pattern)))
|
||
(setf (rg-search-literal rg-cur-search) literal)
|
||
(rg-rerun)))
|
||
|
||
(defun rg-rerun-change-regexp ()
|
||
"Rerun last search but prompt for new regexp."
|
||
(interactive)
|
||
(rg-rerun-change-search-string nil))
|
||
|
||
(defun rg-rerun-change-literal ()
|
||
"Rerun last search but prompt for new literal."
|
||
(interactive)
|
||
(rg-rerun-change-search-string t))
|
||
|
||
(defun rg-rerun-change-files()
|
||
"Rerun last search but prompt for new files."
|
||
(interactive)
|
||
(let ((files (rg-search-files rg-cur-search)))
|
||
(setf (rg-search-files rg-cur-search)
|
||
(completing-read
|
||
(concat "Repeat search in files (default: [" files "]): ")
|
||
(rg-get-type-aliases)
|
||
nil nil nil 'rg-files-history
|
||
files))
|
||
(rg-rerun)))
|
||
|
||
(defun rg-rerun-change-dir()
|
||
"Rerun last search but prompt for new dir."
|
||
(interactive)
|
||
(setf (rg-search-dir rg-cur-search)
|
||
(read-directory-name "In directory: "
|
||
(rg-search-dir rg-cur-search) nil))
|
||
(rg-rerun))
|
||
|
||
(defun rg-next-file (n)
|
||
"Move point to next file's first match.
|
||
Prefix arg N decides how many files to navigate. When
|
||
`rg-group-result' is nil this is the same as invoking
|
||
`compilation-next-file', otherwise this will navigate to the
|
||
next file with grouped matches."
|
||
(interactive "p")
|
||
(if rg-group-result
|
||
(when (rg-navigate-file-group n)
|
||
(forward-line))
|
||
(compilation-next-file n)))
|
||
|
||
(defun rg-prev-file (n)
|
||
"Move point to previous file's first match.
|
||
Prefix arg N decides how many files to navigate. When
|
||
`rg-group-result' is nil this is the same as invoking
|
||
`compilation-previous-file', otherwise this will navigate to the
|
||
previous file with grouped matches."
|
||
(interactive "p")
|
||
(if rg-group-result
|
||
(let ((steps
|
||
;; On match rows we move 2 steps back to get to previous
|
||
;; file, otherwise 1 step. The later is on file
|
||
;; headings, space between files and at the end of search results.
|
||
(if (or (get-text-property (point) 'rg-file-message)
|
||
(save-excursion
|
||
(beginning-of-line)
|
||
(looking-at "^\\(?:rg finished .*\\)*$")))
|
||
n
|
||
(+ n 1))))
|
||
(when (rg-navigate-file-group (- steps))
|
||
(forward-line)))
|
||
(compilation-previous-file n)))
|
||
|
||
(defun rg-back-history ()
|
||
"Navigate back in the search history."
|
||
(interactive)
|
||
(if-let* ((prev (rg-history-back rg-search-history)))
|
||
(progn
|
||
(setq rg-cur-search (rg-search-copy prev))
|
||
(rg-rerun 'no-history))
|
||
(message "No more history elements for back.")))
|
||
|
||
(defun rg-forward-history ()
|
||
"Navigate forward in the search history."
|
||
(interactive)
|
||
(if-let* ((next (rg-history-forward rg-search-history)))
|
||
(progn
|
||
(setq rg-cur-search (rg-search-copy next))
|
||
(rg-rerun 'no-history))
|
||
(message "No more history elements for forward.")))
|
||
|
||
(defun rg-configure-imenu ()
|
||
"Add files with matches to imenu if rg-group-result is enabled."
|
||
(when rg-group-result
|
||
(setq imenu-create-index-function
|
||
(lambda ()
|
||
(goto-char (point-min))
|
||
(let ((elements nil)
|
||
(filepath nil)
|
||
(nextfile (point-min)))
|
||
(while (setq nextfile (rg-navigate-file-message nextfile nil 1))
|
||
(save-excursion
|
||
(goto-char nextfile)
|
||
(and (looking-at-p "^File: ") (forward-char 6))
|
||
(setq filepath (buffer-substring-no-properties (point) (line-end-position))))
|
||
(push (cons filepath nextfile) elements))
|
||
(nreverse elements))))))
|
||
|
||
(defun rg-cur-search-pattern ()
|
||
"Get the current search pattern."
|
||
(rg-search-pattern rg-cur-search))
|
||
|
||
(provide 'rg-result)
|
||
|
||
;; Local Variables:
|
||
;; byte-compile-warnings: (not docstrings)
|
||
;; End:
|
||
|
||
;;; rg-result.el ends here
|