> Thanks for the patch! > > > +(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." > > I think we need to clarify that this variable is not always respected, > but only when description is not set by other means, like > org-link-precise-link-target.
Done. > > + :group 'org-link > > + :type '(choice (const nil) > > + (const filename) > > + (const file-path) > > + (function)) > > + :package-version '(Org . "10.0") > > + :safe #'null) > > Lll the possible values except function are safe. Fixed. Le dim. 21 juin 2026 à 06:19, Ihor Radchenko <[email protected]> a écrit : > Earl Chase <[email protected]> writes: > > > 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. > > Thanks for the patch! > > > +(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." > > I think we need to clarify that this variable is not always respected, > but only when description is not set by other means, like > org-link-precise-link-target. > > > + :group 'org-link > > + :type '(choice (const nil) > > + (const filename) > > + (const file-path) > > + (function)) > > + :package-version '(Org . "10.0") > > + :safe #'null) > > Lll the possible values except function are safe. > > -- > Ihor Radchenko // yantar92, > Org mode maintainer, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> >
From 9d57e79b15ea39efc8d8016c7e77de65466ac3fe 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 | 58 ++++++-- testing/lisp/test-ol.el | 321 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+), 13 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 8e8700972..bc994a05d 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -213,6 +213,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 d3ac5e56f..fe7078cd6 100644 --- a/lisp/ol.el +++ b/lisp/ol.el @@ -279,6 +279,28 @@ 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. +The value of this variable is only respected when +`org-store-link' can not create a link from the link +context, i.e. in `Dired' or non-org-mode files. +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 #'symbolp) + (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. @@ -1035,19 +1057,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." @@ -2720,13 +2751,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 405e59a95..b3ba37983 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
