I added a new custom variable that allows you to select the description
that will be created for file links when no description can be created from
the context.
From 6f9b96da9e3925917da26bb19a4b3ed89c536ddd Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Sun, 7 Jun 2026 09:14:15 -0500
Subject: [PATCH] lisp/ol.el: Custom variable for file link description
* lisp/ol.el (org-link-default-file-link-description): New custom
variable for file link descriptions.
(org-link--file-link-description): Create the description of a
file link based on the value of
`org-link-default-file-link-description'.
(org-link--file-link-to-here): Use
`org-link-default-file-link-description' to create the
description of a file link when a description can not
be created from the context.
(org-store-link): Use `org-link--file-link-description'
to create the description of file links created in dired.
* testing/lisp/test-ol.el (test-org-link/store-link/filename-description):
New tests for `org-store-link'.
(test-org-link/store-link/filepath-description):
New tests for `org-store-link'.
(test-org-link/store-link/function-creates-description):
New tests for `org-store-link'.
(test-org-link/store-link/dired):
New tests for `org-store-link'.
---
etc/ORG-NEWS | 5 +
lisp/ol.el | 55 +++++--
testing/lisp/test-ol.el | 321 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 368 insertions(+), 13 deletions(-)
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 5a72b69ea..d0051fea7 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -208,6 +208,11 @@ environment for descriptive lists when exporting to LaTeX.
Use it when the default ~description~ environment does not fit your
needs. The recommended alternative value is ~itemize~.
+*** New custom variable ~org-link-default-file-link-description~
+
+This option, nil by default, allows you to choose the description
+that will be created for file links. See its docstring for more information.
+
** New functions and changes in function arguments
# This also includes changes in function behavior from Elisp perspective.
diff --git a/lisp/ol.el b/lisp/ol.el
index 73645fb97..d04d06c14 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -277,6 +277,25 @@ filename in the link as an argument and returns the path."
:package-version '(Org . "9.5")
:safe #'symbolp)
+(defcustom org-link-default-file-link-description nil
+ "How the description of file links should be stored.
+Valid values are:
+
+nil No description will be created.
+filename Description will be the filename of the link.
+file-path Description will be the full filepath of the link.
+ Respects the value of `org-link-file-path-type'.
+
+Alternatively, users may supply a custom function that takes the
+path of the link as an argument and returns the description."
+ :group 'org-link
+ :type '(choice (const nil)
+ (const filename)
+ (const file-path)
+ (function))
+ :package-version '(Org . "10.0")
+ :safe #'null)
+
(defcustom org-link-abbrev-alist nil
"Alist of link abbreviations.
The car of each element is a string, to be replaced at the start of a link.
@@ -995,19 +1014,28 @@ Return t when a link has been stored in `org-link-store-props'."
(push (list link desc) org-stored-links)
(message "Link moved to front: %s" (or desc link)))))
+(defun org-link--file-link-description (path)
+ "Use PATH to create a description for file link.
+PATH will be transformed according to the value
+of `org-link-default-file-link-description'."
+ (pcase-exhaustive org-link-default-file-link-description
+ (`nil nil)
+ (`filename (file-name-nondirectory path))
+ (`file-path (org-link--normalize-filename path))
+ ((pred functionp) (funcall org-link-default-file-link-description path))
+ (_ (error "Invalid `org-link-default-file-link-description' value"))))
+
(defun org-link--file-link-to-here ()
"Return as (LINK . DESC) a file link with search string to here."
- (let ((link (concat "file:"
- (abbreviate-file-name
- (buffer-file-name (buffer-base-buffer)))))
- desc)
- (when org-link-context-for-files
- (pcase (org-link-precise-link-target)
- (`nil nil)
- (`(,search-string ,search-desc ,_position)
- (setq link (format "%s::%s" link search-string))
- (setq desc search-desc))))
- (cons link desc)))
+ (let* ((path (buffer-file-name (buffer-base-buffer)))
+ (link (concat "file:" (abbreviate-file-name path)))
+ (context (and org-link-context-for-files (org-link-precise-link-target)))
+ (search-string (car context))
+ (search-desc (cadr context)))
+ (cond
+ ((and search-string search-desc) (cons (format "%s::%s" link search-string) search-desc))
+ (search-string (cons (format "%s::%s" link search-string) (org-link--file-link-description path)))
+ (t (cons link (org-link--file-link-description path))))))
(defun org-link-preview--get-overlays (&optional beg end)
"Return link preview overlays between BEG and END."
@@ -2643,13 +2671,14 @@ NAME."
;; In dired, store a link to the file of the current line
((derived-mode-p 'dired-mode)
- (let ((file (dired-get-filename nil t)))
+ (let ((file (or (dired-get-filename nil t) )))
(setq file (if file
(abbreviate-file-name
(expand-file-name (dired-get-filename nil t)))
;; Otherwise, no file so use current directory.
default-directory))
- (setq link (concat "file:" file))))
+ (setq link (concat "file:" file)
+ desc (org-link--file-link-description file))))
;; Try `org-create-file-search-functions`. If any are
;; successful, create a file link to the current buffer with
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index c8ec09616..90e1d84ac 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -390,6 +390,327 @@ See https://github.com/yantar92/org/issues/4."
(equal (format "[[file:%s::*foo bar][foo bar]]" file)
(org-store-link nil)))))))
+(ert-deftest test-org-link/store-link/filename-description ()
+ "Test `org-store-link' with `org-link-default-file-link-description' set to `filename'."
+
+ (let ((org-link-default-file-link-description 'filename))
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (file-name-nondirectory file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (file-name-nondirectory file))
+ (org-store-link '(16)))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files t))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file (file-name-nondirectory file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (file-name-nondirectory file))
+ (org-store-link nil))))))
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file (file-name-nondirectory file))
+ (org-store-link '(4)))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (file-name-nondirectory file))
+ (org-store-link '(16)))))))
+ ;; Doesn't changed behavior of links
+ ;; who get their name from an org-element
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::*h1][h1]]" file)
+ (org-store-link '(4)))))))))
+
+(ert-deftest test-org-link/store-link/filepath-description ()
+ "Test `org-store-link' with `org-link-default-file-link-description' set to `filepath'."
+ (let ((org-link-default-file-link-description 'file-path))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link '(16)))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files t))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file (org-link--normalize-filename file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file (org-link--normalize-filename file))
+ (org-store-link '(4)))))))
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link '(16)))))))
+ ;; The value of `org-link-file-path-type' is always respectedd.
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil)
+ (org-link-file-path-type 'relative))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link nil))))))
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil)
+ (org-link-file-path-type 'absolute))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link '(16)))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil)
+ (org-link-file-path-type 'adaptive))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file (org-link--normalize-filename file))
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil)
+ (org-link-file-path-type #'identity))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file (org-link--normalize-filename file))
+ (org-store-link '(4)))))))
+ ;; Doesn't changed behavior of links
+ ;; who get their name from an org-element
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files t))
+ (org-test-with-temp-text-in-file "* TODO [#A] COMMENT foo :bar:"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::*foo][foo]]" file)
+ (org-store-link nil))))))))
+
+(ert-deftest test-org-link/store-link/function-creates-description ()
+ "Test `org-store-link' with `org-link-default-file-link-description' set to `function'."
+ (let ((org-link-default-file-link-description #'identity))
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file file)
+ (org-store-link nil))))))
+ (should
+ (let ((org-stored-links nil)
+ (org-id-link-to-org-use-id nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "* h1"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file file)
+ (org-store-link '(16)))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files t))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file file)
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file file)
+ (org-store-link nil))))))
+
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::two][%s]]" file file)
+ (org-store-link '(4)))))))
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files nil))
+ (org-test-with-temp-text-in-file "one\n<point>two"
+ (fundamental-mode)
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s][%s]]" file file)
+ (org-store-link '(16)))))))
+ ;; Doesn't changed behavior of links
+ ;; who get their name from an org-element
+ (should
+ (let ((org-stored-links nil)
+ (org-link-context-for-files t))
+ (org-test-with-temp-text-in-file "* foo[33%]bar"
+ (let ((file (buffer-file-name)))
+ (equal (format "[[file:%s::*foo bar][foo bar]]" file)
+ (org-store-link nil))))))))
+
+(ert-deftest test-org-link/store-link/dired ()
+ "Test `org-store-link' in Dired."
+ ;; no file link description
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description nil))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s]]" test-file)))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; filename as file link description
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description 'filename))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file (file-name-nondirectory test-file))))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; file-path as file link description
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description 'file-path))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file (org-link--normalize-filename test-file))))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; file-path as file link description and
+ ;; `org-link-file-path-type' set to `relative'
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description 'file-path)
+ (org-link-file-path-type 'relative))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file (org-link--normalize-filename test-file))))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; file-path as file link description and
+ ;; `org-link-file-path-type' set to `absolute'
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description 'file-path)
+ (org-link-file-path-type 'absolute))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file (org-link--normalize-filename test-file))))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; file-path as file link description and
+ ;; `org-link-file-path-type' set to a function
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description 'file-path)
+ (org-link-file-path-type #'identity))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file test-file)))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value)))))
+
+ ;; function creates file link description
+ (let* ((org-stored-links nil)
+ (org-link-default-file-link-description #'identity))
+ (org-test-with-temp-text-in-file ""
+ (let* ((test-file (buffer-file-name))
+ (expected-value (format "[[file:%s][%s]]" test-file test-file)))
+ (dired (file-name-directory test-file))
+ (revert-buffer)
+ (dired-goto-file test-file)
+ (should (equal (org-store-link nil) expected-value))))))
+
(ert-deftest test-org-link/precise-link-target ()
"Test `org-link-precise-link-target` specifications."
(org-test-with-temp-text "* H1<point>\n* H2\n"
--
2.54.0