Quantcast
Channel: Sacha Chua - category - emacs
Viewing all articles
Browse latest Browse all 845

Using an Emacs Lisp macro to define quick custom Org Mode links to project files; plus URLs and search

$
0
0

I'd like to get better at writing notes while coding and at turning those notes into blog posts and videos. I want to be able to link to files in projects easily with the ability to complete, follow, and export links. For example, [[subed:subed.el]] should become subed.el, which opens the file if I'm in Emacs and exports a link if I'm publishing a post. I've been making custom link types using org-link-set-parameters. I think it's time to make a macro that defines that set of functions for me. Emacs Lisp macros are a great way to write code to write code.

(defvar my-project-web-base-list nil "Local path . web repo URLs for easy linking.")

(defmacro my-org-project-link (type file-path git-url)
  `(progn
     (defun ,(intern (format "my-org-%s-complete" type)) ()
       ,(format "Complete a file from %s." type)
       (concat ,type ":" (completing-read "File: "
                                          (projectile-project-files ,file-path))))
     (defun ,(intern (format "my-org-%s-follow" type)) (link _)
       ,(format "Open a file from %s." type)
       (find-file
        (expand-file-name
         link
         ,file-path)))
     (defun ,(intern (format "my-org-%s-export" type)) (link desc format _)
       "Export link to file."
       (setq desc (or desc link))
       (when ,git-url
         (setq link (concat ,git-url (replace-regexp-in-string "^/" "" link))))
       (pcase format
         ((or 'html '11ty) (format "<a href=\"%s\">%s</a>"
                                   link
                                   (or desc link)))
         ('md (if desc (format "[%s](%s)" desc link)
                (format "<%s>" link)))
         ('latex (format "\\href{%s}{%s}" link desc))
         ('texinfo (format "@uref{%s,%s}" link desc))
         ('ascii (format "%s (%s)" desc link))
         (_ (format "%s (%s)" desc link))))
     (with-eval-after-load 'org
       (org-link-set-parameters
        ,type
        :complete (quote ,(intern (format "my-org-%s-complete" type)))
        :export (quote ,(intern (format "my-org-%s-export" type)))
        :follow (quote ,(intern (format "my-org-%s-follow" type))))
       (cl-pushnew (cons (expand-file-name ,file-path) ,git-url)
                   my-project-web-base-list
                   :test 'equal))))

Then I can define projects this way:

(my-org-project-link "subed"
                     "~/proj/subed/subed/"
                     "https://codeberg.org/sachac/subed/src/branch/main/subed/")
(my-org-project-link "emacsconf-el"
                     "~/proj/emacsconf/lisp/"
                     "https://git.emacsconf.org/emacsconf-el/tree/")
(my-org-project-link "subed-record"
                     "~/proj/subed-record/"
                     "https://codeberg.org/sachac/subed-record/src/branch/main/")
(my-org-project-link "compile-media"
                     "~/proj/compile-media/"
                     "https://codeberg.org/sachac/compile-media/src/branch/main/"))
(my-org-project-link "ox-11ty"
                     "~/proj/ox-11ty/"
                     "https://github.com/sachac/ox-11ty/blob/master/"))

And I can complete them with the usual C-c C-l (org-insert-link) process:

output-2024-01-07-08:03:26.gif
Figure 1: Completing a custom link with org-insert-link

Sketches are handled by my Org Mode sketch links, but we can add them anyway.

(cl-pushnew (cons (expand-file-name "~/sync/sketches/") "https://sketches.sachachua.com/filename/")
            my-project-web-base-list
            :test 'equal)

I've been really liking being able to refer to various emacsconf-el files by just selecting the link type and completing the filename, so maybe it'll be easier to write about lots of other stuff if I extend that to my other projects.

Quickly search my code

Since my-project-web-base-list is a list of projects I often think about or write about, I can also make something that searches through them. That way, I don't have to care about where my code is.

(defun my-consult-ripgrep-code ()
  (interactive)
  (consult-ripgrep (mapcar 'car my-project-web-base-list)))

I can add .rgignore files in directories to tell ripgrep to ignore things like node_modules or *.json.

I also want to search my Emacs configuration at the same time, although links to my config are handled by my dotemacs link type so I'll leave the URL as nil. This is also the way I can handle other unpublished directories.

(cl-pushnew (cons (expand-file-name "~/sync/emacs/Sacha.org") nil)
            my-project-web-base-list
            :test 'equal)
(cl-pushnew (cons (expand-file-name "~/proj/static-blog/_includes") nil)
            my-project-web-base-list
            :test 'equal)
(cl-pushnew (cons (expand-file-name "~/bin") nil)
            my-project-web-base-list
            :test 'equal)

Actually, let's throw my blog posts and Org files in there as well, since I often have code snippets. If it gets to be too much, I can always have different commands search different things.

(cl-pushnew (cons (expand-file-name "~/proj/static-blog/blog/") "https://sachachua.com/blog/")
            my-project-web-base-list
            :test 'equal)
(cl-pushnew (cons (expand-file-name "~/sync/orgzly") nil)
            my-project-web-base-list
            :test 'equal)
output-2024-01-07-08:04:58.gif
Figure 2: Using my-consult-ripgrep-code

I don't have anything bound to M-s c (code) yet, so let's try that.

(global-set-key (kbd "M-s c") #'my-consult-ripgrep-code)

At some point, it might be fun to get Embark set up so that I can grab a link to something right from the consult-ripgrep interface. In the meantime, I can always jump to it and get the link.

In general, I don't want to have to think about where something is on my laptop or where it's published on the Web, I just want to write about it. One step closer, yay Emacs!

This is part of my Emacs configuration.

Viewing all articles
Browse latest Browse all 845

Trending Articles