Writing Lisp Code with ChatGPT
That was easy!I am currently organizing a visit to a major festival, with over 175 bands playing over 4 days at around a dozen different venues scattered across the city. It's quite a logistical challenge. Which bands do I want to see, and do they play with enough time in between to get from Location A to Hall B?
So, I copied all the data from the organizer's website into an Org document and generated an overview. In Org, it looks like this:
* Bands
:PROPERTIES:
:COLUMNS: %ITEM %Ort %Datum %Uhrzeit %Link1 %Link2 %Genre
:END:
#+BEGIN: columnview :id local :skip-empty-rows t
| ITEM | Ort | Datum | Uhrzeit | Link1 | Link2 | Genre |
|------------------+-------------------------+------------+---------+-------+-------+-------|
| 12 Illusions (D) | Moritzbastei | 17.05.2024 | 21.00 | [[https://www.youtube.com/watch?v=MOPkUBQbCzA][Song1]] | [[https://www.youtube.com/watch?v=_CUVz7neaRM][Song2]] | o |
| A Projection (S) | Volkspalast Kuppelhalle | 19.05.2024 | 17.40 | [[https://www.youtube.com/watch?v=W7CndC-CQrw][Song1]] | [[https://www.youtube.com/watch?v=D1C5KpP3Hic][Song2]] | o |
#+END:
** 12 Illusions (D)
:PROPERTIES:
:Datum: 17.05.2024
:Uhrzeit: 21.00
:Ort: Moritzbastei
:Adresse: Universitätsstraße 9
:Strassenbahn: 9 (Roßplatz)
:Strassenbahn+: 8, 9, 11, 14, 16, 32 (Wilhelm-Leuschner-Platz)
:Bus: 412 (Roßplatz)
:Bus+: N8 (Roßplatz)
:S-Bahn: S1, S2, S3, S4, S5, S5X, S6 (Wilhelm-Leuscher-Platz)
:Link1: [[https://www.youtube.com/watch?v=MOPkUBQbCzA][Song1]]
:Link2: [[https://www.youtube.com/watch?v=_CUVz7neaRM][Song2]]
:Genre: o
:END:
12 ILLUSIONS is a newly-launched German dark electro pop band.
** A Projection (S)
:PROPERTIES:
:Datum: 19.05.2024
:Ort: Volkspalast Kuppelhalle
:Adresse: Messehalle 16 / Puschstraße 10
:Strassenbahn: 16 (An den Tierkliniken)
:Link1: [[https://www.youtube.com/watch?v=W7CndC-CQrw][Song1]]
:Link2: [[https://www.youtube.com/watch?v=D1C5KpP3Hic][Song2]]
:Genre: o
:END:
The table at the top is my overview, and since there are now almost 200 lines in it, it's tedious to scroll to one of these bands in the details section. If the organizer hadn't listed them alphabetically, it would be nearly impossible.
The overview table is generated anew, thanks to Org magic, every time I press C-c C-c on the header line. So, the first column is literally identical to the heading of each band-entry. If this heading contained a link to itself, then the first column would be full of links to the details of the bands.
To generate these links, I have to perform several functions:
- Generate a custom ID for the heading
- Copy this ID to the clipboard
- Select the heading
- Insert the link
and all that nearly 200 times.
It would be nice to have a function at least, that performs these steps, which I can then bind to a key.
A question via social media revealed that while similar things have been created before, they all ended up with slightly different goals. And then I thought, let me see if ChatGPT can solve this problem for me.
The original prompt
Me: Can you write a emacs lisp function, to link an orgmode Heading at point with its own id, creating one if it does not already exist?
ChatGPT: Certainly! Below is a simple Emacs Lisp function that links an Org mode heading at point with its own ID, creating one if it does not already exist:
(defun link-heading-with-id ()
"Link an Org mode heading at point with its own ID, creating one if it does not already exist."
(interactive)
(org-back-to-heading)
(let* ((heading (nth 4 (org-heading-components)))
(id (org-entry-get nil "CUSTOM_ID")))
(if (not id)
(progn
(setq id (concat "id-" (md5 heading)))
(org-set-property "CUSTOM_ID" id)))
(insert (format "[[#%s][%s]]" id heading))))
Not bad, for a first shot. The function first moves the cursor to the beginning of the current heading, then retrieves the heading text and its existing ID, if it has one. If the heading does not have an ID, it generates one using the md5
hash of the heading text. Finally, it inserts a link to the heading with its ID.
Unfortunally the cursor is moved to the beginning of the Heading, and so it is inserting the new Link before the ** Heading, making it no longer a Heading, But maybe ChatGPF can correct that.
Can you alter it, so it replaces the original Heading?
ChatGPT: Certainly! Here's the modified function that replaces the original heading with the linked version:
(defun link-heading-with-id ()
"Replace an Org mode heading at point with a linked version using its own ID, creating one if it does not already exist."
(interactive)
(org-back-to-heading)
(let* ((heading-start (point))
(heading-end (line-end-position))
(heading (nth 4 (org-heading-components)))
(id (org-entry-get nil "CUSTOM_ID")))
(if (not id)
(progn
(setq id (concat "id-" (md5 heading)))
(org-set-property "CUSTOM_ID" id)))
(delete-region heading-start heading-end)
(insert (format "* [[#%s][%s]]" id heading))))
This version of the function deletes the original heading and inserts the linked version in its place. But it still has a problem, that you can see in the last line. It always inserts a level-1 Heading (it prepends one asterisk, fix). Correct yourself ChatGPT.
Alter it, so that it preserves the Heading-Level
ChatGPT: Sure! Here's the modified function that preserves the heading level while replacing the original heading with the linked version:
(defun link-heading-with-id ()
"Replace an Org mode heading at point
with a linked version using its own ID,
preserving the heading level
and creating one if it does not already exist."
(interactive)
(org-back-to-heading)
(let* ((heading-level (org-outline-level))
(heading-start (point))
(heading-end (line-end-position))
(heading (nth 4 (org-heading-components)))
(id (org-entry-get nil "CUSTOM_ID")))
(if (not id)
(progn
(setq id (concat "id-" (md5 heading)))
(org-set-property "CUSTOM_ID" id)))
(delete-region heading-start heading-end)
(insert (format "%s [[#%s][%s]]"
(make-string heading-level ?*)
id heading))))
This version of the function first retrieves the heading level using org-outline-level
. Then, it deletes the original heading and inserts the linked version with the same heading level. I tried it. and it worked as intended
* Bands
:PROPERTIES:
:COLUMNS: %ITEM %Ort %Datum %Uhrzeit %Link1 %Link2 %Genre
:END:
#+BEGIN: columnview :id local :skip-empty-rows t
| ITEM | Ort | Datum | Uhrzeit | Link1 | Link2 | Genre |
|------------------------------------------------------------+--------------+------------+---------+-------+-------+-------|
| [[#id-e9884ccd6b1210ce39f04bc2eb7f108a][12 Illusions (D)]] | Moritzbastei | 17.05.2024 | 21.00 | Song1 | Song2 | o |
#+END:
** [[#id-e9884ccd6b1210ce39f04bc2eb7f108a][12 Illusions (D)]]
:PROPERTIES:
:Datum: 17.05.2024
:Uhrzeit: 21.00
:Ort: Moritzbastei
:Adresse: Universitätsstraße 9
:Strassenbahn: 9 (Roßplatz)
:Strassenbahn+: 8, 9, 11, 14, 16, 32 (Wilhelm-Leuschner-Platz)
:Bus: 412 (Roßplatz)
:Bus+: N8 (Roßplatz)
:S-Bahn: S1, S2, S3, S4, S5, S5X, S6 (Wilhelm-Leuscher-Platz)
:Link1: [[https://www.youtube.com/watch?v=MOPkUBQbCzA][Song1]]
:Link2: [[https://www.youtube.com/watch?v=_CUVz7neaRM][Song2]]
:Genre: o
:CUSTOM_ID: id-e9884ccd6b1210ce39f04bc2eb7f108a
:END:
12 ILLUSIONS is a newly-launched German dark electro pop band.
It works
Only thing that needs to be done is copying it to the Config and binding it to a key Oh, and ChatGPT can also do that
Me: Please make a keybinding that binds M-. to this function
ChatGPT:
Sure! You can bind the link-heading-with-id
function to a keybinding of your choice, such as M-.
. Here's how you can do it:
(global-set-key (kbd "M-.") 'link-heading-with-id)