From mboxrd@z Thu Jan 1 00:00:00 1970 From: Nicolas Goaziou Subject: Re: [RFC] Rewrite indentation functions Date: Thu, 01 May 2014 21:11:12 +0200 Message-ID: <87k3a5nwkv.fsf@gmail.com> References: <87oazjnf55.fsf@gmail.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:42774) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WfwNh-0005aZ-5t for emacs-orgmode@gnu.org; Thu, 01 May 2014 15:11:02 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1WfwNY-0002rn-23 for emacs-orgmode@gnu.org; Thu, 01 May 2014 15:10:53 -0400 Received: from mail-wi0-x22f.google.com ([2a00:1450:400c:c05::22f]:46460) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1WfwNX-0002rL-Ih for emacs-orgmode@gnu.org; Thu, 01 May 2014 15:10:43 -0400 Received: by mail-wi0-f175.google.com with SMTP id cc10so1293538wib.2 for ; Thu, 01 May 2014 12:10:42 -0700 (PDT) Received: from selenimh (ala65-1-82-246-127-176.fbx.proxad.net. [82.246.127.176]) by mx.google.com with ESMTPSA id gi8sm5456771wib.8.2014.05.01.12.10.39 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 01 May 2014 12:10:40 -0700 (PDT) In-Reply-To: <87oazjnf55.fsf@gmail.com> (Nicolas Goaziou's message of "Wed, 30 Apr 2014 15:03:18 +0200") List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: Org Mode List --=-=-= Content-Type: text/plain Nicolas Goaziou 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 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-Rewrite-org-indent-line.patch >From 283588780f3ee64c87b336ff65fc758047687923 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou 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-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 @@ +;;; 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)))))) + + ;;; Editing ;;;; Insert elements -- 1.9.2 --=-=-=--