Bookmark that rg

I like bookmarks, don't you? But something missing was an ability to save a bookmark for an rg.el search1, 2.

Here is my solution after digging into the rg lib:

Code

  1. Define a function to serialize the bookmark
    To find your way back, you'll need to record information. Bookmarks will jam all your data into a big list.

    What data to record? Here, I had to search for local variables that mached ^rg- . Check out using (buffer-local-variables) (originally I did this the slow way of using C-h v). Then I realized it was a struct, and traced through the code to find out what function to be called (rg-run) and record data to pass into to it:

    (defun rg-bookmark-make-record ()
      "Make a bookmark record for the current rg buffer."
      `(,(format "rg: \"%s\" ∋ %s ∀ %s"
                 (rg-search-pattern rg-cur-search)
                 (rg-search-dir rg-cur-search)
                 (rg-search-files rg-cur-search))
        ;; pattern is a synonym of query in rg.el
        (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))
        ;; Found some state in the source code; 
        ;; I don't think these are needed, but they are present 
        ;; (search-cfg ,(rg-set-search-defaults (cdr body)))
        ;; (local-bindings (rg-search-parse-local-bindings search-cfg))
        ;; (iargs (rg-search-parse-interactive-args search-cfg))
        (flags . ,(rg-search-flags rg-cur-search))
        (handler . rg-bookmark-handler)))
    
  2. Now we need a way to read that stored data and jump to the bookmark
    (defun rg-bookmark-handler (record)
      "Jump to a bookmark's url with bookmarked location."
      (let ((query (bookmark-prop-get record 'pattern))
            (files (bookmark-prop-get record 'files))
            (dir (bookmark-prop-get record 'dir))
            (literal (bookmark-prop-get record 'literal))
            (flags (bookmark-prop-get record 'flags)))
        (rg-run query files dir literal nil flags)))
    

    We could shorten this code since there's so much repitition, but let's keep the simplicity for now.

  3. And finally, bookmark-make-record-function must be overridden in the buffer-local vars for the mode:
    (defun rg-set-bookmark-handler ()
      "Assigns `bookmark-make-record-function' to a custom function."
      (set (make-local-variable 'bookmark-make-record-function)
           #'rg-bookmark-make-record))
    
    (add-hook 'rg-mode-hook #'rg-set-bookmark-handler)
    

Test

  1. I activate a rg search for the regexp version: draft and tweak a flag.
  2. I M-x bookmark-set.
  3. The prompt shows
    Set bookmark named (default rg: "version: draft" ∋ /home/refurb/src/my-site/ ∀ org):
    

    I press <Enter>

  4. I inspected the saved bookmark:
    ("rg: \"version: draft\" ∋ /home/refurb/src/my-site/src/content/ ∀ org"
     (pattern . "version: draft")
     (files . "org")
     (dir . "/home/refurb/src/my-site/src/content/")
     (literal)
     (flags)
     (handler . rg-bookmark-handler))
    
  5. Close the ripgrep buffer.
  6. Using M-x bookmark-jump, I can jump and see the ripgrep search I just saved, the buffer loads up, I see my results!

Conclusion

Now you've got another person (me) telling you how great bookmarks are in emacs. Give it a shot and start making your own bookmark types!

I did not cover making a "category" of bookmarks. Drew covers it here on emacs.stackexchange.

The search hits were sparse when I looked for what this use case of bookmarks. Maybe I'm alone out here.

If this post looks similar to one by overvale, it's because I read one of their articles: https://www.overvale.com/emacs/extending-emacs-bookmarks.html

Merry Christmas!

Full code

Maybe I should package this up?

(defun rg-bookmark-make-record ()
  "Make a bookmark record for the current rg buffer."
  `(,(format "rg: \"%s\" ∋ %s ∀ %s"
             (rg-search-pattern rg-cur-search)
             (rg-search-dir rg-cur-search)
             (rg-search-files rg-cur-search))
    ;; pattern is a synonym of query in rg.el
    (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))
    ;; Found some state in the source code; 
    ;; I don't think these are needed, but they are present 
    ;; (search-cfg ,(rg-set-search-defaults (cdr body)))
    ;; (local-bindings (rg-search-parse-local-bindings search-cfg))
    ;; (iargs (rg-search-parse-interactive-args search-cfg))
    (flags . ,(rg-search-flags rg-cur-search))
    (handler . rg-bookmark-handler)))

(defun rg-bookmark-handler (record)
  "Jump to a bookmark's url with bookmarked location."
  (let ((query (bookmark-prop-get record 'pattern))
        (files (bookmark-prop-get record 'files))
        (dir (bookmark-prop-get record 'dir))
        (literal (bookmark-prop-get record 'literal))
        (flags (bookmark-prop-get record 'flags)))
    (rg-run query files dir literal nil flags)))

(defun rg-set-bookmark-handler ()
  "Assigns `bookmark-make-record-function' to a custom function."
  (set (make-local-variable 'bookmark-make-record-function)
       #'rg-bookmark-make-record))

(add-hook 'rg-mode-hook #'rg-set-bookmark-handler)

1

In my opinion, rg.el doesn't have good support for persisting saved searches (nor should that be its focus).

2

Update 2024-12-28, burly.el can bookmark rg, but I've found it doesn't restore window configuration properly with rg buffers.