reddit / emacs

ripgrep is fantastic | Emacs is fantastic | BOOM you get the fantastic rg.el

tonyaldon · 2022-04-15 16:42
(This post in Org format -> [https://github.com/tonyaldon/posts](https://github.com/tonyaldon/posts)) Hey Emacsers, Do you know `rg.el`? [rg.el](https://github.com/dajva/rg.el) is an Emacs UI for the cli [ripgrep](https://github.com/BurntSushi/ripgrep). What does that mean? Well... Let's say we want to learn how to write Elisp macros because it seems to fit with the problem we are facing. We've already read the info node about macros (`M-x eval-expression RET (info "(elisp)Macros")`) that contains many examples but we want more examples. We know that macros are defined with the macro `defmacro`, so why don't we search for calls of `defmacro` in Emacs source code to get usage examples of it? In the directory where we've cloned Emacs source code we can run the following command line (in a terminal) that searches directories for the regexp `\(defmacro` that are of type `elisp`, while respecting gitignore using `ripgrep` (the binary `rg`): rg '\(defmacro' -t elisp This prints the following in the standard output: admin/cus-test.el 304:(defmacro cus-test-load-1 (&rest body) lisp/align.el 1260:(defmacro align--set-marker (marker-var pos &optional type) lisp/custom.el 231:(defmacro defcustom (symbol standard doc &rest args) 389:(defmacro defface (face spec doc &rest args) 492:(defmacro defgroup (symbol members doc &rest args) 1139:(defmacro deftheme (theme &optional doc) ... MORE HITS HERE Now, you do whatever you want with those matches in the terminal. You can browse the ouptut looking for some information, chose a match, the open the corresponding file and go to the appropriate line. Everything in the terminal... OR, you can use `rg.el` and use its command `rg` like this: 1. `M-x rg`, 2. write the regexp: `\(defmacro`, 3. select the directory where Emacs source code is, 4. choose `elisp` as type file, and you get the following buffer named `*rg*` (in the mode `rg-mode`) with exactly the same matches as before with the command line: -*- mode: rg; default-directory: "/tmp/emacs/" -*- rg started at Fri Apr 15 16:56:04 /usr/bin/rg [...] File: admin/cus-test.el 304 (defmacro cus-test-load-1 (&rest body) File: lisp/align.el 1260 (defmacro align--set-marker (marker-var pos &optional type) File: lisp/custom.el 231 (defmacro defcustom (symbol standard doc &rest args) 389 (defmacro defface (face spec doc &rest args) 492 (defmacro defgroup (symbol members doc &rest args) 1139 (defmacro deftheme (theme &optional doc) ... MORE HITS HERE The difference is that now you can press (the bindings are defined in `rg-mode-map`): 1. `n` (bound `next-error-no-select`) to move to the next line with a match, show that file in other buffer and highlight the match, 2. `p` (bound `previous-error-no-select`) move to the previous line with a match, show that file in other buffer and highlight the match. isn't FANTASTIC? Assuming that we've look at some macro definitions, and we've seen that the file `lisp/subr.el` defines many of the macros that we have already used in our own code like `push`, `pop` and `when`. Maybe to reduce the numbers of macro to look at (`1505` calls to `defmacro` \- commit `504779f744ccc33c2177dafa34e21d83f6c640a0`) we can consider only those defined in the file `lisp/subr.el`. This can be done by modifying a little bit the previous command line like this: rg '\(defmacro' lisp/subr.el But, now that we are using `rg.el` (INSIDE EMACS) we no longer want to use directly the terminal for that task. So, we go back to the buffer `*rg*` and we press `f` (bound to `rg-rerun-change-files`) and we write `subr.el` (the last part of the file name), then we press `RET` to rerun `rg` with the same regexp as before (`\(defmacro`), but this time, only matches in the file `lisp/subr.el` are presented in the buffer `*rg*`: -*- mode: rg; default-directory: "/tmp/emacs/" -*- rg started at Fri Apr 15 17:29:43 /usr/bin/rg [...] File: lisp/subr.el 32 (defmacro declare-function (_fn _file &rest _args) 74 (defmacro noreturn (form) 81 (defmacro 1value (form) 88 (defmacro def-edebug-spec (symbol spec) 110 (defmacro lambda (&rest cdr) 136 (defmacro prog2 (form1 form2 &rest body) 143 (defmacro setq-default (&rest args) 163 (defmacro setq-local (&rest pairs) 193 (defmacro defvar-local (var val &optional docstring) 210 (defmacro push (newelt place) 225 (defmacro pop (place) 243 (defmacro when (cond &rest body) ... MORE HITS HERE Now we've reduce our study of writing macros to 52 "classic" macros. Still as before, we can use `n` and `p` to look at those macro definition from `*rg*` buffer, the definition poping up in another buffer. For me, this is insane. I love it. But 52 is still an important number. We want to look at some macro definitions to be able to write our own macro. We need it. Let's almost right now. Well... Let's reduce that number. We observe that the file `lisp/subr.el` defines also the macro `with-current-buffer` and `with-temp-buffer` (used everywhere). So, we decide to only look at the macro prefixed by `with-` defined in the file `lisp/subr.el`. We can do it in the terminal running this command: rg '\(defmacro with' lisp/subr.el but we prefer doing it with `rg.el`. So we go back to the buffer `*rg*`, and now we press `r` (bound to `rg-rerun-change-regexp`). This offers in the minibuffer to modify the current regexp `\(defmacro`. We modify it to be `\(defmacro with`, we hit return, and we get the following 20 matches: -*- mode: rg; default-directory: "/tmp/emacs/" -*- rg started at Fri Apr 15 17:49:57 /usr/bin/rg [...] File: lisp/subr.el 2092 (defmacro with-wrapper-hook (hook args &rest body) 3448 (defmacro with-undo-amalgamate (&rest body) 4188 (defmacro with-current-buffer (buffer-or-name &rest body) 4228 (defmacro with-selected-window (window &rest body) 4253 (defmacro with-selected-frame (frame &rest body) 4331 (defmacro with-output-to-temp-buffer (bufname &rest body) 4387 (defmacro with-temp-file (file &rest body) 4407 (defmacro with-temp-message (message &rest body) 4430 (defmacro with-temp-buffer (&rest body) 4445 (defmacro with-silent-modifications (&rest body) 4469 (defmacro with-output-to-string (&rest body) 4481 (defmacro with-local-quit (&rest body) 4549 (defmacro with-demoted-errors (format &rest body) 4712 (defmacro with-case-table (table &rest body) 4726 (defmacro with-file-modes (modes &rest body) 4738 (defmacro with-existing-directory (&rest body) 5297 (defmacro with-eval-after-load (file &rest body) 5417 (defmacro with-syntax-table (table &rest body) 6384 (defmacro with-mutex (mutex &rest body) 6570 (defmacro with-delayed-message (args &rest body) We can now look at those definition trying to understand how macros are defined and how we can find ideas to solve our problem (either by writing our macro our deciding that a simple function might be enough...) While we are looking at those macro prefixed by `with-`, we remember that we've seen another macro in another file that was matched in the previous search and so visible in the "previous" contents of `*rg*` buffer, and we want to look for it. Do we have to redo everything (`M-x rg`, ....)? Absolutely not! Still in the buffer `*rg*` we can just visit backward and forward the previous searches using `C-c <` (bound to `rg-forward-history`) and `C-c >` (bound to `rg-back-history`). Another mega cool feature of `ripgrep` is the flag `--context` (`-C` in short) that allows to include a number of lines before and after each matches. For instance, if we want to add 2 lines before and after the matches of the regexp `\(defmacro with` in the file `lisp/subr.el`, in the terminal we can run the following command: rg --context 2 '\(defmacro with' lisp/subr.el We can also do this with `rg.el`. Let's go back to the buffer `*rg*` (with the search that matches `\(defmacro with` in the file `lisp/subr.el`). Now we do the following: 1. we press `m` (bound to `rg-menu`) that pops up a menu, 2. we press `-C`, 3. then in the minibuffer we see `--context=`, we write `2` and press `RET`, 4. then we press `g` (bound to `rg-recompile`), and this "reruns" the search adding the context around matches like this: File: lisp/subr.el 2090- 2091- 2092 (defmacro with-wrapper-hook (hook args &rest body) 2093- "Run BODY, using wrapper functions from HOOK with additional ARGS. 2094-HOOK is an abnormal hook. Each hook function in HOOK \"wraps\" -- 3446- (cancel-change-group ,handle)))))) 3447- 3448 (defmacro with-undo-amalgamate (&rest body) 3449- "Like `progn' but perform BODY with amalgamated undo barriers. 3450- -- 4186- `(internal--track-mouse (lambda () ,@body))) 4187- 4188 (defmacro with-current-buffer (buffer-or-name &rest body) 4189- "Execute the forms in BODY with BUFFER-OR-NAME temporarily current. 4190-BUFFER-OR-NAME must be a buffer or the name of an existing buffer. -- 4226- (get-buffer-create (generate-new-buffer-name name) inhibit-buffer-hooks)) 4227- 4228 (defmacro with-selected-window (window &rest body) 4229- "Execute the forms in BODY with WINDOW as the selected window. 4230-The value returned is the value of the last form in BODY. -- ... MORE HITS HERE When I think that life is amazing and then I look at all the work that has already been done everywhere, I think wowwww, this is really amazing. I want to thank you all for all the great programs that lives with us thanks to your imagination and your work. `ripgrep` is fantastic. `Emacs` is fantastic. BOOM you get the fantastic `rg.el`. What can we add to this paradise? We can add org-mode to the party. Yes, if you try to open the following org link (in a org-mode buffer), Emacs will ask you to confirm if you want to execute this elisp form, and by answering yes the result of an `rg` search will pops up in `*rg*` buffer like we did previously (assuming Emacs source code is clone under the directory `/tmp/emacs/`): [[elisp:(rg-run "\\(defmacro with" "subr.el" "/tmp/emacs/" nil nil '("--context=2"))]] WE ARE DONE!!!
MangyCanine · 2022-04-15 17:31
Anyone interested in this should also check out deadgrep: https://github.com/Wilfred/deadgrep
[deleted] · 2022-04-15 17:33
[deleted]
mauguro_ · 2022-04-15 19:04
I think counsel-projectile-rg is also a good tool
dumh3t3r · 2022-04-15 21:48
I've used `rg.el` but never bothered to look how to use it properly, thx for the introduction!
frozeninfate · 2022-04-15 22:35
One tip is to set `transient-default-level` higher if you want to see more options. Figured that one out today when couldn't find `-A` in `rg-menu`.
hmelman · 2022-04-16 01:34
Even better, `rg.el` supports wgrep. In the `*rg*` buffer type `e` for `wgrep-change-to-wgrep-mode`. Now you can edit the text in the results buffer and when you're done type `C-c C-c` and it will write out those changes to all the original files.
jimehgeek · 2022-04-16 01:44
I need to have a proper read through this later. Been meaning to dig deeper into different solutions for some time. For the past few years I’ve basically just used the `helm-ag` package, configured to use `rg` under the hood instead of `ag`. It’s been working more than good enough, but the itch to see if I can get something nicer is starting… lol
tonyaldon · 2022-04-16 06:07
I'm glad you like the intro :)
tonyaldon · 2022-04-16 06:09
Cool I didn't know about that. Thank you for sharing!
tonyaldon · 2022-04-16 06:14
I agree with you. Editing files in `*rg*` buffer via `wgrep` is super cool. Although I use it often it still feel magic to me :)
tonyaldon · 2022-04-16 06:18
I hope this post helps you started moothly with `rg.el`. What is really appealing with `rg.el` is that you can edit the `*rg*` buffer as mentioned by u/hmelman: > Even better, `rg.el` supports wgrep. In the `*rg*` buffer type `e` for `wgrep-change-to-wgrep-mode`. Now you can edit the text in the results buffer and when you're done type `C-c C-c` and it will write out those changes to all the original files.
ideasman_42 · 2022-04-16 07:13
What advantages does `rg.el` have over `counsel-rg` ?
Hagge5 · 2022-04-16 07:38
For helm users there is helm-rg, and the more mature helm-ag (ag is an older slightly slower alternative to rg) can also use ripgrep as a backend with just a few variables changed.
jimehgeek · 2022-04-16 08:52
Thanks for the effort here, I’ll be reading through it properly later today when I sit down at a computer with Emacs :) And yeah, result editing is a crazy powerful feature :D… helm-ag actually has that too, pressing `C-c C-e` while searching activates it. I often use multiple cursors when editing a search result buffer, which adds another layer awesome to the whole thing… lol Another thing I use often with helm-ag, is helm’s follow mode which shows a “preview”-like window with the file and cursor on the relevant line for the highlighted result, and it updates as you navigate the result list.
Phil-Hudson · 2022-04-16 09:05
AFAICT counsel-rg simply takes you to the next match found by `rg`.
Phil-Hudson · 2022-04-16 09:05
Been using `rg.el` for years, never knew it had navigable history. Thank you!
tonyaldon · 2022-04-16 09:58
Both are great tools: 1. if you want minibuffer completion in order to select some part in a code base matching some strings and jump directly to that part, `counsel-rg` does the job in less step that `rg.el` (I think this scenario often happens when you know the code base and want to jump somewhere), 2. if you don't need minibuffer completion but you want to adjust many times the flags you pass to `rg` to tailor the search, see all the results in a buffer and go back and forth between different searches, `rg.el` does the job with more flexibility that `counsel-rg` (I think this scenario often happens when you don't know well the code base and need to browse it (in a non-linear way)). I hope this is useful to answer to your question.
tonyaldon · 2022-04-16 09:59
I'm glad that you've discovered a new feature of `rg.el` :)
tonyaldon · 2022-04-16 10:07
Thank you u/jimehgeek :) So many great packages in Emacs in 2022. You can do almost anything in so many ways, it's crazy. Have a great Emacs session today ;)
wargxs · 2022-04-16 11:29
Helm supports rg out of the box :-)
Hagge5 · 2022-04-16 11:38
Cool, was not aware!
Shivalicious · 2022-04-28 20:50
I was using both ripgrep (through projectile-ripgrep) and rg.el (only for basic ‘dumb’ searching), as it turned out. After seeing this excellent post, I pored over my configuration and settled on rg.el as the better option. I’ve also started using the DWIM variants. They’re so useful! I don’t know how I lived without them. Between `consult-lines`, `consult-ripgrep`, `rg`, `rg-dwim`, `rg-project`, and `rg-dwim-project-dir`, I have a plethora of options for specific scenarios now. Emacs and ripgrep are indeed fantastic.
tonyaldon · 2022-04-29 15:18
I'm glad this article helped you to improve your emacs configuration. > I don't know how I lived without them. I had the same feeling when I started using rg.el :)