From: Ihor Radchenko <yantar92@gmail.com>
To: Michael Brand <michael.ch.brand@gmail.com>,
org-mode-email <Emacs-orgmode@gnu.org>
Cc: Eric S Fraga <esflists@gmail.com>, ST <smntov@gmail.com>,
Christian Moe <mail@christianmoe.com>,
Nicolas Goaziou <mail@nicolasgoaziou.fr>,
John Kitchin <jkitchin@andrew.cmu.edu>
Subject: Re: Structured links to headings with endless depth
Date: Tue, 07 May 2019 11:26:00 +0800 [thread overview]
Message-ID: <874l67eyif.fsf@yantar92-laptop.i-did-not-set--mail-host-address--so-tickle-me> (raw)
In-Reply-To: <CALn3zoiZZUdtFgT4aOaR-zba01W17RQYC=7=Wh=gPM8pZpRiXw@mail.gmail.com>
Dear Michael,
> ... I want self-explaining links with the already existing and
> complete heading structure and don't want to add any ID, CUSTOM_ID or
> <<target>>. See this example:
I am wondering why you are strictly against ID properties.
The IDs can be set automatically. The property drawer can be hidden (see
https://stackoverflow.com/questions/17478260/completely-hide-the-properties-drawer-in-org-mode)
and will not clutter you org file.
An arbitrary id link can be self-explaining if you add a proper link
description: [[id:your_id][composer_1/work_1/movement_1]]. Moreover it
is not fragile against refiling or duplicate entries.
Best,
Ihor
Michael Brand <michael.ch.brand@gmail.com> writes:
> Hi all
>
> On Wed, Mar 14, 2018 at 7:58 AM Michael Brand
> <michael.ch.brand@gmail.com> wrote:
>
>> , (arbitrarily more levels upwards)
>> , * [...]
>> , * <composer>
>> , * <work>
>> , * TODO <movement>
>> , * <interpreter> :5:
>> , - The tag 5 is my rating of this audio recording.
>> , - The audio recording is stored under the file path
>> , [...]/<composer>/<work>/<movement>/<interpreter>/<sth>.mp3
>> ,
>> , * TODO [...]
>> , - The theme is very similar to this prelude
>> , [[/:<composer_1>/<work_1>/<movement_1>]].
>> , * [...]
>> , - [...] like in this piano concert
>> , [[/:<composer_2>/<work_2>]].
>
> Despite all the valuable recommendations in this thread I implemented
> something simple for my very specific use case of a music database
> where I want self-explaining links with the already existing and
> complete heading structure and don't want to add any ID, CUSTOM_ID or
> <<target>>. See this example:
>
> #+begin_src org
> ,#+STARTUP: oddeven showeverything
>
> Specs for outline path of links to a heading, any combinations allowed
> including none:
> - "/" delimits headings of adjacent levels.
> - A leading "/" requires matching the top level heading.
> - "//" delimits heading levels with 0 to n discarded heading levels
> between them.
>
> Demo examples:
> - Goes to tag 1: [[*Chopin/Prelude]]
> - Goes to tag 2: [[*/Prelude]]
> - Goes to tag 3: [[*d/c//b/a]]
> - Goes to tag 4: [[*d/c/b/a]]
> ,* Foo
> ,** Bach
> ,*** Prelude
> ,** Chopin
> ,*** Prelude :1:
> ,* Prelude :2:
> ,* d
> ,** c
> ,*** Bar
> ,**** Baz
> ,***** b
> ,****** a :3:
> ,*** b
> ,**** a :4:
> #+end_src
>
> Limitations of this simplified implementation:
> - Export of links with a path to a heading is not supported.
> - Links to a heading with "/" that existed before are broken.
> - There may be other issues for your use case already discussed in the
> current thread (
> http://lists.gnu.org/archive/html/emacs-orgmode/2018-03/msg00231.html
> ).
>
> Due to the limitations this implementation is for private use only and
> not meant to be commited upstream although the format of the attached
> patches might imply that.
>
> Michael
> From 3a594dfa9967ed4fd70aae04559dde757fb21b1b Mon Sep 17 00:00:00 2001
> From: Michael Brand <michael.ch.brand@gmail.com>
> Date: Mon, 6 May 2019 18:17:52 +0200
> Subject: [PATCH 1/2] org-get-heading: New parameter no-cookie
>
> * lisp/ol.el (org-link-search): Remove regexps for comment and cookie.
> * lisp/org.el (org-get-heading:): New parameter no-cookie used above.
> ---
> lisp/ol.el | 10 ++--------
> lisp/org.el | 11 +++++++++--
> 2 files changed, 11 insertions(+), 10 deletions(-)
>
> diff --git a/lisp/ol.el b/lisp/ol.el
> index a6f76a39f..f5bd63e96 100644
> --- a/lisp/ol.el
> +++ b/lisp/ol.el
> @@ -1108,18 +1108,12 @@ of matched result, which is either `dedicated' or `fuzzy'."
> (format "%s.*\\(?:%s[ \t]\\)?.*%s"
> org-outline-regexp-bol
> org-comment-string
> - (mapconcat #'regexp-quote words ".+")))
> - (cookie-re "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]")
> - (comment-re (format "\\`%s[ \t]+" org-comment-string)))
> + (mapconcat #'regexp-quote words ".+"))))
> (goto-char (point-min))
> (catch :found
> (while (re-search-forward title-re nil t)
> (when (equal words
> - (split-string
> - (replace-regexp-in-string
> - cookie-re ""
> - (replace-regexp-in-string
> - comment-re "" (org-get-heading t t t)))))
> + (split-string (org-get-heading t t t t t)))
> (throw :found t)))
> nil)))
> (beginning-of-line)
> diff --git a/lisp/org.el b/lisp/org.el
> index 94713a7e5..48f7874ac 100644
> --- a/lisp/org.el
> +++ b/lisp/org.el
> @@ -6938,12 +6938,14 @@ So this will delete or add empty lines."
> (insert (make-string n ?\n))
> (move-to-column column)))
>
> -(defun org-get-heading (&optional no-tags no-todo no-priority no-comment)
> +(defun org-get-heading (&optional
> + no-tags no-todo no-priority no-comment no-cookie)
> "Return the heading of the current entry, without the stars.
> When NO-TAGS is non-nil, don't include tags.
> When NO-TODO is non-nil, don't include TODO keywords.
> When NO-PRIORITY is non-nil, don't include priority cookie.
> When NO-COMMENT is non-nil, don't include COMMENT string.
> +When NO-COOKIE is non-nil, don't include cookie string.
> Return nil before first heading."
> (unless (org-before-first-heading-p)
> (save-excursion
> @@ -6958,7 +6960,12 @@ Return nil before first heading."
> (replace-regexp-in-string
> (eval-when-compile
> (format "\\`%s[ \t]+" org-comment-string))
> - "" h))
> + ""
> + (if no-cookie
> + (replace-regexp-in-string
> + "\\[[0-9]*\\(?:%\\|/[0-9]*\\)\\]"
> + "" h)
> + h)))
> (h h)))
> (tags (and (not no-tags) (match-string 5))))
> (mapconcat #'identity
> --
> 2.20.1
>
> From fee37436abbe4a7d6b79161b9230f02de6e7d54d Mon Sep 17 00:00:00 2001
> From: Michael Brand <michael.ch.brand@gmail.com>
> Date: Mon, 6 May 2019 18:19:44 +0200
> Subject: [PATCH 2/2] org-link-search: Search for outline path
>
> * lisp/ol.el (org-link-search): Externalize matching logic to new function
> org-link--heading-path-match-p.
> (org-link--heading-path-split):
> (org-link--heading-path-match-p): New function.
> ---
> lisp/ol.el | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 66 insertions(+), 3 deletions(-)
>
> diff --git a/lisp/ol.el b/lisp/ol.el
> index f5bd63e96..b79efdf6b 100644
> --- a/lisp/ol.el
> +++ b/lisp/ol.el
> @@ -1034,7 +1034,16 @@ of matched result, which is either `dedicated' or `fuzzy'."
> (origin (point))
> (normalized (replace-regexp-in-string "\n[ \t]*" " " s))
> (starred (eq (string-to-char normalized) ?*))
> - (words (split-string (if starred (substring s 1) s)))
> + (heading-path (and starred (substring normalized 1)))
> + (words (split-string
> + (if starred
> + (replace-regexp-in-string "^.*/" "" heading-path)
> + s)))
> + (path-rest
> + (and starred
> + (cdr (org-link--heading-path-split
> + (replace-regexp-in-string "^/" "" heading-path)))))
> + (path-rooted-p (and starred (eq ?/ (string-to-char heading-path))))
> (s-multi-re (mapconcat #'regexp-quote words "\\(?:[ \t\n]+\\)"))
> (s-single-re (mapconcat #'regexp-quote words "[ \t]+"))
> type)
> @@ -1112,8 +1121,8 @@ of matched result, which is either `dedicated' or `fuzzy'."
> (goto-char (point-min))
> (catch :found
> (while (re-search-forward title-re nil t)
> - (when (equal words
> - (split-string (org-get-heading t t t t t)))
> + (when (org-link--heading-path-match-p
> + words path-rest path-rooted-p)
> (throw :found t)))
> nil)))
> (beginning-of-line)
> @@ -1163,6 +1172,60 @@ of matched result, which is either `dedicated' or `fuzzy'."
> (org-show-context 'link-search))
> type))
>
> +(defun org-link--heading-path-split (path)
> + "Split the PATH string and enumerate the headings by contiguous groups.
> +For example \"f/e//d/c/b//a\"
> +=> ((\"a\" . 0) (\"b\" . 0) (\"c\" . 1) (\"d\" . 2) (\"e\" . 0) (\"f\" . 1))"
> + (apply #'append
> + (mapcar (lambda (contiguous)
> + (let* ((headings (reverse (split-string contiguous "/")))
> + (enum (number-sequence 0 (1- (length headings)))))
> + (mapcar* #'cons headings enum)))
> + (reverse (split-string path "//")))))
> +
> +(defun org-link--heading-path-match-p (current path-rest path-rooted-p)
> + "Match heading hierarchy at point with CURRENT and PATH-REST.
> +
> +CURRENT is `split-string' of the string for the requested lowest
> +level heading.
> +
> +PATH-REST is the `cdr' of `org-link--heading-path-split' of the
> +path string originally still including the current heading.
> +PATH-REST can be nil or contains the upper level headings in
> +groups indicated by an enumeration starting at 0. Every 0
> +indicates the beginning of a new group. Examples for PATH-REST
> +values: ((\"a\" . 1) (\"b\" . 2)) which is the `cdr'
> +of ((\"current\" . 0) (\"a\" . 1) (\"b\" . 2)) indicates that
> +there is one group which means that it matches the Org hierarchy
> +b/a/current but not b/x/a/current or b/a/x/current. ((\"a\" .
> +1) (\"b\" . 0)) indicates that there are two groups separated
> +between a and b which means that it matches b/a/current,
> +b/x/a/current, b/x/x/a/current etc. with any number of discarded
> +headings x between the groups but not b/a/x/current. ((\"a\" .
> +0) (\"b\" . 1)) indicates that there are two groups separated
> +between current and a which means that it matches for example
> +b/a/x/current.
> +
> +Non-nil PATH-ROOTED-P means that the first level heading in the
> +buffer must be part of the match."
> + (save-excursion
> + (and (equal current (split-string (org-get-heading t t t t t)))
> + (or (not path-rest)
> + (every (lambda (heading)
> + (let (match)
> + (while (and (org-up-heading-safe)
> + (not (setq match
> + (equal (split-string
> + (car heading))
> + (split-string
> + (org-get-heading
> + t t t t t)))))
> + (zerop (cdr heading))))
> + match))
> + path-rest))
> + (or (not path-rooted-p)
> + (eq 1 (org-outline-level))))))
> +
> (defun org-link-heading-search-string (&optional string)
> "Make search string for the current headline or STRING."
> (let ((s (or string
> --
> 2.20.1
>
next prev parent reply other threads:[~2019-05-07 3:27 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-03-12 10:09 Structured links to headings with endless depth ST
2018-03-12 10:29 ` Eric S Fraga
2018-03-12 10:39 ` ST
2018-03-12 13:08 ` Christian Moe
2018-03-12 13:46 ` ST
2018-03-12 14:10 ` Nicolas Goaziou
2018-03-12 15:08 ` ST
2018-03-14 3:49 ` John Kitchin
2018-03-14 6:58 ` Michael Brand
2019-05-06 16:34 ` Michael Brand
2019-05-07 3:26 ` Ihor Radchenko [this message]
2019-05-07 14:39 ` Michael Brand
2019-05-18 10:44 ` Ihor Radchenko
2018-03-14 10:10 ` ST
2018-03-14 13:26 ` Nicolas Goaziou
2018-03-14 18:11 ` ST
2018-03-14 18:32 ` Nicolas Goaziou
2018-03-14 18:46 ` ST
2018-03-14 14:15 ` John Kitchin
2018-03-14 18:07 ` ST
2018-03-12 12:43 ` ST
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
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=874l67eyif.fsf@yantar92-laptop.i-did-not-set--mail-host-address--so-tickle-me \
--to=yantar92@gmail.com \
--cc=Emacs-orgmode@gnu.org \
--cc=esflists@gmail.com \
--cc=jkitchin@andrew.cmu.edu \
--cc=mail@christianmoe.com \
--cc=mail@nicolasgoaziou.fr \
--cc=michael.ch.brand@gmail.com \
--cc=smntov@gmail.com \
/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 public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).