> A few more comments. > > > +*** 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. > > Please expand a bit. Otherwise, people reading the news will have no > clue whether this option is useful or not. It would be best if the news > entry shows how the new option can be useful. >
Done. > > +(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 > > *cannot > Fixed. > > +context, i.e. in `Dired' or non-org-mode files. > > I think `Dired' quote is not necessary. > Removed the quotes. > > +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 > > "users may supply" reads weird - a user will be reading this! > Maybe "you can supply". > Fixed the language here. > > +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) > > (symbolp #'delete-file) ; -> t > `symbolp' won't cut it. > Replace with a lambda. > > - (let ((file (dired-get-filename nil t))) > > + (let ((file (or (dired-get-filename nil t) ))) > > What is this change about? > My bad, I removed this change. > > + ;; Doesn't changed behavior of links > > *change > Fixed. > > + ;; The value of `org-link-file-path-type' is always respectedd. > > *respected > Fixed. Le sam. 4 juil. 2026 à 04:48, Ihor Radchenko <[email protected]> a écrit : > Earl Chase <[email protected]> writes: > > > Subject: [PATCH] lisp/ol.el: Custom variable for file link description > > A few more comments. > > > +*** 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. > > Please expand a bit. Otherwise, people reading the news will have no > clue whether this option is useful or not. It would be best if the news > entry shows how the new option can be useful. > > > +(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 > > *cannot > > > +context, i.e. in `Dired' or non-org-mode files. > > I think `Dired' quote is not necessary. > > > +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 > > "users may supply" reads weird - a user will be reading this! > Maybe "you can supply". > > > +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) > > (symbolp #'delete-file) ; -> t > `symbolp' won't cut it. > > > - (let ((file (dired-get-filename nil t))) > > + (let ((file (or (dired-get-filename nil t) ))) > > What is this change about? > > > + ;; Doesn't changed behavior of links > > *change > > > + ;; The value of `org-link-file-path-type' is always respectedd. > > *respected > > > -- > 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 6220b7df3d0c0db665100656a4cdf7d0c82ed8d7 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 | 8 + lisp/ol.el | 56 +++++-- testing/lisp/test-ol.el | 321 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 12 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 50a46c6c7..9263bfeb0 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -225,6 +225,14 @@ 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 that are created in Dired, +non-org-mode files, or any other place where org can not use the +link context to create a description. 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..068d25e06 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' cannot 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, you can 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 (lambda (x) (member x (list nil 'filename 'file-path)))) + (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." @@ -2726,7 +2757,8 @@ 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..1487f7d38 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 change 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 respected. + (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 change 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 change 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
