forked from mirrors/org-mode
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.
This commit is contained in:
parent
fea672d30e
commit
51ffcd02dd
|
@ -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
|
||||
|
|
281
lisp/org.el
281
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."
|
||||
|
|
|
@ -440,6 +440,163 @@
|
|||
(buffer-string))))))
|
||||
|
||||
|
||||
|
||||
;;; 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
|
||||
|
||||
|
|
Loading…
Reference in New Issue