org-element: Change return value for element at point in some corner cases

* lisp/org-element.el (org-element-at-point): When point is before any
  element, in the first blank lines of the buffer, return nil.  When
  point is within blank lines just after a headline, return that
  headline.
(org-element-context): Return nil when point is within the blank at
the beginning of the buffer.
* testing/lisp/test-org-element.el: Add tests.
This commit is contained in:
Nicolas Goaziou 2013-01-19 15:29:39 +01:00
parent c300a50402
commit 13e49a6385
2 changed files with 146 additions and 117 deletions

View File

@ -4529,7 +4529,8 @@ As a special case, if point is at the very beginning of a list or
sub-list, returned element will be that list instead of the first sub-list, returned element will be that list instead of the first
item. In the same way, if point is at the beginning of the first item. In the same way, if point is at the beginning of the first
row of a table, returned element will be the table instead of the row of a table, returned element will be the table instead of the
first row. first row. Also, if point is within the first blank lines of
a buffer, return nil.
If optional argument KEEP-TRAIL is non-nil, the function returns If optional argument KEEP-TRAIL is non-nil, the function returns
a list of of elements leading to element at point. The list's a list of of elements leading to element at point. The list's
@ -4545,73 +4546,84 @@ first element of current section."
(beginning-of-line) (beginning-of-line)
(if (not keep-trail) (org-element-headline-parser (point-max) t) (if (not keep-trail) (org-element-headline-parser (point-max) t)
(list (org-element-headline-parser (point-max) t)))) (list (org-element-headline-parser (point-max) t))))
;; Otherwise move at the beginning of the section containing ;; Otherwise try to move at the beginning of the section
;; point. ;; containing point.
(let ((origin (point)) (let ((origin (point))
(end (save-excursion (end (save-excursion
(org-with-limited-levels (outline-next-heading)) (point))) (org-with-limited-levels (outline-next-heading)) (point)))
element type special-flag trail struct prevs parent) element type special-flag trail struct prevs parent)
(org-with-limited-levels (org-with-limited-levels
(if (org-with-limited-levels (org-before-first-heading-p)) (if (org-before-first-heading-p) (goto-char (point-min))
(goto-char (point-min))
(org-back-to-heading) (org-back-to-heading)
(forward-line))) (forward-line)))
(org-skip-whitespace) (skip-chars-forward " \r\t\n" origin)
(beginning-of-line) (beginning-of-line)
;; Parse successively each element, skipping those ending (if (looking-at "[ \t]*$")
;; before original position. ;; If point is still at a blank line, we didn't reach
(catch 'exit ;; section beginning. it means we started either at the
(while t ;; beginning of the buffer, before any element, or in the
(setq element ;; blank area after an headline. In the first case, return
(org-element--current-element end 'element special-flag struct) ;; a dummy `org-data' element. In the second case, return
type (car element)) ;; the headline.
(org-element-put-property element :parent parent) (progn (skip-chars-backward " \r\t\n")
(when keep-trail (push element trail)) (cond ((bobp) nil)
(cond (keep-trail
;; 1. Skip any element ending before point. Also skip (list (org-element-headline-parser (point-max) t)))
;; element ending at point when we're sure that another (t (org-element-headline-parser (point-max) t))))
;; element has started. ;; Parse successively each element, skipping those ending
((let ((elem-end (org-element-property :end element))) ;; before original position.
(when (or (< elem-end origin) (catch 'exit
(and (= elem-end origin) (/= elem-end end))) (while t
(goto-char elem-end)))) (setq element (org-element--current-element
;; 2. An element containing point is always the element at end 'element special-flag struct)
;; point. type (car element))
((not (memq type org-element-greater-elements)) (org-element-put-property element :parent parent)
(throw 'exit (if keep-trail trail element))) (when keep-trail (push element trail))
;; 3. At any other greater element type, if point is (cond
;; within contents, move into it. ;; 1. Skip any element ending before point. Also skip
(t ;; element ending at point when we're sure that another
(let ((cbeg (org-element-property :contents-begin element)) ;; element has started.
(cend (org-element-property :contents-end element))) ((let ((elem-end (org-element-property :end element)))
(if (or (not cbeg) (not cend) (> cbeg origin) (< cend origin) (when (or (< elem-end origin)
;; Create an anchor for tables and plain lists: (and (= elem-end origin) (/= elem-end end)))
;; when point is at the very beginning of these (goto-char elem-end))))
;; elements, ignoring affiliated keywords, ;; 2. An element containing point is always the element at
;; target them instead of their contents. ;; point.
(and (= cbeg origin) (memq type '(plain-list table))) ((not (memq type org-element-greater-elements))
;; When point is at contents end, do not move (throw 'exit (if keep-trail trail element)))
;; into elements with an explicit ending, but ;; 3. At any other greater element type, if point is
;; return that element instead. ;; within contents, move into it.
(and (= cend origin) (t
(memq type (let ((cbeg (org-element-property :contents-begin element))
'(center-block (cend (org-element-property :contents-end element)))
drawer dynamic-block inlinetask item (if (or (not cbeg) (not cend) (> cbeg origin) (< cend origin)
plain-list property-drawer quote-block ;; Create an anchor for tables and plain lists:
special-block)))) ;; when point is at the very beginning of these
(throw 'exit (if keep-trail trail element)) ;; elements, ignoring affiliated keywords,
(setq parent element) ;; target them instead of their contents.
(case type (and (= cbeg origin) (memq type '(plain-list table)))
(plain-list ;; When point is at contents end, do not move
(setq special-flag 'item ;; into elements with an explicit ending, but
struct (org-element-property :structure element))) ;; return that element instead.
(item (setq special-flag nil)) (and (= cend origin)
(property-drawer (memq type
(setq special-flag 'node-property struct nil)) '(center-block
(table (setq special-flag 'table-row struct nil)) drawer dynamic-block inlinetask item
(otherwise (setq special-flag nil struct nil))) plain-list property-drawer quote-block
(setq end cend) special-block))))
(goto-char cbeg))))))))))) (throw 'exit (if keep-trail trail element))
(setq parent element)
(case type
(plain-list
(setq special-flag 'item
struct (org-element-property :structure element)))
(item (setq special-flag nil))
(property-drawer
(setq special-flag 'node-property struct nil))
(table (setq special-flag 'table-row struct nil))
(otherwise (setq special-flag nil struct nil)))
(setq end cend)
(goto-char cbeg))))))))))))
;;;###autoload ;;;###autoload
(defun org-element-context (&optional element) (defun org-element-context (&optional element)
@ -4619,12 +4631,13 @@ first element of current section."
Return value is a list like (TYPE PROPS) where TYPE is the type Return value is a list like (TYPE PROPS) where TYPE is the type
of the element or object and PROPS a plist of properties of the element or object and PROPS a plist of properties
associated to it. associated to it, or nil if point is within the first blank lines
of the buffer.
Possible types are defined in `org-element-all-elements' and Possible types are defined in `org-element-all-elements' and
`org-element-all-objects'. Properties depend on element or `org-element-all-objects'. Properties depend on element or
object type, but always include :begin, :end, :parent object type, but always include `:begin', `:end', `:parent' and
and :post-blank properties. `:post-blank' properties.
Optional argument ELEMENT, when non-nil, is the closest element Optional argument ELEMENT, when non-nil, is the closest element
containing point, as returned by `org-element-at-point'. containing point, as returned by `org-element-at-point'.
@ -4632,60 +4645,65 @@ Providing it allows for quicker computation."
(org-with-wide-buffer (org-with-wide-buffer
(let* ((origin (point)) (let* ((origin (point))
(element (or element (org-element-at-point))) (element (or element (org-element-at-point)))
(type (car element)) (type (org-element-type element))
end) end)
;; Check if point is inside an element containing objects or at (cond
;; a secondary string. In that case, move to beginning of the ;; If point is within blank lines at the beginning of the
;; element or secondary string and set END to the other side. ;; buffer, return nil.
(if (not (or (let ((post (org-element-property :post-affiliated element))) ((not element) nil)
(and post (> post origin) ;; Check if point is inside an element containing objects or at
(< (org-element-property :begin element) origin) ;; a secondary string. In that case, move to beginning of the
(progn (beginning-of-line) ;; element or secondary string and set END to the other side.
(looking-at org-element--affiliated-re) ((not (or (let ((post (org-element-property :post-affiliated element)))
(member (upcase (match-string 1)) (and post (> post origin)
org-element-parsed-keywords)) (< (org-element-property :begin element) origin)
;; We're at an affiliated keyword. Change
;; type to retrieve correct restrictions.
(setq type 'keyword)
;; Determine if we're at main or dual value.
(if (and (match-end 2) (<= origin (match-end 2)))
(progn (goto-char (match-beginning 2))
(setq end (match-end 2)))
(goto-char (match-end 0))
(setq end (line-end-position)))))
(and (eq type 'item)
(let ((tag (org-element-property :tag element)))
(and tag
(progn
(beginning-of-line)
(search-forward tag (point-at-eol))
(goto-char (match-beginning 0))
(and (>= origin (point))
(<= origin
;; `1+' is required so some
;; successors can match
;; properly their object.
(setq end (1+ (match-end 0)))))))))
(and (memq type '(headline inlinetask))
(progn (beginning-of-line) (progn (beginning-of-line)
(skip-chars-forward "* ") (looking-at org-element--affiliated-re)
(setq end (point-at-eol)))) (member (upcase (match-string 1))
(and (memq type '(paragraph table-row verse-block)) org-element-parsed-keywords))
(let ((cbeg (org-element-property ;; We're at an affiliated keyword. Change
:contents-begin element)) ;; type to retrieve correct restrictions.
(cend (org-element-property (setq type 'keyword)
:contents-end element))) ;; Determine if we're at main or dual value.
(and (>= origin cbeg) (if (and (match-end 2) (<= origin (match-end 2)))
(<= origin cend) (progn (goto-char (match-beginning 2))
(progn (goto-char cbeg) (setq end cend))))) (setq end (match-end 2)))
(and (eq type 'keyword) (goto-char (match-end 0))
(let ((key (org-element-property :key element))) (setq end (line-end-position)))))
(and (member key org-element-document-properties) (and (eq type 'item)
(progn (beginning-of-line) (let ((tag (org-element-property :tag element)))
(search-forward key (line-end-position) t) (and tag
(forward-char) (progn
(setq end (line-end-position)))))))) (beginning-of-line)
element (search-forward tag (point-at-eol))
(goto-char (match-beginning 0))
(and (>= origin (point))
(<= origin
;; `1+' is required so some
;; successors can match
;; properly their object.
(setq end (1+ (match-end 0)))))))))
(and (memq type '(headline inlinetask))
(progn (beginning-of-line)
(skip-chars-forward "* ")
(setq end (point-at-eol))))
(and (memq type '(paragraph table-row verse-block))
(let ((cbeg (org-element-property
:contents-begin element))
(cend (org-element-property
:contents-end element)))
(and (>= origin cbeg)
(<= origin cend)
(progn (goto-char cbeg) (setq end cend)))))
(and (eq type 'keyword)
(let ((key (org-element-property :key element)))
(and (member key org-element-document-properties)
(progn (beginning-of-line)
(search-forward key (line-end-position) t)
(forward-char)
(setq end (line-end-position))))))))
element)
(t
(let ((restriction (org-element-restriction type)) (let ((restriction (org-element-restriction type))
(parent element) (parent element)
candidates) candidates)
@ -4723,7 +4741,7 @@ Providing it allows for quicker computation."
(setq parent object (setq parent object
restriction (org-element-restriction object) restriction (org-element-restriction object)
end cend))))))) end cend)))))))
parent)))))) parent)))))))
(defsubst org-element-nested-p (elem-A elem-B) (defsubst org-element-nested-p (elem-A elem-B)
"Non-nil when elements ELEM-A and ELEM-B are nested." "Non-nil when elements ELEM-A and ELEM-B are nested."

View File

@ -2661,6 +2661,15 @@ Paragraph \\alpha."
(org-test-with-temp-text "- Para1\n- Para2\n\nPara3" (org-test-with-temp-text "- Para1\n- Para2\n\nPara3"
(progn (forward-line 2) (progn (forward-line 2)
(org-element-type (org-element-at-point)))))) (org-element-type (org-element-at-point))))))
;; Special case: within the first blank lines in buffer, return nil.
(should-not (org-test-with-temp-text "\nParagraph" (org-element-at-point)))
;; Special case: within the blank lines after a headline, return
;; that headline.
(should
(eq 'headline
(org-test-with-temp-text "* Headline\n\nParagraph"
(progn (forward-line)
(org-element-type (org-element-at-point))))))
;; With an optional argument, return trail. ;; With an optional argument, return trail.
(should (should
(equal '(paragraph center-block) (equal '(paragraph center-block)
@ -2733,7 +2742,9 @@ Paragraph \\alpha."
(org-test-with-temp-text "Some *text with _underline_ text*" (org-test-with-temp-text "Some *text with _underline_ text*"
(progn (progn
(search-forward "under") (search-forward "under")
(org-element-type (org-element-context (org-element-at-point)))))))) (org-element-type (org-element-context (org-element-at-point)))))))
;; Return nil when point is within the first blank lines.
(should-not (org-test-with-temp-text "\n* Headline" (org-element-context))))
(provide 'test-org-element) (provide 'test-org-element)