18

How do I remove an existing hyperlink without having to wade in and manually delete a bunch of brackets? When I try to org-insert-link and just delete the existing link, I get Lisp error: (error "Empty link").

I want to remove the link and preserve the text (i.e. the description).

1
  • Starting with Org mode version 9.4 (commit 6d62c76d2), deleting the link part does not give an error any longer: C-c C-l and deleting the existing link works.
    – NickD
    May 9 at 15:28
14

The following elisp function will take a link around the current point as recognised by org-bracket-link-regexp, so either [[Link][Description]] or [[Link]], and replace it by Description in the first case or Link in the second case.

New version: for org 9.3 or newer (also added save-excursion as in the answer of @Chris)

(defun afs/org-replace-link-by-link-description ()
    "Replace an org link by its description or if empty its address"
  (interactive)
  (if (org-in-regexp org-link-bracket-re 1)
      (save-excursion
        (let ((remove (list (match-beginning 0) (match-end 0)))
              (description
               (if (match-end 2) 
                   (org-match-string-no-properties 2)
                 (org-match-string-no-properties 1))))
          (apply 'delete-region remove)
          (insert description)))))

Alternatively the following elisp function will do such a replacement on all links in the buffer or region:

(defun afs/org-replace-all-links-by-description (&optional start end)
  "Find all org links and replace by their descriptions."
  (interactive
   (if (use-region-p) (list (region-beginning) (region-end))
     (list (point-min) (point-max))))
  (save-excursion
    (save-restriction
      (narrow-to-region start end)
      (goto-char (point-min))
      (while (re-search-forward org-link-bracket-re nil t)
        (replace-match (match-string-no-properties 
                        (if (match-end 2) 2 1)))))))

The above is for org 9.4, where org-match-string-no-properities has been obseleted in favour of match-string-no-properties.

Old version: before org 9.3

(defun afs/org-replace-link-by-link-description ()
    "Replace an org link by its description or if empty its address"
  (interactive)
  (if (org-in-regexp org-bracket-link-regexp 1)
      (let ((remove (list (match-beginning 0) (match-end 0)))
        (description (if (match-end 3) 
                 (org-match-string-no-properties 3)
                 (org-match-string-no-properties 1))))
    (apply 'delete-region remove)
    (insert description))))

In org version 9.3 org-bracket-link-regexp was obsoleted, but aliased to the new org-link-bracket-re. The new regexp is documented in ol.el as

org-link-bracket-re
(rx (seq "[["
         ;; URI part: match group 1.
         (group
          (one-or-more
               (or (not (any "[]\\"))
               (and "\\" (zero-or-more "\\\\") (any "[]"))
               (and (one-or-more "\\") (not (any "[]"))))))
         "]"
         ;; Description (optional): match group 2.
         (opt "[" (group (+? anything)) "]")
         "]"))

so the second group now matches the description of the link if present.

5
  • 1
    That's a very neat compact solution! I updated my answer to support [[LINK]] format org links too. I learned about match-beginning and match-end from your answer. Apr 16 '15 at 14:09
  • I had to change 3 to 2 in @Andrew Swan answer. I am assuming the current version of regexp search returns only 2 matches: the link and the description. Maybe this change happened through numerous upgrades of org-mode since 2015. I am running Org mode 9.3.7 on GNU Emacs 27.1. With that change, it works perfectly!
    – midas0441
    Sep 19 '20 at 16:17
  • 1
    @Raj Many thanks. Code updated. This changed in version 9.3. Sep 20 '20 at 8:23
  • I want to do this on all links in the selection; How to adapt this?
    – HappyFace
    Aug 17 at 9:23
  • @HappyFace I have now added a function to do this. Aug 17 at 11:16
7

I tried to add this to the answer from @Andrew, but it was too long for a comment...

I really liked his solution, except that it moved the cursor. (Technically I guess it moved the point. Anyway...) Fortunately, it was easy to add save-excursion to avoid that:

(defun afs/org-replace-link-by-link-description ()
  "Replace an org link by its description or if empty its address"
  (interactive)
  (if (org-in-regexp org-bracket-link-regexp 1)
      (save-excursion
        (let ((remove (list (match-beginning 0) (match-end 0)))
              (description (if (match-end 3) 
                               (org-match-string-no-properties 3)
                             (org-match-string-no-properties 1))))
          (apply 'delete-region remove)
          (insert description)))))
6

There is a solution which avoids using custom parsing with regexes and directly uses the built-in org-element API:

(defun org-link-delete-link ()
  "Remove the link part of an org-mode link at point and keep
only the description"
  (interactive)
  (let ((elem (org-element-context)))
    (if (eq (car elem) 'link)
        (let* ((content-begin (org-element-property :contents-begin elem))
               (content-end  (org-element-property :contents-end elem))
               (link-begin (org-element-property :begin elem))
               (link-end (org-element-property :end elem)))
          (if (and content-begin content-end)
              (let ((content (buffer-substring-no-properties content-begin content-end)))
                (delete-region link-begin link-end)
                (insert content)))))))
4
  • Can you add how to bind your command to a key so it's sufficient for novices?
    – user12563
    Apr 21 '19 at 22:05
  • 1
    This seems to delete white spaces after the link. Any idea how to keep them?
    – Timm
    Feb 23 '20 at 11:25
  • I want to do this on all links in the selection; How to adapt this?
    – HappyFace
    Aug 17 at 9:23
  • @Timm If you query (org-element-property :post-blank elem) you can get the number of trailing spaces after the link. Then you could choose to "reinsert" them when you replace the element. Oct 15 at 1:38
4

The quickest may be to place the cursor before the link, then type C-M-space (mark-sexp), which will mark the entire link. Then delete it by typing a backspace (if you use delete-selection-mode) or C-w.

6
  • 1
    This seems to delete the entire link, but I want to remove the link and preserve the text (i.e. the description). Apr 16 '15 at 1:15
  • 1
    Sorry, I guess I misunderstood your question. Apr 16 '15 at 8:01
  • 1
    To the downvoters out there: The question was ambiguous when I answered it. It has been edited since then, removing the ambiguity. I do leave the answer up, though, since it still seems the easiest way to delete the entire link, description and all. Apr 17 '15 at 7:03
  • This frequently does not select the whole link. Some ]] stay around after deleting the selection. This sometimes works in the same document at the same link and sometimes it doesn't. When I org-toggle-link-display after selection, sometimes the whole link is selected, sometimes only the caption text. Also with the expand-region package, sometimes if have to execute er/expand-region 4 times until text outside the link is selected, sometimes only to times even though I follow exactly the same steps beginning with Emacs restart. May 7 '20 at 10:52
  • @GünterZöchbauer That's quite odd, I think, unless of course either the link text or the link itself contains unbalanced delimiters. I can't think of a reasonable explanation. (Which doesn't mean one can't be found.) May 7 '20 at 19:03
4

Call this command when the point is anywhere after the first [[ brackets of an org-link (or anywhere on/after an hyper-linked org-link).

An org link will be deleted if it is of the format [[LINK][DESCRIPTION]] or [[LINK]] in an org-mode buffer; else nothing will happen.

For safety, the discarded LINK from org-link is saved to the kill-ring in the event a need arises to use that link elsewhere.

(defun my/org-delete-link ()
  "Replace an org link of the format [[LINK][DESCRIPTION]] with DESCRIPTION.
If the link is of the format [[LINK]], delete the whole org link.

In both the cases, save the LINK to the kill-ring.

Execute this command while the point is on or after the hyper-linked org link."
  (interactive)
  (when (derived-mode-p 'org-mode)
    (let ((search-invisible t) start end)
      (save-excursion
        (when (re-search-backward "\\[\\[" nil :noerror)
          (when (re-search-forward "\\[\\[\\(.*?\\)\\(\\]\\[.*?\\)*\\]\\]" nil :noerror)
            (setq start (match-beginning 0))
            (setq end   (match-end 0))
            (kill-new (match-string-no-properties 1)) ; Save the link to kill-ring
            (replace-regexp "\\[\\[.*?\\(\\]\\[\\(.*?\\)\\)*\\]\\]" "\\2" nil start end)))))))
1

This very quick and very dirty macro would be one way, not the best way:

(fset 'my/org-remove-link
   [?\M-a delete delete ?\M-x ?z ?a ?p ?- ?t ?o ?- ?c ?h ?a ?r return ?\[ ?\C-e backspace backspace ?\C-x])
1

To remove all links in the selection:

(defun night/org-remove-link-to-desc-at-point ()
    "Replace an org link by its description or if empty its address"
  (interactive)
  (if (org-in-regexp org-link-bracket-re 1)
      (save-excursion
        (let ((remove (list (match-beginning 0) (match-end 0)))
              (description
               (if (match-end 2)
                   (org-match-string-no-properties 2)
                 (org-match-string-no-properties 1))))
          (apply 'delete-region remove)
          (insert description)))))

(defun night/org-remove-link-to-desc (beg end)
  (interactive "r")
  (save-mark-and-excursion
    (goto-char beg)
    (while (re-search-forward org-link-bracket-re end t)
      (goto-char (match-beginning 0))
      (night/org-remove-link-to-desc-at-point))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.