all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: "Rick Lupton" <mail@ricklupton.name>
To: "Ihor Radchenko" <yantar92@posteo.net>
Cc: "Y. E." <emacs-orgmode@gnu.org>
Subject: Re: [PATCH v2] org-id: allow using parent's existing id in links to headlines
Date: Sun, 28 Jan 2024 22:47:14 +0000	[thread overview]
Message-ID: <1b50d1a4-8573-4dcc-9427-8970f67e632a@app.fastmail.com> (raw)
In-Reply-To: <87msu7r902.fsf@localhost>

[-- Attachment #1: Type: text/plain, Size: 12049 bytes --]

Hi,

Thanks for trying it out.  Updated patches attached, comments below.

On Mon, 18 Dec 2023, at 12:27 PM, Ihor Radchenko wrote:
> I played around with the patch a bit and found a couple of rough edges:
>
> 1. When I try to open a link to non-existing search target, like
>    <id:some-id::non-existing-target>, I get a query to create a new
>    heading. If I reply "yes", a new heading is created. However, the
>    heading is created at the end of the file and is always level 1,
>    regardless of the "some-id" parent context.
>    It would make more sense to create a new heading at the end of the
>    id:some-id subtree.

Fixed in updated patches -- first patch adds generic new flexibility to `org-insert-heading', second patch uses it so new headings now added at correct level at the end of the id:sub-id subtree.

> 2. Consider the following setting:
>    (setq org-id-link-consider-parent-id t)
>    (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
>
>    Then, create the following Org file
>
> * Sub
> * Parent here
> ** This is test
> :PROPERTIES:
> :ID:       fe40252e-0527-44c1-a990-12498991f167
> :END:
>
> *** Sub <point here>
> :PROPERTIES:
> :CUSTOM_ID:       subid
> :END:
>
>    When you M-x org-store-link, the stored link has ::*Sub instead of
>    the expected ::#subid

Updated so that search strings prefer custom-ids (::#subid) to headline matches (::*Sub).  This makes this example behave as you expect.

The correct behaviour of org-store-link doesn't seem totally obvious to me about id vs custom-id links.  Currently org-store-link has special logic to store TWO links (one <file:xx::#subid>, one <file:xx::*Sub>) when a CUSTOM_ID is present. In the manual, it says:

     If the headline has a ‘CUSTOM_ID’ property, store a link to this
     custom ID.  In addition or alternatively, depending on the value of
     ‘org-id-link-to-org-use-id’, create and/or use a globally unique
     ‘ID’ property for the link(1).  So using this command in Org
     buffers potentially creates two links: a human-readable link from
     the custom ID, and one that is globally unique and works even if
     the entry is moved from file to file.  The ‘ID’ property can be
     either a UUID (default) or a timestamp, depending on
     ‘org-id-method’.  Later, when inserting the link, you need to
     decide which one to use.

That refers to ID links specifically, but now, using the generic link store functions, there is only the possibility to store one link type, so it's not possible to neatly keep exactly the same behaviour (i.e. for ID links but not for other external link types).

I think the intention of what's described in the manual is to distinguish "human-readable" vs "persistent id" links.  There could be other types of "persistent id" links apart from org-id links, such as mu4e: links to email message-ids.  Therefore I've updated org-store-link to simply store a <file:xx.org::#custom-id> link as an additional option, whether or not the first matched link was an org-id link (this is the current behaviour) or another external link type (this is changed behaviour).

Added a note to ORG-NEWS about this.

> 3. Consider
>    (setq org-id-link-consider-parent-id t)
>    (setq org-id-link-to-org-use-id t)
>
>    Then, create a new empty Org file
>    M-x org-store-link with create a top-level properties drawer with ID
>    and store the link. However, that link will not be a simple ID link,
>    but also have ::PROPERTIES search string, which is not expected.

This is because it is trying to link to the current line of the file, which contains the text "PROPERTIES".  On main, with (setq org-id-link-to-org-use-id nil), you see the equivalent behaviour (a link to [[file:test.org:::PROPERTIES:]]) when point is before the first heading.  So, this seems consistent with non-org-id links?

(these links don't actually work with the default value of `org-link-search-must-match-exact-headline', but I think that's a separate issue).

>> +  #+vindex: org-id-link-consider-parent-id
>> +  When ~org-id-link-consider-parent-id~ is ~t~, parent =ID= properties
>> +  are considered.  This allows linking to specific targets, named
>> +  blocks, or headlines (which may not have a globally unique =ID=
>> +  themselves) within the context of a parent headline or file which
>> +  does.
>
> It would be nice to add an example, similar to what you did in the docstring.

Added.

>
>> -(defun org-man-store-link ()
>> +(defun org-man-store-link (&optional _interactive?)
>>    "Store a link to a man page."
>>    (when (memq major-mode '(Man-mode woman-mode))
>>      ;; This is a man page, we do make this link.
>> @@ -21312,13 +21324,15 @@ A review of =ol-man.el=:
>
> Please, update the actual built-in :store functions in lisp/ol-*.el to
> handle the new optional argument as well.

Updated.

>> +**** =org-link= store functions are passed an ~interactive?~ argument
>> +
>> +The ~:store:~ functions set for link types using
>> +~org-link-set-parameters~ are now passed an ~interactive?~ argument,
>> +indicating whether ~org-store-link~ was called interactively.
>
> Please also explain that the existing functions are not broken.

Done.

>> +*** ~org-id-store-link~ now adds search strings for precise link targets
>> +
>> +This new behaviour can be disabled generally by setting
>> +~org-id-link-use-context~ to ~nil~, or when storing a specific link by
>> +passing a prefix argument to ~org-store-link~.
>
> universal argument.
> There are several possible prefix arguments in `org-store-link', but
> only C-u (universal argument) will give the described effect.
> Also, won't the behavior be _toggled_ by the universal argument?

Updated.

>> +When using this feature, IDs should not include =::=, which is used in
>> +links to indicate the start of the search string.  For backwards
>> +compability, existing IDs including =::= will still be matched (but
>> +cannot be used together with precise link targets).
>
> Please add an org-lint checker that warns about such IDs and mention
> this checker in the above.

Added.

> Also, this paragraph belongs to "Breaking changes", not "new and changed
> options".

That's where it is, I think.

>> +*** New option ~org-id-link-consider-parent-id~ to allow =id:= links to parent headlines
>> +
>> +For =id:= links, when this option is enabled, ~org-store-link~ will
>> +look for ids from parent/ancestor headlines, if the current headline
>> +does not have an id.
>> +
>> +Combined with the new ability for =id:= links to use search strings
>> +for precise link targets (when =org-id-link-use-context= is =t=, which
>> +is the default), this allows linking to specific headlines without
>> +requiring every headline to have an id property, as long as the
>> +headline is unique within a subtree that does have an id property.
>> +
>> +By giving files top-level id properties, links to headlines in the
>> +file can be made more robust by using the file id instead of the file
>> +path.
>
> Please, provide an example here as well.

Done.

>> +(defun org-link--try-link-store-functions (interactive?)
>> +  "Try storing external links, prompting if more than one is possible.
>> +
>> +Each function returned by `org-store-link-functions' is called in
>> +turn.  If multiple functions return non-nil, prompt for which
>> +link should be stored.
>> +
>> +Return t when a link has been stored in `org-link-store-props'."
>
> Please document INTERACTIVE? argument in the docstring.

Done.

>> +  (let ((results-alist nil))
>> +    (dolist (f (org-store-link-functions))
>> +      (when (condition-case nil
>> +                (funcall f interactive?)
>> +              ;; XXX: The store function used (< Org 9.7) to accept no
>> +              ;; arguments; provide backward compatibility support for
>> +              ;; them.
>
> Use FIXME, not XXX. (I have no idea why it is XXX in the existing code).

Changed.

>> +(defun org-link-precise-link-target (&optional relative-to)
>> +  "Determine search string and description for storing a link.
>> +
>> +If a search string is found, return cons cell (SEARCH-STRING
>> +. DESC).  Otherwise, return nil.
>> +
>> +If there is an active region, the contents is used (see
>> +`org-link--context-from-region').
>
> It is not clear from this sentence whether the contents is used for
> SEARCH-STRING of DESC.
>
>> +In org-mode buffers, if point is at a named element (e.g. a
>> +source block), the name is used. If within a heading, the current
>> +heading is used.
>
> Please use double space between sentences.
>
>> +Optional argument RELATIVE-TO specifies the buffer position where
>> +the search will start from.  If the search target that would be
>> +returned is already at this location, return nil to avoid
>> +unnecessary search strings (for example, when using search
>> +strings to find targets within org-id links)."
>
> It is not clear what will happen if RELATIVE-TO is before/after point.

Updated the docstring.

>> -    (let (link cpltxt desc search custom-id agenda-link) ;; description
>> +    (let ((org-link-context-for-files (org-xor org-link-context-for-files
>> +                                               (equal arg '(4))))
>> +          link cpltxt desc search custom-id agenda-link) ;; description
>>        (cond
>>         ;; Store a link using an external link type, if any function is
>> -       ;; available. If more than one can generate a link from current
>> -       ;; location, ask which one to use.
>> +       ;; available.  If more than one can generate a link from
>> +       ;; current location, ask which one to use.  Negate
>> +       ;; `org-context-in-file-links' when given a single prefix arg.
>
> The part of the comment about negation, should probably be moved near
> the let binding of `org-link-context-for-files'.

Done.

>> +For example, given this org file:
>> +
>> +* Parent
>> +:PROPERTIES:
>> +:ID: abc
>> +:END:
>> +** Child 1
>> +** Child 2
>> +
>> +With `org-id-link-consider-parent-id' set to t, storing a link
>> +with point at \"Child 1\" will produce a link \"id:abc\" to
>> +\"Parent\".
>
> This is actually confusing. May we only consider parent when
> `org-id-link-use-context' is enabled?

Yes, I was trying to keep them independent but I agree it's probably more useful to only consider parent when `org-id-link-use-context' is enabled (which in turn depends on `org-context-in-file-links' being enabled).

>> -(defun org-id-get (&optional epom create prefix)
>> +(defun org-id-get (&optional epom create prefix inherit)
>>    "Get the ID property of the entry at EPOM.
>>  EPOM is an element, marker, or buffer position.
>>  If EPOM is nil, refer to the entry at point.
>>  If the entry does not have an ID, the function returns nil.
>> +If INHERIT is non-nil, parents' IDs are also considered.
>>  However, when CREATE is non-nil, create an ID if none is present already.
>>  PREFIX will be passed through to `org-id-new'.
>>  In any case, the ID of the entry is returned."
>
> What about both CREATE and INHERIT being non-nil?

Rewrote the docstring.

Also removed INHERIT argument for `org-id-get-create' again, as other functions can be re-written to use `org-id-get' directly, and INHERIT isn't particularly useful when using `org-id-get-create' interactively.

>> +;;;###autoload
>> +(defun org-id-store-link-maybe (&optional interactive?)
>> +  "Store a link to the current entry using its ID if enabled.
>> +
>> +The value of `org-id-link-to-org-use-id' determines whether an ID
>> +link should be stored, using `org-id-store-link'.
>> +
>> +Assume the function is called interactively if INTERACTIVE? is
>> +non-nil."
>> +  (interactive "p")
>
> Do we really need to make it interactive?

No, removed.

Thanks,
Rick

[-- Attachment #2: 0001-lisp-org.el-org-insert-heading-allow-specifying-head.patch --]
[-- Type: application/octet-stream, Size: 5080 bytes --]

From 04d677f48004467875a656c4bd79d78e559f1016 Mon Sep 17 00:00:00 2001
From: Rick Lupton <mail@ricklupton.name>
Date: Wed, 3 Jan 2024 22:37:38 +0000
Subject: [PATCH 1/2] lisp/org.el (org-insert-heading): allow specifying
 heading level

* lisp/org.el (org-insert-heading): Change optional argument TOP to
LEVEL, accepting a number to force a specific heading level.
* testing/lisp/test-org.el (test-org/insert-heading): Add tests
* etc/ORG-NEWS: Document changes
---
 etc/ORG-NEWS             |  6 ++++++
 lisp/org.el              | 21 ++++++++++++++-------
 testing/lisp/test-org.el | 26 ++++++++++++++++++++++++--
 3 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 1bf7eb5b4..ec01004f8 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -941,6 +941,12 @@ as the function can also act on objects.
 
 *** ~org-export-get-parent-element~ is renamed to ~org-element-parent-element~ and moved to =lisp/org-element.el=
 
+*** ~org-insert-heading~ optional argument =TOP= is now =LEVEL=
+
+A numeric value forces a heading at that level to be inserted.  For
+backwards compatibility, non-numeric non-nil values insert level 1
+headings as before.
+
 ** Miscellaneous
 *** =org-crypt.el= now applies initial visibility settings to decrypted entries
 
diff --git a/lisp/org.el b/lisp/org.el
index 796545392..87b94a54d 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -6352,7 +6352,7 @@ headline instead of current one."
     (`(heading . ,value) value)
     (_ nil)))
 
-(defun org-insert-heading (&optional arg invisible-ok top)
+(defun org-insert-heading (&optional arg invisible-ok level)
   "Insert a new heading or an item with the same depth at point.
 
 If point is at the beginning of a heading, insert a new heading
@@ -6381,12 +6381,19 @@ When INVISIBLE-OK is set, stop at invisible headlines when going
 back.  This is important for non-interactive uses of the
 command.
 
-When optional argument TOP is non-nil, insert a level 1 heading,
-unconditionally."
+When optional argument LEVEL is a number, insert a heading at
+that level.  For backwards compatibility, when LEVEL is non-nil
+but not a number, insert a level-1 heading."
   (interactive "P")
   (let* ((blank? (org--blank-before-heading-p (equal arg '(16))))
-	 (level (org-current-level))
-	 (stars (make-string (if (and level (not top)) level 1) ?*)))
+         (current-level (org-current-level))
+         (num-stars (or
+                     ;; Backwards compat: if LEVEL non-nil, level is 1
+                     (and level (if (wholenump level) level 1))
+                     current-level
+                     ;; This `1' is for when before first headline
+                     1))
+         (stars (make-string num-stars ?*)))
     (cond
      ((or org-insert-heading-respect-content
 	  (member arg '((4) (16)))
@@ -6395,7 +6402,7 @@ unconditionally."
       ;; Position point at the location of insertion.  Make sure we
       ;; end up on a visible headline if INVISIBLE-OK is nil.
       (org-with-limited-levels
-       (if (not level) (outline-next-heading) ;before first headline
+       (if (not current-level) (outline-next-heading) ;before first headline
 	 (org-back-to-heading invisible-ok)
 	 (when (equal arg '(16)) (org-up-heading-safe))
 	 (org-end-of-subtree invisible-ok 'to-heading)))
@@ -6408,7 +6415,7 @@ unconditionally."
                           (org-before-first-heading-p)))
         (insert "\n")
         (backward-char))
-      (when (and (not level) (not (eobp)) (not (bobp)))
+      (when (and (not current-level) (not (eobp)) (not (bobp)))
         (when (org-at-heading-p) (insert "\n"))
         (backward-char))
       (unless (and blank? (org-previous-line-empty-p))
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 822cbc67a..fc50dc787 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -1980,8 +1980,30 @@ CLOCK: [2022-09-17 sam. 11:00]--[2022-09-17 sam. 11:46] =>  0:46"
 	    (let ((org-insert-heading-respect-content nil))
 	      (org-insert-heading '(16)))
 	    (buffer-string))))
-  ;; When optional TOP-LEVEL argument is non-nil, always insert
-  ;; a level 1 heading.
+  ;; When optional LEVEL argument is a number, insert a heading at
+  ;; that level.
+  (should
+   (equal "* H1\n** H2\n* "
+	  (org-test-with-temp-text "* H1\n** H2<point>"
+	    (org-insert-heading nil nil 1)
+	    (buffer-string))))
+  (should
+   (equal "* H1\n** H2\n** "
+	  (org-test-with-temp-text "* H1\n** H2<point>"
+	    (org-insert-heading nil nil 2)
+	    (buffer-string))))
+  (should
+   (equal "* H1\n** H2\n*** "
+	  (org-test-with-temp-text "* H1\n** H2<point>"
+	    (org-insert-heading nil nil 3)
+	    (buffer-string))))
+  (should
+   (equal "* H1\n- item\n* "
+	  (org-test-with-temp-text "* H1\n- item<point>"
+	    (org-insert-heading nil nil 1)
+	    (buffer-string))))
+  ;; When optional LEVEL argument is non-nil, always insert a level 1
+  ;; heading.
   (should
    (equal "* H1\n** H2\n* "
 	  (org-test-with-temp-text "* H1\n** H2<point>"
-- 
2.39.2 (Apple Git-143)


[-- Attachment #3: 0002-org-id.el-Extend-links-with-search-strings-inherit-p.patch --]
[-- Type: application/octet-stream, Size: 55325 bytes --]

From 245e2decedb5741a509779a1a896f3982cf8f54c Mon Sep 17 00:00:00 2001
From: Rick Lupton <mail@ricklupton.name>
Date: Sun, 19 Nov 2023 14:52:05 +0000
Subject: [PATCH 2/2] org-id.el: Extend links with search strings, inherit
 parent IDs

* lisp/ol.el (org-store-link): Refactor org-id links to use standard
`org-store-link-functions'.
(org-link-search): Create new headings at appropriate level.
(org-link-precise-link-target): New function extracting logic to
identify a precise link target, e.g. a heading, named object, or text
search.
(org-link-try-link-store-functions): Extract logic to call external
link store functions. Pass them a new `interactive?' argument.
* lisp/ol-bbdb.el (org-bbdb-store-link):
* lisp/ol-bibtex.el (org-bibtex-store-link):
* lisp/ol-docview.el (org-docview-store-link):
* lisp/ol-eshell.el (org-eshell-store-link):
* lisp/ol-eww.el (org-eww-store-link):
* lisp/ol-gnus.el (org-gnus-store-link):
* lisp/ol-info.el (org-info-store-link):
* lisp/ol-irc.el (org-irc-store-link):
* lisp/ol-man.el (org-man-store-link):
* lisp/ol-mhe.el (org-mhe-store-link):
* lisp/ol-rmail.el (org-rmail-store-link): Accept optional arg.
* lisp/org-id.el (org-id-link-consider-parent-id): New option to allow
a parent heading with an id to be considered as a link target.
(org-id-link-use-context): New option to add context to org-id links.
(org-id-get): Add optional `inherit' argument which considers parents'
IDs if the current entry does not have one.
(org-id-store-link): Consider IDs of parent headings as link targets
when current heading has no ID and `org-id-link-consider-parent-id' is
set. Add a search string to the link when enabled.
(org-id-store-link-maybe): Function set as :store option for custom id
link property. Move logic from `org-store-link' here to determine when
an org-id link should be stored using `org-id-store-link'.
(org-id-open): Recognise search strings after "::" in org-id links.
* lisp/org-lint.el: add checker for "::" in ID properties.
* testing/lisp/test-ol.el: Add tests for
`org-link-precise-link-target' and `org-id-store-link' functions,
testing new options.
* doc/org-manual.org: Update documentation about links.
* etc/ORG-NEWS: Document changes and new options.

These feature allows for more precise links when using org-id to link to
org headings, without requiring every single headline to have an id.

Link: https://list.orgmode.org/118435e8-0b20-46fd-af6a-88de8e19fac6@app.fastmail.com/
---
 doc/org-manual.org      | 134 +++++++++++--------
 etc/ORG-NEWS            |  64 +++++++++
 lisp/ol-bbdb.el         |   2 +-
 lisp/ol-bibtex.el       |   2 +-
 lisp/ol-docview.el      |   2 +-
 lisp/ol-eshell.el       |   2 +-
 lisp/ol-eww.el          |   2 +-
 lisp/ol-gnus.el         |   2 +-
 lisp/ol-info.el         |   2 +-
 lisp/ol-irc.el          |   2 +-
 lisp/ol-man.el          |   2 +-
 lisp/ol-mhe.el          |   2 +-
 lisp/ol-rmail.el        |   2 +-
 lisp/ol.el              | 284 +++++++++++++++++++++++++---------------
 lisp/org-id.el          | 165 ++++++++++++++++++++---
 lisp/org-lint.el        |  16 +++
 testing/lisp/test-ol.el | 126 ++++++++++++++++++
 17 files changed, 620 insertions(+), 191 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 7e5ac0673..1f1013e69 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -3297,10 +3297,6 @@ Here is the full set of built-in link types:
 
   File links.  File name may be remote, absolute, or relative.
 
-  Additionally, you can specify a line number, or a text search.
-  In Org files, you may link to a headline name, a custom ID, or a
-  code reference instead.
-
   As a special case, "file" prefix may be omitted if the file name
   is complete, e.g., it starts with =./=, or =/=.
 
@@ -3364,44 +3360,50 @@ Here is the full set of built-in link types:
 
   Execute a shell command upon activation.
 
+
+For =file:= and =id:= links, you can additionally specify a line
+number, or a text search string, separated by =::=.  In Org files, you
+may link to a headline name, a custom ID, or a code reference instead.
+
 The following table illustrates the link types above, along with their
 options:
 
-| Link Type  | Example                                                  |
-|------------+----------------------------------------------------------|
-| http       | =http://staff.science.uva.nl/c.dominik/=                 |
-| https      | =https://orgmode.org/=                                   |
-| doi        | =doi:10.1000/182=                                        |
-| file       | =file:/home/dominik/images/jupiter.jpg=                  |
-|            | =/home/dominik/images/jupiter.jpg= (same as above)       |
-|            | =file:papers/last.pdf=                                   |
-|            | =./papers/last.pdf= (same as above)                      |
-|            | =file:/ssh:me@some.where:papers/last.pdf= (remote)       |
-|            | =/ssh:me@some.where:papers/last.pdf= (same as above)     |
-|            | =file:sometextfile::NNN= (jump to line number)           |
-|            | =file:projects.org=                                      |
-|            | =file:projects.org::some words= (text search)[fn:12]     |
-|            | =file:projects.org::*task title= (headline search)       |
-|            | =file:projects.org::#custom-id= (headline search)        |
-| attachment | =attachment:projects.org=                                |
-|            | =attachment:projects.org::some words= (text search)      |
-| docview    | =docview:papers/last.pdf::NNN=                           |
-| id         | =id:B7423F4D-2E8A-471B-8810-C40F074717E9=                |
-| news       | =news:comp.emacs=                                        |
-| mailto     | =mailto:adent@galaxy.net=                                |
-| mhe        | =mhe:folder= (folder link)                               |
-|            | =mhe:folder#id= (message link)                           |
-| rmail      | =rmail:folder= (folder link)                             |
-|            | =rmail:folder#id= (message link)                         |
-| gnus       | =gnus:group= (group link)                                |
-|            | =gnus:group#id= (article link)                           |
-| bbdb       | =bbdb:R.*Stallman= (record with regexp)                  |
-| irc        | =irc:/irc.com/#emacs/bob=                                |
-| help       | =help:org-store-link=                                    |
-| info       | =info:org#External links=                                |
-| shell      | =shell:ls *.org=                                         |
-| elisp      | =elisp:(find-file "Elisp.org")= (Elisp form to evaluate) |
-|            | =elisp:org-agenda= (interactive Elisp command)           |
+| Link Type  | Example                                                            |
+|------------+--------------------------------------------------------------------|
+| http       | =http://staff.science.uva.nl/c.dominik/=                           |
+| https      | =https://orgmode.org/=                                             |
+| doi        | =doi:10.1000/182=                                                  |
+| file       | =file:/home/dominik/images/jupiter.jpg=                            |
+|            | =/home/dominik/images/jupiter.jpg= (same as above)                 |
+|            | =file:papers/last.pdf=                                             |
+|            | =./papers/last.pdf= (same as above)                                |
+|            | =file:/ssh:me@some.where:papers/last.pdf= (remote)                 |
+|            | =/ssh:me@some.where:papers/last.pdf= (same as above)               |
+|            | =file:sometextfile::NNN= (jump to line number)                     |
+|            | =file:projects.org=                                                |
+|            | =file:projects.org::some words= (text search)[fn:12]               |
+|            | =file:projects.org::*task title= (headline search)                 |
+|            | =file:projects.org::#custom-id= (headline search)                  |
+| attachment | =attachment:projects.org=                                          |
+|            | =attachment:projects.org::some words= (text search)                |
+| docview    | =docview:papers/last.pdf::NNN=                                     |
+| id         | =id:B7423F4D-2E8A-471B-8810-C40F074717E9=                          |
+|            | =id:B7423F4D-2E8A-471B-8810-C40F074717E9::*task= (headline search) |
+| news       | =news:comp.emacs=                                                  |
+| mailto     | =mailto:adent@galaxy.net=                                          |
+| mhe        | =mhe:folder= (folder link)                                         |
+|            | =mhe:folder#id= (message link)                                     |
+| rmail      | =rmail:folder= (folder link)                                       |
+|            | =rmail:folder#id= (message link)                                   |
+| gnus       | =gnus:group= (group link)                                          |
+|            | =gnus:group#id= (article link)                                     |
+| bbdb       | =bbdb:R.*Stallman= (record with regexp)                            |
+| irc        | =irc:/irc.com/#emacs/bob=                                          |
+| help       | =help:org-store-link=                                              |
+| info       | =info:org#External links=                                          |
+| shell      | =shell:ls *.org=                                                   |
+| elisp      | =elisp:(find-file "Elisp.org")= (Elisp form to evaluate)           |
+|            | =elisp:org-agenda= (interactive Elisp command)                     |
 
 #+cindex: VM links
 #+cindex: Wanderlust links
@@ -3462,8 +3464,9 @@ current buffer:
 - /Org mode buffers/ ::
 
   For Org files, if there is a =<<target>>= at point, the link points
-  to the target.  Otherwise it points to the current headline, which
-  is also the description.
+  to the target.  If there is a named block (using =#+name:=) at
+  point, the link points to that name.  Otherwise it points to the
+  current headline, which is also the description.
 
   #+vindex: org-id-link-to-org-use-id
   #+cindex: @samp{CUSTOM_ID}, property
@@ -3481,6 +3484,29 @@ current buffer:
   timestamp, depending on ~org-id-method~.  Later, when inserting the
   link, you need to decide which one to use.
 
+  #+vindex: org-id-link-consider-parent-id
+  When ~org-id-link-consider-parent-id~ is ~t~ (and
+  ~org-context-in-file-links~ and ~org-id-link-use-context~ are both
+  enabled), parent =ID= properties are considered.  This allows
+  linking to specific targets, named blocks, or headlines (which may
+  not have a globally unique =ID= themselves) within the context of a
+  parent headline or file which does.
+
+  For example, given this org file with those variables set:
+
+  #+begin_src org
+  ,* Parent
+  :PROPERTIES:
+  :ID: abc
+  :END:
+  ,** Child 1
+  ,** Child 2
+  #+end_src
+
+  Storing a link with point at "Child 1" will produce a link
+  =<id:abc::*Child 1>=, which precisely links to the "Child 1"
+  headline even though it does not have its own ID.
+
 - /Email/News clients: VM, Rmail, Wanderlust, MH-E, Gnus/ ::
 
   #+vindex: org-link-email-description-format
@@ -3754,13 +3780,15 @@ the link completion function like this:
 (org-link-set-parameter "type" :complete #'some-completion-function)
 #+end_src
 
-** Search Options in File Links
+** Search Options in File and ID Links
 :PROPERTIES:
 :DESCRIPTION: Linking to a specific location.
 :ALT_TITLE: Search Options
 :END:
 #+cindex: search option in file links
+#+cindex: search option in id links
 #+cindex: file links, searching
+#+cindex: id links, searching
 #+cindex: attachment links, searching
 
 File links can contain additional information to make Emacs jump to a
@@ -3772,8 +3800,8 @@ example, when the command ~org-store-link~ creates a link (see
 line as a search string that can be used to find this line back later
 when following the link with {{{kbd(C-c C-o)}}}.
 
-Note that all search options apply for Attachment links in the same
-way that they apply for File links.
+Note that all search options apply for Attachment and ID links in the
+same way that they apply for File links.
 
 Here is the syntax of the different ways to attach a search to a file
 link, together with explanations for each:
@@ -21355,7 +21383,7 @@ The following =ol-man.el= file implements it
 PATH should be a topic that can be thrown at the man command."
   (funcall org-man-command path))
 
-(defun org-man-store-link ()
+(defun org-man-store-link (&optional _interactive?)
   "Store a link to a man page."
   (when (memq major-mode '(Man-mode woman-mode))
     ;; This is a man page, we do make this link.
@@ -21415,13 +21443,15 @@ A review of =ol-man.el=:
 
    For example, ~org-man-store-link~ is responsible for storing a link
    when ~org-store-link~ (see [[*Handling Links]]) is called from a buffer
-   displaying a man page.  It first checks if the major mode is
-   appropriate.  If check fails, the function returns ~nil~, which
-   means it isn't responsible for creating a link to the current
-   buffer.  Otherwise the function makes a link string by combining
-   the =man:= prefix with the man topic.  It also provides a default
-   description.  The function ~org-insert-link~ can insert it back
-   into an Org buffer later on.
+   displaying a man page.  It is passed an argument ~interactive?~
+   which this function does not use, but other store functions use to
+   behave differently when a link is stored interactively by the user.
+   It first checks if the major mode is appropriate.  If check fails,
+   the function returns ~nil~, which means it isn't responsible for
+   creating a link to the current buffer.  Otherwise the function
+   makes a link string by combining the =man:= prefix with the man
+   topic.  It also provides a default description.  The function
+   ~org-insert-link~ can insert it back into an Org buffer later on.
 
 ** Adding Export Backends
 :PROPERTIES:
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ec01004f8..1115e3bb4 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -345,6 +345,14 @@ timestamp object.  Possible values: ~timerange~, ~daterange~, ~nil~.
 ~org-element-timestamp-interpreter~ takes into account this property
 and returns an appropriate timestamp string.
 
+**** =org-link= store functions are passed an ~interactive?~ argument
+
+The ~:store:~ functions set for link types using
+~org-link-set-parameters~ are now passed an ~interactive?~ argument,
+indicating whether ~org-store-link~ was called interactively.
+
+Existing store functions will continue to work.
+
 *** ~org-priority=show~ command no longer adjusts for scheduled/deadline
 
 In agenda views, ~org-priority=show~ command previously displayed the
@@ -423,6 +431,27 @@ The change is breaking when ~org-use-property-inheritance~ is set to ~t~.
 *** ~org-babel-lilypond-compile-lilyfile~ ignores optional second argument
 
 The =TEST= parameter is better served by Emacs debugging tools.
+
+*** ~org-id-store-link~ now adds search strings for precise link targets
+
+This new behaviour can be disabled generally by setting
+~org-id-link-use-context~ to ~nil~, or the setting can be toggled for
+a single call to ~org-store-link~ with a universal argument.
+
+When using this feature, IDs should not include =::=, which is used in
+links to indicate the start of the search string.  For backwards
+compability, existing IDs including =::= will still be matched (but
+cannot be used together with precise link targets).  An org-lint
+checker has been added to warn about this.
+
+*** ~org-store-link~ behaviour storing additional =CUSTOM_ID= links has changed
+
+As well as an =id:= link, ~org-store-link~ stores an additional "human
+readable" link using a node's =CUSTOM_ID= property, if available.
+This behaviour has been expanded to store an additional =CUSTOM_ID=
+link when storing any type of external link type in an Org file, not
+just =id:= links.
+
 ** New and changed options
 *** The default value of ~org-attach-store-link-p~ is now ~attached~
 
@@ -659,6 +688,35 @@ manner with ~run-python~.
 This allows to run functions after ~org-indent~ intializes a buffer to
 enrich its properties.
 
+*** New option ~org-id-link-consider-parent-id~ to allow =id:= links to parent headlines
+
+For =id:= links, when this option is enabled, ~org-store-link~ will
+look for ids from parent/ancestor headlines, if the current headline
+does not have an id.
+
+Combined with the new ability for =id:= links to use search strings
+for precise link targets (when =org-id-link-use-context= is =t=, which
+is the default), this allows linking to specific headlines without
+requiring every headline to have an id property, as long as the
+headline is unique within a subtree that does have an id property.
+
+For example, given this org file:
+
+#+begin_src org
+,* Parent
+:PROPERTIES:
+:ID: abc
+:END:
+,** Child 1
+,** Child 2
+#+end_src
+
+Storing a link with point at "Child 1" will produce a link
+=<id:abc::*Child 1>=, which precisely links to the "Child 1" headline
+even though it does not have its own ID.  By giving files top-level id
+properties, links to headlines in the file can also be made more
+robust by using the file id instead of the file path.
+
 ** New features
 *** =ob-plantuml.el=: Support tikz file format output
 
@@ -947,6 +1005,12 @@ A numeric value forces a heading at that level to be inserted.  For
 backwards compatibility, non-numeric non-nil values insert level 1
 headings as before.
 
+*** New optional argument for ~org-id-get~
+
+New optional argument =INHERIT= means inherited ID properties from
+parent entries are considered when getting an entry's ID (see
+~org-id-link-consider-parent-id~ option).
+
 ** Miscellaneous
 *** =org-crypt.el= now applies initial visibility settings to decrypted entries
 
diff --git a/lisp/ol-bbdb.el b/lisp/ol-bbdb.el
index be3924fc9..6ea060f70 100644
--- a/lisp/ol-bbdb.el
+++ b/lisp/ol-bbdb.el
@@ -226,7 +226,7 @@ date year)."
 
 ;;; Implementation
 
-(defun org-bbdb-store-link ()
+(defun org-bbdb-store-link (&optional _interactive?)
   "Store a link to a BBDB database entry."
   (when (eq major-mode 'bbdb-mode)
     ;; This is BBDB, we make this link!
diff --git a/lisp/ol-bibtex.el b/lisp/ol-bibtex.el
index c5a950e2d..38468f32f 100644
--- a/lisp/ol-bibtex.el
+++ b/lisp/ol-bibtex.el
@@ -507,7 +507,7 @@ ARG, when non-nil, is a universal prefix argument.  See
 `org-open-file' for details."
   (org-link-open-as-file path arg))
 
-(defun org-bibtex-store-link ()
+(defun org-bibtex-store-link (&optional _interactive?)
   "Store a link to a BibTeX entry."
   (when (eq major-mode 'bibtex-mode)
     (let* ((search (org-create-file-search-in-bibtex))
diff --git a/lisp/ol-docview.el b/lisp/ol-docview.el
index b31f1ce5e..0907ddee1 100644
--- a/lisp/ol-docview.el
+++ b/lisp/ol-docview.el
@@ -83,7 +83,7 @@
       (error "No such file: %s" path))
     (when page (doc-view-goto-page page))))
 
-(defun org-docview-store-link ()
+(defun org-docview-store-link (&optional _interactive?)
   "Store a link to a docview buffer."
   (when (eq major-mode 'doc-view-mode)
     ;; This buffer is in doc-view-mode
diff --git a/lisp/ol-eshell.el b/lisp/ol-eshell.el
index 2c7ec6bef..595dd0ee0 100644
--- a/lisp/ol-eshell.el
+++ b/lisp/ol-eshell.el
@@ -60,7 +60,7 @@ followed by a colon."
     (insert command)
     (eshell-send-input)))
 
-(defun org-eshell-store-link ()
+(defun org-eshell-store-link (&optional _interactive?)
   "Store eshell link.
 When opened, the link switches back to the current eshell buffer and
 the current working directory."
diff --git a/lisp/ol-eww.el b/lisp/ol-eww.el
index 40b820d2b..c13dbf339 100644
--- a/lisp/ol-eww.el
+++ b/lisp/ol-eww.el
@@ -62,7 +62,7 @@
   "Open URL with Eww in the current buffer."
   (eww url))
 
-(defun org-eww-store-link ()
+(defun org-eww-store-link (&optional _interactive?)
   "Store a link to the url of an EWW buffer."
   (when (eq major-mode 'eww-mode)
     (org-link-store-props
diff --git a/lisp/ol-gnus.el b/lisp/ol-gnus.el
index e105fdb2c..b9ee8683f 100644
--- a/lisp/ol-gnus.el
+++ b/lisp/ol-gnus.el
@@ -123,7 +123,7 @@ If `org-store-link' was called with a prefix arg the meaning of
 	      (url-encode-url message-id))
     (concat "gnus:" group "#" message-id)))
 
-(defun org-gnus-store-link ()
+(defun org-gnus-store-link (&optional _interactive?)
   "Store a link to a Gnus folder or message."
   (pcase major-mode
     (`gnus-group-mode
diff --git a/lisp/ol-info.el b/lisp/ol-info.el
index 0edf9a13f..6062cab34 100644
--- a/lisp/ol-info.el
+++ b/lisp/ol-info.el
@@ -50,7 +50,7 @@
                          :insert-description #'org-info-description-as-command)
 
 ;; Implementation
-(defun org-info-store-link ()
+(defun org-info-store-link (&optional _interactive?)
   "Store a link to an Info file and node."
   (when (eq major-mode 'Info-mode)
     (let ((link (concat "info:"
diff --git a/lisp/ol-irc.el b/lisp/ol-irc.el
index 78c4884b0..b263e52db 100644
--- a/lisp/ol-irc.el
+++ b/lisp/ol-irc.el
@@ -103,7 +103,7 @@ attributes that are found."
     parts))
 
 ;;;###autoload
-(defun org-irc-store-link ()
+(defun org-irc-store-link (&optional _interactive?)
   "Dispatch to the appropriate function to store a link to an IRC session."
   (cond
    ((eq major-mode 'erc-mode)
diff --git a/lisp/ol-man.el b/lisp/ol-man.el
index e3f13815e..42aacea81 100644
--- a/lisp/ol-man.el
+++ b/lisp/ol-man.el
@@ -82,7 +82,7 @@ matched strings in man buffer."
             (set-window-point window point)
             (set-window-start window point)))))))
 
-(defun org-man-store-link ()
+(defun org-man-store-link (&optional _interactive?)
   "Store a link to a README file."
   (when (memq major-mode '(Man-mode woman-mode))
     ;; This is a man page, we do make this link
diff --git a/lisp/ol-mhe.el b/lisp/ol-mhe.el
index 106cfedc9..a32481324 100644
--- a/lisp/ol-mhe.el
+++ b/lisp/ol-mhe.el
@@ -80,7 +80,7 @@ supported by MH-E."
 (org-link-set-parameters "mhe" :follow #'org-mhe-open :store #'org-mhe-store-link)
 
 ;; Implementation
-(defun org-mhe-store-link ()
+(defun org-mhe-store-link (&optional _interactive?)
   "Store a link to an MH-E folder or message."
   (when (or (eq major-mode 'mh-folder-mode)
 	    (eq major-mode 'mh-show-mode))
diff --git a/lisp/ol-rmail.el b/lisp/ol-rmail.el
index f6031ab52..f1f753b6f 100644
--- a/lisp/ol-rmail.el
+++ b/lisp/ol-rmail.el
@@ -51,7 +51,7 @@
 			 :store #'org-rmail-store-link)
 
 ;; Implementation
-(defun org-rmail-store-link ()
+(defun org-rmail-store-link (&optional _interactive?)
   "Store a link to an Rmail folder or message."
   (when (or (eq major-mode 'rmail-mode)
 	    (eq major-mode 'rmail-summary-mode))
diff --git a/lisp/ol.el b/lisp/ol.el
index cf59c8556..59b87eba7 100644
--- a/lisp/ol.el
+++ b/lisp/ol.el
@@ -63,7 +63,6 @@
 (declare-function org-find-property "org" (property &optional value))
 (declare-function org-get-heading "org" (&optional no-tags no-todo no-priority no-comment))
 (declare-function org-id-find-id-file "org-id" (id))
-(declare-function org-id-store-link "org-id" ())
 (declare-function org-insert-heading "org" (&optional arg invisible-ok top))
 (declare-function org-load-modules-maybe "org" (&optional force))
 (declare-function org-mark-ring-push "org" (&optional pos buffer))
@@ -815,6 +814,60 @@ spec."
   (org-with-point-at (car region)
     (not (org-in-regexp org-link-any-re))))
 
+(defun org-link--try-link-store-functions (interactive?)
+  "Try storing external links, prompting if more than one is possible.
+
+Each function returned by `org-store-link-functions' is called in
+turn.  If multiple functions return non-nil, prompt for which
+link should be stored.
+
+Argument INTERACTIVE? indicates whether `org-store-link' was
+called interactively and is passed to the link store functions.
+
+Return t when a link has been stored in `org-link-store-props'."
+  (let ((results-alist nil))
+    (dolist (f (org-store-link-functions))
+      (when (condition-case nil
+                (funcall f interactive?)
+              ;; FIXME: The store function used (< Org 9.7) to accept
+              ;; no arguments; provide backward compatibility support
+              ;; for them.
+              (wrong-number-of-arguments
+               (funcall f)))
+        ;; FIXME: return value is not link's plist, so we store the
+        ;; new value before it is modified.  It would be cleaner to
+        ;; ask store link functions to return the plist instead.
+        (push (cons f (copy-sequence org-store-link-plist))
+              results-alist)))
+    (pcase results-alist
+      (`nil nil)
+      (`((,_ . ,_)) t)	;single choice: nothing to do
+      (`((,name . ,_) . ,_)
+       ;; Reinstate link plist associated to the chosen
+       ;; function.
+       (apply #'org-link-store-props
+              (cdr (assoc-string
+                    (completing-read
+                     (format "Store link with (default %s): " name)
+                     (mapcar #'car results-alist)
+                     nil t nil nil (symbol-name name))
+                    results-alist)))
+       t))))
+
+(defun org-link--add-to-stored-links (link desc)
+  "Add LINK to `org-stored-links' with description DESC."
+  (cond
+   ((not (member (list link desc) org-stored-links))
+    (push (list link desc) org-stored-links)
+    (message "Stored: %s" (or desc link)))
+   ((equal (list link desc) (car org-stored-links))
+    (message "This link has already been stored"))
+   (t
+    (setq org-stored-links
+          (delete (list link desc) org-stored-links))
+    (push (list link desc) org-stored-links)
+    (message "Link moved to front: %s" (or desc link)))))
+
 \f
 ;;; Public API
 
@@ -1280,7 +1333,11 @@ respects buffer narrowing."
 	   (yes-or-no-p "No match - create this as a new heading? "))
       (goto-char (point-max))
       (unless (bolp) (newline))
-      (org-insert-heading nil t t)
+      ;; Find appropriate level for new heading
+      (let ((level (save-excursion
+                     (goto-char (point-min))
+                     (+ 1 (or (org-current-level) 0)))))
+        (org-insert-heading nil t level))
       (insert s "\n")
       (forward-line -1))
      ;; Only headlines are looked after.  No need to process
@@ -1332,6 +1389,66 @@ priority cookie or tag."
 	  (org-link--normalize-string
 	   (or string (org-get-heading t t t t)))))
 
+(defun org-link-precise-link-target (&optional relative-to)
+  "Determine search string and description for storing a link.
+
+If a search string (see 'org-link-search') is found, return cons
+cell (SEARCH-STRING . DESC).  Otherwise, return nil.
+
+If there is an active region, the contents (or a part of it, see
+`org-link-context-for-files') is used as the search string.
+
+In Org buffers, if point is at a named element (such as a source
+block), the name is used for the search string.  If at a heading,
+its CUSTOM_ID is used to form a search string of the form
+\"#id\", if present, otherwise the current heading text is used
+in the form \"*Heading\".
+
+If none of those finds a suitable search string, the current line
+is used as the search string.
+
+The description DESC is nil (meaning the user will be prompted
+for a description when inserting the link) for search strings
+based on a region or the current line.  For other cases, DESC is
+a cleaned-up version of the name or heading at point.
+
+Optional argument RELATIVE-TO specifies the buffer position where
+the search will start from.  If the search target that would be
+returned is a heading before or at this location, return nil to
+avoid unnecessary search strings (for example, when using search
+strings to find targets within org-id links)."
+  (let ((result
+         (cond
+          ((derived-mode-p 'org-mode)
+           (let* ((element (org-element-at-point))
+                  (name (org-element-property :name element))
+                  (heading (org-element-lineage element 'headline t))
+                  (custom-id (org-entry-get nil "CUSTOM_ID")))
+             (cond
+              ((let ((region (org-link--context-from-region)))
+                 (and region (cons (org-link--normalize-string region t) nil))))
+              (name
+               (cons name name))
+              ((org-before-first-heading-p)
+               (cons (org-link--normalize-string (org-current-line-string) t) nil))
+              ((and heading
+                    (> (org-element-begin heading) (or relative-to 0)))
+               (cons (if custom-id (concat "#" custom-id)
+                       (org-link-heading-search-string))
+                     (org-link--normalize-string
+                      (org-get-heading t t t t)))))))
+
+          ;; Not in an org-mode buffer
+          (t
+           (cons (org-link--normalize-string
+                  (or (org-link--context-from-region) (org-current-line-string))
+                  t)
+                 nil)))))
+
+    ;; Only use search option if there is some text.
+    (when (org-string-nw-p (car result))
+      result)))
+
 (defun org-link-open-as-file (path in-emacs)
   "Pretend PATH is a file name and open it.
 
@@ -1404,7 +1521,7 @@ PATH is a symbol name, as a string."
     ((and (pred boundp) variable) (describe-variable variable))
     (name (user-error "Unknown function or variable: %s" name))))
 
-(defun org-link--store-help ()
+(defun org-link--store-help (&optional _interactive?)
   "Store \"help\" type link."
   (when (eq major-mode 'help-mode)
     (let ((symbol
@@ -1539,7 +1656,11 @@ prefix ARG forces storing a link for each line in the
 active region.
 
 Assume the function is called interactively if INTERACTIVE? is
-non-nil."
+non-nil.
+
+In Org buffers, an additional \"human-readable\" simple file link
+is stored as an alternative to persistent org-id or other links,
+if the current heading has a CUSTOM_ID property."
   (interactive "P\np")
   (org-load-modules-maybe)
   (if (and (equal arg '(64)) (org-region-active-p))
@@ -1554,36 +1675,19 @@ non-nil."
 	    (move-beginning-of-line 2)
 	    (set-mark (point)))))
     (setq org-store-link-plist nil)
-    (let (link cpltxt desc search custom-id agenda-link) ;; description
+    ;; Negate `org-context-in-file-links' when given a single universal arg.
+    (let ((org-link-context-for-files (org-xor org-link-context-for-files
+                                               (equal arg '(4))))
+          link cpltxt desc search agenda-link) ;; description
       (cond
        ;; Store a link using an external link type, if any function is
-       ;; available. If more than one can generate a link from current
-       ;; location, ask which one to use.
+       ;; available, unless external link types are skipped for this
+       ;; call using two universal args.  If more than one function
+       ;; can generate a link from current location, ask the user
+       ;; which one to use.
        ((and (not (equal arg '(16)))
-	     (let ((results-alist nil))
-	       (dolist (f (org-store-link-functions))
-		 (when (funcall f)
-		   ;; XXX: return value is not link's plist, so we
-		   ;; store the new value before it is modified.  It
-		   ;; would be cleaner to ask store link functions to
-		   ;; return the plist instead.
-		   (push (cons f (copy-sequence org-store-link-plist))
-			 results-alist)))
-	       (pcase results-alist
-		 (`nil nil)
-		 (`((,_ . ,_)) t)	;single choice: nothing to do
-		 (`((,name . ,_) . ,_)
-		  ;; Reinstate link plist associated to the chosen
-		  ;; function.
-		  (apply #'org-link-store-props
-			 (cdr (assoc-string
-			       (completing-read
-                                (format "Store link with (default %s): " name)
-                                (mapcar #'car results-alist)
-                                nil t nil nil (symbol-name name))
-			       results-alist)))
-		  t))))
-	(setq link (plist-get org-store-link-plist :link))
+             (org-link--try-link-store-functions interactive?))
+        (setq link (plist-get org-store-link-plist :link))
         ;; If store function actually set `:description' property, use
         ;; it, even if it is nil.  Otherwise, fallback to nil (ask user).
 	(setq desc (plist-get org-store-link-plist :description)))
@@ -1634,6 +1738,7 @@ non-nil."
 	    (org-with-point-at m
 	      (setq agenda-link (org-store-link nil interactive?))))))
 
+       ;; Calendar mode
        ((eq major-mode 'calendar-mode)
 	(let ((cd (calendar-cursor-to-date)))
 	  (setq link
@@ -1642,6 +1747,7 @@ non-nil."
 		 (org-encode-time 0 0 0 (nth 1 cd) (nth 0 cd) (nth 2 cd))))
 	  (org-link-store-props :type "calendar" :date cd)))
 
+       ;; Image mode
        ((eq major-mode 'image-mode)
 	(setq cpltxt (concat "file:"
 			     (abbreviate-file-name buffer-file-name))
@@ -1659,15 +1765,22 @@ non-nil."
 	  (setq cpltxt (concat "file:" file)
 		link cpltxt)))
 
+       ;; Try `org-create-file-search-functions`.  If any are
+       ;; successful, create a file link to the current buffer with
+       ;; the provided search string.  (sets `link` and `cpltxt` to
+       ;; the same thing; it looks like the intention originally was
+       ;; that cpltxt was a description, which might have been set by
+       ;; the search-function (removed in switch to lexical binding)).
        ((setq search (run-hook-with-args-until-success
 		      'org-create-file-search-functions))
 	(setq link (concat "file:" (abbreviate-file-name buffer-file-name)
 			   "::" search))
 	(setq cpltxt (or link))) ;; description
 
+       ;; Main logic for storing built-in link types in org-mode
+       ;; buffers
        ((and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode))
 	(org-with-limited-levels
-	 (setq custom-id (org-entry-get nil "CUSTOM_ID"))
 	 (cond
 	  ;; Store a link using the target at point
 	  ((org-in-regexp "[^<]<<\\([^<>]+\\)>>[^>]" 1)
@@ -1681,74 +1794,34 @@ non-nil."
                  ;; links.  Maybe the case of identical target and
                  ;; description should be handled by `org-insert-link'.
                  cpltxt nil
-                 desc nil
-                 ;; Do not append #CUSTOM_ID link below.
-                 custom-id nil))
-	  ((and (featurep 'org-id)
-		(or (eq org-id-link-to-org-use-id t)
-		    (and interactive?
-			 (or (eq org-id-link-to-org-use-id 'create-if-interactive)
-			     (and (eq org-id-link-to-org-use-id
-				      'create-if-interactive-and-no-custom-id)
-				  (not custom-id))))
-		    (and org-id-link-to-org-use-id (org-entry-get nil "ID"))))
-	   ;; Store a link using the ID at point
-	   (setq link (condition-case nil
-			  (prog1 (org-id-store-link)
-			    (setq desc (plist-get org-store-link-plist :description)))
-			(error
-			 ;; Probably before first headline, link only to file
-			 (concat "file:"
-				 (abbreviate-file-name
-				  (buffer-file-name (buffer-base-buffer))))))))
-	  (t
+                 desc nil))
+          (t
 	   ;; Just link to current headline.
 	   (setq cpltxt (concat "file:"
 				(abbreviate-file-name
 				 (buffer-file-name (buffer-base-buffer)))))
-	   ;; Add a context search string.
-	   (when (org-xor org-link-context-for-files (equal arg '(4)))
-	     (let* ((element (org-element-at-point))
-		    (name (org-element-property :name element))
-		    (context
-		     (cond
-		      ((let ((region (org-link--context-from-region)))
-			 (and region (org-link--normalize-string region t))))
-		      (name)
-		      ((org-before-first-heading-p)
-		       (org-link--normalize-string (org-current-line-string) t))
-		      (t (org-link-heading-search-string)))))
-	       (when (org-string-nw-p context)
-		 (setq cpltxt (format "%s::%s" cpltxt context))
-		 (setq desc
-		       (or name
-			   ;; Although description is not a search
-			   ;; string, use `org-link--normalize-string'
-			   ;; to prettify it (contiguous white spaces)
-			   ;; and remove volatile contents (statistics
-			   ;; cookies).
-			   (and (not (org-before-first-heading-p))
-				(org-link--normalize-string
-				 (org-get-heading t t t t)))
-			   "NONE")))))
-	   (setq link cpltxt)))))
-
+           (when org-link-context-for-files
+             (pcase (org-link-precise-link-target)
+               (`nil nil)
+               (`(,search-string . ,search-desc)
+                (setq cpltxt (format "%s::%s" cpltxt search-string))
+                (setq desc search-desc))))
+           (setq link cpltxt)))))
+
+       ;; Buffer linked to file, but not an org-mode buffer.
        ((buffer-file-name (buffer-base-buffer))
 	;; Just link to this file here.
 	(setq cpltxt (concat "file:"
 			     (abbreviate-file-name
 			      (buffer-file-name (buffer-base-buffer)))))
 	;; Add a context search string.
-	(when (org-xor org-link-context-for-files (equal arg '(4)))
-	  (let ((context (org-link--normalize-string
-			  (or (org-link--context-from-region)
-			      (org-current-line-string))
-			  t)))
-	    ;; Only use search option if there is some text.
-	    (when (org-string-nw-p context)
-	      (setq cpltxt (format "%s::%s" cpltxt context))
-	      (setq desc "NONE"))))
-	(setq link cpltxt))
+        (when org-link-context-for-files
+          (pcase (org-link-precise-link-target)
+            (`nil nil)
+            (`(,search-string . ,search-desc)
+             (setq cpltxt (format "%s::%s" cpltxt search-string))
+             (setq desc search-desc))))
+        (setq link cpltxt))
 
        (interactive?
 	(user-error "No method for storing a link from this buffer"))
@@ -1764,24 +1837,19 @@ non-nil."
       ;; Store and return the link
       (if (not (and interactive? link))
 	  (or agenda-link (and link (org-link-make-string link desc)))
-        (dotimes (_ (if custom-id 2 1)) ; Store 2 links when CUSTOM-ID is non-nil.
-          (cond
-           ((not (member (list link desc) org-stored-links))
-            (push (list link desc) org-stored-links)
-	    (message "Stored: %s" (or desc link)))
-           ((equal (list link desc) (car org-stored-links))
-            (message "This link has already been stored"))
-           (t
-            (setq org-stored-links
-                  (delete (list link desc) org-stored-links))
-            (push (list link desc) org-stored-links)
-            (message "Link moved to front: %s" (or desc link))))
-	  (when custom-id
-	    (setq link (concat "file:"
-			       (abbreviate-file-name
-			        (buffer-file-name (buffer-base-buffer)))
-			       "::#" custom-id))))
-	(car org-stored-links)))))
+        (org-link--add-to-stored-links link desc)
+        ;; In org buffers, store an additional "human-readable" link
+        ;; using custom id, if available.
+        (when (and (buffer-file-name (buffer-base-buffer))
+                   (derived-mode-p 'org-mode)
+                   (org-entry-get nil "CUSTOM_ID"))
+          (setq link (concat "file:"
+                             (abbreviate-file-name
+                              (buffer-file-name (buffer-base-buffer)))
+                             "::#" (org-entry-get nil "CUSTOM_ID")))
+          (unless (equal (list link desc) (car org-stored-links))
+            (org-link--add-to-stored-links link desc)))
+        (car org-stored-links)))))
 
 ;;;###autoload
 (defun org-insert-link (&optional complete-file link-location description)
diff --git a/lisp/org-id.el b/lisp/org-id.el
index 8647a57cc..d70bec3c1 100644
--- a/lisp/org-id.el
+++ b/lisp/org-id.el
@@ -129,6 +129,46 @@ nil   Never use an ID to make a link, instead link using a text search for
 	  (const :tag "Only use existing" use-existing)
 	  (const :tag "Do not use ID to create link" nil)))
 
+(defcustom org-id-link-consider-parent-id nil
+  "Non-nil means storing a link to an Org entry considers inherited IDs.
+
+When this option is non-nil and `org-id-link-use-context' is
+enabled, ID properties inherited from parent entries will be
+considered when storing an ID link.  If no ID is found in this
+way, a new one may be created as normal (see
+`org-id-link-to-org-use-id').
+
+For example, given this org file:
+
+* Parent
+:PROPERTIES:
+:ID: abc
+:END:
+** Child 1
+** Child 2
+
+With `org-id-link-consider-parent-id' and
+`org-id-link-use-context' both enabled, storing a link with point
+at \"Child 1\" will produce a link \"<id:abc::*Child 1>\".  This
+allows linking to uniquely-named sub-entries within a parent
+entry with an ID, without requiring every sub-entry to have its
+own ID."
+  :group 'org-link-store
+  :group 'org-id
+  :package-version '(Org . "9.7")
+  :type 'boolean)
+
+(defcustom org-id-link-use-context t
+  "Non-nil means enables search string context in org-id links.
+
+Search strings are added by `org-id-store-link' when both the
+general option `org-link-context-for-files' and the org-id option
+`org-id-link-use-context' are non-nil."
+  :group 'org-link-store
+  :group 'org-id
+  :package-version '(Org . "9.7")
+  :type 'boolean)
+
 (defcustom org-id-uuid-program "uuidgen"
   "The uuidgen program."
   :group 'org-id
@@ -280,15 +320,21 @@ This is useful when working with contents in a temporary buffer
 that will be copied back to the original.")
 
 ;;;###autoload
-(defun org-id-get (&optional epom create prefix)
-  "Get the ID property of the entry at EPOM.
-EPOM is an element, marker, or buffer position.
-If EPOM is nil, refer to the entry at point.
-If the entry does not have an ID, the function returns nil.
-However, when CREATE is non-nil, create an ID if none is present already.
-PREFIX will be passed through to `org-id-new'.
-In any case, the ID of the entry is returned."
-  (let ((id (org-entry-get epom "ID")))
+(defun org-id-get (&optional epom create prefix inherit)
+  "Get the ID of the entry at EPOM.
+
+EPOM is an element, marker, or buffer position.  If EPOM is nil,
+refer to the entry at point.
+
+If INHERIT is non-nil, ID properties inherited from parent
+entries are considered.  Otherwise, only ID properties on the
+entry itself are considered.
+
+When CREATE is nil, return the ID of the entry if found,
+otherwise nil.  When CREATE is non-nil, create an ID if none has
+been found, and return the new ID.  PREFIX will be passed through
+to `org-id-new'."
+  (let ((id (org-entry-get epom "ID" (and inherit t))))
     (cond
      ((and id (stringp id) (string-match "\\S-" id))
       id)
@@ -703,18 +749,45 @@ optional argument MARKERP, return the position as a new marker."
 ;; Calling the following function is hard-coded into `org-store-link',
 ;; so we do have to add it to `org-store-link-functions'.
 
+(defun org-id--get-id-to-store-link (&optional create)
+  "Get or create the relevant ID for storing a link.
+
+Optional argument CREATE is passed to `org-id-get'.
+
+Inherited IDs are only considered when
+`org-id-link-consider-parent-id', `org-id-link-use-context' and
+`org-link-context-for-files' are all enabled, since inherited IDs
+are confusing without the additional search string context."
+  (let* ((inherit-id (and org-id-link-consider-parent-id
+                          org-id-link-use-context
+                          org-link-context-for-files)))
+    (org-id-get nil create nil inherit-id)))
+
 ;;;###autoload
 (defun org-id-store-link ()
   "Store a link to the current entry, using its ID.
 
-If before first heading store first title-keyword as description
-or filename if no title."
+The link description is based on the heading, or if before the
+first heading, the title keyword if available, or else the
+filename.
+
+When `org-link-context-for-files' and `org-id-link-use-context'
+are non-nil, add a search string to the link.  The link
+description is then based on the search string target.
+
+When in addition `org-id-link-consider-parent-id' is non-nil, the
+ID can be inherited from a parent entry, with the search string
+used to still link to the current location."
   (interactive)
-  (when (and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode))
-    (let* ((link (concat "id:" (org-id-get-create)))
+  (when (and (buffer-file-name (buffer-base-buffer))
+             (derived-mode-p 'org-mode))
+    (let* ((link (concat "id:" (org-id--get-id-to-store-link 'create)))
+           (id-location (or (and org-entry-property-inherited-from
+                                 (marker-position org-entry-property-inherited-from))
+                            (save-excursion (org-back-to-heading-or-point-min) (point))))
 	   (case-fold-search nil)
 	   (desc (save-excursion
-		   (org-back-to-heading-or-point-min t)
+                   (goto-char id-location)
                    (cond ((org-before-first-heading-p)
                           (let ((keywords (org-collect-keywords '("TITLE"))))
                             (if keywords
@@ -726,14 +799,59 @@ or filename if no title."
 			      (match-string 4)
 			    (match-string 0)))
                          (t link)))))
+      (when (and org-link-context-for-files org-id-link-use-context)
+        (pcase (org-link-precise-link-target id-location)
+          (`nil nil)
+          (`(,search-string . ,search-desc)
+           (setq link (concat link "::" search-string))
+           (setq desc search-desc))))
       (org-link-store-props :link link :description desc :type "id")
       link)))
 
-(defun org-id-open (id _)
-  "Go to the entry with id ID."
-  (org-mark-ring-push)
-  (let ((m (org-id-find id 'marker))
-	cmd)
+;;;###autoload
+(defun org-id-store-link-maybe (&optional interactive?)
+  "Store a link to the current entry using its ID if enabled.
+
+The value of `org-id-link-to-org-use-id' determines whether an ID
+link should be stored, using `org-id-store-link'.
+
+Assume the function is called interactively if INTERACTIVE? is
+non-nil."
+  (when (and (buffer-file-name (buffer-base-buffer))
+             (derived-mode-p 'org-mode)
+             (or (eq org-id-link-to-org-use-id t)
+                 (and interactive?
+                      (or (eq org-id-link-to-org-use-id 'create-if-interactive)
+                          (and (eq org-id-link-to-org-use-id
+                                   'create-if-interactive-and-no-custom-id)
+                               (not (org-entry-get nil "CUSTOM_ID")))))
+                 ;; 'use-existing
+                 (and org-id-link-to-org-use-id
+                      (org-id--get-id-to-store-link))))
+    (org-id-store-link)))
+
+(defun org-id-open (link _)
+  "Go to the entry indicated by id link LINK.
+
+The link can include a search string after \"::\", which is
+passed to `org-link-search'.
+
+For backwards compatibility with IDs that contain \"::\", if no
+match is found for the ID, the full link string including \"::\"
+will be tried as an ID."
+  (let* ((option (and (string-match "::\\(.*\\)\\'" link)
+		      (match-string 1 link)))
+	 (id (if (not option) link
+               (substring link 0 (match-beginning 0))))
+         m cmd)
+    (org-mark-ring-push)
+    (setq m (org-id-find id 'marker))
+    (when (and (not m) option)
+      ;; Backwards compatibility: if id is not found, try treating
+      ;; whole link as an id.
+      (setq m (org-id-find link 'marker))
+      (when m
+        (setq option nil)))
     (unless m
       (error "Cannot find entry with ID \"%s\"" id))
     ;; Use a buffer-switching command in analogy to finding files
@@ -750,9 +868,16 @@ or filename if no title."
 	(funcall cmd (marker-buffer m)))
     (goto-char m)
     (move-marker m nil)
+    (when option
+      (save-restriction
+        (unless (org-before-first-heading-p)
+          (org-narrow-to-subtree))
+        (org-link-search option)))
     (org-fold-show-context)))
 
-(org-link-set-parameters "id" :follow #'org-id-open)
+(org-link-set-parameters "id"
+  :follow #'org-id-open
+  :store #'org-id-store-link-maybe)
 
 (provide 'org-id)
 
diff --git a/lisp/org-lint.el b/lisp/org-lint.el
index 4d2a55d15..b23afcca3 100644
--- a/lisp/org-lint.el
+++ b/lisp/org-lint.el
@@ -65,6 +65,7 @@
 ;; - special properties in properties drawers,
 ;; - obsolete syntax for properties drawers,
 ;; - invalid duration in EFFORT property,
+;; - invalid ID property with a double colon,
 ;; - missing definition for footnote references,
 ;; - missing reference for footnote definitions,
 ;; - non-footnote definitions in footnote section,
@@ -686,6 +687,16 @@ Use :header-args: instead"
 	       (list (org-element-begin p)
 		     (format "Invalid effort duration format: %S" value))))))))
 
+(defun org-lint-invalid-id-property (ast)
+  (org-element-map ast 'node-property
+    (lambda (p)
+      (when (equal "ID" (org-element-property :key p))
+	(let ((value (org-element-property :value p)))
+	  (and (org-string-nw-p value)
+               (string-match-p "::" value)
+	       (list (org-element-begin p)
+		     (format "IDs should not include \"::\": %S" value))))))))
+
 (defun org-lint-link-to-local-file (ast)
   (org-element-map ast 'link
     (lambda (l)
@@ -1684,6 +1695,11 @@ AST is the buffer parse tree."
   #'org-lint-invalid-effort-property
   :categories '(properties))
 
+(org-lint-add-checker 'invalid-id-property
+  "Report search string delimiter \"::\" in ID property"
+  #'org-lint-invalid-id-property
+  :categories '(properties))
+
 (org-lint-add-checker 'undefined-footnote-reference
   "Report missing definition for footnote references"
   #'org-lint-undefined-footnote-reference
diff --git a/testing/lisp/test-ol.el b/testing/lisp/test-ol.el
index e0cec0854..fa8d15c2b 100644
--- a/testing/lisp/test-ol.el
+++ b/testing/lisp/test-ol.el
@@ -381,6 +381,132 @@ See https://github.com/yantar92/org/issues/4."
 	 (equal (format "[[file:%s::*foo bar][foo bar]]" file file)
 		(org-store-link nil)))))))
 
+(ert-deftest test-org-link/precise-link-target ()
+  "Test `org-link-precise-link-target` specifications."
+  (should
+   (equal '("*H1" . "H1")
+          (org-test-with-temp-text "* H1<point>\n* H2\n"
+            (org-link-precise-link-target))))
+  (should
+   (equal '("foo" . "foo")
+          (org-test-with-temp-text "* H1\n#+name: foo<point>\n#+begin_example\nhi\n#+end_example\n"
+            (org-link-precise-link-target))))
+  (should
+   (equal '("Text" . nil)
+          (org-test-with-temp-text "\nText<point>\n* H1\n"
+            (org-link-precise-link-target))))
+  (should
+   (equal nil
+          (org-test-with-temp-text "\n<point>\n* H1\n"
+            (org-link-precise-link-target))))
+  ;; relative to a heading
+  (should
+   (equal nil
+          (org-test-with-temp-text "* H1<point>\n* H2\n"
+            (org-link-precise-link-target 1))))
+  (should
+   (equal '("*H2" . "H2")
+          (org-test-with-temp-text "* H1\n* H2<point>\n"
+            (org-link-precise-link-target 1))))
+  (should
+   (equal nil
+          (org-test-with-temp-text "* H1\n* H2<point>\n"
+            (org-link-precise-link-target 6))))
+  )
+
+(defmacro test-ol-stored-link-with-text (text &rest body)
+  "Return :link and :description from link stored in body."
+  (declare (indent 1))
+  `(let (org-store-link-plist)
+     (org-test-with-temp-text-in-file ,text
+       ,@body
+       (list (plist-get org-store-link-plist :link)
+             (plist-get org-store-link-plist :description)))))
+
+(ert-deftest test-org-link/id-store-link ()
+  "Test `org-id-store-link' specifications."
+  (let ((org-id-link-to-org-use-id nil))
+    (should
+     (equal '(nil nil)
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:ID: abc\n:END:\n"
+              (org-id-store-link-maybe t)))))
+  ;; On a headline, link to that headline's ID.  Use heading as the
+  ;; description of the link.
+  (let ((org-id-link-to-org-use-id t))
+    (should
+     (equal '("id:abc" "H1")
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:ID: abc\n:END:\n"
+              (org-id-store-link-maybe t)))))
+  ;; Remove TODO keywords etc from description of the link.
+  (let ((org-id-link-to-org-use-id t))
+    (should
+     (equal '("id:abc" "H1")
+            (test-ol-stored-link-with-text "* TODO [#A] H1 :tag:\n:PROPERTIES:\n:ID: abc\n:END:\n"
+              (org-id-store-link-maybe t)))))
+  ;; create-if-interactive
+  (let ((org-id-link-to-org-use-id 'create-if-interactive))
+    (should
+     (equal '("id:abc" "H1")
+            (cl-letf (((symbol-function 'org-id-new)
+                       (lambda (&rest _rest) "abc")))
+              (test-ol-stored-link-with-text "* H1\n"
+                (org-id-store-link-maybe t)))))
+    (should
+     (equal '(nil nil)
+            (test-ol-stored-link-with-text "* H1\n"
+              (org-id-store-link-maybe nil)))))
+  ;; create-if-interactive-and-no-custom-id
+  (let ((org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))
+    (should
+     (equal '("id:abc" "H1")
+            (cl-letf (((symbol-function 'org-id-new)
+                       (lambda (&rest _rest) "abc")))
+              (test-ol-stored-link-with-text "* H1\n"
+                (org-id-store-link-maybe t)))))
+    (should
+     (equal '(nil nil)
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:CUSTOM_ID: xyz\n:END:\n"
+              (org-id-store-link-maybe t))))
+    (should
+     (equal '(nil nil)
+            (test-ol-stored-link-with-text "* H1\n"
+              (org-id-store-link-maybe nil))))))
+
+(ert-deftest test-org-link/id-store-link-using-parent ()
+  "Test `org-id-store-link' specifications with `org-id-link-consider-parent-id` set."
+  ;; when using context to still find specific heading
+  (let ((org-id-link-consider-parent-id t)
+        (org-id-link-use-context t))
+    (should
+     (equal '("id:abc::*H2" "H2")
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:ID: abc\n:END:\n** H2\n<point>"
+              (org-id-store-link))))
+    (should
+     (equal '("id:abc::name" "name")
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:ID: abc\n:END:\n\n#+name: name\n<point>#+begin_example\nhi\n#+end_example\n"
+              (org-id-store-link))))
+    (should
+     (equal '("id:abc" "H1")
+            (test-ol-stored-link-with-text "* H1<point>\n:PROPERTIES:\n:ID: abc\n:END:\n** H2\n"
+              (org-id-store-link)))))
+  ;; when not using context, description should be the parent/file
+  (let ((org-id-link-consider-parent-id t)
+        (org-id-link-use-context nil))
+    (should
+     (equal '("id:abc" "H1")
+            (test-ol-stored-link-with-text "* H1\n:PROPERTIES:\n:ID: abc\n:END:\n** H2\n<point>"
+              (org-id-store-link))))
+    (should
+     (let ((result (test-ol-stored-link-with-text ":PROPERTIES:\n:ID: top\n:END:\n:* H1\n<point>"
+                     (org-id-store-link))))
+       (equal "id:top" (car result))
+       ;; strip random buffer file name
+       (equal "org-test" (substring (cadr result) 0 8))))
+    (should
+     (equal '("id:top" "title")
+            (test-ol-stored-link-with-text ":PROPERTIES:\n:ID: top\n:END:\n#+TITLE: title\n\n:* H1\n<point>"
+              (org-id-store-link))))))
+
 \f
 ;;; Radio Targets
 
-- 
2.39.2 (Apple Git-143)


  parent reply	other threads:[~2024-01-28 22:48 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-07-24 11:40 [PATCH] org-id: allow using parent's existing id in links to headlines Rick Lupton
2023-07-25  7:43 ` Ihor Radchenko
2023-07-25 15:16   ` Max Nikulin
2023-07-26  8:10     ` Ihor Radchenko
2023-07-27  0:16       ` Samuel Wales
2023-07-27  7:42         ` IDs below headline level (for paragraphs, lists, etc) (was: [PATCH] org-id: allow using parent's existing id in links to headlines) Ihor Radchenko
2023-07-28 20:00           ` Rick Lupton
2023-07-28 19:56       ` [PATCH] org-id: allow using parent's existing id in links to headlines Rick Lupton
2023-07-29  8:33         ` Ihor Radchenko
2023-11-09 20:56   ` Rick Lupton
2023-11-10 10:03     ` Ihor Radchenko
2023-11-19 15:21       ` Rick Lupton
2023-12-04 13:23         ` Rick Lupton
2023-12-10 13:35         ` Ihor Radchenko
2023-12-14 20:42           ` Rick Lupton
2023-12-15 12:55             ` Ihor Radchenko
2023-12-15 16:16               ` Rick Lupton
2023-12-16 14:20                 ` Ihor Radchenko
2023-12-17 19:07                   ` [PATCH v2] " Rick Lupton
2023-12-18 12:27                     ` Ihor Radchenko
2024-01-02 16:13                       ` Rick Lupton
2024-01-03 14:17                         ` Ihor Radchenko
2024-01-28 22:47                       ` Rick Lupton [this message]
2024-01-29  0:20                         ` Samuel Wales
2024-01-29 13:06                           ` Ihor Radchenko
2024-01-30  0:03                             ` Samuel Wales
2024-02-03 15:08                               ` Ihor Radchenko
2024-11-13  3:23                                 ` Samuel Wales
2024-01-29 13:00                         ` Ihor Radchenko
2024-01-31 18:11                           ` Rick Lupton
2024-02-01 12:13                             ` Ihor Radchenko
2024-02-01 16:37                               ` Rick Lupton
2024-02-03 13:10                             ` Ihor Radchenko
2024-02-08  8:24                               ` [PATCH] lisp/ol.el: Improve docstring Rick Lupton
2024-02-08 14:52                                 ` Ihor Radchenko
2024-02-08  8:46                               ` [PATCH v2] org-id: allow using parent's existing id in links to headlines Rick Lupton
2024-02-08 13:02                                 ` Ihor Radchenko
2024-02-08 22:30                                   ` Rick Lupton
2024-02-09 12:09                                     ` Ihor Radchenko
2024-02-09 12:47                                       ` Rick Lupton
2024-02-09 12:57                                         ` Ihor Radchenko
2024-02-24 10:48                                           ` Bastien Guerry
2024-02-24 13:02                                             ` Ihor Radchenko
2024-02-24 15:57                                               ` Rick Lupton
2024-03-05 14:05                                               ` Stefan
2024-03-05 14:51                                                 ` Ihor Radchenko
2023-11-04 23:01 ` [PATCH] " Rick Lupton
2023-11-05 12:31   ` Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1b50d1a4-8573-4dcc-9427-8970f67e632a@app.fastmail.com \
    --to=mail@ricklupton.name \
    --cc=emacs-orgmode@gnu.org \
    --cc=yantar92@posteo.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.