Hi Ihor

Thanks for the comments, I've attached the updated patch.

> 1. Use `insert' rather than `insert-char' that is for interactive use.
> 2. Use more readable ?\t notation

I realized that just inserting the spaces actually did mess up things
in some edge-cases so I changed approach. It feels ugly having almost
the exact same bit of code within a couple lines, but I don't see an
obvious solution.

This is also my first time messing with test writing in elisp so hope there's
nothing too egregious.

While working on it I did however run into two other issues in the
indenting behavior that might also need fixing:

1. `org-indent-line' indents the end tag of some blocks to match the
begin tag, but doesn't do so with src blocks, among others.

MRE: With the pointer before =#+end_src=  in the following, do `M-x 
org-indent-line'
  #+begin_src fundamental
    foo
#+end_src
Expected: line is indented by 2
Actual: line is indented by 4

I thought it was a bit weird since the code for calculating the src
block content indent does handle it if the block itself is indented, and
it worked fine for other block types. It seems this is because src
blocks (+ export, example, et al) aren't greater elements and don't
have a `contents-end' property.

I was looking at fixing it for all cases but realized it would require
messing with the element parsers which I'm honestly really not looking
to get into, but I've attached a draft patch that works for src
blocks. It's a pretty theoretical problem, but if you think it's worth
fixing and that the patch makes sense I'd be happy to clean it up and
add a test.

2. At first I just wanted to report that tabbing with
`org-src-tab-acts-natively' at nil didn't follow
`org-edit-src-content-indentation', but looking into it I realize
there's a nuance to it. I think there is one "actual" bug and two
ambiguities in intention/design. The description of `org-indent-line'
says:

> - 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.*   (<-- This is the current behavior of 
`org-indent-region' and therefore `org-indent-block')
>
> - Otherwise, *indent like the first non-blank line above.* (<-- This is the 
> current behavior of `org-indent-line')

MRE:
With `org-src-tab-acts-natively' set to nil and pointer in content of the 
following, press TAB.
#+begin_src fundamental
   foo
bar
#+end_src
Expected: Nothing happens.
Actual: Indent is set to 3, equal to line above.

So that's the actual bug, nothing with the content indent
specifically. However, I'm not sure that bringing it in line with the
description is necessarily what makes best sense. Right now there's
even a test that contradicts the function description, and the
description of `org-src-tab-acts-natively' also doesn't say anything
about what happens when it is set to nil. I can't speak for the people
who already use this setting, but having TAB fall back to the current
behavior of `org-indent-line' makes more intuitive sense to me than
having TAB do nothing when you disable native indent.

I think it needs to be clarified whether the intent of the customization is
to completely disable all auto-indentation of code from the
org-buffer; versus just disabling the use of temporary edit-buffers at
every newline/TAB, e.g. to get around issues with language modes or
slow-downs or whatever.

(I guess having all three settings could also make sense, since just keeping
your fingers off of TAB wouldn't fully achieve the `do nothing' mode.)

On the content indent, my confusion arose due to the fact that nothing
in the chain of descriptions related to it gave any indication that
the customization on code indentation should impact it.
1. `org-src-tab-acts-natively' itself doesn't mention it.
2. The description of `org-edit-src-content-indentation' says nothing
   about `org-src-tab-acts-natively', it mentions
   `org-src-preserve-indentation'; but
3. That one also doesn't say anything about
   `org-src-tab-acts-natively'. It does specifically mention that
   `org-indent-block' should add the content indent; but
4. The descriptions of `org-indent-block' and `-region', which it
   relies on, are both very low on detail so the intended behavior is
   unclear; in any case `org-indent-region' only adds the content
   indent when native tab is enabled.

Point being that it is unclear whether `tab-acts-natively' is at all
supposed to change how and when the content indent is added.

Currently the content indent is added when returning from the edit
buffer, so, despite the bug, it's skipped when native indenting is
disabled since the edit buffer isn't used. I personally don't think it
makes a lot of sense that the value of `tab-acts-natively' should
affect this behavior, see for example the MRE below. IMO it should
only regard the code indentation and not whether org indents the block
content.

MRE for lack of content indentation:
With `org-src-tab-acts-natively' and `org-src-preserve-indentation'
set to nil and above-zero `org-edit-src-content-indentation', press
enter at the end of a src block header.

#+begin_src fundamental<pointer>
#+end_src

My expectation: you are placed at the content indent.
Actual: You are placed at column 0.

I didn't prepare a patch since my favored solutions would require
changes to multiple functions including descriptions, so I thought I'd
hear your thoughts before putting in the effort.

LRA

>From 583eb40d1ef59261d5114c7e278324ae3774bdb4 Mon Sep 17 00:00:00 2001
From: Lukas Rudd Andersen <l...@phdk.org>
Date: Tue, 25 Mar 2025 12:49:24 +0100
Subject: [PATCH] lisp/org.el Fix bug in source-block content indentation

* org.el (org-indent-line): Indent to content before indenting with mode.
Indent to content before the block contents are put in the edit buffer
to indent according to mode, ensuring that the whole block is properly
cleaned of content indentation, avoiding unintended over-indentation
when the contents of the edit buffer are reinserted.
* test-org.el (org-indent-line): Add test.

TINYCHANGE
---
 lisp/org.el              |  4 ++++
 testing/lisp/test-org.el | 14 ++++++++++++--
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index e9f11db1e..3fcc03697 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -19469,6 +19469,10 @@ Also align node properties according to `org-property-format'."
                       (org-with-point-at (org-element-property :begin element)
                         (+ (org-current-text-indentation)
                            org-edit-src-content-indentation)))))
+               ;; Avoid over-indenting when beginning of a new line is not empty.
+               ;; https://list.orgmode.org/omcpuwz--...@phdk.org/
+               (when block-content-ind
+                 (save-excursion (indent-line-to block-content-ind)))
                (ignore-errors ; do not err when there is no proper major mode
                  ;; It is important to call `indent-according-to-mode'
                  ;; rather than `indent-line-function' here or we may
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 2487c9ace..a55c4162e 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -1220,8 +1220,9 @@ Otherwise, evaluate RESULT as an sexp and return its result."
 	 (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.  Otherwise, indent
-  ;; according to line above.
+  ;; `org-src-tab-acts-natively' is non-nil, only add
+  ;; `org-edit-src-content-indentation' to lines with indentation that
+  ;; is lower. Otherwise, indent according to line above.
   (should
    (= 6
       (org-test-with-temp-text
@@ -1230,6 +1231,15 @@ Otherwise, evaluate RESULT as an sexp and return its result."
 	      (org-edit-src-content-indentation 0))
 	  (org-indent-line))
 	(org-get-indentation))))
+  (should
+   (= 2
+      (org-test-with-temp-text
+	  "#+BEGIN_SRC emacs-lisp\n  (and A\n<point>B)\n#+END_SRC"
+	(let ((org-src-tab-acts-natively t)
+	      (org-edit-src-content-indentation 2))
+	  (org-indent-line))
+        (forward-line -1)
+	(org-get-indentation))))
   (should
    (= 1
       (org-test-with-temp-text
-- 
2.47.2

>From 8f02b2c9e5c661747afd766dc77fdf59b1b80e81 Mon Sep 17 00:00:00 2001
From: Lukas Rudd Andersen <l...@phdk.org>
Date: Fri, 28 Mar 2025 03:16:09 +0100
Subject: [PATCH] lisp/org.el: Indent end of src-blocks.

---
 lisp/org.el | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index e9f11db1e..f1b1e6bac 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -19354,10 +19354,15 @@ ELEMENT."
 	  ;; and contents.
 	  ((and post-affiliated (= (line-beginning-position) post-affiliated))
 	   (org--get-expected-indentation element t))
-	  ;; POS is after contents in a greater element.  Indent like
-	  ;; the beginning of the element.
-	  ((and (memq type org-element-greater-elements)
-		(let ((cend (org-element-contents-end element)))
+	  ;; POS is after contents in a greater element or src-block.
+	  ;; Indent like the beginning of the element.
+	  ((and (or (memq type org-element-greater-elements)
+                    (org-element-type-p element 'src-block))
+		(let ((cend (or (org-element-contents-end element)
+                                (org-with-wide-buffer
+			         (goto-char (org-element-end element))
+			         (skip-chars-backward " \r\t\n")
+			         (line-beginning-position)))))
 		  (and cend (<= cend pos))))
 	   ;; As a special case, if point is at the end of a footnote
 	   ;; definition or an item, indent like the very last element
-- 
2.47.2

Reply via email to