It's been a while since my question on this mailing list about block
boundaries [1].  For my private code I came up with the function below
to both get and test on all sorts of block boundaries.  Like this:

  (and-let* ((element (or element (org-element-at-point)))
             ;; returns nil if point is outside of markup
             (boundaries (org-element-boundaries element 'markup t))
             (start (car boundaries))
             (end (cdr boundaries)))
    ...)

API and code are surely rather sound than ingenious, but if you
consider it (or some variation of it) useful for inclusion into Org
mode, just let me know, I'd prepare a proper patch plus tests plus ...

The second part of the code is for interactive testing only.

If you feel I should provide examples where this could be actually
used in Org's code base, just let me know - I can set up some example
patches as well.

Thanks!

[1]: 
https://list.orgmode.org/628222e2-d516-4ff0-b244-66f20f286...@vodafonemail.de/



(defun org-element-boundaries (element boundary-type &optional point-test)
  "Return a cons (START . END) of the boundaries of ELEMENT.
Determine START and END depending on BOUNDARY-TYPE, which can be any of the
following symbols:

`element', `gross-elt': Return the outer element boundaries, which include
leading affiliated keywords and trailing blank lines [1].
`markup', `net-elt': Return the boundaries of the element sans affiliated
keywords and trailing blank lines.  For block-like elements this coincides
with the markup boundaries [2].

For block-like elements, that is, elements having separate markup lines
like source blocks, the following boundary types are also available:

`contents', `gross-contents': Return the boundaries of the block contents
[3].
`net-contents': Return the boundaries of the block contents sans leading
and trailing whitespace (' \\t\\n\\r') [4].

Here is an example block that shows the boundaries as returned by this
function (leading and trailing blanks denoted with underscores):

[1...#+name: foo
[2...#+begin_example
[3...__[4...foo
bar
__baz...4]__
__...3]
#+end_example__...2]
__...1]
Bar.

BOUNDARY-TYPE can also be a cons (START-TYPE . END-TYPE) to determine START
and END with respect to different boundary types.

For certain combinations of contents-based boundary types and degenerate
blocks, the literal interpretation of above rules could result in END being
smaller than START.  In these cases this function returns START and END
equal to CONTENTS-START, where CONTENTS-START is the position of the
beginning of line after the opening markup line.

Except for that special case and boundary type `net-contents' in general,
START is always at the beginning and END is always at the end of a line.
START is always smaller than or equal to END.

If optional parameter POINT-TEST equals `exclusive', this function returns
nil instead of the cons if point is not exclusively/strictly between START
and END.  Any other non-nil value changes that to a non-exclusive test,
where point is allowed to equal START or END.  POINT-TEST can also be a
cons (START-TEST . END-TEST) to test START or END vs. point with respect to
different values of strictness."
  (let* ((start-type (or (car-safe boundary-type) boundary-type))
         (end-type   (or (cdr-safe boundary-type) boundary-type))
         (start-test (if (consp point-test) (car point-test) point-test))
         (end-test   (if (consp point-test) (cdr point-test) point-test))
         (point      (point))
         start end contents-start)
    (org-with-wide-buffer
     (setq start
           (pcase-exhaustive start-type
             ((or 'element 'gross-elt)
              (org-element-begin element))
             ((or 'markup 'net-elt)
              (org-element-post-affiliated element))
             ((or 'contents 'gross-contents)
              (goto-char (org-element-post-affiliated element))
              (forward-line 1)
              (setq contents-start (point))
              (point))
             ('net-contents
              (goto-char (org-element-post-affiliated element))
              (forward-line 1)
              (setq contents-start (point))
              (skip-chars-forward " \t\n\r")
              (point))))
     (setq end
           (progn
             (goto-char (org-element-end element))
             (pcase-exhaustive end-type
               ;; adjust element end depending on the
               ;; surrounding or following element
               ((or 'element 'gross-elt)
                (if (looking-at "[ \t\n\r]*$")
                    (line-end-position 1)
                  (line-end-position 0)))
               ((or 'markup 'net-elt)
                (skip-chars-backward " \t\n\r")
                (line-end-position 1))
               ((or 'contents 'gross-contents)
                (skip-chars-backward " \t\n\r")
                (line-end-position 0))
               ('net-contents
                (skip-chars-backward " \t\n\r")
                (forward-line 0)
                (skip-chars-backward " \t\n\r")
                (point)))))
     ;; handle empty or whitespace-only blocks.  CONTENTS-START
     ;; is nil if START-TYPE is not contents-based.  Which is OK,
     ;; since in that case END is larger than START.
     (when (< end start)
       (setq start contents-start
             end   contents-start))
     (and (cond ((not start-test))
                ((eq start-test 'exclusive)
                 (< start point))
                ((<= start point)))
          (cond ((not end-test))
                ((eq end-test 'exclusive)
                 (< point end))
                ((<= point end)))
          (cons start end)))))



;;; for interactive test purposes only
(defface org-element-boundaries-test-point-ok
  '((t :background "green" :foreground "black" :weight bold))
  "Face to show if point is inside boundaries.")
(defface org-element-boundaries-test-point-nok
  '((t :background "red" :foreground "black" :weight bold))
  "Face to show if point is not inside boundaries.")
(defvar org-element-boundaries-test-element nil)
(defvar org-element-boundaries-test-overlay nil)
(defun org-element-boundaries-test (element btct)
  "Test `org-element-boundaries' in an interactive way.
Use `org-element-boundaries-test-element' as default for ELEMENT and map
numeric prefix argument BTCT per digits to boundary type and test type
as follows:

     0: ('gross-elt      nil)
     1: ('gross-elt      nil)
     2: ('net-elt        nil)
     3: ('gross-contents nil)
     4: ('net-contents   nil)
     N: ('gross-elt      nil)
    NM: ((N . M)         nil)
   NM0: ((N . M)         nil)
   NM1: ((N . M)         t)
   NM2: ((N . M)         'exclusive)
   NMI: ((N . M)         nil)
  NMIJ: ((N . M)         (I . J))

So a prefix argument 1312 results in a call

  (org-element-boundaries ELEMENT '(gross-elt . gross-contents)
                                  '(t . exclusive))"
  (interactive
   (list (or org-element-boundaries-test-element
             (org-element-at-point))
         (prefix-numeric-value current-prefix-arg)))
  (let* ((bt [gross-elt gross-elt net-elt gross-contents net-contents
              gross-elt gross-elt gross-elt gross-elt gross-elt])
         (ct [nil t exclusive nil nil nil nil nil nil nil])
         (btct (cond ((< btct 0)    1100)
                     ((<= btct 9)   (+ (* btct 1000) (* btct 100)))
                     ((<= btct 99)  (* btct 100))
                     ((<= btct 999) (+ (* btct 10) (% btct 10)))
                     (t             (% btct 10000))))
         (btct (mapcar (lambda (n) (- n 48)) (number-to-string btct)))
         (n (aref bt (nth 0 btct)))
         (m (aref bt (nth 1 btct)))
         (i (aref ct (nth 2 btct)))
         (j (aref ct (nth 3 btct)))
         (b (org-element-boundaries element (cons n m) nil))
         (bt (org-element-boundaries element (cons n m) (cons i j))))
    (if org-element-boundaries-test-overlay
        (move-overlay org-element-boundaries-test-overlay (car b) (cdr b))
      (setq org-element-boundaries-test-overlay
            (make-overlay  (car b) (cdr b))))
    (if bt
        (overlay-put org-element-boundaries-test-overlay
                     'face 'org-element-boundaries-test-point-ok)
      (overlay-put org-element-boundaries-test-overlay
                   'face 'org-element-boundaries-test-point-nok)
      (message "POINT OUT OF BOUNDARIES"))))

Reply via email to