You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
23 KiB
617 lines
23 KiB
;;; ag.el --- A front-end for ag ('the silver searcher'), the C ack replacement. |
|
|
|
;; Copyright (C) 2013-2014 Wilfred Hughes <me@wilfred.me.uk> |
|
;; |
|
;; Author: Wilfred Hughes <me@wilfred.me.uk> |
|
;; Created: 11 January 2013 |
|
;; Version: 0.47 |
|
;; Package-Version: 20150814.1655 |
|
;; Package-Requires: ((dash "2.8.0") (s "1.9.0") (cl-lib "0.5")) |
|
;;; Commentary: |
|
|
|
;; Please see README.md for documentation, or read it online at |
|
;; https://github.com/Wilfred/ag.el/#agel |
|
|
|
;;; License: |
|
|
|
;; This file is not part of GNU Emacs. |
|
;; However, it is distributed under the same license. |
|
|
|
;; GNU Emacs is free software; you can redistribute it and/or modify |
|
;; it under the terms of the GNU General Public License as published by |
|
;; the Free Software Foundation; either version 3, or (at your option) |
|
;; any later version. |
|
|
|
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the |
|
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
;; Boston, MA 02110-1301, USA. |
|
|
|
;;; Code: |
|
(eval-when-compile (require 'cl)) ;; dolist, defun*, flet |
|
(require 'cl-lib) ;; cl-letf |
|
(require 'dired) ;; dired-sort-inhibit |
|
(require 'dash) |
|
(require 's) |
|
(require 'find-dired) ;; find-dired-filter |
|
|
|
(defcustom ag-executable |
|
"ag" |
|
"Name of the ag executable to use." |
|
:type 'string |
|
:group 'ag) |
|
|
|
(defcustom ag-arguments |
|
(list "--line-number" "--smart-case" "--nogroup" "--column" "--stats" "--") |
|
"Default arguments passed to ag. |
|
|
|
Ag.el requires --nogroup and --column, so we recommend you add any |
|
additional arguments to the start of this list. |
|
|
|
--line-number is required on Window, as otherwise ag will not |
|
print line numbers when the input is a stream." |
|
:type '(repeat (string)) |
|
:group 'ag) |
|
|
|
(defcustom ag-highlight-search nil |
|
"Non-nil means we highlight the current search term in results. |
|
|
|
This requires the ag command to support --color-match, which is only in v0.14+" |
|
:type 'boolean |
|
:group 'ag) |
|
|
|
(defcustom ag-reuse-buffers nil |
|
"Non-nil means we reuse the existing search results buffer or |
|
dired results buffer, rather than creating one buffer per unique |
|
search." |
|
:type 'boolean |
|
:group 'ag) |
|
|
|
(defcustom ag-reuse-window nil |
|
"Non-nil means we open search results in the same window, |
|
hiding the results buffer." |
|
:type 'boolean |
|
:group 'ag) |
|
|
|
(defcustom ag-project-root-function nil |
|
"A function to determine the project root for `ag-project'. |
|
|
|
If set to a function, call this function with the name of the |
|
file or directory for which to determine the project root |
|
directory. |
|
|
|
If set to nil, fall back to finding VCS root directories." |
|
:type '(choice (const :tag "Default (VCS root)" nil) |
|
(function :tag "Function")) |
|
:group 'ag) |
|
|
|
(defcustom ag-ignore-list nil |
|
"A list of patterns to ignore when searching." |
|
:type '(repeat (string)) |
|
:group 'ag) |
|
|
|
(require 'compile) |
|
|
|
;; Although ag results aren't exactly errors, we treat them as errors |
|
;; so `next-error' and `previous-error' work. However, we ensure our |
|
;; face inherits from `compilation-info-face' so the results are |
|
;; styled appropriately. |
|
(defface ag-hit-face '((t :inherit compilation-info)) |
|
"Face name to use for ag matches." |
|
:group 'ag) |
|
|
|
(defface ag-match-face '((t :inherit match)) |
|
"Face name to use for ag matches." |
|
:group 'ag) |
|
|
|
(defvar ag-search-finished-hook nil |
|
"Hook run when ag completes a search in a buffer.") |
|
|
|
(defun ag/run-finished-hook (buffer how-finished) |
|
"Run the ag hook to signal that the search has completed." |
|
(with-current-buffer buffer |
|
(run-hooks 'ag-search-finished-hook))) |
|
|
|
(defmacro ag/with-patch-function (fun-name fun-args fun-body &rest body) |
|
"Temporarily override the definition of FUN-NAME whilst BODY is executed. |
|
|
|
Assumes FUNCTION is already defined (see http://emacs.stackexchange.com/a/3452/304)." |
|
`(cl-letf (((symbol-function ,fun-name) |
|
(lambda ,fun-args ,fun-body))) |
|
,@body)) |
|
|
|
(defun ag/next-error-function (n &optional reset) |
|
"Open the search result at point in the current window or a |
|
different window, according to `ag-reuse-window'." |
|
(if ag-reuse-window |
|
;; prevent changing the window |
|
(ag/with-patch-function |
|
'pop-to-buffer (buffer &rest args) (switch-to-buffer buffer) |
|
(compilation-next-error-function n reset)) |
|
|
|
;; just navigate to the results as normal |
|
(compilation-next-error-function n reset))) |
|
|
|
;; Note that we want to use as tight a regexp as we can to try and |
|
;; handle weird file names (with colons in them) as well as possible. |
|
;; E.g. we use [1-9][0-9]* rather than [0-9]+ so as to accept ":034:" |
|
;; in file names. |
|
(defvar ag/file-column-pattern |
|
"^\\(.+?\\):\\([1-9][0-9]*\\):\\([1-9][0-9]*\\):" |
|
"A regexp pattern that groups output into filename, line number and column number.") |
|
|
|
(define-compilation-mode ag-mode "Ag" |
|
"Ag results compilation mode" |
|
(set (make-local-variable 'compilation-error-regexp-alist) |
|
(list 'compilation-ag-nogroup)) |
|
(set (make-local-variable 'compilation-error-regexp-alist-alist) |
|
(list (cons 'compilation-ag-nogroup (list ag/file-column-pattern 1 2 3)))) |
|
(set (make-local-variable 'compilation-error-face) 'ag-hit-face) |
|
(set (make-local-variable 'next-error-function) #'ag/next-error-function) |
|
(set (make-local-variable 'compilation-finish-functions) |
|
#'ag/run-finished-hook) |
|
(add-hook 'compilation-filter-hook 'ag-filter nil t)) |
|
|
|
(define-key ag-mode-map (kbd "p") #'compilation-previous-error) |
|
(define-key ag-mode-map (kbd "n") #'compilation-next-error) |
|
(define-key ag-mode-map (kbd "k") '(lambda () (interactive) |
|
(let (kill-buffer-query-functions) (kill-buffer)))) |
|
|
|
(defun ag/buffer-name (search-string directory regexp) |
|
"Return a buffer name formatted according to ag.el conventions." |
|
(cond |
|
(ag-reuse-buffers "*ag search*") |
|
(regexp (format "*ag search regexp:%s dir:%s*" search-string directory)) |
|
(:else (format "*ag search text:%s dir:%s*" search-string directory)))) |
|
|
|
(defun ag/format-ignore (ignores) |
|
"Prepend '--ignore' to every item in IGNORES." |
|
(apply #'append |
|
(mapcar (lambda (item) (list "--ignore" item)) ignores))) |
|
|
|
(defun* ag/search (string directory |
|
&key (regexp nil) (file-regex nil) (file-type nil)) |
|
"Run ag searching for the STRING given in DIRECTORY. |
|
If REGEXP is non-nil, treat STRING as a regular expression." |
|
(let ((default-directory (file-name-as-directory directory)) |
|
(arguments ag-arguments) |
|
(shell-command-switch "-c")) |
|
(unless regexp |
|
(setq arguments (cons "--literal" arguments))) |
|
(if ag-highlight-search |
|
(setq arguments (append '("--color" "--color-match" "30;43") arguments)) |
|
(setq arguments (append '("--nocolor") arguments))) |
|
(when (char-or-string-p file-regex) |
|
(setq arguments (append `("--file-search-regex" ,file-regex) arguments))) |
|
(when file-type |
|
(setq arguments (cons (format "--%s" file-type) arguments))) |
|
(when ag-ignore-list |
|
(setq arguments (append (ag/format-ignore ag-ignore-list) arguments))) |
|
(unless (file-exists-p default-directory) |
|
(error "No such directory %s" default-directory)) |
|
(let ((command-string |
|
(mapconcat #'shell-quote-argument |
|
(append (list ag-executable) arguments (list string ".")) |
|
" "))) |
|
;; If we're called with a prefix, let the user modify the command before |
|
;; running it. Typically this means they want to pass additional arguments. |
|
(when current-prefix-arg |
|
;; Make a space in the command-string for the user to enter more arguments. |
|
(setq command-string (ag/replace-first command-string " -- " " -- ")) |
|
;; Prompt for the command. |
|
(let ((adjusted-point (- (length command-string) (length string) 5))) |
|
(setq command-string |
|
(read-from-minibuffer "ag command: " |
|
(cons command-string adjusted-point))))) |
|
;; Call ag. |
|
(compilation-start |
|
command-string |
|
#'ag-mode |
|
`(lambda (mode-name) ,(ag/buffer-name string directory regexp)))))) |
|
|
|
(defun ag/dwim-at-point () |
|
"If there's an active selection, return that. |
|
Otherwise, get the symbol at point, as a string." |
|
(cond ((use-region-p) |
|
(buffer-substring-no-properties (region-beginning) (region-end))) |
|
((symbol-at-point) |
|
(substring-no-properties |
|
(symbol-name (symbol-at-point)))))) |
|
|
|
(defun ag/buffer-extension-regex () |
|
"If the current buffer has an extension, return |
|
a PCRE pattern that matches files with that extension. |
|
Returns an empty string otherwise." |
|
(let ((file-name (buffer-file-name))) |
|
(if (stringp file-name) |
|
(format "\\.%s$" (ag/escape-pcre (file-name-extension file-name))) |
|
""))) |
|
|
|
(defun ag/longest-string (&rest strings) |
|
"Given a list of strings and nils, return the longest string." |
|
(let ((longest-string nil)) |
|
(dolist (string strings) |
|
(cond ((null longest-string) |
|
(setq longest-string string)) |
|
((stringp string) |
|
(when (< (length longest-string) |
|
(length string)) |
|
(setq longest-string string))))) |
|
longest-string)) |
|
|
|
(defun ag/replace-first (string before after) |
|
"Replace the first occurrence of BEFORE in STRING with AFTER." |
|
(replace-regexp-in-string |
|
(concat "\\(" (regexp-quote before) "\\)" ".*\\'") |
|
after string |
|
nil nil 1)) |
|
|
|
(autoload 'vc-git-root "vc-git") |
|
|
|
(require 'vc-svn) |
|
;; Emacs 23.4 doesn't provide vc-svn-root. |
|
(unless (functionp 'vc-svn-root) |
|
(defun vc-svn-root (file) |
|
(vc-find-root file vc-svn-admin-directory))) |
|
|
|
(autoload 'vc-hg-root "vc-hg") |
|
|
|
(defun ag/project-root (file-path) |
|
"Guess the project root of the given FILE-PATH. |
|
|
|
Use `ag-project-root-function' if set, or fall back to VCS |
|
roots." |
|
(if ag-project-root-function |
|
(funcall ag-project-root-function file-path) |
|
(or (ag/longest-string |
|
(vc-git-root file-path) |
|
(vc-svn-root file-path) |
|
(vc-hg-root file-path)) |
|
file-path))) |
|
|
|
(defun ag/dired-align-size-column () |
|
(beginning-of-line) |
|
(when (looking-at "^ ") |
|
(forward-char 2) |
|
(search-forward " " nil t 4) |
|
(let* ((size-start (point)) |
|
(size-end (search-forward " " nil t)) |
|
(width (and size-end (- size-end size-start)))) |
|
(when (and size-end |
|
(< width 12) |
|
(> width 1)) |
|
(goto-char size-start) |
|
(insert (make-string (- 12 width) ? )))))) |
|
|
|
(defun ag/dired-filter (proc string) |
|
"Filter the output of ag to make it suitable for `dired-mode'." |
|
(let ((buf (process-buffer proc)) |
|
(inhibit-read-only t)) |
|
(if (buffer-name buf) |
|
(with-current-buffer buf |
|
(save-excursion |
|
(save-restriction |
|
(widen) |
|
(let ((beg (point-max))) |
|
(goto-char beg) |
|
(insert string) |
|
(goto-char beg) |
|
(or (looking-at "^") |
|
(progn |
|
(ag/dired-align-size-column) |
|
(forward-line 1))) |
|
(while (looking-at "^") |
|
(insert " ") |
|
(ag/dired-align-size-column) |
|
(forward-line 1)) |
|
(goto-char beg) |
|
(beginning-of-line) |
|
|
|
;; Remove occurrences of default-directory. |
|
(while (search-forward (concat " " default-directory) nil t) |
|
(replace-match " " nil t)) |
|
|
|
(goto-char (point-max)) |
|
(if (search-backward "\n" (process-mark proc) t) |
|
(progn |
|
(dired-insert-set-properties (process-mark proc) |
|
(1+ (point))) |
|
(move-marker (process-mark proc) (1+ (point))))))))) |
|
(delete-process proc)))) |
|
|
|
(defun ag/dired-sentinel (proc state) |
|
"Update the status/modeline after the process finishes." |
|
(let ((buf (process-buffer proc)) |
|
(inhibit-read-only t)) |
|
(if (buffer-name buf) |
|
(with-current-buffer buf |
|
(let ((buffer-read-only nil)) |
|
(save-excursion |
|
(goto-char (point-max)) |
|
(insert "\n ag " state) |
|
(forward-char -1) ;Back up before \n at end of STATE. |
|
(insert " at " (substring (current-time-string) 0 19)) |
|
(forward-char 1) |
|
(setq mode-line-process |
|
(concat ":" (symbol-name (process-status proc)))) |
|
;; Since the buffer and mode line will show that the |
|
;; process is dead, we can delete it now. Otherwise it |
|
;; will stay around until M-x list-processes. |
|
(delete-process proc) |
|
(force-mode-line-update))) |
|
(run-hooks 'dired-after-readin-hook) |
|
(message "%s finished." (current-buffer)))))) |
|
|
|
(defun ag/kill-process () |
|
"Kill the `ag' process running in the current buffer." |
|
(interactive) |
|
(let ((ag (get-buffer-process (current-buffer)))) |
|
(and ag (eq (process-status ag) 'run) |
|
(eq (process-filter ag) (function find-dired-filter)) |
|
(condition-case nil |
|
(delete-process ag) |
|
(error nil))))) |
|
|
|
(defun ag/escape-pcre (regexp) |
|
"Escape the PCRE-special characters in REGEXP so that it is |
|
matched literally." |
|
(let ((alphanum "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")) |
|
(apply #'concat |
|
(mapcar |
|
(lambda (c) |
|
(cond |
|
((not (string-match-p (regexp-quote c) alphanum)) |
|
(concat "\\" c)) |
|
(t c))) |
|
(mapcar #'char-to-string (string-to-list regexp)))))) |
|
|
|
;;;###autoload |
|
(defun ag (string directory) |
|
"Search using ag in a given DIRECTORY for a given search STRING, |
|
with STRING defaulting to the symbol under point. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive (list (ag/read-from-minibuffer "Search string") |
|
(read-directory-name "Directory: "))) |
|
(ag/search string directory)) |
|
|
|
;;;###autoload |
|
(defun ag-files (string file-type directory) |
|
"Search using ag in a given DIRECTORY for a given search STRING, |
|
limited to files that match FILE-TYPE. STRING defaults to |
|
the symbol under point. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive (list (ag/read-from-minibuffer "Search string") |
|
(ag/read-file-type) |
|
(read-directory-name "Directory: "))) |
|
(apply #'ag/search string directory file-type)) |
|
|
|
;;;###autoload |
|
(defun ag-regexp (string directory) |
|
"Search using ag in a given directory for a given regexp. |
|
The regexp should be in PCRE syntax, not Emacs regexp syntax. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive "sSearch regexp: \nDDirectory: ") |
|
(ag/search string directory :regexp t)) |
|
|
|
;;;###autoload |
|
(defun ag-project (string) |
|
"Guess the root of the current project and search it with ag |
|
for the given string. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive (list (ag/read-from-minibuffer "Search string"))) |
|
(ag/search string (ag/project-root default-directory))) |
|
|
|
;;;###autoload |
|
(defun ag-project-files (string file-type) |
|
"Search using ag for a given search STRING, |
|
limited to files that match FILE-TYPE. STRING defaults to the |
|
symbol under point. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive (list (ag/read-from-minibuffer "Search string") |
|
(ag/read-file-type))) |
|
(apply 'ag/search string (ag/project-root default-directory) file-type)) |
|
|
|
(defun ag/read-from-minibuffer (prompt) |
|
"Read a value from the minibuffer with PROMPT. |
|
If there's a string at point, offer that as a default." |
|
(let* ((suggested (ag/dwim-at-point)) |
|
(final-prompt |
|
(if suggested |
|
(format "%s (default %s): " prompt suggested) |
|
(format "%s: " prompt))) |
|
;; Ask the user for input, but add `suggested' to the history |
|
;; so they can use M-n if they want to modify it. |
|
(user-input (read-from-minibuffer |
|
final-prompt |
|
nil nil nil nil suggested))) |
|
;; Return the input provided by the user, or use `suggested' if |
|
;; the input was empty. |
|
(if (> (length user-input) 0) |
|
user-input |
|
suggested))) |
|
|
|
;;;###autoload |
|
(defun ag-project-regexp (regexp) |
|
"Guess the root of the current project and search it with ag |
|
for the given regexp. The regexp should be in PCRE syntax, not |
|
Emacs regexp syntax. |
|
|
|
If called with a prefix, prompts for flags to pass to ag." |
|
(interactive (list (ag/read-from-minibuffer "Search regexp"))) |
|
(ag/search regexp (ag/project-root default-directory) :regexp t)) |
|
|
|
(autoload 'symbol-at-point "thingatpt") |
|
|
|
;;;###autoload |
|
(defalias 'ag-project-at-point 'ag-project) |
|
(make-obsolete 'ag-project-at-point 'ag-project "0.19") |
|
|
|
;;;###autoload |
|
(defalias 'ag-regexp-project-at-point 'ag-project-regexp) |
|
(make-obsolete 'ag-regexp-project-at-point 'ag-project-regexp "0.46") |
|
|
|
;;;###autoload |
|
(defun ag-dired (dir pattern) |
|
"Recursively find files in DIR matching PATTERN. |
|
|
|
The PATTERN is matched against the full path to the file, not |
|
only against the file name. |
|
|
|
The results are presented as a `dired-mode' buffer with |
|
`default-directory' being DIR. |
|
|
|
See also `ag-dired-regexp'." |
|
(interactive "DDirectory: \nsFile pattern: ") |
|
(ag-dired-regexp dir (ag/escape-pcre pattern))) |
|
|
|
;;;###autoload |
|
(defun ag-dired-regexp (dir regexp) |
|
"Recursively find files in DIR matching REGEXP. |
|
REGEXP should be in PCRE syntax, not Emacs regexp syntax. |
|
|
|
The REGEXP is matched against the full path to the file, not |
|
only against the file name. |
|
|
|
Results are presented as a `dired-mode' buffer with |
|
`default-directory' being DIR. |
|
|
|
See also `find-dired'." |
|
(interactive "DDirectory: \nsFile regexp: ") |
|
(let* ((dired-buffers dired-buffers) ;; do not mess with regular dired buffers |
|
(orig-dir dir) |
|
(dir (file-name-as-directory (expand-file-name dir))) |
|
(buffer-name (if ag-reuse-buffers |
|
"*ag dired*" |
|
(format "*ag dired pattern:%s dir:%s*" regexp dir))) |
|
(cmd (concat ag-executable " --nocolor -g '" regexp "' " |
|
(shell-quote-argument dir) |
|
" | grep -v '^$' | sed s/\\'/\\\\\\\\\\'/ | xargs -I '{}' ls " |
|
dired-listing-switches " '{}' &"))) |
|
(with-current-buffer (get-buffer-create buffer-name) |
|
(switch-to-buffer (current-buffer)) |
|
(widen) |
|
(kill-all-local-variables) |
|
(if (fboundp 'read-only-mode) |
|
(read-only-mode -1) |
|
(setq buffer-read-only nil)) |
|
(let ((inhibit-read-only t)) (erase-buffer)) |
|
(setq default-directory dir) |
|
(run-hooks 'dired-before-readin-hook) |
|
(shell-command cmd (current-buffer)) |
|
(insert " " dir ":\n") |
|
(insert " " cmd "\n") |
|
(dired-mode dir) |
|
(let ((map (make-sparse-keymap))) |
|
(set-keymap-parent map (current-local-map)) |
|
(define-key map "\C-c\C-k" 'ag/kill-process) |
|
(use-local-map map)) |
|
(set (make-local-variable 'dired-sort-inhibit) t) |
|
(set (make-local-variable 'revert-buffer-function) |
|
`(lambda (ignore-auto noconfirm) |
|
(ag-dired-regexp ,orig-dir ,regexp))) |
|
(if (fboundp 'dired-simple-subdir-alist) |
|
(dired-simple-subdir-alist) |
|
(set (make-local-variable 'dired-subdir-alist) |
|
(list (cons default-directory (point-min-marker))))) |
|
(let ((proc (get-buffer-process (current-buffer)))) |
|
(set-process-filter proc #'ag/dired-filter) |
|
(set-process-sentinel proc #'ag/dired-sentinel) |
|
;; Initialize the process marker; it is used by the filter. |
|
(move-marker (process-mark proc) 1 (current-buffer))) |
|
(setq mode-line-process '(":%s"))))) |
|
|
|
;;;###autoload |
|
(defun ag-project-dired (pattern) |
|
"Recursively find files in current project matching PATTERN. |
|
|
|
See also `ag-dired'." |
|
(interactive "sFile pattern: ") |
|
(ag-dired-regexp (ag/project-root default-directory) (ag/escape-pcre pattern))) |
|
|
|
;;;###autoload |
|
(defun ag-project-dired-regexp (regexp) |
|
"Recursively find files in current project matching REGEXP. |
|
|
|
See also `ag-dired-regexp'." |
|
(interactive "sFile regexp: ") |
|
(ag-dired-regexp (ag/project-root default-directory) regexp)) |
|
|
|
;;;###autoload |
|
(defun ag-kill-buffers () |
|
"Kill all `ag-mode' buffers." |
|
(interactive) |
|
(dolist (buffer (buffer-list)) |
|
(when (eq (buffer-local-value 'major-mode buffer) 'ag-mode) |
|
(kill-buffer buffer)))) |
|
|
|
;;;###autoload |
|
(defun ag-kill-other-buffers () |
|
"Kill all `ag-mode' buffers other than the current buffer." |
|
(interactive) |
|
(let ((current-buffer (current-buffer))) |
|
(dolist (buffer (buffer-list)) |
|
(when (and |
|
(eq (buffer-local-value 'major-mode buffer) 'ag-mode) |
|
(not (eq buffer current-buffer))) |
|
(kill-buffer buffer))))) |
|
|
|
;; Taken from grep-filter, just changed the color regex. |
|
(defun ag-filter () |
|
"Handle match highlighting escape sequences inserted by the ag process. |
|
This function is called from `compilation-filter-hook'." |
|
(when ag-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 ag matches and delete marking sequences. |
|
(while (re-search-forward "\033\\[30;43m\\(.*?\\)\033\\[[0-9]*m" end 1) |
|
(replace-match (propertize (match-string 1) |
|
'face nil 'font-lock-face 'ag-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))))))) |
|
|
|
(defun ag/get-supported-types () |
|
"Query the ag executable for which file types it recognises." |
|
(let* ((ag-output (shell-command-to-string (format "%s --list-file-types" ag-executable))) |
|
(lines (-map #'s-trim (s-lines ag-output))) |
|
(types (--keep (when (s-starts-with? "--" it) (s-chop-prefix "--" it )) lines)) |
|
(extensions (--map (s-split " " it) (--filter (s-starts-with? "." it) lines)))) |
|
(-zip types extensions))) |
|
|
|
(defun ag/read-file-type () |
|
"Prompt the user for a known file type, or let them specify a PCRE regex." |
|
(let* ((all-types-with-extensions (ag/get-supported-types)) |
|
(all-types (mapcar 'car all-types-with-extensions)) |
|
(file-type |
|
(completing-read "Select file type: " |
|
(append '("custom (provide a PCRE regex)") all-types))) |
|
(file-type-extensions |
|
(cdr (assoc file-type all-types-with-extensions)))) |
|
(if file-type-extensions |
|
(list :file-type file-type) |
|
(list :file-regex |
|
(read-from-minibuffer "Filenames which match PCRE: " |
|
(ag/buffer-extension-regex)))))) |
|
|
|
(provide 'ag) |
|
;;; ag.el ends here
|
|
|