emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [O] Structured links to headings with endless depth


From: Ihor Radchenko
Subject: Re: [O] Structured links to headings with endless depth
Date: Tue, 07 May 2019 11:26:00 +0800

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 <address@hidden> writes:

> Hi all
>
> On Wed, Mar 14, 2018 at 7:58 AM Michael Brand
> <address@hidden> 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 <address@hidden>
> 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 <address@hidden>
> 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
>



reply via email to

[Prev in Thread] Current Thread [Next in Thread]