Emacs is easy if you read code
If you regard a package as a collection of APIs and read its code, Emacs is easy to master.
For example, here is a useful tip on using counsel-ag and wgrep to edit multiple files I recently learned.
To understand this black magic, you only need know counsel-ag-occur
from counsel.el
(v0.9.1):
(defun counsel-ag-occur ()
"Generate a custom occur buffer for `counsel-ag'."
(unless (eq major-mode 'ivy-occur-grep-mode)
(ivy-occur-grep-mode))
(setq default-directory counsel--git-grep-dir)
(let* ((regex (counsel-unquote-regex-parens
(setq ivy--old-re
(ivy--regex
(progn (string-match "\"\\(.*\\)\"" (buffer-name))
(match-string 1 (buffer-name)))))))
(cands (split-string
(shell-command-to-string
(format counsel-ag-base-command (shell-quote-argument regex)))
"\n"
t)))
;; Need precise number of header lines for `wgrep' to work.
(insert (format "-*- mode:grep; default-directory: %S -*-\n\n\n"
default-directory))
(insert (format "%d candidates:\n" (length cands)))
(ivy--occur-insert-lines
(mapcar
(lambda (cand) (concat "./" cand))
cands))))
(ivy-set-occur 'counsel-ag 'counsel-ag-occur)
(ivy-set-display-transformer 'counsel-ag 'counsel-git-grep-transformer)
Inside counsel-ag-occur
:
- The variable
regex
is the regular expression built from the filter string you input. Please note thatregex
is unquoted bycounsel-unquote-regex-parens
so it can be used in shell. If you useregex
in Emacs Lisp, you don't need unquote it - The variable
cands
is the candidate lines created by runningag
withregex
as parameters in shell - Then a wgrep-friendly buffer is created
After spending 5 minutes to understand the internals, you can easily implement similar features.
Now let's develop our own black magic by enhancing the wgrep-friendly buffer.
My project uses Perforce as VCS. So I need check out files and make them writable before using wgrep.
Read code of wgrep.el
(v2.1.10),
(defun wgrep-prepare-context ()
(save-restriction
(let ((start (wgrep-goto-first-found))
(end (wgrep-goto-end-of-found)))
(narrow-to-region start end)
(goto-char (point-min))
(funcall wgrep-results-parser))))
wgrep-results-parser
is actually alias of wgrep-parse-command-results
whose code is too much to paste here. You can M-x find-function wgrep-parse-command-results
to read its code.
By combining wgrep-prepare-context
and wgrep-parse-command-results
I got my own access-files-in-wgrep-buffer
:
(defun access-files-in-wgrep-buffer()
(interactive)
(save-restriction
(let* ((start (wgrep-goto-first-found))
(end (wgrep-goto-end-of-found))
fn-accessed)
(narrow-to-region start end)
(goto-char (point-min))
(unless (featurep 'wgrep) (require 'featurep))
(while (not (eobp))
(if (looking-at wgrep-line-file-regexp)
(let* ((fn (match-string-no-properties 1)))
(unless (string= fn fn-accessed)
(setq fn-accessed fn)
(message "File relative path=%s" fn))))
(forward-line 1)))))
You can replace the line (message "File relative path=%s" fn)
to (shell-command (format "any-shell-cli %s" fn))
to do anything on the files.
You can insert definition of access-files-in-wgrep-buffer
into your .emacs
and run M-x access-files-in-wgrep-buffer
in wgrep buffer to have a test.
For example, I modified access-files-in-wgrep-buffer
to p4edit-in-grep-buffer
to checkout files under Perforce control,
(defun p4edit-in-wgrep-buffer()
"'p4 edit' files in wgrep buffer.
Turn off `read-only-mode' of opened files."
(interactive)
(save-restriction
(let* ((start (wgrep-goto-first-found))
(end (wgrep-goto-end-of-found))
fn-accessed)
(narrow-to-region start end)
(goto-char (point-min))
(unless (featurep 'wgrep) (require 'featurep))
(while (not (eobp))
(if (looking-at wgrep-line-file-regexp)
(let* ((filename (match-string-no-properties 1)) buf)
(unless (string= filename fn-accessed)
(setq fn-accessed filename)
(shell-command (format "p4 edit %s" filename))
(if (setq buf (get-file-buffer filename))
(with-current-buffer buf
;; turn off read-only since we've already `p4 edit'
(read-only-mode -1))))))
(forward-line 1)))))