2025-09-29 Emacs news

Posted: - Modified: | emacs, emacs-news

: Fixed title for RDF editor

Very niche, but I'm happy to see that nethack-el is actively being worked on again. I remember having a lot of fun with that. =)

Also, the theme for October's Emacs Carnival is maintenance. Check out the posts for September's theme of obscure packages, too!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

Org Mode: a LaTeX letter that includes PDFs and hyperlinked page numbers

| org

I messed up on one of my tax forms, so I needed to send the tax agency a single document that included the amended tax return and the supporting slips, with my name, social insurance number, and reference number on every page. It turned out to be rather complicated trying to get calculated \pageref to work with \includepdf, so I just used \hyperlink with hard-coded page numbers. I also needed to use qpdf --decrypt input.pdf output.pdf to decrypt a PDF I downloaded from one of my banks before I could include it with \includepdf.

Here's what I wanted to do with this Org Mode / LaTeX example:

  • Coloured header on all pages with info and page numbers
  • Including PDFs
  • Hyperlinks to specific pages
* Letter
#+DATE: 2025-09-24
#+LATEX_CLASS: letter
#+OPTIONS: toc:nil ^:nil title:nil
#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
#+LATEX_HEADER: \hypersetup{hidelinks}
#+LATEX_HEADER: \usepackage{pdfpages}
#+LATEX_HEADER: \usepackage{fancyhdr}
#+LATEX_HEADER: \usepackage{lastpage}
#+LATEX_HEADER: \usepackage{xcolor}
#+LATEX_HEADER: \signature{FULL NAME GOES HERE}
#+LATEX_HEADER: \fancypagestyle{plain}{
#+LATEX_HEADER: \fancyhf{}
#+LATEX_HEADER: \fancyhead[L]{\color{teal}\hyperlink{page.1}{HEADER INFO}}
#+LATEX_HEADER: \fancyhead[R]{\color{teal}\thepage\ of \pageref{LastPage}}
#+LATEX_HEADER: }
#+LATEX_HEADER: \pagestyle{plain}
#+LATEX_HEADER: \makeatletter
#+LATEX_HEADER: \let\ps@empty\ps@plain
#+LATEX_HEADER: \let\ps@firstpage\ps@plain
#+LATEX_HEADER: \makeatother
#+LATEX_HEADER: \renewcommand{\headrulewidth}{0pt}
#+LATEX_HEADER: \newcommand{\pdf}[1]{\includepdf[link,pages=-, scale=.8]{#1}}
#+LATEX_HEADER: \newcommand{\pages}[2]{\hyperlink{page.#1}{#1}-\hyperlink{page.#2}{#2}}
#+LATEX: \begin{letter}{}
#+LATEX: \opening{Dear person I am writing to:}

Text of the letter goes here.
Please find attached:

| Pages                             | |
| @@latex:\pages{2}{10}@@           | Description of filename1.pdf |
| @@latex:\hyperlink{page.5}{5}@@ | Can link to a specific page |
| @@latex:\pages{11}{15}@@           | Description of filename2.pdf |

#+LATEX:\closing{Best regards,}

#+LATEX: \end{letter}

#+LATEX: \pdf{filename1.pdf}
#+LATEX: \pdf{filename2.pdf}

After filling it in, I exported it with C-c C-e (org-export) C-s (to limit it to the subtree) l p (to export a PDF via LaTeX).

Not the end of the world. At least I learned a little more LaTeX and Org Mode along the way!

View org source for this post

Visual vocabulary practice - ABCs

| drawing

I've been giving myself more time to just enjoy drawing: not trying to untangle a thought, just wandering around and seeing where the lines and colours take me. While looking for examples of sketchnotes for self-facilitation for my post on finding the shape of my thoughts, I came across Sketchnotes: Changing The Way You See Your Thoughts — Creative Soul of Denise Nicole. I liked the ABC exercise near the bottom. For fun, I copied the same words and tried my own spin on things.

Text from sketch

Visual vocabulary practice - ABCs 2025-09-21-04

  • anchor
  • banner
  • calculate
  • DNA
  • energy
  • freeze
  • guitar
  • height
  • Instagram
  • judge
  • kid
  • ladder
  • meeting (A+ drew the details)
  • network
  • obstacle
  • planning
  • quote
  • repeat
  • scroll
  • think
  • universe
  • volley
  • weight
  • x-ray
  • yo-yo
  • zoo
  • extra: jar
  • extra: light

When A+ saw what I was doing, she asked me to swap out my meeting icon from "people around a table" to her online meetings at virtual school. She even added details: "This is the kid with the Minecraft background, this is the kid with big headphones…" I enjoyed watching her in this state of playful focus. I wonder what else I can draw that she might have fun taking over. "Meeting" is my favourite one in this set, but since that's mostly A+'s, my next favourite is "freeze." Canada gets even colder than -20C, but for me, -20C is definitely stay inside weather.

I've also been enjoying Kamo's books, like How to Draw Cute Doodles and Illustrations. Her style reminds me of the Illustration School series by Sachiko Umoto, which I also liked. I think I tend towards simple and approachable rather than realistic or technically impressive. Learning how to draw concrete things might help me get better at drawing abstract things. It's fun to slow down and pay attention to more details, too. Turns out I'd been drawing guitar holes in the wrong place all this time. Now I know!

Related posts:

Here are some other resources that might be helpful:

View org source for this post

EmacsConf infrastructure upgrades

| emacsconf

With the EmacsConf call for proposals now closed, I have a little time before EmacsConf speakers send in their pre-recorded videos come in for captioning. I decided to dust off the infrastructure to see what makes sense to upgrade.

We use Etherpad for collaborative note-taking during EmacsConf. It's straightforward to use and pretty reliable. Conference participants can use it to share notes, questions, and links. They can also use IRC to ask questions, and volunteers copy those questions into the pad for the talk. Hosts and speakers can keep an eye on the pad for questions. We send speakers a copy of their talk's pad after the conference, and we post that along with other follow-up questions on the conference wiki. Here's an example: Writing academic papers in Org-Roam.

A native Emacs solution for collaborative notes would be even better. CRDT was great for experimenting with real-time collaboration within Emacs, but I'm not sure it can handle a ton of simultaneous connections and I don't want to find out in the middle of the conference. Also, requiring Emacs would leave out the people who only have a web browser handy. It would be super cool if we had something with Emacs, web, and IRC interfaces, but for now, Etherpad will do.

We started by using Wikimedia's instance, and then we moved to hosting our own. For the past two years, we've used Etherpad 1.9.7. Etherpad is currently at version 2.5.0. There are some performance improvements, bugfixes, and security fixes, so I think it'll be worth upgrading to that. I don't know of any specific issues or upgrades, but it's a good idea to stay closer to the latest release than to get too far out of date.

I switched our roles/pad/tasks/main.yml to use systemli.etherpad from ansible-galaxy. I also figured out how to set up a Vagrant virtual machine that I could destroy and reconfigure with vagrant destroy; vagrant up --provision. Here's my Vagrant file for that:

# -*- mode: ruby -*-
Vagrant.configure("2") do |config|
  config.vm.box = "debian/bookworm64"
  config.vm.define "pad" do |pad|
  end
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048"
  end
  config.vm.network "private_network", ip: "192.168.56.2"
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "../../vagrant-playbook.yml"
  end
end

The playbook it refers to has this:

- name: Pre-flight checks and package installation
  hosts: pad
  become: true
  gather_facts: false
  pre_tasks:
    - name: Ensure ntpdate is installed for time sync
      ansible.builtin.apt:
        name: ntpdate
        state: present
        update_cache: yes
    - name: Synchronize system clock
      ansible.builtin.command: ntpdate pool.ntp.org
      changed_when: true
    - name: Ensure ACL package is installed
      ansible.builtin.apt:
        name: acl
        state: present
- name: Load vars
  hosts: pad
  tags: always
  tasks:
    - include_vars:
        file: vagrant-vars.yml
- name: Set up pad proxy
  hosts: pad
  tags: proxy
  roles:
    - pad-proxy
- name: Set up pad
  hosts: pad
  tags: pad
  roles:
    - pad

I had used Vagrant in 2013, and it felt good to have the time to set up more testing infrastructure. I liked being able to test my pad and pad-proxy roles against a local virtual machine. I could figure out whatever tweaks I needed without messing up the production instance that we use for some meetups in between conferences.

Our production instance is on Debian 10 (Buster). That has reached its end of life for security updates, so apt-cache update doesn't work on it any more, and those steps in my Ansible playbook fail. I'm waiting for Amin Bandali to work on upgrading that server, since he has other stuff running on it. By setting update_cache variable that I override in my inventory.yml and referring to it with update_cache: "" in my task, I can conditionally disable the apt-cache update steps. That let me run the playbook against the production server, and now we're on Etherpad 2.x.

I recently updated our BigBlueButton instance to version 3.0.12. We've decided to stay with Icecast 2.4.4-1 for doing the livestreaming. We'll probably also keep OBS 29.1.2 and ffmpeg 6.0.1 instead of upgrading. With no must-have new features and other organizers' limited availability, it's better to keep those parts stable. This year, we'll continue using whisperx to help with the first draft of captions, but we'll probably try large-v3 instead of large-v2 by default. Some people find that large-v3's performance is better, some people find it's worse, so we'll see. Now that I know about whisperx's --initial_prompt option, I might be able to nudge it to the vocabulary and punctuation style we like.

Since the bones seem pretty solid, I'm looking forward to refamiliarizing myself with the Emacs Lisp code I wrote to help run the conference. I saved a bunch of improvement ideas from last year, and I can't wait to turn them into code. That's probably going to be lots of fun!

View org source for this post

Emacs: Cycle through different paragraph formats: all on one line, wrapped, max one sentence per line, one sentence per line

Posted: - Modified: | emacs

: Add move-to-left-margin to work around bug when using fill-paragraph-semlf at the end of a paragraph.

I came across Schauderbasis - reformat paragraph via @EFLS@mastodon.social. Now I want M-q to cycle through different ways of wrapping text:

  • all on one line
  • according to fill-column
  • at most one sentence per line (although still wrapping at fill-column)
  • at most one sentence per line (don't even try to keep it within fill-column).

Screencast cycling through different paragraph formats

Now that semantic linefeeds are part of core Emacs (as of 2025-06-14), the code for cycling through different paragraph formats can be pretty short. Most of it is actually just the logic for cycling through different commands. That might come in handy elsewhere. There's an unfill package as well, but since the code for unfilling a paragraph is very simple, I'll just include that part.

Note that fill-paragraph-semlf pays attention to sentence-end-double-space, and it doesn't handle comments yet. I also have some code to check if I'm in a comment and skip those filling methods if needed.

This might encourage me to write shorter sentences. I can move sentences around with M-Shift-up and M-Shift-down in Org Mode, which is pretty handy. Also, one sentence per line makes diffs easier to read. But wrapped text is annoying to edit in Orgzly Revived on my phone, because the wrapping makes a very ragged edge on a narrow screen. I might unwrap things that I want to edit there. With a little bit of tweaking to skip source blocks, I can narrow to the subtree, select my whole buffer, and cycle the formatting however I like.

(defvar my-repeat-counter '()
  "How often `my-repeat-next' was called in a row using the same command.
This is an alist of (cat count list) so we can use it for different functions.")

(defun my-unfill-paragraph ()
  "Replace newline chars in current paragraph by single spaces.
This command does the inverse of `fill-paragraph'."
  (interactive)
  (let ((fill-column most-positive-fixnum))
    (fill-paragraph)))

(defun my-fill-paragraph-semlf-long ()
  (interactive)
  (let ((fill-column most-positive-fixnum))
    (fill-paragraph-semlf)))

(defun my-repeat-next (category &optional element-list reset)
  "Return the next element for CATEGORY.
Initialize with ELEMENT-LIST if this is the first time."
  (let* ((counter
          (or (assoc category my-repeat-counter)
              (progn
                (push (list category -1 element-list)
                      my-repeat-counter)
                (assoc category my-repeat-counter)))))
    (setf (elt (cdr counter) 0)
          (mod
           (if reset 0 (1+ (elt (cdr counter) 0)))
           (length (elt (cdr counter) 1))))
    (elt (elt (cdr counter) 1) (elt (cdr counter) 0))))

(defun my-in-prefixed-comment-p ()
  (or (member 'font-lock-comment-delimiter-face (face-at-point nil t))
      (member 'font-lock-comment-face (face-at-point nil t))
      (save-excursion
        (beginning-of-line)
        (comment-search-forward (line-end-position) t))))

;; It might be nice to figure out what state we're
;; in and then cycle to the next one if we're just
;; working with a single paragraph. In the
;; meantime, just going by repeats is fine.
(defun my-reformat-paragraph-or-region ()
  "Cycles the paragraph between three states: filled/unfilled/fill-sentences.
If a region is selected, handle all paragraphs within that region."
  (interactive)
  (let ((func (my-repeat-next 'my-reformat-paragraph
                              '(fill-paragraph my-unfill-paragraph fill-paragraph-semlf
                                               my-fill-paragraph-semlf-long)
                              (not (eq this-command last-command))))
        (deactivate-mark nil))
    (if (region-active-p)
        (save-restriction
          (save-excursion
            (narrow-to-region (region-beginning) (region-end))
            (goto-char (point-min))
            (while (not (eobp))
              (skip-syntax-forward " ")
              (let ((elem (and (derived-mode-p 'org-mode)
                               (org-element-context))))
                (cond
                 ((eq (org-element-type elem) 'headline)
                  (org-forward-paragraph))
                 ((member (org-element-type elem)
                          '(src-block export-block headline property-drawer))
                  (goto-char
                   (org-element-end (org-element-context))))
                 (t
                  (funcall func)
                  (if fill-forward-paragraph-function
                      (funcall fill-forward-paragraph-function)
                    (forward-paragraph))))))))
      (save-excursion
        (move-to-left-margin)
        (funcall func)))))

(keymap-global-set "M-q" #'my-reformat-paragraph-or-region)

Sometimes I use writeroom-mode to make the lines look even narrower, with lots of margin on the side.

Related:

This is part of my Emacs configuration.
View org source for this post

2025-09-22 Emacs news

| emacs, emacs-news

If you want to write functions that let you pick values with completion, check out Manuel and Corwin's posts for simple examples, or chmouel's post for a yasnippet version. Enjoy!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

Adding Org Mode link awesomeness elsewhere: my-org-insert-link-dwim

Posted: - Modified: | emacs, org

: Changed my mind, I want the clipboard URL to be used by default. More bugfixes. : Fix bug in my-page-title. Add mastodon-toot-mode-map.

I love so many things about Org Mode's links. I can use C-c C-l (org-insert-link) to insert a link. If I've selected some text, C-c C-l turns the text into the link's description. I can define my own custom link types with interactive completion, default descriptions, and export formats. This is so nice, I want it in all the different places I write links in:

  • Markdown, like on the EmacsConf wiki; then I don't have to remember Markdown's syntax for links
  • mastodon.el toots
  • Oddmuse, like on EmacsWiki
  • HTML/Web mode
  • Org Mode HTML export blocks

Some considerations inspired by Emacs DWIM: do what ✨I✨ mean, which I used as a starting point:

  • I want Emacs to use the URL from the clipboard.
  • If I haven't already selected some text, I want to use the page title or the custom link description as a default description.
  • I want to be able to use my custom link types for completion, but I want it to insert the external web links if I'm putting the link into a non-Org Mode buffer (or in a source or export block that isn't Org Mode). For example, let's say I select dotemacs:my-org-insert-link-dwim with completion. In Org Mode, it should use that as the link target so that I can follow the link to my config and have it exported as an HTML link. In Markdown, it should be inserted as [Adding Org Mode niceties elsewhere: my-org-insert-link-dwim](https://sachachua.com/dotemacs#my-org-insert-link-dwim).

Mostly, this is motivated by my annoyance with having to work with different link syntaxes:

HTML <a href="https://example.com">title</a>
Org [[https://example.com][title]]
Plain text title https://example.com
Markdown [https://example.com](title)
Oddmuse [https://example.com title]

I want things to Just Work.

Screencast showing how I insert links

Play by play:

  1. 0:00:00 inserting a custom dotemacs link with completion
  2. 0:00:11 inserting a link to a blog post
  3. 0:00:28 selecting text in an HTML export block and adding a link to it
  4. 0:00:48 adding a bookmark link as a plain text link in a Python src block

Here's the my-org-insert-link-dwim function, using my-org-link-as-url from Copy web link and my-org-set-link-target-with-search from Using web searches and bookmarks to quickly link placeholders in Org Mode:

(defun my-org-insert-link-dwim ()
  "Like `org-insert-link' but with personal dwim preferences."
  (interactive)
  (let* ((point-in-link (and (derived-mode-p 'org-mode) (org-in-regexp org-link-any-re 1)))
         (point-in-html-block (and (derived-mode-p 'org-mode)
                                   (let ((elem (org-element-context)))
                                     (and (eq (org-element-type elem) 'export-block)
                                          (string= (org-element-property :type elem) "HTML")))))
         (point-in-src-or-export-block
          (and (derived-mode-p 'org-mode)
               (let ((elem (org-element-context)))
                 (and (member (org-element-type elem) '(src-block export-block))
                      (not (string= (org-element-property :type elem) "Org"))))))
         (url (cond
               ((my-org-in-bracketed-text-link-p) nil)
               ((not point-in-link) (my-org-read-link
                                     ;; clipboard
                                     (when (string-match-p "^http" (current-kill 0))
                                       (current-kill 0))
                                     ))))
         (region-content (when (region-active-p)
                           (buffer-substring-no-properties (region-beginning)
                                                           (region-end))))
         (title (or region-content
                    (when (or (string-match (regexp-quote "*new toot*") (buffer-name))
                              (derived-mode-p '(markdown-mode web-mode oddmuse-mode))
                              point-in-html-block
                              point-in-src-or-export-block
                              (not (and (derived-mode-p 'org-mode)
                                        point-in-link)))
                      (read-string "Title: "
                                   (or (my-org-link-default-description url nil)
                                       (my-page-title url)))))))
    ;; resolve the links; see my-org-link-as-url in  https://sachachua.com/dotemacs#web-link
    (unless (and (derived-mode-p 'org-mode)
                 (not (or point-in-html-block point-in-src-or-export-block)))
      (setq url (my-org-link-as-url url)))
    (when (region-active-p) (delete-region (region-beginning) (region-end)))
    (cond
     ((or (string-match (regexp-quote "*new toot*") (buffer-name))
          (derived-mode-p 'markdown-mode))
      (insert (format "[%s](%s)" title url)))
     ((or (derived-mode-p '(web-mode html-mode)) point-in-html-block)
      (insert (format "<a href=\"%s\">%s</a>" url title)))
     ((derived-mode-p 'oddmuse-mode)
      (insert (format "[%s %s]" url title)))
     ((or point-in-src-or-export-block
          (not (derived-mode-p 'org-mode)))
      (insert title " " url))
     ((and region-content url (not point-in-link))
      (insert (org-link-make-string url region-content)))
     ((and url (not point-in-link))
      (insert (org-link-make-string
               url
               (or title
                   (read-string "Title: "
                                (or (my-org-link-default-description url nil)
                                    (my-page-title url)))))))
     ;; bracketed [[plain text]]; see Using web searches and bookmarks to quickly link placeholders in Org Mode https://sachachua.com/dotemacs#completion-consult-consult-omni-using-web-searches-and-bookmarks-to-quickly-link-placeholders-in-org-mode
     ((my-org-set-link-target-with-search))
     ;; In Org Mode, edit the link
     ((call-interactively 'org-insert-link)))))

Consistent keybindings mean less thinking.

(dolist (group '((org . org-mode-map)
                 (markdown-mode . markdown-mode-map)
                 (mastodon-toot . mastodon-toot-mode-map)
                 (web-mode . web-mode-map)
                 (oddmuse-mode . oddmuse-mode-map)
                 (text-mode . text-mode-map)
                 (html-mode . html-mode-map)))
  (with-eval-after-load (car group)
    (keymap-set (symbol-value (cdr group))  "C-c C-l" #'my-org-insert-link-dwim)))

All right, let's dig into the details. This code gets the page title so that we can use it as the link's description. I like to simplify some page titles. For example, when I link to Reddit or HN discussions, I just want to use "Reddit" or "HN".

(defun my-page-title (url)
  "Get the page title for URL. Simplify some titles."
  (condition-case nil
      (pcase url
        ((rx "reddit.com") "Reddit")
        ((rx "news.ycombinator.com") "HN")
        ((rx "lobste.rs") "lobste.rs")
        (_
         (with-current-buffer (url-retrieve-synchronously url)
           (string-trim
            (replace-regexp-in-string
             "[ \n]+" " "
             (replace-regexp-in-string
              "\\(^Github - \\|:: Sacha Chua\\)" ""
              (or
               (dom-texts (car
                           (dom-by-tag (libxml-parse-html-region
                                        (point-min)
                                        (point-max))
                                       'title)))
               "")))))))
    (error nil)))

Let's use that as the default for https: links too.

(defun my-org-link-https-insert-description (link desc)
  "Default to the page title."
  (unless desc (my-page-title link)))

(with-eval-after-load 'org
  (org-link-set-parameters "https" :insert-description #'my-org-link-https-insert-description))

I want to get the default description for a link, even if it uses a custom link type. I extracted this code from org-insert-link.

(defun my-org-link-default-description (link desc)
  "Return the default description for an Org Mode LINK.
This uses :insert-description if defined."
  (let* ((abbrevs org-link-abbrev-alist-local)
         (all-prefixes (append (mapcar #'car abbrevs)
                               (mapcar #'car org-link-abbrev-alist)
                               (org-link-types)))
         (type
          (cond
           ((and all-prefixes
                 (string-match (rx-to-string `(: string-start (submatch (or ,@all-prefixes)) ":")) link))
            (match-string 1 link))
           ((file-name-absolute-p link) "file")
           ((string-match "\\`\\.\\.?/" link) "file"))))
    (when (org-link-get-parameter type :insert-description)
      (let ((def (org-link-get-parameter type :insert-description)))
        (condition-case nil
            (cond
             ((stringp def) def)
             ((functionp def)
              (funcall def link desc)))
          (error
           nil))))))

Now I want an Emacs Lisp function that interactively reads a link with completion, but doesn't actually insert it. I extracted this logic from org-read-link.

my-org-read-link, extracted from org-read-link
(defun my-org-read-link (&optional default)
  "Act like `org-insert-link'. Return link."
  (let* ((wcf (current-window-configuration))
         (origbuf (current-buffer))
         (abbrevs org-link-abbrev-alist-local)
         (all-prefixes (append (mapcar #'car abbrevs)
                               (mapcar #'car org-link-abbrev-alist)
                               (org-link-types)))

         link)
    (unwind-protect
        ;; Fake a link history, containing the stored links.
        (let ((org-link--history
               (append (mapcar #'car org-stored-links)
                       org-link--insert-history)))
          (setq link
                (org-completing-read
                 (org-format-prompt "Insert link" (or default (caar org-stored-links)))
                 (append
                  (mapcar (lambda (x) (concat x ":")) all-prefixes)
                  (mapcar #'car org-stored-links)
                  ;; Allow description completion.  Avoid "nil" option
                  ;; in the case of `completing-read-default' when
                  ;; some links have no description.
                  (delq nil (mapcar 'cadr org-stored-links)))
                 nil nil nil
                 'org-link--history
                 (or default (caar org-stored-links))))
          (unless (org-string-nw-p link) (user-error "No link selected"))
          (dolist (l org-stored-links)
            (when (equal link (cadr l))
              (setq link (car l))))
          (when (or (member link all-prefixes)
                    (and (equal ":" (substring link -1))
                         (member (substring link 0 -1) all-prefixes)
                         (setq link (substring link 0 -1))))
            (setq link (with-current-buffer origbuf
                         (org-link--try-special-completion link)))))
      (when-let* ((window (get-buffer-window "*Org Links*" t)))
        (quit-window 'kill window))
      (set-window-configuration wcf)
      (when (get-buffer "*Org Links*")
        (kill-buffer "*Org Links*")))
    link))

So now the my-org-insert-link-dwim function can read a link with completion (unless I'm getting it from the clipboard), get the default description from the link (using custom links' :insert-description or the webpage's title), and either wrap the link around the region or insert it in whatever syntax makes sense.

On a related note, you might also enjoy:

And elsewhere:

This is part of my Emacs configuration.
View org source for this post