emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
@ 2017-04-03  4:15 Brent Goodrick
  2017-04-22  8:41 ` Nicolas Goaziou
  0 siblings, 1 reply; 7+ messages in thread
From: Brent Goodrick @ 2017-04-03  4:15 UTC (permalink / raw)
  To: emacs-orgmode

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

Hi,

I found a bug in org-mode where emacs-lisp code that is in a
already-indented source block in an org-mode buffer is improperly
indented when editing it via C-c '. Take the following contrived
example emacs-lisp source code:

 1. Here is a list item with a emacs-lisp source block:
    #+BEGIN_SRC emacs-lisp :results value
      (let ((uuid "c2327c73-6da3-4421-8bda-194783a00e8f"))
        (progn
          (let ((xxx 'yyy))
            (let ((xxx 'yyy))
              (while t
                (message "infinite loop"))))))
    #+END_SRC

After C-c ', indenting it, and C-c ' again, it renders as
follows (tabs converted to spaces for this email, since I have
`indent-tabs-mode' set to t in my emacs-lisp mode, which
is the Emacs default):

 1. Here is a list item with a emacs-lisp source block:
    #+BEGIN_SRC emacs-lisp :results value
      (let ((uuid "c2327c73-6da3-4421-8bda-194783a00e8f"))
        (progn
          (let ((xxx 'yyy))
        (let ((xxx 'yyy))
          (while t
            (message "infinite loop"))))))
    #+END_SRC

Notice how the indentation looks bad due to the mixture of tabs
and spaces.

The bug is in the `org-src--contents-for-write-back' function. It
uses a temp buffer. The temp buffer's major-mode is left to be
the default, which is fundamental-mode, which knows nothing about
how to indent lisp code properly. So in the fix below, I run the
major-mode function from the original buffer. But even with that
fix, the indentation must also use spaces in order to avoid
mixing tabs and spaces in the resulting Org buffer.  My fix,
shown below, is to initialize the major-mode, and set
`indent-tabs-mode' to nil, before it runs the `write-back'
code.

See "CHANGE" comments for the changes made:

(defun org-src--contents-for-write-back ()
  "Return buffer contents in a format appropriate for write back.
        Assume point is in the corresponding edit buffer."
  (let ((indentation (or org-src--block-indentation 0))
    (preserve-indentation org-src--preserve-indentation)
    (contents (org-with-wide-buffer (buffer-string)))
    (write-back org-src--allow-write-back))
    ;; CHANGE: Save off the original mode into orig-major-mode:
    (let ((orig-major-mode major-mode))
      (with-temp-buffer
        (insert (org-no-properties contents))
        ;; CHANGE: Switch to the original mode to obtain mode-specific
indentation behavior:
        (funcall orig-major-mode)
        ;; CHANGE: Do not mix tabs and spaces during indentation:
        (setq indent-tabs-mode nil)
    (goto-char (point-min))
    (when (functionp write-back) (funcall write-back))
    (unless (or preserve-indentation (= indentation 0))
      (let ((ind (make-string indentation ?\s)))
        (goto-char (point-min))
        (while (not (eobp))
          (when (looking-at-p "[ \t]*\\S-") (insert ind))
          (forward-line))))
    (buffer-string)))))

If the above change seems correct, can someone apply it to org-mode?

Thanks,
Brent Goodrick

[-- Attachment #2: Type: text/html, Size: 3574 bytes --]

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-03  4:15 org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers Brent Goodrick
@ 2017-04-22  8:41 ` Nicolas Goaziou
  2017-04-23  3:21   ` Brent Goodrick
  0 siblings, 1 reply; 7+ messages in thread
From: Nicolas Goaziou @ 2017-04-22  8:41 UTC (permalink / raw)
  To: Brent Goodrick; +Cc: emacs-orgmode

Hello,

Brent Goodrick <bgoodr@gmail.com> writes:

> I found a bug in org-mode where emacs-lisp code that is in a
> already-indented source block in an org-mode buffer is improperly
> indented when editing it via C-c '. Take the following contrived
> example emacs-lisp source code:
>
>  1. Here is a list item with a emacs-lisp source block:
>
>     #+BEGIN_SRC emacs-lisp :results value
>       (let ((uuid "c2327c73-6da3-4421-8bda-194783a00e8f"))
>         (progn
>           (let ((xxx 'yyy))
>             (let ((xxx 'yyy))
>               (while t
>                 (message "infinite loop"))))))
>     #+END_SRC
>
>
> After C-c ', indenting it, and C-c ' again, it renders as
> follows (tabs converted to spaces for this email, since I have
> `indent-tabs-mode' set to t in my emacs-lisp mode, which
> is the Emacs default):
>
>  1. Here is a list item with a emacs-lisp source block:
>
>     #+BEGIN_SRC emacs-lisp :results value
>       (let ((uuid "c2327c73-6da3-4421-8bda-194783a00e8f"))
>         (progn
>           (let ((xxx 'yyy))
>         (let ((xxx 'yyy))
>           (while t
>             (message "infinite loop"))))))
>     #+END_SRC
>
> Notice how the indentation looks bad due to the mixture of tabs
> and spaces.

Indeed. Thank you.

> The bug is in the `org-src--contents-for-write-back' function. It
> uses a temp buffer. The temp buffer's major-mode is left to be
> the default, which is fundamental-mode, which knows nothing about
> how to indent lisp code properly.

And it doesn't need to. This function doesn't care about the code, but
indents it rigidly according to original source block.  IOW, I don't
think changing the major mode is required.

> So in the fix below, I run the major-mode function from the original
> buffer. But even with that fix, the indentation must also use spaces
> in order to avoid mixing tabs and spaces in the resulting Org buffer.

Why do you think it is a good thing that tabs and spaces shouldn't be
mixed. For example, imagine that the source code requires
`indent-tabs-mode' being non-nil, but Org source buffer indentation is
space only, i.e., with `indent-tabs-mode' being nil.

Shouldn't the resulting block be indented with spaces from column 0 to
block boundaries' indentation, and then follow with space indentation?

WDYT?

Regards,

-- 
Nicolas Goaziou

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-22  8:41 ` Nicolas Goaziou
@ 2017-04-23  3:21   ` Brent Goodrick
  2017-04-27 23:04     ` Nicolas Goaziou
  0 siblings, 1 reply; 7+ messages in thread
From: Brent Goodrick @ 2017-04-23  3:21 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: emacs-orgmode

On Sat, Apr 22, 2017 at 1:41 AM, Nicolas Goaziou <mail@nicolasgoaziou.fr> wrote:
>
> > The bug is in the `org-src--contents-for-write-back' function. It
> > uses a temp buffer. The temp buffer's major-mode is left to be
> > the default, which is fundamental-mode, which knows nothing about
> > how to indent lisp code properly.
>
> And it doesn't need to. This function doesn't care about the code, but
> indents it rigidly according to original source block.  IOW, I don't
> think changing the major mode is required.
>
> > So in the fix below, I run the major-mode function from the original
> > buffer. But even with that fix, the indentation must also use spaces
> > in order to avoid mixing tabs and spaces in the resulting Org buffer.
>
> Why do you think it is a good thing that tabs and spaces shouldn't be
> mixed. For example, imagineh that the source code requires
> `indent-tabs-mode' being non-nil, but Org source buffer indentation is
> space only, i.e., with `indent-tabs-mode' being nil.

Thanks for looking into this!

In the current implementation of org-src--contents-for-write-back, the
`with-temp-buffer` uses fundamental-mode.  Later, while inside that
temp buffer, spaces are inserted in to indent the entire source block
over to where it needs to be (in my original post, notice that I have
the source block within a list item so the source block needs to be
aligned properly under that list item, no matter to what depth that
list item is). It is in mode hook functions that certain changes to
indentation can occur, so that is why I'm switching into that
mode. But that is not enough: In order for the text to be aligned
properly inside the org mode buffer after indentation, there cannot be
a mix of tabs and spaces, as shown in my original post. IIRC,
`indent-to' is called within the `write-back' function, and
`indent-to' is affected by the `indent-tabs-mode' value, which by
default in emacs lisp mode buffers, is t.

You might try my implementation both without the change to
`indent-tabs-mode' to see how improperly indented the resulting source
block looks.

>
> Shouldn't the resulting block be indented with spaces from column 0 to
> block boundaries' indentation, and then follow with space indentation?
>

Yes, if I understand what you are meaning. In fact, I think that is,
in effect, what my replacement function is doing.  Basically the
bottom line is that you can't mix tabs and spaces in the final Org
buffer and have it look correctly indented in that buffer, and the
change to indent-tabs-mode to nil will be buffer local inside that
with-temp-buffer so nothing is affected in any other buffer.

For your reference, below is my replacement function that has
elaborated comments that hopefully clarify things a bit more:

(defun org-src--contents-for-write-back-fix-indentation ()
  "Return buffer contents in a format appropriate for write back.
        Assume point is in the corresponding edit buffer."
  (let ((indentation (or org-src--block-indentation 0))
        (preserve-indentation org-src--preserve-indentation)
        (contents (org-with-wide-buffer (buffer-string)))
        (write-back org-src--allow-write-back))
    ;; --- BEGIN CHANGES FROM ORIGINAL DEFINITION ---
    ;;
    ;; Save off the original mode into orig-major-mode:
    ;;
    (let ((orig-major-mode major-mode))
      (with-temp-buffer
        ;;
        ;; Insert the contents as was done before:
        ;;
        (insert (org-no-properties contents))
        ;;
        ;; First change: Switch to the original mode for indentation by
        ;; switching its mode to be the original one. This is because that mode
        ;; handles mode-specific indentation behavior:
        ;;
        (funcall orig-major-mode)
        ;;
        ;; Second change: Do not use tabs here. If the mode being switched to
        ;; has indent-tabs-mode set to t, that is fine for separate buffers, but
        ;; for when org source blocks are shown in Org buffers, mixing tabs and
        ;; spaces will mess up the view (see this for emacs lisp code blocks
        ;; when indent-tabs-mode is set to t) when write-back calls `indent-to'.
        ;;
        ;; The alternative is to require everyone to set indent-tabs-mode to nil
        ;; in their mode hooks for all modes used in Org mode, but that seems
        ;; slightly heavy-handed.
        ;;
        (setq indent-tabs-mode nil)
        ;; --- END CHANGES FROM ORIGINAL DEFINITION ---
        (goto-char (point-min))
        (when (functionp write-back) (funcall write-back))
        (unless (or preserve-indentation (= indentation 0))
          (let ((ind (make-string indentation ?\s)))
            (goto-char (point-min))
            (while (not (eobp))
              (when (looking-at-p "[ \t]*\\S-") (insert ind))
              (forward-line))))
        (buffer-string)))))

I am curious to see if there is a better fix that what I've coded up above.

Thanks again for your help,
-Brent

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-23  3:21   ` Brent Goodrick
@ 2017-04-27 23:04     ` Nicolas Goaziou
  2017-04-28 15:29       ` Brent Goodrick
  0 siblings, 1 reply; 7+ messages in thread
From: Nicolas Goaziou @ 2017-04-27 23:04 UTC (permalink / raw)
  To: Brent Goodrick; +Cc: emacs-orgmode

Hello,

Brent Goodrick <bgoodr@gmail.com> writes:

> In the current implementation of org-src--contents-for-write-back, the
> `with-temp-buffer` uses fundamental-mode.

Correct.

> Later, while inside that temp buffer, spaces are inserted in to indent
> the entire source block over to where it needs to be (in my original
> post, notice that I have the source block within a list item so the
> source block needs to be aligned properly under that list item, no
> matter to what depth that list item is).

Correct.

> It is in mode hook functions that certain changes to indentation can
> occur, so that is why I'm switching into that mode.

This is where I don't follow you. At this point, indentation changes are
tailored for the source, i.e., Org, buffer. Special indentation rules
from the source block mode do not apply here.

> But that is not enough: In order for the text to be aligned properly
> inside the org mode buffer after indentation, there cannot be a mix of
> tabs and spaces, as shown in my original post. IIRC, `indent-to' is
> called within the `write-back' function, and `indent-to' is affected
> by the `indent-tabs-mode' value, which by default in emacs lisp mode
> buffers, is t.

You cannot set `indent-tabs-mode' to nil and discard user's
configuration. What if I want it to be non-nil in Org buffers?

Another option is to delete any leading indentation and indent it again
according to `indent-tabs-mode' value in source buffer.

WDYT?

Regards,

-- 
Nicolas Goaziou

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-27 23:04     ` Nicolas Goaziou
@ 2017-04-28 15:29       ` Brent Goodrick
  2017-04-29  9:59         ` Nicolas Goaziou
  0 siblings, 1 reply; 7+ messages in thread
From: Brent Goodrick @ 2017-04-28 15:29 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: emacs-orgmode

On Thu, Apr 27, 2017 at 4:04 PM, Nicolas Goaziou <mail@nicolasgoaziou.fr> wrote:
>
> Hello,
>
> Brent Goodrick <bgoodr@gmail.com> writes:
>
> > In the current implementation of org-src--contents-for-write-back, the
> > `with-temp-buffer` uses fundamental-mode.
>
> Correct.
>
> > Later, while inside that temp buffer, spaces are inserted in to indent
> > the entire source block over to where it needs to be (in my original
> > post, notice that I have the source block within a list item so the
> > source block needs to be aligned properly under that list item, no
> > matter to what depth that list item is).
>
> Correct.
>
> > It is in mode hook functions that certain changes to indentation can
> > occur, so that is why I'm switching into that mode.
>
> This is where I don't follow you. At this point, indentation changes are
> tailored for the source, i.e., Org, buffer. Special indentation rules
> from the source block mode do not apply here.

I do not understand what is meant by "tailored for the source" which
is the Org buffer. All of the indentation changes being made here are
within the temporary buffer created by with-temp-buffer, which is
using fundamental-mode which is not the same as either emacs-lisp-mode
or org-mode. That is the wrong mode to be in when running `indent-line-to`
function since it is that particular editing mode that the user has
control over the `indent-tabs-mode` variable (typically from mode hook
functions). So I conclude that the temporary buffer, at that point in
execution, has to be in the mode of the language used when indenting.

> > But that is not enough: In order for the text to be aligned properly
> > inside the org mode buffer after indentation, there cannot be a mix of
> > tabs and spaces, as shown in my original post. IIRC, `indent-to' is
> > called within the `write-back' function, and `indent-to' is affected
> > by the `indent-tabs-mode' value, which by default in emacs lisp mode
> > buffers, is t.
>
> You cannot set `indent-tabs-mode' to nil and discard user's
> configuration. What if I want it to be non-nil in Org buffers?

I agree with that. I now see that what I proposed earlier is a not a
solution for that reason. So I have a further suggested change below.

Also I mistakenly referred to `indent-to` when the function is actually
`indent-line-to`.

> Another option is to delete any leading indentation and indent it again
> according to `indent-tabs-mode' value in source buffer.

By "in source buffer", do you mean that after the text is inserted
into the Org mode buffer, the code then reindents it again, while the
current mode in that buffer is org-mode (i.e., no longer inside the
temporary buffer, which is fundamental-mode)? If that is the
interpretation, then how is Org mode to know about the syntax of emacs
lisp code? I think it won't and it will indent the emacs lisp code
without proper handling of parens. In fact I played with that a bit
and it is also not going to work (yields left justified text since
fundamental mode knows nothing the parens in Emacs lisp).

Here is my somewhat improved fix. I'm still switching into the
original language mode inside the temp buffer.

(defun org-src--contents-for-write-back ()
  "Return buffer contents in a format appropriate for write back.
        Assume point is in the corresponding edit buffer."
  (let ((indentation (or org-src--block-indentation 0))
        (preserve-indentation org-src--preserve-indentation)
        (contents (org-with-wide-buffer (buffer-string)))
        (write-back org-src--allow-write-back))
    (let ((orig-major-mode major-mode))
      (with-temp-buffer
        (insert (org-no-properties contents))
        ;; CHANGE: Switch into the mode of the language that was being edited so
        ;; that `indent-tabs-mode` will be respected:
        (funcall orig-major-mode)
        (goto-char (point-min))
        (when (functionp write-back)
          ;; CHANGE: Hack to allow stepping through through this function in
          ;; edebug: I commented out this line:
          ;;
          ;;   (funcall write-back)
          ;;
          ;; and inlined the value of write-back here and reindented for
          ;; readability:
          (when (> org-edit-src-content-indentation 0)
            (while (not (eobp))
              (unless (looking-at "[     ]*$")
                (indent-line-to (+ (org-get-indentation)
org-edit-src-content-indentation)))
              (forward-line))))
        ;; CHANGE: Switch into org-mode so that `indent-tabs-mode` in org mode
        ;; buffers will also be respected:
        (org-mode)
        (unless (or preserve-indentation (= indentation 0))
          (let ((ind (make-string indentation ?\s)))
            (goto-char (point-min))
            (while (not (eobp))
              (when (looking-at-p "[ \t]*\\S-")
                ;; CHANGE: Replace this:
                ;;   (insert ind)
                ;; with this:
                (indent-line-to (+ (org-get-indentation) indentation)))
              (forward-line))))
        (buffer-string)))))

I've tested this with org-mode's indent-tabs-mode set to t and nil and
it seems to display correctly.

Hence, I'm now thinking the possible bug here was the "(insert ind)"
in the last change above, in addition to the need to switch modes.

WDYT?

Thanks,
Brent

P.S.: Thanks for your help on this!

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-28 15:29       ` Brent Goodrick
@ 2017-04-29  9:59         ` Nicolas Goaziou
  2017-04-29 16:41           ` Brent Goodrick
  0 siblings, 1 reply; 7+ messages in thread
From: Nicolas Goaziou @ 2017-04-29  9:59 UTC (permalink / raw)
  To: Brent Goodrick; +Cc: emacs-orgmode

Hello,

Brent Goodrick <bgoodr@gmail.com> writes:

> I do not understand what is meant by "tailored for the source" which
> is the Org buffer. All of the indentation changes being made here are
> within the temporary buffer created by with-temp-buffer, which is
> using fundamental-mode which is not the same as either emacs-lisp-mode
> or org-mode. That is the wrong mode to be in when running `indent-line-to`
> function since it is that particular editing mode that the user has
> control over the `indent-tabs-mode` variable (typically from mode hook
> functions). So I conclude that the temporary buffer, at that point in
> execution, has to be in the mode of the language used when indenting.

This is not necessary. `org-src--contents-for-write-back' merely adds up
indentation to the existing one. In particular, it doesn't re-indent
lines. The indentation being added depends on Org mode (was the block in
a list? Is it a src block where special indentation rules apply...), not
on the major mode from the edit buffer.

However, you have a point, as we need to somehow retain the values of
`indent-tabs-mode' and `tab-width' from Org source buffer, since those
may differ from the ones used in the temporary buffer.

Also, calling `org-mode' again in the temporary buffer, in addition to
being slow, wouldn't preserve, e.g., local values from the source
buffer. So I think the best thing to do is to store `indent-tabs-mode'
and `tab-width' from source buffer and apply them back into the
temporary buffer.

I committed a change along those lines, along with tests, in the maint
branch. Does it fix the issues you were encountering?

Regards,

-- 
Nicolas Goaziou

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

* Re: org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers
  2017-04-29  9:59         ` Nicolas Goaziou
@ 2017-04-29 16:41           ` Brent Goodrick
  0 siblings, 0 replies; 7+ messages in thread
From: Brent Goodrick @ 2017-04-29 16:41 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: emacs-orgmode

On Sat, Apr 29, 2017 at 2:59 AM, Nicolas Goaziou <mail@nicolasgoaziou.fr> wrote:
> Hello,
>
> Brent Goodrick <bgoodr@gmail.com> writes:
>
>> I do not understand what is meant by "tailored for the source" which
>> is the Org buffer. All of the indentation changes being made here are
>> within the temporary buffer created by with-temp-buffer, which is
>> using fundamental-mode which is not the same as either emacs-lisp-mode
>> or org-mode. That is the wrong mode to be in when running `indent-line-to`
>> function since it is that particular editing mode that the user has
>> control over the `indent-tabs-mode` variable (typically from mode hook
>> functions). So I conclude that the temporary buffer, at that point in
>> execution, has to be in the mode of the language used when indenting.
>
> This is not necessary. `org-src--contents-for-write-back' merely adds up
> indentation to the existing one.

Agreed.

> In particular, it doesn't re-indent
> lines. The indentation being added depends on Org mode (was the block in
> a list? Is it a src block where special indentation rules apply...), not
> on the major mode from the edit buffer.
>
> However, you have a point, as we need to somehow retain the values of
> `indent-tabs-mode' and `tab-width' from Org source buffer, since those
> may differ from the ones used in the temporary buffer.
>
> Also, calling `org-mode' again in the temporary buffer, in addition to
> being slow,

Yes, I wondered about the performance impact. Agreed.

> wouldn't preserve, e.g., local values from the source
> buffer. So I think the best thing to do is to store `indent-tabs-mode'
> and `tab-width' from source buffer and apply them back into the
> temporary buffer.
>
> I committed a change along those lines, along with tests, in the maint
> branch. Does it fix the issues you were encountering?

It works splendidly!

Thanks Nicolas.
--Brent

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

end of thread, other threads:[~2017-04-29 16:41 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-03  4:15 org-src--contents-for-write-back : preserve original major-mode, and avoid mixing tabs and spaces in org-mode buffers Brent Goodrick
2017-04-22  8:41 ` Nicolas Goaziou
2017-04-23  3:21   ` Brent Goodrick
2017-04-27 23:04     ` Nicolas Goaziou
2017-04-28 15:29       ` Brent Goodrick
2017-04-29  9:59         ` Nicolas Goaziou
2017-04-29 16:41           ` Brent Goodrick

Code repositories for project(s) associated with this public 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).