Browse Source

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.
Nicolas Goaziou 12 years ago
parent
commit
13e49a6385
2 changed files with 146 additions and 117 deletions
  1. 134 116
      lisp/org-element.el
  2. 12 1
      testing/lisp/test-org-element.el

+ 134 - 116
lisp/org-element.el

@@ -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
-     ;; point.
+     ;; Otherwise try to move at the beginning of the section
+     ;; 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))
-	    (goto-char (point-min))
+	(if (org-before-first-heading-p) (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
-       ;; before original position.
-       (catch 'exit
-         (while t
-           (setq element
-		 (org-element--current-element end 'element special-flag struct)
-                 type (car element))
-	   (org-element-put-property element :parent parent)
-	   (when keep-trail (push element trail))
-           (cond
-	    ;; 1. Skip any element ending before point.  Also skip
-	    ;;    element ending at point when we're sure that another
-	    ;;    element has started.
-	    ((let ((elem-end (org-element-property :end element)))
-	       (when (or (< elem-end origin)
-			 (and (= elem-end origin) (/= elem-end end)))
-		 (goto-char elem-end))))
-	    ;; 2. An element containing point is always the element at
-	    ;;    point.
-	    ((not (memq type org-element-greater-elements))
-	     (throw 'exit (if keep-trail trail element)))
-	    ;; 3. At any other greater element type, if point is
-	    ;;    within contents, move into it.
-	    (t
-	     (let ((cbeg (org-element-property :contents-begin element))
-		   (cend (org-element-property :contents-end element)))
-	       (if (or (not cbeg) (not cend) (> cbeg origin) (< cend origin)
-		       ;; Create an anchor for tables and plain lists:
-		       ;; when point is at the very beginning of these
-		       ;; elements, ignoring affiliated keywords,
-		       ;; target them instead of their contents.
-		       (and (= cbeg origin) (memq type '(plain-list table)))
-		       ;; When point is at contents end, do not move
-		       ;; into elements with an explicit ending, but
-		       ;; return that element instead.
-		       (and (= cend origin)
-			    (memq type
-				  '(center-block
-				    drawer dynamic-block inlinetask item
-				    plain-list property-drawer quote-block
-				    special-block))))
-		   (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)))))))))))
+       (if (looking-at "[ \t]*$")
+	   ;; If point is still at a blank line, we didn't reach
+	   ;; section beginning.  it means we started either at the
+	   ;; beginning of the buffer, before any element, or in the
+	   ;; blank area after an headline.  In the first case, return
+	   ;; a dummy `org-data' element.  In the second case, return
+	   ;; the headline.
+	   (progn (skip-chars-backward " \r\t\n")
+		  (cond ((bobp) nil)
+			(keep-trail
+			 (list (org-element-headline-parser (point-max) t)))
+			(t (org-element-headline-parser (point-max) t))))
+	 ;; Parse successively each element, skipping those ending
+	 ;; before original position.
+	 (catch 'exit
+	   (while t
+	     (setq element (org-element--current-element
+			    end 'element special-flag struct)
+		   type (car element))
+	     (org-element-put-property element :parent parent)
+	     (when keep-trail (push element trail))
+	     (cond
+	      ;; 1. Skip any element ending before point.  Also skip
+	      ;;    element ending at point when we're sure that another
+	      ;;    element has started.
+	      ((let ((elem-end (org-element-property :end element)))
+		 (when (or (< elem-end origin)
+			   (and (= elem-end origin) (/= elem-end end)))
+		   (goto-char elem-end))))
+	      ;; 2. An element containing point is always the element at
+	      ;;    point.
+	      ((not (memq type org-element-greater-elements))
+	       (throw 'exit (if keep-trail trail element)))
+	      ;; 3. At any other greater element type, if point is
+	      ;;    within contents, move into it.
+	      (t
+	       (let ((cbeg (org-element-property :contents-begin element))
+		     (cend (org-element-property :contents-end element)))
+		 (if (or (not cbeg) (not cend) (> cbeg origin) (< cend origin)
+			 ;; Create an anchor for tables and plain lists:
+			 ;; when point is at the very beginning of these
+			 ;; elements, ignoring affiliated keywords,
+			 ;; target them instead of their contents.
+			 (and (= cbeg origin) (memq type '(plain-list table)))
+			 ;; When point is at contents end, do not move
+			 ;; into elements with an explicit ending, but
+			 ;; return that element instead.
+			 (and (= cend origin)
+			      (memq type
+				    '(center-block
+				      drawer dynamic-block inlinetask item
+				      plain-list property-drawer quote-block
+				      special-block))))
+		     (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
-and :post-blank properties.
+object type, but always include `:begin', `:end', `:parent' and
+`: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
-     ;; a secondary string.  In that case, move to beginning of the
-     ;; element or secondary string and set END to the other side.
-     (if (not (or (let ((post (org-element-property :post-affiliated element)))
-		    (and post (> post origin)
-			 (< (org-element-property :begin element) origin)
-			 (progn (beginning-of-line)
-				(looking-at org-element--affiliated-re)
-				(member (upcase (match-string 1))
-					org-element-parsed-keywords))
-			 ;; 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))
+     (cond
+      ;; If point is within blank lines at the beginning of the
+      ;; buffer, return nil.
+      ((not element) nil)
+      ;; Check if point is inside an element containing objects or at
+      ;; a secondary string.  In that case, move to beginning of the
+      ;; element or secondary string and set END to the other side.
+      ((not (or (let ((post (org-element-property :post-affiliated element)))
+		  (and post (> post origin)
+		       (< (org-element-property :begin element) origin)
 		       (progn (beginning-of-line)
 		       (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
+			      (looking-at org-element--affiliated-re)
+			      (member (upcase (match-string 1))
+				      org-element-parsed-keywords))
+		       ;; 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)
+			    (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."

+ 12 - 1
testing/lisp/test-org-element.el

@@ -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)