emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [O] [RFC] Rewrite indentation functions


From: Nicolas Goaziou
Subject: Re: [O] [RFC] Rewrite indentation functions
Date: Sat, 03 May 2014 13:47:28 +0200

Hello,

Eric Abrahamsen <address@hidden> 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
>From 104a091d127b746662adfa7b3f71a602ef5f816e Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <address@hidden>
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 @@
 
 
 
+;;; 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))))))
+
+
 ;;; Editing
 
 ;;;; Insert elements
-- 
1.9.2

>From 4a0440d4178e51410cc1e6911e47f4ab2b8795ac Mon Sep 17 00:00:00 2001
From: Nicolas Goaziou <address@hidden>
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)))))
+
+
 
 ;;; Editing
 
-- 
1.9.2


reply via email to

[Prev in Thread] Current Thread [Next in Thread]