Browse Source

Fix indentation in lists

* lisp/org-list.el (org-list-item-body-column): Take into
  consideration empty items and bullets followed by two spaces.

* lisp/org.el (org--get-expected-indentation): Fix return value for
  items in lists.
(org-indent-region): Fix infloop when indenting some types of plain
lists.  Also fix error when region starts with blank lines at the
beginning of the buffer.

* testing/lisp/test-org.el (test-org/indent-region): Add tests.
Nicolas Goaziou 5 years ago
parent
commit
ce2090ccfd
3 changed files with 83 additions and 44 deletions
  1. 13 10
      lisp/org-list.el
  2. 57 33
      lisp/org.el
  3. 13 1
      testing/lisp/test-org.el

+ 13 - 10
lisp/org-list.el

@@ -2051,16 +2051,19 @@ Possible values are: `folded', `children' or `subtree'.  See
 
 (defun org-list-item-body-column (item)
   "Return column at which body of ITEM should start."
-  (let (bpos bcol tpos tcol)
-    (save-excursion
-      (goto-char item)
-      (looking-at "[ \t]*\\(\\S-+\\)\\(.*[ \t]+::\\)?\\([ \t]+\\|$\\)")
-      (setq bpos (match-beginning 1) tpos (match-end 0)
-	    bcol (progn (goto-char bpos) (current-column))
-	    tcol (progn (goto-char tpos) (current-column)))
-      (when (> tcol (+ bcol org-description-max-indent))
-	(setq tcol (+ bcol 5))))
-    tcol))
+  (save-excursion
+    (goto-char item)
+    (looking-at "[ \t]*\\(\\S-+\\)\\(.*[ \t]+::\\)?\\([ \t]+\\|$\\)")
+    (if (match-beginning 2)
+	(let ((start (1+ (match-end 2)))
+	      (ind (org-get-indentation)))
+	  (if (> start (+ ind org-description-max-indent)) (+ ind 5) start))
+      (+ (progn (goto-char (match-end 1)) (current-column))
+	 (if (and org-list-two-spaces-after-bullet-regexp
+		  (org-string-match-p org-list-two-spaces-after-bullet-regexp
+				      (match-string 1)))
+	     2
+	   1)))))
 
 
 

+ 57 - 33
lisp/org.el

@@ -22415,9 +22415,13 @@ ELEMENT."
 	  (if (not org-adapt-indentation) 0
 	    (let ((level (org-current-level)))
 	      (if level (1+ level) 0))))
-	 ((item plain list)
+	 (item
 	  (org-list-item-body-column
 	   (org-element-property :post-affiliated element)))
+	 (plain-list
+	  (save-excursion
+	    (goto-char (org-element-property :post-affiliated element))
+	    (org-get-indentation)))
 	 (otherwise
 	  (goto-char start)
 	  (org-get-indentation))))
@@ -22439,19 +22443,25 @@ ELEMENT."
 	 (while t
 	   (if (= (point-min) start) (throw 'exit 0)
 	     (goto-char (1- start))
-	     (let ((previous (org-element-at-point)))
-	       (while (let ((parent (org-element-property :parent previous)))
-			(and parent
-			     (setq previous parent)
-			     (<= (org-element-property :end parent) start))))
-	       (cond ((or (not previous)
-			  (> (org-element-property :end previous) start))
-		      (throw 'exit (org--get-expected-indentation previous t)))
-		     ((memq (org-element-type previous)
-			    '(footnote-definition inlinetask))
-		      (setq start (org-element-property :begin previous)))
-		     (t (goto-char (org-element-property :begin previous))
-			(throw 'exit (org-get-indentation)))))))))
+	     (let* ((previous (org-element-at-point))
+		    (parent previous))
+	       (while (and parent (<= (org-element-property :end parent) start))
+		 (setq previous parent
+		       parent (org-element-property :parent parent)))
+	       (cond
+		((not previous) (throw 'exit 0))
+		((> (org-element-property :end previous) start)
+		 (throw 'exit (org--get-expected-indentation previous t)))
+		((memq (org-element-type previous)
+		       '(footnote-definition inlinetask))
+		 (setq start (org-element-property :begin previous)))
+		(t (goto-char (org-element-property :begin previous))
+		   (throw 'exit
+			  (if (bolp) (org-get-indentation)
+			    ;; At first paragraph in an item or
+			    ;; a footnote definition.
+			    (org--get-expected-indentation
+			     (org-element-property :parent previous) t))))))))))
       ;; Otherwise, move to the first non-blank line above.
       (t
        (beginning-of-line)
@@ -22584,13 +22594,14 @@ assumed to be significant there."
   (interactive "r")
   (save-excursion
     (goto-char start)
-    (beginning-of-line)
+    (skip-chars-forward " \r\t\n")
+    (unless (eobp) (beginning-of-line))
     (let ((indent-to
 	   (lambda (ind pos)
 	     ;; Set IND as indentation for all lines between point and
 	     ;; POS or END, whichever comes first.  Blank lines are
 	     ;; ignored.  Leave point after POS once done.
-	     (let ((limit (copy-marker  (min end pos))))
+	     (let ((limit (copy-marker (min end pos))))
 	       (while (< (point) limit)
 		 (unless (org-looking-at-p "[ \t]*$") (org-indent-line-to ind))
 		 (forward-line))
@@ -22602,17 +22613,18 @@ assumed to be significant there."
 		 (type (org-element-type element))
 		 (element-end (copy-marker (org-element-property :end element)))
 		 (ind (org--get-expected-indentation element nil)))
-	    (if (or (memq type '(paragraph table table-row))
-		    (not (or (org-element-property :contents-begin element)
-			     (memq type
-				   '(example-block export-block src-block)))))
-		;; Elements here are indented as a single block.  Also
-		;; align node properties.
-		(progn
-		  (when (eq type 'node-property)
-		    (org--align-node-property)
-		    (beginning-of-line))
-		  (funcall indent-to ind element-end))
+	    (cond
+	     ((or (memq type '(paragraph table table-row))
+		  (not (or (org-element-property :contents-begin element)
+			   (memq type
+				 '(example-block export-block src-block)))))
+	      ;; Elements here are indented as a single block.  Also
+	      ;; align node properties.
+	      (when (eq type 'node-property)
+		(org--align-node-property)
+		(beginning-of-line))
+	      (funcall indent-to ind element-end))
+	     (t
 	      ;; Elements in this category consist of three parts:
 	      ;; before the contents, the contents, and after the
 	      ;; contents.  The contents are treated specially,
@@ -22636,8 +22648,9 @@ assumed to be significant there."
 			 ;; from the second line.
 			 (org-with-wide-buffer
 			  (goto-char post)
-			  (forward-line)
-			  (point)))
+			  (end-of-line)
+			  (skip-chars-forward " \r\t\n")
+			  (if (eobp) (point) (line-beginning-position))))
 			(t (org-element-property :contents-begin element)))))
 		     (cend (copy-marker
 			    (or (org-element-property :contents-end element)
@@ -22646,13 +22659,24 @@ assumed to be significant there."
 				 (goto-char element-end)
 				 (skip-chars-backward " \r\t\n")
 				 (line-beginning-position))))))
-		(funcall indent-to ind cbeg)
+		;; Do not change items indentation individually as it
+		;; might break the list as a whole.  On the other
+		;; hand, when at a plain list, indent it as a whole.
+		(cond ((eq type 'plain-list)
+		       (let ((offset (- ind (org-get-indentation))))
+			 (unless (zerop offset)
+			   (indent-rigidly (org-element-property :begin element)
+					   (org-element-property :end element)
+					   offset))
+			 (goto-char cbeg)))
+		      ((eq type 'item) (goto-char cbeg))
+		      (t (funcall indent-to ind cbeg)))
 		(when (< (point) end)
 		  (case type
 		    ((example-block export-block verse-block))
 		    (src-block
-		     ;; In a source block, indent source code according
-		     ;; to language major mode, but only if
+		     ;; In a source block, indent source code
+		     ;; according to language major mode, but only if
 		     ;; `org-src-tab-acts-natively' is non-nil.
 		     (when (and (< (point) end) org-src-tab-acts-natively)
 		       (ignore-errors
@@ -22667,7 +22691,7 @@ assumed to be significant there."
 		  (when (< (point) end) (funcall indent-to ind element-end)))
 		(set-marker post nil)
 		(set-marker cbeg nil)
-		(set-marker cend nil)))
+		(set-marker cend nil))))
 	    (set-marker element-end nil))))
       (set-marker end nil))))
 

+ 13 - 1
testing/lisp/test-org.el

@@ -655,15 +655,27 @@
 	    (let ((org-property-format "%-10s %s"))
 	      (org-indent-region (point-min) (point-max)))
 	    (buffer-string))))
-  ;; Special case: plain lists and footnote definitions.
+  ;; Indent plain lists.
   (should
    (equal "- A\n  B\n  - C\n\n    D"
 	  (org-test-with-temp-text "- A\n   B\n  - C\n\n     D"
 	    (org-indent-region (point-min) (point-max))
 	    (buffer-string))))
+  (should
+   (equal "- A\n\n- B"
+	  (org-test-with-temp-text " - A\n\n - B"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Indent footnote definitions.
   (should
    (equal "[fn:1] Definition\n\nDefinition"
 	  (org-test-with-temp-text "[fn:1] Definition\n\n  Definition"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Special case: Start indenting on a blank line.
+  (should
+   (equal "\nParagraph"
+	  (org-test-with-temp-text "\n  Paragraph"
 	    (org-indent-region (point-min) (point-max))
 	    (buffer-string)))))