emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [RFC] Rewrite indentation functions
@ 2014-04-30 13:03 Nicolas Goaziou
  2014-04-30 17:08 ` Vikas Rawal
                   ` (3 more replies)
  0 siblings, 4 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-04-30 13:03 UTC (permalink / raw)
  To: Org Mode List

[-- Attachment #1: Type: text/plain, Size: 1801 bytes --]

Hello,

I would like to install the following patches on master. Basically, they
consist of a full rewrite of all indentation related functions, with
explicit rules in docstrings, comprehensive test suites, and backed-up
by the parser.

The following changes in `org-indent-line' are expected:

  1. Indentation of the first line of an element should be, when
     applicable, relative to the /first line/ of the element before.
     Therefore, in the following example

         Some long paragraph
       with multiple line

       XAnother paragraph

     indenting line starting with "X" will align it with "Some", not
     "with".  This is consistent with plain lists

         - A list with some
           long paragraph

       XAnother paragraph

     where last line should be indented like "-", not "long".

  2. It should be possible to indent example block, verse block or
     export block contents, as `org-indent-line' usually happens on
     behalf of the user, who is assumed to know what he is doing.

     Though, this will not be the case in `org-indent-region', as
     changes could happen without the user knowing about it (e.g., when
     indenting a complete, mostly hidden, buffer).

  3. It should be possible to indent fixed-width areas.

`org-indent-region' also applies on hidden lines, with a few exceptions,
as explained above. Also, it should be a lot faster when
`org-src-tab-acts-natively' is non-nil, and complete without errors. It
could be made faster, but the main bottleneck in this function is now
`org-edit-src-code', which will need to be revamped at some point.

Internally, `org-src-native-tab-command-maybe' is merged into
`org-indent-line' since this should be a core feature, not something
installed via a hook.


WDYT?


Regards,

-- 
Nicolas Goaziou

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Rewrite-org-indent-line.patch --]
[-- Type: text/x-diff, Size: 17192 bytes --]

From 054e3e6e4d445da3e3c31283c7ed29dcb651b7ea Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Tue, 24 Dec 2013 14:04:17 +0100
Subject: [PATCH 1/3] Rewrite `org-indent-line'

* lisp/org.el (org--get-expected-indentation): New function.
(org-indent-line): Use new function.  Also merge functionalities with
`org-src-native-tab-command-maybe'.

* lisp/org-src.el (org-src-native-tab-command-maybe): Remove function.

* testing/lisp/test-org.el (test-org/indent-line): New test.
---
 lisp/org-src.el          |  11 --
 lisp/org.el              | 267 ++++++++++++++++++++++++++++-------------------
 testing/lisp/test-org.el | 136 ++++++++++++++++++++++++
 3 files changed, 295 insertions(+), 119 deletions(-)

diff --git a/lisp/org-src.el b/lisp/org-src.el
index 791f934..8d60f68 100644
--- a/lisp/org-src.el
+++ b/lisp/org-src.el
@@ -895,17 +895,6 @@ issued in the language major mode buffer."
   :version "24.1"
   :group 'org-babel)
 
-(defun org-src-native-tab-command-maybe ()
-  "Perform language-specific TAB action.
-Alter code block according to what TAB does in the language major mode."
-  (and org-src-tab-acts-natively
-       (org-in-src-block-p)
-       (not (equal this-command 'org-shifttab))
-       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
-	 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
-
-(add-hook 'org-tab-first-hook 'org-src-native-tab-command-maybe)
-
 (defun org-src-font-lock-fontify-block (lang start end)
   "Fontify code block.
 This function is called by emacs automatic fontification, as long
diff --git a/lisp/org.el b/lisp/org.el
index 44a4e44..f70c9c2 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22206,116 +22206,167 @@ hierarchy of headlines by UP levels before marking the subtree."
 
 ;;; Indentation
 
+(defun org--get-expected-indentation (element contentsp)
+  "Expected indentation column for current line, according to ELEMENT.
+ELEMENT is an element containing point.  CONTENTSP is non-nil
+when indentation is to be computed according to contents of
+ELEMENT."
+  (let ((type (org-element-type element))
+	(start (org-element-property :begin element)))
+    (org-with-wide-buffer
+     (cond
+      (contentsp
+       (case type
+	 (footnote-definition 0)
+	 ((headline inlinetask nil)
+	  (if (not org-adapt-indentation) 0
+	    (let ((level (org-current-level)))
+	      (if level (1+ level) 0))))
+	 ((item plain list)
+	  (org-list-item-body-column
+	   (or (org-element-property :post-affiliated element) start)))
+	 (otherwise
+	  (goto-char start)
+	  (org-get-indentation))))
+      ((memq type '(footnote-definition headline inlinetask nil)) 0)
+      ;; First paragraph of a footnote definition or an item.
+      ;; Indent like parent.
+      ((< (line-beginning-position) start)
+       (org--get-expected-indentation
+	(org-element-property :parent element) t))
+      ;; At first line: indent according to previous sibling, if
+      ;; any, or to parent's contents.
+      ((= (line-beginning-position) start)
+       (if (= (point-min) start) 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))))
+	   (if (or (not previous)
+		   (> (org-element-property :end previous) start))
+	       (org--get-expected-indentation previous t)
+	     (goto-char (org-element-property :begin previous))
+	     (org-get-indentation)))))
+      ;; Otherwise, move to the first non-blank line above.
+      (t
+       (beginning-of-line)
+       (let ((pos (point)))
+	 (skip-chars-backward " \r\t\n")
+	 (cond
+	  ;; Line above is the first one of a paragraph at the
+	  ;; beginning of an item or a footnote definition.  Indent
+	  ;; like parent.
+	  ((< (line-beginning-position) start)
+	   (org--get-expected-indentation
+	    (org-element-property :parent element) t))
+	  ;; Two blank lines end a footnote definition or a plain
+	  ;; list.  When we indent an empty line after them, the
+	  ;; containing list or footnote definition is over, so it
+	  ;; qualifies as a previous sibling.  Therefore, we indent
+	  ;; like its first line.
+	  ((and (memq type '(footnote-definition plain-list))
+		(> (count-lines (point) pos) 2))
+	   (goto-char start)
+	   (org-get-indentation))
+	  ;; POS is after contents in a greater element.  Indent like
+	  ;; the beginning of the element.
+	  ;;
+	  ;; As a special case, if point is at the end of a footnote
+	  ;; definition or an item, indent like the very last element
+	  ;; within.
+	  ((and (org-element-property :contents-end element)
+		(<= (org-element-property :contents-end element) pos))
+	   (if (memq type '(footnote-definition item plain-list))
+	       (org--get-expected-indentation (org-element-at-point) nil)
+	     (goto-char start)
+	     (org-get-indentation)))
+	  ;; In any other case, indent like the current line.
+	  (t (org-get-indentation)))))))))
+
 (defun org-indent-line ()
-  "Indent line depending on context."
+  "Indent line depending on context.
+
+Indentation is done according to the following rules:
+
+  - Footnote definitions, headlines and inline tasks have to
+    start at column 0.
+
+  - On the very first line of an element, consider, in order, the
+    next rules until one matches:
+
+    1. If there's a sibling element before, indent like its first
+       line.
+
+    2. If element has a parent, indent like its contents.  More
+       precisely, if parent is an item, indent after the
+       description part, if any, or the bullet (see
+       ``org-list-description-max-indent').  Else, indent like
+       parent's first line.
+
+    3. Otherwise, indent relatively to current level, if
+       `org-adapt-indentation' is non-nil, or to left margin.
+
+  - On a blank line at the end of a plain list, an item, or
+    a footnote definition, indent like the very last element
+    within.
+
+  - In the code part of a source block, use language major mode
+    to indent current line if `org-src-tab-acts-natively' is
+    non-nil.  If it is nil, do nothing.
+
+  - Otherwise, indent like the first non-blank line above.
+
+The function doesn't indent an item as it could break the whole
+list structure.  Instead, use \\<org-mode-map>\\[org-shiftmetaleft] or \
+\\[org-shiftmetaright].
+
+Also align node properties according to `org-property-format'."
   (interactive)
-  (let* ((pos (point))
-	 (itemp (org-at-item-p))
-	 (case-fold-search t)
-	 (org-drawer-regexp (or org-drawer-regexp "\000"))
-	 (inline-task-p (and (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)))
-	 (inline-re (and inline-task-p
-			 (org-inlinetask-outline-regexp)))
-	 column)
-    (if (and orgstruct-is-++ (eq pos (point)))
-	(let ((indent-line-function (cadadr (assoc 'indent-line-function org-fb-vars))))
-	  (indent-according-to-mode))
-      (beginning-of-line 1)
-      (cond
-       ;; Headings
-       ((looking-at org-outline-regexp) (setq column 0))
-       ;; Footnote definition
-       ((looking-at org-footnote-definition-re) (setq column 0))
-       ;; Literal examples
-       ((looking-at "[ \t]*:\\( \\|$\\)")
-	(setq column (org-get-indentation))) ; do nothing
-       ;; Lists
-       ((ignore-errors (goto-char (org-in-item-p)))
-	(setq column (if itemp
-			 (org-get-indentation)
-		       (org-list-item-body-column (point))))
-	(goto-char pos))
-       ;; Drawers
-       ((and (looking-at "[ \t]*:END:")
-	     (save-excursion (re-search-backward org-drawer-regexp nil t)))
-	(save-excursion
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column))))
-       ;; Special blocks
-       ((and (looking-at "[ \t]*#\\+end_\\([a-z]+\\)")
-	     (save-excursion
-	       (re-search-backward
-		(concat "^[ \t]*#\\+begin_" (downcase (match-string 1))) nil t)))
-	(setq column (org-get-indentation (match-string 0))))
-       ((and (not (looking-at "[ \t]*#\\+begin_"))
-	     (org-between-regexps-p "^[ \t]*#\\+begin_" "[ \t]*#\\+end_"))
-	(save-excursion
-	  (re-search-backward "^[ \t]*#\\+begin_\\([a-z]+\\)" nil t))
-	(setq column
-	      (cond ((equal (downcase (match-string 1)) "src")
-		     ;; src blocks: let `org-edit-src-exit' handle them
-		     (org-get-indentation))
-		    ((equal (downcase (match-string 1)) "example")
-		     (max (org-get-indentation)
-			  (org-get-indentation (match-string 0))))
-		    (t
-		     (org-get-indentation (match-string 0))))))
-       ;; This line has nothing special, look at the previous relevant
-       ;; line to compute indentation
-       (t
-	(beginning-of-line 0)
-	(while (and (not (bobp))
-		    (not (looking-at org-table-line-regexp))
-		    (not (looking-at org-drawer-regexp))
-		    ;; When point started in an inline task, do not move
-		    ;; above task starting line.
-		    (not (and inline-task-p (looking-at inline-re)))
-		    ;; Skip drawers, blocks, empty lines, verbatim,
-		    ;; comments, tables, footnotes definitions, lists,
-		    ;; inline tasks.
-		    (or (and (looking-at "[ \t]*:END:")
-			     (re-search-backward org-drawer-regexp nil t))
-			(and (looking-at "[ \t]*#\\+end_")
-			     (re-search-backward "[ \t]*#\\+begin_"nil t))
-			(looking-at "[ \t]*[\n:#|]")
-			(looking-at org-footnote-definition-re)
-			(and (not inline-task-p)
-			     (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)
-			     (or (org-inlinetask-goto-beginning) t))))
-	  (beginning-of-line 0))
-	(cond
-	 ;; There was a list item above.
-	 ((ignore-errors (goto-char (org-in-item-p)))
-	  (goto-char (org-list-get-top-point (org-list-struct)))
-	  (setq column (org-get-indentation)))
-	 ;; There was an heading above.
-	 ((looking-at "\\*+[ \t]+")
-	  (if (not org-adapt-indentation)
-	      (setq column 0)
-	    (goto-char (match-end 0))
-	    (setq column (current-column))))
-	 ;; A drawer had started and is unfinished
-	 ((looking-at org-drawer-regexp)
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column)))
-	 ;; Else, nothing noticeable found: get indentation and go on.
-	 (t (setq column (org-get-indentation))))))
-      ;; Now apply indentation and move cursor accordingly
-      (goto-char pos)
-      (if (<= (current-column) (current-indentation))
-	  (org-indent-line-to column)
-	(save-excursion (org-indent-line-to column)))
-      ;; Special polishing for properties, see `org-property-format'
-      (setq column (current-column))
-      (beginning-of-line 1)
-      (if (looking-at org-property-re)
-	  (replace-match (concat (match-string 4)
-				 (format org-property-format
-					 (match-string 1) (match-string 3)))
-			 t t))
-      (org-move-to-column column))))
+  (cond
+   (orgstruct-is-++
+    (let ((indent-line-function
+	   (cadadr (assq 'indent-line-function org-fb-vars))))
+      (indent-according-to-mode)))
+   ((org-at-heading-p) 'noindent)
+   (t
+    (let* ((element (save-excursion (beginning-of-line) (org-element-at-point)))
+	   (type (org-element-type element)))
+      (cond ((and (memq type '(plain-list item))
+		  (= (line-beginning-position)
+		     (or (org-element-property :post-affiliated element)
+			 (org-element-property :begin element))))
+	     'noindent)
+	    ((and (eq type 'src-block)
+		  (> (line-beginning-position)
+		     (org-element-property :post-affiliated element))
+		  (< (line-beginning-position)
+		     (org-with-wide-buffer
+		      (goto-char (org-element-property :end element))
+		      (skip-chars-backward " \r\t\n")
+		      (line-beginning-position))))
+	     (if (not org-src-tab-acts-natively) 'noindent
+	       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
+		 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
+	    (t
+	     (let ((column (org--get-expected-indentation element nil)))
+	       ;; Preserve current column.
+	       (if (<= (current-column) (current-indentation))
+		   (org-indent-line-to column)
+		 (save-excursion (org-indent-line-to column))))
+	     ;; Align node property.  Also preserve current column.
+	     (when (eq type 'node-property)
+	       (let ((column (current-column)))
+		 (save-excursion
+		   (beginning-of-line)
+		   (looking-at org-property-re))
+		 (replace-match (concat (match-string 4)
+					(format org-property-format
+						(match-string 1)
+						(match-string 3)))
+				t t)
+		 (org-move-to-column column)))))))))
 
 (defun org-indent-drawer ()
   "Indent the drawer at point."
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index e8d8078..3aea52e 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -441,6 +441,142 @@
 
 
 \f
+;;; Indentation
+
+(ert-deftest test-org/indent-line ()
+  "Test `org-indent-line' specifications."
+  ;; Do not indent footnote definitions or headlines.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H"
+      (org-indent-line)
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "[fn:1] fn"
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Do not indent before first headline.
+  (should
+   (zerop
+    (org-test-with-temp-text ""
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Indent according to headline level otherwise, unless
+  ;; `org-adapt-indentation' is nil.
+  (should
+   (= 2
+      (org-test-with-temp-text "* H\nA"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\nA"
+      (forward-line)
+      (let ((org-adapt-indentation nil)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Indenting preserves point position.
+  (should
+   (org-test-with-temp-text "* H\nAB"
+     (forward-line)
+     (forward-char)
+     (let ((org-adapt-indentation t)) (org-indent-line))
+     (looking-at "B")))
+  ;; Do not change indentation at an item.
+  (should
+   (= 1
+      (org-test-with-temp-text "* H\n - A"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  ;; On blank lines at the end of a list, indent like last element
+  ;; within it if the line is still in the list.  Otherwise, indent
+  ;; like the whole list.
+  (should
+   (= 4
+      (org-test-with-temp-text "* H\n- A\n  - AA\n"
+	(goto-char (point-max))
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n- A\n  - AA\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Likewise, on a blank line at the end of a footnote definition,
+  ;; indent at column 0 if line belongs to the definition.  Otherwise,
+  ;; indent like the definition itself.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; After the end of the contents of a greater element, indent like
+  ;; the beginning of the element.
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 2)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; At the first line of an element, indent like previous element's
+  ;; first line or according to parent.
+  (should
+   (= 2
+      (org-test-with-temp-text "A\n\n  B\n\nC"
+	(goto-char (point-max))
+	(org-indent-line)
+	(org-get-indentation))))
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 1)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; Within code part of a source block, use language major mode if
+  ;; `org-src-tab-acts-natively' is non-nil.  Do nothing otherwise.
+  (should
+   (= 6
+      (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	(forward-line 2)
+	(let ((org-src-tab-acts-natively t)
+	      (org-edit-src-content-indentation 0))
+	  (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+      (forward-line 2)
+      (let ((org-src-tab-acts-natively nil)
+	    (org-edit-src-content-indentation 0))
+	(org-indent-line))
+      (org-get-indentation))))
+  ;; Otherwise, indent like the first non-blank line above.
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_CENTER\nline1\n\n  line2\n#+END_CENTER"
+      (forward-line 3)
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Align node properties according to `org-property-format'.
+  (should
+   (equal ":PROPERTIES:\n:key:      value\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key: value\n:END:"
+	    (forward-line)
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-line)
+	      (buffer-string))))))
+
+\f
 ;;; Editing
 
 ;;;; Insert elements
-- 
1.9.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Rewrite-org-indent-region.patch --]
[-- Type: text/x-diff, Size: 7898 bytes --]

From 09aaee6e1bdc5bec6e57b9c0a12ac1d6ea5867d8 Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Sun, 27 Apr 2014 16:48:02 +0200
Subject: [PATCH 2/3] Rewrite `org-indent-region'

* lisp/org.el (org-indent-region): Update function according to recent
  `org-indent-line' change.  Optimize it.

* testing/lisp/test-org.el (test-org/indent-region): New test.
---
 lisp/org.el              | 105 +++++++++++++++++++++++++++++++++++++++++++----
 testing/lisp/test-org.el |  63 ++++++++++++++++++++++++++++
 2 files changed, 161 insertions(+), 7 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index f70c9c2..0ea5bc8 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22410,15 +22410,106 @@ Also align node properties according to `org-property-format'."
   (message "Block at point indented"))
 
 (defun org-indent-region (start end)
-  "Indent region."
+  "Indent each non-blank line in the region.
+Called from a program, START and END specify the region to
+indent.  The function will not indent contents of example blocks,
+verse blocks and export blocks as leading white spaces are
+assumed to be significant there."
   (interactive "r")
   (save-excursion
-    (let ((line-end (org-current-line end)))
-      (goto-char start)
-      (while (< (org-current-line) line-end)
-	(cond ((org-in-src-block-p t) (org-src-native-tab-command-maybe))
-	      (t (call-interactively 'org-indent-line)))
-	(move-beginning-of-line 2)))))
+    (goto-char start)
+    (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))))
+	       (while (< (point) limit)
+		 (unless (org-looking-at-p "[ \t]*$") (org-indent-line-to ind))
+		 (forward-line))
+	       (set-marker limit nil))))
+	  (end (copy-marker end)))
+      (while (< (point) end)
+	(if (or (org-looking-at-p " \r\t\n") (org-at-heading-p)) (forward-line)
+	  (let* ((element (org-element-at-point))
+		 (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)
+		    (looking-at org-property-re)
+		    (replace-match (concat (match-string 4)
+					   (format org-property-format
+						   (match-string 1)
+						   (match-string 3)))
+				   t t)
+		    (beginning-of-line))
+		  (funcall indent-to ind element-end))
+	      ;; Elements in this category consist of three parts:
+	      ;; before the contents, the contents, and after the
+	      ;; contents.  The contents are treated specially,
+	      ;; according to the element type, or not indented at
+	      ;; all.  Other parts are indented as a single block.
+	      (let* ((post (copy-marker
+			    (or (org-element-property :post-affiliated element)
+				(org-element-property :begin element))))
+		     (cbeg
+		      (copy-marker
+		       (cond
+			((not (org-element-property :contents-begin element))
+			 ;; Fake contents for source blocks.
+			 (org-with-wide-buffer
+			  (goto-char post)
+			  (forward-line)
+			  (point)))
+			((memq type '(footnote-definition item plain-list))
+			 ;; Contents in these elements could start on
+			 ;; the same line as the beginning of the
+			 ;; element.  Make sure we start indenting
+			 ;; from the second line.
+			 (org-with-wide-buffer
+			  (goto-char post)
+			  (forward-line)
+			  (point)))
+			(t (org-element-property :contents-begin element)))))
+		     (cend (copy-marker
+			    (or (org-element-property :contents-end element)
+				;; Fake contents for source blocks.
+				(org-with-wide-buffer
+				 (goto-char element-end)
+				 (skip-chars-backward " \r\t\n")
+				 (line-beginning-position))))))
+		(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
+		     ;; `org-src-tab-acts-natively' is non-nil.
+		     (when (and (< (point) end) org-src-tab-acts-natively)
+		       (ignore-errors
+			 (let (org-src-strip-leading-and-trailing-blank-lines
+			       ;; Region boundaries in edit buffer.
+			       (start (1+ (- (point) cbeg)))
+			       (end (- (min cend end) cbeg)))
+			   (org-babel-do-in-edit-buffer
+			    (indent-region start end))))))
+		    (t (org-indent-region (point) (min cend end))))
+		  (goto-char (min cend end))
+		  (when (< (point) end) (funcall indent-to ind element-end)))
+		(set-marker post nil)
+		(set-marker cbeg nil)
+		(set-marker cend nil)))
+	    (set-marker element-end nil))))
+      (set-marker end nil))))
 
 
 ;;; Filling
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 3aea52e..d88c235 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -576,6 +576,69 @@
 	      (org-indent-line)
 	      (buffer-string))))))
 
+(ert-deftest test-org/indent-region ()
+  "Test `org-indent-region' specifications."
+  ;; Indent paragraph.
+  (should
+   (equal "A\nB\nC"
+	  (org-test-with-temp-text " A\nB\n  C"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Indent greater elements along with their contents.
+  (should
+   (equal "#+BEGIN_CENTER\nA\nB\n#+END_CENTER"
+	  (org-test-with-temp-text "#+BEGIN_CENTER\n A\n  B\n#+END_CENTER"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Ignore contents of verse blocks and example blocks.
+  (should
+   (equal "#+BEGIN_VERSE\n A\n  B\n#+END_VERSE"
+	  (org-test-with-temp-text "#+BEGIN_VERSE\n A\n  B\n#+END_VERSE"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  (should
+   (equal "#+BEGIN_EXAMPLE\n A\n  B\n#+END_EXAMPLE"
+	  (org-test-with-temp-text "#+BEGIN_EXAMPLE\n A\n  B\n#+END_EXAMPLE"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Indent according to mode if `org-src-tab-acts-natively' is
+  ;; non-nil.  Otherwise, do not indent code at all.
+  (should
+   (equal "#+BEGIN_SRC emacs-lisp\n(and A\n     B)\n#+END_SRC"
+	  (org-test-with-temp-text
+	      "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	    (let ((org-src-tab-acts-natively t)
+		  (org-edit-src-content-indentation 0))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  (should
+   (equal "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	  (org-test-with-temp-text
+	      "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	    (let ((org-src-tab-acts-natively nil)
+		  (org-edit-src-content-indentation 0))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  ;; Align node properties according to `org-property-format'.
+  (should
+   (equal ":PROPERTIES:\n:key:      value\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key: value\n:END:"
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  ;; Special case: plain lists and footnote definitions.
+  (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 "[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)))))
+
+
 \f
 ;;; Editing
 
-- 
1.9.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-Rewrite-org-indent-drawer-and-org-indent-block.patch --]
[-- Type: text/x-diff, Size: 3440 bytes --]

From c7a9e0475f5aaedd9feccddbdfcbcb524c872aef Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Mon, 28 Apr 2014 18:38:31 +0200
Subject: [PATCH 3/3] Rewrite `org-indent-drawer' and `org-indent-block'

* lisp/org.el (org-indent-block, org-indent-drawer): Rewrite functions.
---
 lisp/org.el | 75 ++++++++++++++++++++++++++++---------------------------------
 1 file changed, 34 insertions(+), 41 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 0ea5bc8..b5b2cf8 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22368,47 +22368,6 @@ Also align node properties according to `org-property-format'."
 				t t)
 		 (org-move-to-column column)))))))))
 
-(defun org-indent-drawer ()
-  "Indent the drawer at point."
-  (interactive)
-  (let ((p (point))
-	(e (and (save-excursion (re-search-forward ":END:" nil t))
-		(match-end 0)))
-	(folded
-	 (save-excursion
-	   (end-of-line)
-	   (when (overlays-at (point))
-	     (member 'invisible (overlay-properties
-				 (car (overlays-at (point)))))))))
-    (when folded (org-cycle))
-    (indent-for-tab-command)
-    (while (and (move-beginning-of-line 2) (< (point) e))
-      (indent-for-tab-command))
-    (goto-char p)
-    (when folded (org-cycle)))
-  (message "Drawer at point indented"))
-
-(defun org-indent-block ()
-  "Indent the block at point."
-  (interactive)
-  (let ((p (point))
-	(case-fold-search t)
-	(e (and (save-excursion (re-search-forward "#\\+end_?\\(?:[a-z]+\\)?" nil t))
-		(match-end 0)))
-	(folded
-	 (save-excursion
-	   (end-of-line)
-	   (when (overlays-at (point))
-	     (member 'invisible (overlay-properties
-				 (car (overlays-at (point)))))))))
-    (when folded (org-cycle))
-    (indent-for-tab-command)
-    (while (and (move-beginning-of-line 2) (< (point) e))
-      (indent-for-tab-command))
-    (goto-char p)
-    (when folded (org-cycle)))
-  (message "Block at point indented"))
-
 (defun org-indent-region (start end)
   "Indent each non-blank line in the region.
 Called from a program, START and END specify the region to
@@ -22511,6 +22470,40 @@ assumed to be significant there."
 	    (set-marker element-end nil))))
       (set-marker end nil))))
 
+(defun org-indent-drawer ()
+  "Indent the drawer at point."
+  (interactive)
+  (unless (save-excursion
+	    (beginning-of-line)
+	    (org-looking-at-p org-drawer-regexp))
+    (user-error "Not at a drawer"))
+  (let ((element (org-element-at-point)))
+    (unless (memq (org-element-type element) '(drawer property-drawer))
+      (user-error "Not at a drawer"))
+    (org-with-wide-buffer
+     (org-indent-region (org-element-property :begin element)
+			(org-element-property :end element))))
+  (message "Drawer at point indented"))
+
+(defun org-indent-block ()
+  "Indent the block at point."
+  (interactive)
+  (unless (save-excursion
+	    (beginning-of-line)
+	    (let ((case-fold-search t))
+	      (org-looking-at-p "[ \t]*#\\+\\(begin\\|end\\)_")))
+    (user-error "Not at a block"))
+  (let ((element (org-element-at-point)))
+    (unless (memq (org-element-type element)
+		  '(comment-block center-block example-block export-block
+				  quote-block special-block src-block
+				  verse-block))
+      (user-error "Not at a block"))
+    (org-with-wide-buffer
+     (org-indent-region (org-element-property :begin element)
+			(org-element-property :end element))))
+  (message "Block at point indented"))
+
 
 ;;; Filling
 
-- 
1.9.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-04-30 13:03 [RFC] Rewrite indentation functions Nicolas Goaziou
@ 2014-04-30 17:08 ` Vikas Rawal
  2014-04-30 18:59   ` Sebastien Vauban
  2014-05-01 19:11 ` Nicolas Goaziou
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 18+ messages in thread
From: Vikas Rawal @ 2014-04-30 17:08 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: org-mode mailing list

> 
>  1. Indentation of the first line of an element should be, when
>     applicable, relative to the /first line/ of the element before.
>     Therefore, in the following example
> 
>         Some long paragraph
>       with multiple line
> 
>       XAnother paragraph
> 

It is almost surely something that you must have thought of. 

You seem to be using X as a special character. What if a paragraph starts with X? Would you want to use a double X? 

It does not seem very elegant to use X. Would it break ispell? Can we have a space between X and the actual first word of the paragraph (as we do with *s for headlines and -/+ for lists)? And could one use another character?

Vikas

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-04-30 17:08 ` Vikas Rawal
@ 2014-04-30 18:59   ` Sebastien Vauban
  0 siblings, 0 replies; 18+ messages in thread
From: Sebastien Vauban @ 2014-04-30 18:59 UTC (permalink / raw)
  To: emacs-orgmode-mXXj517/zsQ

Vikas Rawal wrote:
>> 
>>  1. Indentation of the first line of an element should be, when
>>     applicable, relative to the /first line/ of the element before.
>>     Therefore, in the following example
>> 
>>         Some long paragraph
>>       with multiple line
>> 
>>       XAnother paragraph
>> 
>
> It is almost surely something that you must have thought of. 
>
> You seem to be using X as a special character. What if a paragraph
> starts with X? Would you want to use a double X?
>
> It does not seem very elegant to use X. Would it break ispell? Can we
> have a space between X and the actual first word of the paragraph (as
> we do with *s for headlines and -/+ for lists)? And could one use
> another character?

IIUC, you miss the legend "X = position of cursor"...

Best regards,
  Seb

-- 
Sebastien Vauban

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-04-30 13:03 [RFC] Rewrite indentation functions Nicolas Goaziou
  2014-04-30 17:08 ` Vikas Rawal
@ 2014-05-01 19:11 ` Nicolas Goaziou
  2014-05-02  5:38 ` Eric Abrahamsen
  2014-05-06  9:41 ` Bastien
  3 siblings, 0 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-01 19:11 UTC (permalink / raw)
  To: Org Mode List

[-- Attachment #1: Type: text/plain, Size: 642 bytes --]

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> I would like to install the following patches on master. Basically, they
> consist of a full rewrite of all indentation related functions, with
> explicit rules in docstrings, comprehensive test suites, and backed-up
> by the parser.

Here's an update for the first patch, in order to fix behaviour after
a footnote definition or an inline task. More specifically, in the
following example,

  * Headline
  
    Some

  [fn:1] Definition


  XParagraph

line with point at "X" should be indented like "Some", since it doesn't
belong to the footnote definition.


Regards,

-- 
Nicolas Goaziou

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Rewrite-org-indent-line.patch --]
[-- Type: text/x-diff, Size: 17750 bytes --]

From 283588780f3ee64c87b336ff65fc758047687923 Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Tue, 24 Dec 2013 14:04:17 +0100
Subject: [PATCH 1/3] Rewrite `org-indent-line'

* lisp/org.el (org--get-expected-indentation): New function.
(org-indent-line): Use new function.  Also merge functionalities with
`org-src-native-tab-command-maybe'.

* lisp/org-src.el (org-src-native-tab-command-maybe): Remove function.

* testing/lisp/test-org.el (test-org/indent-line): New test.
---
 lisp/org-src.el          |  11 --
 lisp/org.el              | 273 ++++++++++++++++++++++++++++-------------------
 testing/lisp/test-org.el | 143 +++++++++++++++++++++++++
 3 files changed, 308 insertions(+), 119 deletions(-)

diff --git a/lisp/org-src.el b/lisp/org-src.el
index 791f934..8d60f68 100644
--- a/lisp/org-src.el
+++ b/lisp/org-src.el
@@ -895,17 +895,6 @@ issued in the language major mode buffer."
   :version "24.1"
   :group 'org-babel)
 
-(defun org-src-native-tab-command-maybe ()
-  "Perform language-specific TAB action.
-Alter code block according to what TAB does in the language major mode."
-  (and org-src-tab-acts-natively
-       (org-in-src-block-p)
-       (not (equal this-command 'org-shifttab))
-       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
-	 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
-
-(add-hook 'org-tab-first-hook 'org-src-native-tab-command-maybe)
-
 (defun org-src-font-lock-fontify-block (lang start end)
   "Fontify code block.
 This function is called by emacs automatic fontification, as long
diff --git a/lisp/org.el b/lisp/org.el
index 44a4e44..3db6e86 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22206,116 +22206,173 @@ hierarchy of headlines by UP levels before marking the subtree."
 
 ;;; Indentation
 
+(defun org--get-expected-indentation (element contentsp)
+  "Expected indentation column for current line, according to ELEMENT.
+ELEMENT is an element containing point.  CONTENTSP is non-nil
+when indentation is to be computed according to contents of
+ELEMENT."
+  (let ((type (org-element-type element))
+	(start (org-element-property :begin element)))
+    (org-with-wide-buffer
+     (cond
+      (contentsp
+       (case type
+	 (footnote-definition 0)
+	 ((headline inlinetask nil)
+	  (if (not org-adapt-indentation) 0
+	    (let ((level (org-current-level)))
+	      (if level (1+ level) 0))))
+	 ((item plain list)
+	  (org-list-item-body-column
+	   (or (org-element-property :post-affiliated element) start)))
+	 (otherwise
+	  (goto-char start)
+	  (org-get-indentation))))
+      ((memq type '(footnote-definition headline inlinetask nil)) 0)
+      ;; First paragraph of a footnote definition or an item.
+      ;; Indent like parent.
+      ((< (line-beginning-position) start)
+       (org--get-expected-indentation
+	(org-element-property :parent element) t))
+      ;; At first line: indent according to previous sibling, if any,
+      ;; ignoring footnote definitions and inline tasks, or parent's
+      ;; contents.
+      ((= (line-beginning-position) start)
+       (catch 'exit
+	 (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)))))))))
+      ;; Otherwise, move to the first non-blank line above.
+      (t
+       (beginning-of-line)
+       (let ((pos (point)))
+	 (skip-chars-backward " \r\t\n")
+	 (cond
+	  ;; Two blank lines end a footnote definition or a plain
+	  ;; list.  When we indent an empty line after them, the
+	  ;; containing list or footnote definition is over, so it
+	  ;; qualifies as a previous sibling.  Therefore, we indent
+	  ;; like its first line.
+	  ((and (memq type '(footnote-definition plain-list))
+		(> (count-lines (point) pos) 2))
+	   (goto-char start)
+	   (org-get-indentation))
+	  ;; Line above is the first one of a paragraph at the
+	  ;; beginning of an item or a footnote definition.  Indent
+	  ;; like parent.
+	  ((< (line-beginning-position) start)
+	   (org--get-expected-indentation
+	    (org-element-property :parent element) t))
+	  ;; POS is after contents in a greater element.  Indent like
+	  ;; the beginning of the element.
+	  ;;
+	  ;; As a special case, if point is at the end of a footnote
+	  ;; definition or an item, indent like the very last element
+	  ;; within.
+	  ((and (org-element-property :contents-end element)
+		(<= (org-element-property :contents-end element) pos))
+	   (if (memq type '(footnote-definition item plain-list))
+	       (org--get-expected-indentation (org-element-at-point) nil)
+	     (goto-char start)
+	     (org-get-indentation)))
+	  ;; In any other case, indent like the current line.
+	  (t (org-get-indentation)))))))))
+
 (defun org-indent-line ()
-  "Indent line depending on context."
+  "Indent line depending on context.
+
+Indentation is done according to the following rules:
+
+  - Footnote definitions, headlines and inline tasks have to
+    start at column 0.
+
+  - On the very first line of an element, consider, in order, the
+    next rules until one matches:
+
+    1. If there's a sibling element before, ignoring footnote
+       definitions and inline tasks, indent like its first line.
+
+    2. If element has a parent, indent like its contents.  More
+       precisely, if parent is an item, indent after the
+       description part, if any, or the bullet (see
+       ``org-list-description-max-indent').  Else, indent like
+       parent's first line.
+
+    3. Otherwise, indent relatively to current level, if
+       `org-adapt-indentation' is non-nil, or to left margin.
+
+  - On a blank line at the end of a plain list, an item, or
+    a footnote definition, indent like the very last element
+    within.
+
+  - In the code part of a source block, use language major mode
+    to indent current line if `org-src-tab-acts-natively' is
+    non-nil.  If it is nil, do nothing.
+
+  - Otherwise, indent like the first non-blank line above.
+
+The function doesn't indent an item as it could break the whole
+list structure.  Instead, use \\<org-mode-map>\\[org-shiftmetaleft] or \
+\\[org-shiftmetaright].
+
+Also align node properties according to `org-property-format'."
   (interactive)
-  (let* ((pos (point))
-	 (itemp (org-at-item-p))
-	 (case-fold-search t)
-	 (org-drawer-regexp (or org-drawer-regexp "\000"))
-	 (inline-task-p (and (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)))
-	 (inline-re (and inline-task-p
-			 (org-inlinetask-outline-regexp)))
-	 column)
-    (if (and orgstruct-is-++ (eq pos (point)))
-	(let ((indent-line-function (cadadr (assoc 'indent-line-function org-fb-vars))))
-	  (indent-according-to-mode))
-      (beginning-of-line 1)
-      (cond
-       ;; Headings
-       ((looking-at org-outline-regexp) (setq column 0))
-       ;; Footnote definition
-       ((looking-at org-footnote-definition-re) (setq column 0))
-       ;; Literal examples
-       ((looking-at "[ \t]*:\\( \\|$\\)")
-	(setq column (org-get-indentation))) ; do nothing
-       ;; Lists
-       ((ignore-errors (goto-char (org-in-item-p)))
-	(setq column (if itemp
-			 (org-get-indentation)
-		       (org-list-item-body-column (point))))
-	(goto-char pos))
-       ;; Drawers
-       ((and (looking-at "[ \t]*:END:")
-	     (save-excursion (re-search-backward org-drawer-regexp nil t)))
-	(save-excursion
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column))))
-       ;; Special blocks
-       ((and (looking-at "[ \t]*#\\+end_\\([a-z]+\\)")
-	     (save-excursion
-	       (re-search-backward
-		(concat "^[ \t]*#\\+begin_" (downcase (match-string 1))) nil t)))
-	(setq column (org-get-indentation (match-string 0))))
-       ((and (not (looking-at "[ \t]*#\\+begin_"))
-	     (org-between-regexps-p "^[ \t]*#\\+begin_" "[ \t]*#\\+end_"))
-	(save-excursion
-	  (re-search-backward "^[ \t]*#\\+begin_\\([a-z]+\\)" nil t))
-	(setq column
-	      (cond ((equal (downcase (match-string 1)) "src")
-		     ;; src blocks: let `org-edit-src-exit' handle them
-		     (org-get-indentation))
-		    ((equal (downcase (match-string 1)) "example")
-		     (max (org-get-indentation)
-			  (org-get-indentation (match-string 0))))
-		    (t
-		     (org-get-indentation (match-string 0))))))
-       ;; This line has nothing special, look at the previous relevant
-       ;; line to compute indentation
-       (t
-	(beginning-of-line 0)
-	(while (and (not (bobp))
-		    (not (looking-at org-table-line-regexp))
-		    (not (looking-at org-drawer-regexp))
-		    ;; When point started in an inline task, do not move
-		    ;; above task starting line.
-		    (not (and inline-task-p (looking-at inline-re)))
-		    ;; Skip drawers, blocks, empty lines, verbatim,
-		    ;; comments, tables, footnotes definitions, lists,
-		    ;; inline tasks.
-		    (or (and (looking-at "[ \t]*:END:")
-			     (re-search-backward org-drawer-regexp nil t))
-			(and (looking-at "[ \t]*#\\+end_")
-			     (re-search-backward "[ \t]*#\\+begin_"nil t))
-			(looking-at "[ \t]*[\n:#|]")
-			(looking-at org-footnote-definition-re)
-			(and (not inline-task-p)
-			     (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)
-			     (or (org-inlinetask-goto-beginning) t))))
-	  (beginning-of-line 0))
-	(cond
-	 ;; There was a list item above.
-	 ((ignore-errors (goto-char (org-in-item-p)))
-	  (goto-char (org-list-get-top-point (org-list-struct)))
-	  (setq column (org-get-indentation)))
-	 ;; There was an heading above.
-	 ((looking-at "\\*+[ \t]+")
-	  (if (not org-adapt-indentation)
-	      (setq column 0)
-	    (goto-char (match-end 0))
-	    (setq column (current-column))))
-	 ;; A drawer had started and is unfinished
-	 ((looking-at org-drawer-regexp)
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column)))
-	 ;; Else, nothing noticeable found: get indentation and go on.
-	 (t (setq column (org-get-indentation))))))
-      ;; Now apply indentation and move cursor accordingly
-      (goto-char pos)
-      (if (<= (current-column) (current-indentation))
-	  (org-indent-line-to column)
-	(save-excursion (org-indent-line-to column)))
-      ;; Special polishing for properties, see `org-property-format'
-      (setq column (current-column))
-      (beginning-of-line 1)
-      (if (looking-at org-property-re)
-	  (replace-match (concat (match-string 4)
-				 (format org-property-format
-					 (match-string 1) (match-string 3)))
-			 t t))
-      (org-move-to-column column))))
+  (cond
+   (orgstruct-is-++
+    (let ((indent-line-function
+	   (cadadr (assq 'indent-line-function org-fb-vars))))
+      (indent-according-to-mode)))
+   ((org-at-heading-p) 'noindent)
+   (t
+    (let* ((element (save-excursion (beginning-of-line) (org-element-at-point)))
+	   (type (org-element-type element)))
+      (cond ((and (memq type '(plain-list item))
+		  (= (line-beginning-position)
+		     (or (org-element-property :post-affiliated element)
+			 (org-element-property :begin element))))
+	     'noindent)
+	    ((and (eq type 'src-block)
+		  (> (line-beginning-position)
+		     (org-element-property :post-affiliated element))
+		  (< (line-beginning-position)
+		     (org-with-wide-buffer
+		      (goto-char (org-element-property :end element))
+		      (skip-chars-backward " \r\t\n")
+		      (line-beginning-position))))
+	     (if (not org-src-tab-acts-natively) 'noindent
+	       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
+		 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
+	    (t
+	     (let ((column (org--get-expected-indentation element nil)))
+	       ;; Preserve current column.
+	       (if (<= (current-column) (current-indentation))
+		   (org-indent-line-to column)
+		 (save-excursion (org-indent-line-to column))))
+	     ;; Align node property.  Also preserve current column.
+	     (when (eq type 'node-property)
+	       (let ((column (current-column)))
+		 (save-excursion
+		   (beginning-of-line)
+		   (looking-at org-property-re))
+		 (replace-match (concat (match-string 4)
+					(format org-property-format
+						(match-string 1)
+						(match-string 3)))
+				t t)
+		 (org-move-to-column column)))))))))
 
 (defun org-indent-drawer ()
   "Indent the drawer at point."
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index e8d8078..521d8e4 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -441,6 +441,149 @@
 
 
 \f
+;;; Indentation
+
+(ert-deftest test-org/indent-line ()
+  "Test `org-indent-line' specifications."
+  ;; Do not indent footnote definitions or headlines.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H"
+      (org-indent-line)
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "[fn:1] fn"
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Do not indent before first headline.
+  (should
+   (zerop
+    (org-test-with-temp-text ""
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Indent according to headline level otherwise, unless
+  ;; `org-adapt-indentation' is nil.
+  (should
+   (= 2
+      (org-test-with-temp-text "* H\nA"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\nA"
+      (forward-line)
+      (let ((org-adapt-indentation nil)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Indenting preserves point position.
+  (should
+   (org-test-with-temp-text "* H\nAB"
+     (forward-line)
+     (forward-char)
+     (let ((org-adapt-indentation t)) (org-indent-line))
+     (looking-at "B")))
+  ;; Do not change indentation at an item.
+  (should
+   (= 1
+      (org-test-with-temp-text "* H\n - A"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  ;; On blank lines at the end of a list, indent like last element
+  ;; within it if the line is still in the list.  Otherwise, indent
+  ;; like the whole list.
+  (should
+   (= 4
+      (org-test-with-temp-text "* H\n- A\n  - AA\n"
+	(goto-char (point-max))
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n- A\n  - AA\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Likewise, on a blank line at the end of a footnote definition,
+  ;; indent at column 0 if line belongs to the definition.  Otherwise,
+  ;; indent like the definition itself.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; After the end of the contents of a greater element, indent like
+  ;; the beginning of the element.
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 2)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; At the first line of an element, indent like previous element's
+  ;; first line, ignoring footnotes definitions and inline tasks, or
+  ;; according to parent.
+  (should
+   (= 2
+      (org-test-with-temp-text "A\n\n  B\n\nC"
+	(goto-char (point-max))
+	(org-indent-line)
+	(org-get-indentation))))
+  (should
+   (= 1
+      (org-test-with-temp-text " A\n\n[fn:1] B\n\n\nC"
+	(goto-char (point-max))
+	(org-indent-line)
+	(org-get-indentation))))
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 1)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; Within code part of a source block, use language major mode if
+  ;; `org-src-tab-acts-natively' is non-nil.  Do nothing otherwise.
+  (should
+   (= 6
+      (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	(forward-line 2)
+	(let ((org-src-tab-acts-natively t)
+	      (org-edit-src-content-indentation 0))
+	  (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+      (forward-line 2)
+      (let ((org-src-tab-acts-natively nil)
+	    (org-edit-src-content-indentation 0))
+	(org-indent-line))
+      (org-get-indentation))))
+  ;; Otherwise, indent like the first non-blank line above.
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_CENTER\nline1\n\n  line2\n#+END_CENTER"
+      (forward-line 3)
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Align node properties according to `org-property-format'.
+  (should
+   (equal ":PROPERTIES:\n:key:      value\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key: value\n:END:"
+	    (forward-line)
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-line)
+	      (buffer-string))))))
+
+\f
 ;;; Editing
 
 ;;;; Insert elements
-- 
1.9.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-04-30 13:03 [RFC] Rewrite indentation functions Nicolas Goaziou
  2014-04-30 17:08 ` Vikas Rawal
  2014-05-01 19:11 ` Nicolas Goaziou
@ 2014-05-02  5:38 ` Eric Abrahamsen
  2014-05-02  7:32   ` Nicolas Goaziou
  2014-05-06  9:41 ` Bastien
  3 siblings, 1 reply; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-02  5:38 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> I would like to install the following patches on master. Basically, they
> consist of a full rewrite of all indentation related functions, with
> explicit rules in docstrings, comprehensive test suites, and backed-up
> by the parser.

Wish I was competent to actually review this, but... In lieu of that,
I'd be happy to run it and report errors. If you think a separate
testing branch is warranted, that might be an idea. Otherwise I'd say
let it drop and we'll pick up the pieces :)

> The following changes in `org-indent-line' are expected:
>
>   1. Indentation of the first line of an element should be, when
>      applicable, relative to the /first line/ of the element before.
>      Therefore, in the following example
>
>          Some long paragraph
>        with multiple line
>
>        XAnother paragraph
>
>      indenting line starting with "X" will align it with "Some", not
>      "with".  This is consistent with plain lists
>
>          - A list with some
>            long paragraph
>
>        XAnother paragraph
>
>      where last line should be indented like "-", not "long".
>
>   2. It should be possible to indent example block, verse block or
>      export block contents, as `org-indent-line' usually happens on
>      behalf of the user, who is assumed to know what he is doing.
>
>      Though, this will not be the case in `org-indent-region', as
>      changes could happen without the user knowing about it (e.g., when
>      indenting a complete, mostly hidden, buffer).
>
>   3. It should be possible to indent fixed-width areas.
>
> `org-indent-region' also applies on hidden lines, with a few exceptions,
> as explained above. Also, it should be a lot faster when
> `org-src-tab-acts-natively' is non-nil, and complete without errors. It
> could be made faster, but the main bottleneck in this function is now
> `org-edit-src-code', which will need to be revamped at some point.
>
> Internally, `org-src-native-tab-command-maybe' is merged into
> `org-indent-line' since this should be a core feature, not something
> installed via a hook.
>
>
> WDYT?
>
>
> Regards,

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-02  5:38 ` Eric Abrahamsen
@ 2014-05-02  7:32   ` Nicolas Goaziou
  2014-05-02 10:01     ` Eric Abrahamsen
  2014-05-03  7:47     ` Eric Abrahamsen
  0 siblings, 2 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-02  7:32 UTC (permalink / raw)
  To: Eric Abrahamsen; +Cc: emacs-orgmode

Hello,

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> Wish I was competent to actually review this, but... In lieu of that,
> I'd be happy to run it and report errors. If you think a separate
> testing branch is warranted, that might be an idea. Otherwise I'd say
> let it drop and we'll pick up the pieces :)

You can create a local branch in your git repo and apply the patches
I sent (be sure to use the second version of the first patch) there.

I can certainly wait for your feedback. If it turns out to be mostly
good and no one objects, I will then apply the patches and fix the
remnant issues on master branch.

Thank you.


Regards,

-- 
Nicolas Goaziou

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-02  7:32   ` Nicolas Goaziou
@ 2014-05-02 10:01     ` Eric Abrahamsen
  2014-05-03  7:47     ` Eric Abrahamsen
  1 sibling, 0 replies; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-02 10:01 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>> Wish I was competent to actually review this, but... In lieu of that,
>> I'd be happy to run it and report errors. If you think a separate
>> testing branch is warranted, that might be an idea. Otherwise I'd say
>> let it drop and we'll pick up the pieces :)
>
> You can create a local branch in your git repo and apply the patches
> I sent (be sure to use the second version of the first patch) there.
>
> I can certainly wait for your feedback. If it turns out to be mostly
> good and no one objects, I will then apply the patches and fix the
> remnant issues on master branch.

Done! I'll try to give it a little exercise over the next couple of
days, though I guess I'm not expecting much breakage.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-02  7:32   ` Nicolas Goaziou
  2014-05-02 10:01     ` Eric Abrahamsen
@ 2014-05-03  7:47     ` Eric Abrahamsen
  2014-05-03  8:47       ` Eric Abrahamsen
  1 sibling, 1 reply; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-03  7:47 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>> Wish I was competent to actually review this, but... In lieu of that,
>> I'd be happy to run it and report errors. If you think a separate
>> testing branch is warranted, that might be an idea. Otherwise I'd say
>> let it drop and we'll pick up the pieces :)
>
> You can create a local branch in your git repo and apply the patches
> I sent (be sure to use the second version of the first patch) there.
>
> I can certainly wait for your feedback. If it turns out to be mostly
> good and no one objects, I will then apply the patches and fix the
> remnant issues on master branch.

Hi Nicolas,

Right now I'm seeing breakage with `org-set-property' -- this only
happens on the indentation patches branch. Adding an EXPORT_AUTHOR
property with that command, value of "asdfadsf", gives me this:

* Test Heading
  :PROPERTIES:

  :EXPORT_AUTHOR: asdfasdfnil        nil

Extra blank nil, spurious "nils", and no :END:

I'm starting dinner and won't be able to go spelunking right now. I'll
get to soon, if you aren't seeing this, or don't sort it out first.

Eric

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-03  7:47     ` Eric Abrahamsen
@ 2014-05-03  8:47       ` Eric Abrahamsen
  2014-05-03 11:47         ` Nicolas Goaziou
  0 siblings, 1 reply; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-03  8:47 UTC (permalink / raw)
  To: emacs-orgmode

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> Nicolas Goaziou <n.goaziou@gmail.com> writes:
>
>> Hello,
>>
>> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>>
>>> Wish I was competent to actually review this, but... In lieu of that,
>>> I'd be happy to run it and report errors. If you think a separate
>>> testing branch is warranted, that might be an idea. Otherwise I'd say
>>> let it drop and we'll pick up the pieces :)
>>
>> You can create a local branch in your git repo and apply the patches
>> I sent (be sure to use the second version of the first patch) there.
>>
>> I can certainly wait for your feedback. If it turns out to be mostly
>> good and no one objects, I will then apply the patches and fix the
>> remnant issues on master branch.
>
> Hi Nicolas,
>
> Right now I'm seeing breakage with `org-set-property' -- this only
> happens on the indentation patches branch. Adding an EXPORT_AUTHOR
> property with that command, value of "asdfadsf", gives me this:
>
> * Test Heading
>   :PROPERTIES:
>
>   :EXPORT_AUTHOR: asdfasdfnil        nil
>
> Extra blank nil, spurious "nils", and no :END:
>
> I'm starting dinner and won't be able to go spelunking right now. I'll
> get to soon, if you aren't seeing this, or don't sort it out first.
>
> Eric

Specifically, in this section of `org-indent-line':

(when (eq type 'node-property)
	       (let ((column (current-column)))
		 (save-excursion
		   (beginning-of-line)
		   (looking-at org-property-re))
		 (replace-match (concat (match-string 4)
					(format org-property-format
						(match-string 1)
						(match-string 3)))
				t t)
		 (org-move-to-column column)))

Those match-string calls toward the end both return "nil", and the
"nil"s get inserted directly into the buffer. I tried this with a
minimal setup (load-paths only, and a blank Org file) and could
reproduce.

E

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-03  8:47       ` Eric Abrahamsen
@ 2014-05-03 11:47         ` Nicolas Goaziou
  2014-05-04  3:25           ` Eric Abrahamsen
                             ` (2 more replies)
  0 siblings, 3 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-03 11:47 UTC (permalink / raw)
  To: Eric Abrahamsen; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1126 bytes --]

Hello,

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

>> Right now I'm seeing breakage with `org-set-property' -- this only
>> happens on the indentation patches branch. Adding an EXPORT_AUTHOR
>> property with that command, value of "asdfadsf", gives me this:
>>
>> * Test Heading
>>   :PROPERTIES:
>>
>>   :EXPORT_AUTHOR: asdfasdfnil        nil
>>
>> Extra blank nil, spurious "nils", and no :END:

[...]

> Specifically, in this section of `org-indent-line':
>
> (when (eq type 'node-property)
> 	       (let ((column (current-column)))
> 		 (save-excursion
> 		   (beginning-of-line)
> 		   (looking-at org-property-re))
> 		 (replace-match (concat (match-string 4)
> 					(format org-property-format
> 						(match-string 1)
> 						(match-string 3)))
> 				t t)
> 		 (org-move-to-column column)))
>
> Those match-string calls toward the end both return "nil", and the
> "nil"s get inserted directly into the buffer. I tried this with a
> minimal setup (load-paths only, and a blank Org file) and could
> reproduce.

Indeed. I attach a replacement for both patch 1 and 2.

Thank you.


Regards,

-- 
Nicolas Goaziou

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Rewrite-org-indent-line.patch --]
[-- Type: text/x-diff, Size: 18503 bytes --]

From 104a091d127b746662adfa7b3f71a602ef5f816e Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Tue, 24 Dec 2013 14:04:17 +0100
Subject: [PATCH 1/3] Rewrite `org-indent-line'

* lisp/org.el (org--get-expected-indentation,
  org--align-node-property): New functions.
(org-indent-line): Use new function.  Also merge functionalities with
`org-src-native-tab-command-maybe'.

* lisp/org-src.el (org-src-native-tab-command-maybe): Remove function.

* testing/lisp/test-org.el (test-org/indent-line): New test.
---
 lisp/org-src.el          |  11 --
 lisp/org.el              | 281 +++++++++++++++++++++++++++++------------------
 testing/lisp/test-org.el | 157 ++++++++++++++++++++++++++
 3 files changed, 330 insertions(+), 119 deletions(-)

diff --git a/lisp/org-src.el b/lisp/org-src.el
index 791f934..8d60f68 100644
--- a/lisp/org-src.el
+++ b/lisp/org-src.el
@@ -895,17 +895,6 @@ issued in the language major mode buffer."
   :version "24.1"
   :group 'org-babel)
 
-(defun org-src-native-tab-command-maybe ()
-  "Perform language-specific TAB action.
-Alter code block according to what TAB does in the language major mode."
-  (and org-src-tab-acts-natively
-       (org-in-src-block-p)
-       (not (equal this-command 'org-shifttab))
-       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
-	 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
-
-(add-hook 'org-tab-first-hook 'org-src-native-tab-command-maybe)
-
 (defun org-src-font-lock-fontify-block (lang start end)
   "Fontify code block.
 This function is called by emacs automatic fontification, as long
diff --git a/lisp/org.el b/lisp/org.el
index 44a4e44..3559209 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22206,116 +22206,181 @@ hierarchy of headlines by UP levels before marking the subtree."
 
 ;;; Indentation
 
+(defun org--get-expected-indentation (element contentsp)
+  "Expected indentation column for current line, according to ELEMENT.
+ELEMENT is an element containing point.  CONTENTSP is non-nil
+when indentation is to be computed according to contents of
+ELEMENT."
+  (let ((type (org-element-type element))
+	(start (org-element-property :begin element)))
+    (org-with-wide-buffer
+     (cond
+      (contentsp
+       (case type
+	 (footnote-definition 0)
+	 ((headline inlinetask nil)
+	  (if (not org-adapt-indentation) 0
+	    (let ((level (org-current-level)))
+	      (if level (1+ level) 0))))
+	 ((item plain list)
+	  (org-list-item-body-column
+	   (or (org-element-property :post-affiliated element) start)))
+	 (otherwise
+	  (goto-char start)
+	  (org-get-indentation))))
+      ((memq type '(headline inlinetask nil))
+       (if (save-excursion (beginning-of-line) (looking-at "[ \t]*$"))
+	   (org--get-expected-indentation element t)
+	 0))
+      ((eq type 'footnote-definition) 0)
+      ;; First paragraph of a footnote definition or an item.
+      ;; Indent like parent.
+      ((< (line-beginning-position) start)
+       (org--get-expected-indentation
+	(org-element-property :parent element) t))
+      ;; At first line: indent according to previous sibling, if any,
+      ;; ignoring footnote definitions and inline tasks, or parent's
+      ;; contents.
+      ((= (line-beginning-position) start)
+       (catch 'exit
+	 (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)))))))))
+      ;; Otherwise, move to the first non-blank line above.
+      (t
+       (beginning-of-line)
+       (let ((pos (point)))
+	 (skip-chars-backward " \r\t\n")
+	 (cond
+	  ;; Two blank lines end a footnote definition or a plain
+	  ;; list.  When we indent an empty line after them, the
+	  ;; containing list or footnote definition is over, so it
+	  ;; qualifies as a previous sibling.  Therefore, we indent
+	  ;; like its first line.
+	  ((and (memq type '(footnote-definition plain-list))
+		(> (count-lines (point) pos) 2))
+	   (goto-char start)
+	   (org-get-indentation))
+	  ;; Line above is the first one of a paragraph at the
+	  ;; beginning of an item or a footnote definition.  Indent
+	  ;; like parent.
+	  ((< (line-beginning-position) start)
+	   (org--get-expected-indentation
+	    (org-element-property :parent element) t))
+	  ;; POS is after contents in a greater element.  Indent like
+	  ;; the beginning of the element.
+	  ;;
+	  ;; As a special case, if point is at the end of a footnote
+	  ;; definition or an item, indent like the very last element
+	  ;; within.
+	  ((let ((cend (org-element-property :contents-end element)))
+	     (and cend (<= cend pos)))
+	   (if (memq type '(footnote-definition item plain-list))
+	       (org--get-expected-indentation (org-element-at-point) nil)
+	     (goto-char start)
+	     (org-get-indentation)))
+	  ;; In any other case, indent like the current line.
+	  (t (org-get-indentation)))))))))
+
+(defun org--align-node-property ()
+  "Align node property at point.
+Alignment is done according to `org-property-format', which see."
+  (when (save-excursion
+	  (beginning-of-line)
+	  (looking-at org-property-re))
+    (replace-match
+     (concat (match-string 4)
+	     (format org-property-format (match-string 1) (match-string 3)))
+     t t)))
+
 (defun org-indent-line ()
-  "Indent line depending on context."
+  "Indent line depending on context.
+
+Indentation is done according to the following rules:
+
+  - Footnote definitions, headlines and inline tasks have to
+    start at column 0.
+
+  - On the very first line of an element, consider, in order, the
+    next rules until one matches:
+
+    1. If there's a sibling element before, ignoring footnote
+       definitions and inline tasks, indent like its first line.
+
+    2. If element has a parent, indent like its contents.  More
+       precisely, if parent is an item, indent after the
+       description part, if any, or the bullet (see
+       ``org-list-description-max-indent').  Else, indent like
+       parent's first line.
+
+    3. Otherwise, indent relatively to current level, if
+       `org-adapt-indentation' is non-nil, or to left margin.
+
+  - On a blank line at the end of a plain list, an item, or
+    a footnote definition, indent like the very last element
+    within.
+
+  - In the code part of a source block, use language major mode
+    to indent current line if `org-src-tab-acts-natively' is
+    non-nil.  If it is nil, do nothing.
+
+  - Otherwise, indent like the first non-blank line above.
+
+The function doesn't indent an item as it could break the whole
+list structure.  Instead, use \\<org-mode-map>\\[org-shiftmetaleft] or \
+\\[org-shiftmetaright].
+
+Also align node properties according to `org-property-format'."
   (interactive)
-  (let* ((pos (point))
-	 (itemp (org-at-item-p))
-	 (case-fold-search t)
-	 (org-drawer-regexp (or org-drawer-regexp "\000"))
-	 (inline-task-p (and (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)))
-	 (inline-re (and inline-task-p
-			 (org-inlinetask-outline-regexp)))
-	 column)
-    (if (and orgstruct-is-++ (eq pos (point)))
-	(let ((indent-line-function (cadadr (assoc 'indent-line-function org-fb-vars))))
-	  (indent-according-to-mode))
-      (beginning-of-line 1)
-      (cond
-       ;; Headings
-       ((looking-at org-outline-regexp) (setq column 0))
-       ;; Footnote definition
-       ((looking-at org-footnote-definition-re) (setq column 0))
-       ;; Literal examples
-       ((looking-at "[ \t]*:\\( \\|$\\)")
-	(setq column (org-get-indentation))) ; do nothing
-       ;; Lists
-       ((ignore-errors (goto-char (org-in-item-p)))
-	(setq column (if itemp
-			 (org-get-indentation)
-		       (org-list-item-body-column (point))))
-	(goto-char pos))
-       ;; Drawers
-       ((and (looking-at "[ \t]*:END:")
-	     (save-excursion (re-search-backward org-drawer-regexp nil t)))
-	(save-excursion
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column))))
-       ;; Special blocks
-       ((and (looking-at "[ \t]*#\\+end_\\([a-z]+\\)")
-	     (save-excursion
-	       (re-search-backward
-		(concat "^[ \t]*#\\+begin_" (downcase (match-string 1))) nil t)))
-	(setq column (org-get-indentation (match-string 0))))
-       ((and (not (looking-at "[ \t]*#\\+begin_"))
-	     (org-between-regexps-p "^[ \t]*#\\+begin_" "[ \t]*#\\+end_"))
-	(save-excursion
-	  (re-search-backward "^[ \t]*#\\+begin_\\([a-z]+\\)" nil t))
-	(setq column
-	      (cond ((equal (downcase (match-string 1)) "src")
-		     ;; src blocks: let `org-edit-src-exit' handle them
-		     (org-get-indentation))
-		    ((equal (downcase (match-string 1)) "example")
-		     (max (org-get-indentation)
-			  (org-get-indentation (match-string 0))))
-		    (t
-		     (org-get-indentation (match-string 0))))))
-       ;; This line has nothing special, look at the previous relevant
-       ;; line to compute indentation
-       (t
-	(beginning-of-line 0)
-	(while (and (not (bobp))
-		    (not (looking-at org-table-line-regexp))
-		    (not (looking-at org-drawer-regexp))
-		    ;; When point started in an inline task, do not move
-		    ;; above task starting line.
-		    (not (and inline-task-p (looking-at inline-re)))
-		    ;; Skip drawers, blocks, empty lines, verbatim,
-		    ;; comments, tables, footnotes definitions, lists,
-		    ;; inline tasks.
-		    (or (and (looking-at "[ \t]*:END:")
-			     (re-search-backward org-drawer-regexp nil t))
-			(and (looking-at "[ \t]*#\\+end_")
-			     (re-search-backward "[ \t]*#\\+begin_"nil t))
-			(looking-at "[ \t]*[\n:#|]")
-			(looking-at org-footnote-definition-re)
-			(and (not inline-task-p)
-			     (featurep 'org-inlinetask)
-			     (org-inlinetask-in-task-p)
-			     (or (org-inlinetask-goto-beginning) t))))
-	  (beginning-of-line 0))
-	(cond
-	 ;; There was a list item above.
-	 ((ignore-errors (goto-char (org-in-item-p)))
-	  (goto-char (org-list-get-top-point (org-list-struct)))
-	  (setq column (org-get-indentation)))
-	 ;; There was an heading above.
-	 ((looking-at "\\*+[ \t]+")
-	  (if (not org-adapt-indentation)
-	      (setq column 0)
-	    (goto-char (match-end 0))
-	    (setq column (current-column))))
-	 ;; A drawer had started and is unfinished
-	 ((looking-at org-drawer-regexp)
-	  (goto-char (1- (match-beginning 1)))
-	  (setq column (current-column)))
-	 ;; Else, nothing noticeable found: get indentation and go on.
-	 (t (setq column (org-get-indentation))))))
-      ;; Now apply indentation and move cursor accordingly
-      (goto-char pos)
-      (if (<= (current-column) (current-indentation))
-	  (org-indent-line-to column)
-	(save-excursion (org-indent-line-to column)))
-      ;; Special polishing for properties, see `org-property-format'
-      (setq column (current-column))
-      (beginning-of-line 1)
-      (if (looking-at org-property-re)
-	  (replace-match (concat (match-string 4)
-				 (format org-property-format
-					 (match-string 1) (match-string 3)))
-			 t t))
-      (org-move-to-column column))))
+  (cond
+   (orgstruct-is-++
+    (let ((indent-line-function
+	   (cadadr (assq 'indent-line-function org-fb-vars))))
+      (indent-according-to-mode)))
+   ((org-at-heading-p) 'noindent)
+   (t
+    (let* ((element (save-excursion (beginning-of-line) (org-element-at-point)))
+	   (type (org-element-type element)))
+      (cond ((and (memq type '(plain-list item))
+		  (= (line-beginning-position)
+		     (or (org-element-property :post-affiliated element)
+			 (org-element-property :begin element))))
+	     'noindent)
+	    ((and (eq type 'src-block)
+		  (> (line-beginning-position)
+		     (org-element-property :post-affiliated element))
+		  (< (line-beginning-position)
+		     (org-with-wide-buffer
+		      (goto-char (org-element-property :end element))
+		      (skip-chars-backward " \r\t\n")
+		      (line-beginning-position))))
+	     (if (not org-src-tab-acts-natively) 'noindent
+	       (let ((org-src-strip-leading-and-trailing-blank-lines nil))
+		 (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")))))
+	    (t
+	     (let ((column (org--get-expected-indentation element nil)))
+	       ;; Preserve current column.
+	       (if (<= (current-column) (current-indentation))
+		   (org-indent-line-to column)
+		 (save-excursion (org-indent-line-to column))))
+	     ;; Align node property.  Also preserve current column.
+	     (when (eq type 'node-property)
+	       (let ((column (current-column)))
+		 (org--align-node-property)
+		 (org-move-to-column column)))))))))
 
 (defun org-indent-drawer ()
   "Indent the drawer at point."
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index e8d8078..7b31115 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -441,6 +441,163 @@
 
 
 \f
+;;; Indentation
+
+(ert-deftest test-org/indent-line ()
+  "Test `org-indent-line' specifications."
+  ;; Do not indent footnote definitions or headlines.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H"
+      (org-indent-line)
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "[fn:1] fn"
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Do not indent before first headline.
+  (should
+   (zerop
+    (org-test-with-temp-text ""
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Indent according to headline level otherwise, unless
+  ;; `org-adapt-indentation' is nil.
+  (should
+   (= 2
+      (org-test-with-temp-text "* H\nA"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (= 2
+      (org-test-with-temp-text "* H\n\nA"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\nA"
+      (forward-line)
+      (let ((org-adapt-indentation nil)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Indenting preserves point position.
+  (should
+   (org-test-with-temp-text "* H\nAB"
+     (forward-line)
+     (forward-char)
+     (let ((org-adapt-indentation t)) (org-indent-line))
+     (looking-at "B")))
+  ;; Do not change indentation at an item.
+  (should
+   (= 1
+      (org-test-with-temp-text "* H\n - A"
+	(forward-line)
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  ;; On blank lines at the end of a list, indent like last element
+  ;; within it if the line is still in the list.  Otherwise, indent
+  ;; like the whole list.
+  (should
+   (= 4
+      (org-test-with-temp-text "* H\n- A\n  - AA\n"
+	(goto-char (point-max))
+	(let ((org-adapt-indentation t)) (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n- A\n  - AA\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; Likewise, on a blank line at the end of a footnote definition,
+  ;; indent at column 0 if line belongs to the definition.  Otherwise,
+  ;; indent like the definition itself.
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "* H\n[fn:1] Definition\n\n\n\n"
+      (goto-char (point-max))
+      (let ((org-adapt-indentation t)) (org-indent-line))
+      (org-get-indentation))))
+  ;; After the end of the contents of a greater element, indent like
+  ;; the beginning of the element.
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 2)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; At the first line of an element, indent like previous element's
+  ;; first line, ignoring footnotes definitions and inline tasks, or
+  ;; according to parent.
+  (should
+   (= 2
+      (org-test-with-temp-text "A\n\n  B\n\nC"
+	(goto-char (point-max))
+	(org-indent-line)
+	(org-get-indentation))))
+  (should
+   (= 1
+      (org-test-with-temp-text " A\n\n[fn:1] B\n\n\nC"
+	(goto-char (point-max))
+	(org-indent-line)
+	(org-get-indentation))))
+  (should
+   (= 1
+      (org-test-with-temp-text " #+BEGIN_CENTER\n  Contents\n#+END_CENTER"
+	(forward-line 1)
+	(org-indent-line)
+	(org-get-indentation))))
+  ;; Within code part of a source block, use language major mode if
+  ;; `org-src-tab-acts-natively' is non-nil.  Do nothing otherwise.
+  (should
+   (= 6
+      (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	(forward-line 2)
+	(let ((org-src-tab-acts-natively t)
+	      (org-edit-src-content-indentation 0))
+	  (org-indent-line))
+	(org-get-indentation))))
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+      (forward-line 2)
+      (let ((org-src-tab-acts-natively nil)
+	    (org-edit-src-content-indentation 0))
+	(org-indent-line))
+      (org-get-indentation))))
+  ;; Otherwise, indent like the first non-blank line above.
+  (should
+   (zerop
+    (org-test-with-temp-text "#+BEGIN_CENTER\nline1\n\n  line2\n#+END_CENTER"
+      (forward-line 3)
+      (org-indent-line)
+      (org-get-indentation))))
+  ;; Align node properties according to `org-property-format'.  Handle
+  ;; nicely empty values.
+  (should
+   (equal ":PROPERTIES:\n:key:      value\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key: value\n:END:"
+	    (forward-line)
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-line)
+	      (buffer-string)))))
+  (should
+   (equal ":PROPERTIES:\n:key:\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key:\n:END:"
+	    (forward-line)
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-line)
+	      (buffer-string))))))
+
+\f
 ;;; Editing
 
 ;;;; Insert elements
-- 
1.9.2


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Rewrite-org-indent-region.patch --]
[-- Type: text/x-diff, Size: 8016 bytes --]

From 4a0440d4178e51410cc1e6911e47f4ab2b8795ac Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Sun, 27 Apr 2014 16:48:02 +0200
Subject: [PATCH 2/3] Rewrite `org-indent-region'

* lisp/org.el (org-indent-region): Update function according to recent
  `org-indent-line' change.  Optimize it.

* testing/lisp/test-org.el (test-org/indent-region): New test.
---
 lisp/org.el              | 100 +++++++++++++++++++++++++++++++++++++++++++----
 testing/lisp/test-org.el |  70 +++++++++++++++++++++++++++++++++
 2 files changed, 163 insertions(+), 7 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 3559209..af34d99 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22424,15 +22424,101 @@ Also align node properties according to `org-property-format'."
   (message "Block at point indented"))
 
 (defun org-indent-region (start end)
-  "Indent region."
+  "Indent each non-blank line in the region.
+Called from a program, START and END specify the region to
+indent.  The function will not indent contents of example blocks,
+verse blocks and export blocks as leading white spaces are
+assumed to be significant there."
   (interactive "r")
   (save-excursion
-    (let ((line-end (org-current-line end)))
-      (goto-char start)
-      (while (< (org-current-line) line-end)
-	(cond ((org-in-src-block-p t) (org-src-native-tab-command-maybe))
-	      (t (call-interactively 'org-indent-line)))
-	(move-beginning-of-line 2)))))
+    (goto-char start)
+    (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))))
+	       (while (< (point) limit)
+		 (unless (org-looking-at-p "[ \t]*$") (org-indent-line-to ind))
+		 (forward-line))
+	       (set-marker limit nil))))
+	  (end (copy-marker end)))
+      (while (< (point) end)
+	(if (or (org-looking-at-p " \r\t\n") (org-at-heading-p)) (forward-line)
+	  (let* ((element (org-element-at-point))
+		 (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))
+	      ;; Elements in this category consist of three parts:
+	      ;; before the contents, the contents, and after the
+	      ;; contents.  The contents are treated specially,
+	      ;; according to the element type, or not indented at
+	      ;; all.  Other parts are indented as a single block.
+	      (let* ((post (copy-marker
+			    (or (org-element-property :post-affiliated element)
+				(org-element-property :begin element))))
+		     (cbeg
+		      (copy-marker
+		       (cond
+			((not (org-element-property :contents-begin element))
+			 ;; Fake contents for source blocks.
+			 (org-with-wide-buffer
+			  (goto-char post)
+			  (forward-line)
+			  (point)))
+			((memq type '(footnote-definition item plain-list))
+			 ;; Contents in these elements could start on
+			 ;; the same line as the beginning of the
+			 ;; element.  Make sure we start indenting
+			 ;; from the second line.
+			 (org-with-wide-buffer
+			  (goto-char post)
+			  (forward-line)
+			  (point)))
+			(t (org-element-property :contents-begin element)))))
+		     (cend (copy-marker
+			    (or (org-element-property :contents-end element)
+				;; Fake contents for source blocks.
+				(org-with-wide-buffer
+				 (goto-char element-end)
+				 (skip-chars-backward " \r\t\n")
+				 (line-beginning-position))))))
+		(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
+		     ;; `org-src-tab-acts-natively' is non-nil.
+		     (when (and (< (point) end) org-src-tab-acts-natively)
+		       (ignore-errors
+			 (let (org-src-strip-leading-and-trailing-blank-lines
+			       ;; Region boundaries in edit buffer.
+			       (start (1+ (- (point) cbeg)))
+			       (end (- (min cend end) cbeg)))
+			   (org-babel-do-in-edit-buffer
+			    (indent-region start end))))))
+		    (t (org-indent-region (point) (min cend end))))
+		  (goto-char (min cend end))
+		  (when (< (point) end) (funcall indent-to ind element-end)))
+		(set-marker post nil)
+		(set-marker cbeg nil)
+		(set-marker cend nil)))
+	    (set-marker element-end nil))))
+      (set-marker end nil))))
 
 
 ;;; Filling
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 7b31115..2b2b8f6 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -597,6 +597,76 @@
 	      (org-indent-line)
 	      (buffer-string))))))
 
+(ert-deftest test-org/indent-region ()
+  "Test `org-indent-region' specifications."
+  ;; Indent paragraph.
+  (should
+   (equal "A\nB\nC"
+	  (org-test-with-temp-text " A\nB\n  C"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Indent greater elements along with their contents.
+  (should
+   (equal "#+BEGIN_CENTER\nA\nB\n#+END_CENTER"
+	  (org-test-with-temp-text "#+BEGIN_CENTER\n A\n  B\n#+END_CENTER"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Ignore contents of verse blocks and example blocks.
+  (should
+   (equal "#+BEGIN_VERSE\n A\n  B\n#+END_VERSE"
+	  (org-test-with-temp-text "#+BEGIN_VERSE\n A\n  B\n#+END_VERSE"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  (should
+   (equal "#+BEGIN_EXAMPLE\n A\n  B\n#+END_EXAMPLE"
+	  (org-test-with-temp-text "#+BEGIN_EXAMPLE\n A\n  B\n#+END_EXAMPLE"
+	    (org-indent-region (point-min) (point-max))
+	    (buffer-string))))
+  ;; Indent according to mode if `org-src-tab-acts-natively' is
+  ;; non-nil.  Otherwise, do not indent code at all.
+  (should
+   (equal "#+BEGIN_SRC emacs-lisp\n(and A\n     B)\n#+END_SRC"
+	  (org-test-with-temp-text
+	      "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	    (let ((org-src-tab-acts-natively t)
+		  (org-edit-src-content-indentation 0))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  (should
+   (equal "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	  (org-test-with-temp-text
+	      "#+BEGIN_SRC emacs-lisp\n (and A\nB)\n#+END_SRC"
+	    (let ((org-src-tab-acts-natively nil)
+		  (org-edit-src-content-indentation 0))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  ;; Align node properties according to `org-property-format'.  Handle
+  ;; nicely empty values.
+  (should
+   (equal ":PROPERTIES:\n:key:      value\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key: value\n:END:"
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  (should
+   (equal ":PROPERTIES:\n:key:\n:END:"
+	  (org-test-with-temp-text ":PROPERTIES:\n:key:\n:END:"
+	    (let ((org-property-format "%-10s %s"))
+	      (org-indent-region (point-min) (point-max)))
+	    (buffer-string))))
+  ;; Special case: plain lists and footnote definitions.
+  (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 "[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)))))
+
+
 \f
 ;;; Editing
 
-- 
1.9.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-03 11:47         ` Nicolas Goaziou
@ 2014-05-04  3:25           ` Eric Abrahamsen
  2014-05-04  3:30           ` Eric Abrahamsen
  2014-05-04  3:39           ` Eric Abrahamsen
  2 siblings, 0 replies; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-04  3:25 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>>> Right now I'm seeing breakage with `org-set-property' -- this only
>>> happens on the indentation patches branch. Adding an EXPORT_AUTHOR
>>> property with that command, value of "asdfadsf", gives me this:
>>>
>>> * Test Heading
>>>   :PROPERTIES:
>>>
>>>   :EXPORT_AUTHOR: asdfasdfnil        nil
>>>
>>> Extra blank nil, spurious "nils", and no :END:
>
> [...]
>
>> Specifically, in this section of `org-indent-line':
>>
>> (when (eq type 'node-property)
>> 	       (let ((column (current-column)))
>> 		 (save-excursion
>> 		   (beginning-of-line)
>> 		   (looking-at org-property-re))
>> 		 (replace-match (concat (match-string 4)
>> 					(format org-property-format
>> 						(match-string 1)
>> 						(match-string 3)))
>> 				t t)
>> 		 (org-move-to-column column)))
>>
>> Those match-string calls toward the end both return "nil", and the
>> "nil"s get inserted directly into the buffer. I tried this with a
>> minimal setup (load-paths only, and a blank Org file) and could
>> reproduce.
>
> Indeed. I attach a replacement for both patch 1 and 2.

Looks good! Nothing else to report so far...

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-03 11:47         ` Nicolas Goaziou
  2014-05-04  3:25           ` Eric Abrahamsen
@ 2014-05-04  3:30           ` Eric Abrahamsen
  2014-05-04 19:45             ` Nicolas Goaziou
  2014-05-04  3:39           ` Eric Abrahamsen
  2 siblings, 1 reply; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-04  3:30 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>>> Right now I'm seeing breakage with `org-set-property' -- this only
>>> happens on the indentation patches branch. Adding an EXPORT_AUTHOR
>>> property with that command, value of "asdfadsf", gives me this:
>>>
>>> * Test Heading
>>>   :PROPERTIES:
>>>
>>>   :EXPORT_AUTHOR: asdfasdfnil        nil
>>>
>>> Extra blank nil, spurious "nils", and no :END:
>
> [...]
>
>> Specifically, in this section of `org-indent-line':
>>
>> (when (eq type 'node-property)
>> 	       (let ((column (current-column)))
>> 		 (save-excursion
>> 		   (beginning-of-line)
>> 		   (looking-at org-property-re))
>> 		 (replace-match (concat (match-string 4)
>> 					(format org-property-format
>> 						(match-string 1)
>> 						(match-string 3)))
>> 				t t)
>> 		 (org-move-to-column column)))
>>
>> Those match-string calls toward the end both return "nil", and the
>> "nil"s get inserted directly into the buffer. I tried this with a
>> minimal setup (load-paths only, and a blank Org file) and could
>> reproduce.
>
> Indeed. I attach a replacement for both patch 1 and 2.

One other thing: I was going to poke around with code block indentation
and see if I could break it, but I can't get the third patch to apply
cleanly any more. I don't know if it's because I rebased this testing
branch against an update master, or if your changes to the first two
patches mean the third needs to be edited accordingly...

It fails at org.el:22368, which is this line inside `org-indent-line':

(skip-chars-backward " \r\t\n")

Perhaps we need a new version of patch 3?

Eric

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-03 11:47         ` Nicolas Goaziou
  2014-05-04  3:25           ` Eric Abrahamsen
  2014-05-04  3:30           ` Eric Abrahamsen
@ 2014-05-04  3:39           ` Eric Abrahamsen
  2014-05-05  9:30             ` Nicolas Goaziou
  2 siblings, 1 reply; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-04  3:39 UTC (permalink / raw)
  To: emacs-orgmode

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> Hello,
>
> Eric Abrahamsen <eric@ericabrahamsen.net> writes:
>
>>> Right now I'm seeing breakage with `org-set-property' -- this only
>>> happens on the indentation patches branch. Adding an EXPORT_AUTHOR
>>> property with that command, value of "asdfadsf", gives me this:
>>>
>>> * Test Heading
>>>   :PROPERTIES:
>>>
>>>   :EXPORT_AUTHOR: asdfasdfnil        nil
>>>
>>> Extra blank nil, spurious "nils", and no :END:

One last thing! In the course of this I also noticed that, in a buffer
that contains a broken property drawer, you can't set properties on
*any* other heading. When `org-buffer-property-keys' goes looking for
valid keys used elsewhere in the current buffer, it calls
`org-get-property-block' on the broken drawer, and errors when that
function returns nil.

I tried changing the call to (org-get-property-block nil nil t), and
that seems to solve it: at least the broken drawer is repaired, and I
can successfully set properties. Dunno if that's the right way to handle
it, though.

Eric

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-04  3:30           ` Eric Abrahamsen
@ 2014-05-04 19:45             ` Nicolas Goaziou
  0 siblings, 0 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-04 19:45 UTC (permalink / raw)
  To: Eric Abrahamsen; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 140 bytes --]

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> Perhaps we need a new version of patch 3?

Here it is.


Regards,

-- 
Nicolas Goaziou

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0003-Rewrite-org-indent-drawer-and-org-indent-block.patch --]
[-- Type: text/x-diff, Size: 3461 bytes --]

From 66d0ab7d1025969e5fd383b93ffe1fb1b05a83a8 Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <n.goaziou@gmail.com>
Date: Mon, 28 Apr 2014 18:38:31 +0200
Subject: [PATCH 3/3] Rewrite `org-indent-drawer' and `org-indent-block'

* lisp/org.el (org-indent-block, org-indent-drawer): Rewrite functions.
---
 lisp/org.el | 75 ++++++++++++++++++++++++++++---------------------------------
 1 file changed, 34 insertions(+), 41 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index af34d99..e8d6fa9 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -22382,47 +22382,6 @@ Also align node properties according to `org-property-format'."
 		 (org--align-node-property)
 		 (org-move-to-column column)))))))))
 
-(defun org-indent-drawer ()
-  "Indent the drawer at point."
-  (interactive)
-  (let ((p (point))
-	(e (and (save-excursion (re-search-forward ":END:" nil t))
-		(match-end 0)))
-	(folded
-	 (save-excursion
-	   (end-of-line)
-	   (when (overlays-at (point))
-	     (member 'invisible (overlay-properties
-				 (car (overlays-at (point)))))))))
-    (when folded (org-cycle))
-    (indent-for-tab-command)
-    (while (and (move-beginning-of-line 2) (< (point) e))
-      (indent-for-tab-command))
-    (goto-char p)
-    (when folded (org-cycle)))
-  (message "Drawer at point indented"))
-
-(defun org-indent-block ()
-  "Indent the block at point."
-  (interactive)
-  (let ((p (point))
-	(case-fold-search t)
-	(e (and (save-excursion (re-search-forward "#\\+end_?\\(?:[a-z]+\\)?" nil t))
-		(match-end 0)))
-	(folded
-	 (save-excursion
-	   (end-of-line)
-	   (when (overlays-at (point))
-	     (member 'invisible (overlay-properties
-				 (car (overlays-at (point)))))))))
-    (when folded (org-cycle))
-    (indent-for-tab-command)
-    (while (and (move-beginning-of-line 2) (< (point) e))
-      (indent-for-tab-command))
-    (goto-char p)
-    (when folded (org-cycle)))
-  (message "Block at point indented"))
-
 (defun org-indent-region (start end)
   "Indent each non-blank line in the region.
 Called from a program, START and END specify the region to
@@ -22520,6 +22479,40 @@ assumed to be significant there."
 	    (set-marker element-end nil))))
       (set-marker end nil))))
 
+(defun org-indent-drawer ()
+  "Indent the drawer at point."
+  (interactive)
+  (unless (save-excursion
+	    (beginning-of-line)
+	    (org-looking-at-p org-drawer-regexp))
+    (user-error "Not at a drawer"))
+  (let ((element (org-element-at-point)))
+    (unless (memq (org-element-type element) '(drawer property-drawer))
+      (user-error "Not at a drawer"))
+    (org-with-wide-buffer
+     (org-indent-region (org-element-property :begin element)
+			(org-element-property :end element))))
+  (message "Drawer at point indented"))
+
+(defun org-indent-block ()
+  "Indent the block at point."
+  (interactive)
+  (unless (save-excursion
+	    (beginning-of-line)
+	    (let ((case-fold-search t))
+	      (org-looking-at-p "[ \t]*#\\+\\(begin\\|end\\)_")))
+    (user-error "Not at a block"))
+  (let ((element (org-element-at-point)))
+    (unless (memq (org-element-type element)
+		  '(comment-block center-block example-block export-block
+				  quote-block special-block src-block
+				  verse-block))
+      (user-error "Not at a block"))
+    (org-with-wide-buffer
+     (org-indent-region (org-element-property :begin element)
+			(org-element-property :end element))))
+  (message "Block at point indented"))
+
 
 ;;; Filling
 
-- 
1.9.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-04  3:39           ` Eric Abrahamsen
@ 2014-05-05  9:30             ` Nicolas Goaziou
  0 siblings, 0 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-05  9:30 UTC (permalink / raw)
  To: Eric Abrahamsen; +Cc: emacs-orgmode

Hello,

Eric Abrahamsen <eric@ericabrahamsen.net> writes:

> One last thing! In the course of this I also noticed that, in a buffer
> that contains a broken property drawer, you can't set properties on
> *any* other heading. When `org-buffer-property-keys' goes looking for
> valid keys used elsewhere in the current buffer, it calls
> `org-get-property-block' on the broken drawer, and errors when that
> function returns nil.
>
> I tried changing the call to (org-get-property-block nil nil t), and
> that seems to solve it: at least the broken drawer is repaired, and I
> can successfully set properties. Dunno if that's the right way to handle
> it, though.

IIUC, this problem is unrelated to indentation. Would you mind starting
a new thread for this, with an ECM?

Thank you.


Regards,

-- 
Nicolas Goaziou

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-04-30 13:03 [RFC] Rewrite indentation functions Nicolas Goaziou
                   ` (2 preceding siblings ...)
  2014-05-02  5:38 ` Eric Abrahamsen
@ 2014-05-06  9:41 ` Bastien
  2014-05-07  0:51   ` Eric Abrahamsen
  2014-05-07 15:40   ` Nicolas Goaziou
  3 siblings, 2 replies; 18+ messages in thread
From: Bastien @ 2014-05-06  9:41 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: Org Mode List

Hi Nicolas,

Nicolas Goaziou <n.goaziou@gmail.com> writes:

> I would like to install the following patches on master.

Please go ahead, that's the easiest way to get more feedback and Eric
did half the job already, so we must be good.

Thanks for working on this,

-- 
 Bastien

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-06  9:41 ` Bastien
@ 2014-05-07  0:51   ` Eric Abrahamsen
  2014-05-07 15:40   ` Nicolas Goaziou
  1 sibling, 0 replies; 18+ messages in thread
From: Eric Abrahamsen @ 2014-05-07  0:51 UTC (permalink / raw)
  To: emacs-orgmode

Bastien <bzg@gnu.org> writes:

> Hi Nicolas,
>
> Nicolas Goaziou <n.goaziou@gmail.com> writes:
>
>> I would like to install the following patches on master.
>
> Please go ahead, that's the easiest way to get more feedback and Eric
> did half the job already, so we must be good.
>
> Thanks for working on this,

I've been running all the three new versions of the patches for a few
days now, and haven't succeeded in breaking anything!

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC] Rewrite indentation functions
  2014-05-06  9:41 ` Bastien
  2014-05-07  0:51   ` Eric Abrahamsen
@ 2014-05-07 15:40   ` Nicolas Goaziou
  1 sibling, 0 replies; 18+ messages in thread
From: Nicolas Goaziou @ 2014-05-07 15:40 UTC (permalink / raw)
  To: Bastien; +Cc: Org Mode List

Hello,

Bastien <bzg@gnu.org> writes:

> Please go ahead, that's the easiest way to get more feedback and Eric
> did half the job already, so we must be good.

Applied.


Regards,

-- 
Nicolas Goaziou

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2014-05-07 15:40 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-04-30 13:03 [RFC] Rewrite indentation functions Nicolas Goaziou
2014-04-30 17:08 ` Vikas Rawal
2014-04-30 18:59   ` Sebastien Vauban
2014-05-01 19:11 ` Nicolas Goaziou
2014-05-02  5:38 ` Eric Abrahamsen
2014-05-02  7:32   ` Nicolas Goaziou
2014-05-02 10:01     ` Eric Abrahamsen
2014-05-03  7:47     ` Eric Abrahamsen
2014-05-03  8:47       ` Eric Abrahamsen
2014-05-03 11:47         ` Nicolas Goaziou
2014-05-04  3:25           ` Eric Abrahamsen
2014-05-04  3:30           ` Eric Abrahamsen
2014-05-04 19:45             ` Nicolas Goaziou
2014-05-04  3:39           ` Eric Abrahamsen
2014-05-05  9:30             ` Nicolas Goaziou
2014-05-06  9:41 ` Bastien
2014-05-07  0:51   ` Eric Abrahamsen
2014-05-07 15:40   ` Nicolas Goaziou

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).