reddit / emacs
Newline behavior like other editors
Vaddi3 · 2022-09-28 16:30
Does Emacs have a function that makes `RET` behave like in other editors such as Kate or vscode?
In the following I'm going to emulate what `whitespace-mode` does for better clarity, i.e, `·` represents space and `$` represents newline. I'm also going to use `|` to present the cursor.
# Plain Text
Consider that you are editing a `plaint text` file. If your editor is Kate, you can use both `RET` and `C-RET` (in Emacs parlance) to get two different results.
For example, if your file is
Some text.$
············|
you can use `RET` to get
Some text.$
············$
············|
or `C-RET` to get
Some text.$
············$
|
Natively Emacs uses `RET` to get the behavior of Kate's `C-RET`. I also think that Emacs does not offer any function that acts like Kate's `RET`, so I hacked a function that I believe does what I'm after:
(defun newline-and-indent-to-previous-indent-column ()
(interactive)
(let ((column (current-indentation)))
(newline)
(indent-to column)
))
# Python
Consider that you are editing a `python` file.
In other editors when you press `RET`, one of two things happens (unless the cursor is after a dedent statement):
+ If the cursor is at the end of a line that doesn't have the header of control flow statements, function definitions, etc. Then `RET` makes the cursor move one line down and go to the indent column of the previous line (the one you were before pressing the key).
+ If the cursor is at the end of a line that has a header of a control flow statement or function definition, etc.
Then pressing `RET` makes the cursor move one line down and increase the indentation level relative to the previous line.
Focusing our attention on the second type of event, current Emacs does not do this. If you have `electric-indent-mode` active, you can either move down and reset all the indentation with `C-j` or keep the indentation level with `RET` (and vice versa if you don't have `electric-indent-mode` active). The latter is a bit annoying when you have the following situation:
if True:$
····if True:$
········print('hey')$
····|
and press `RET`.
What then happens is
if True:$
····if True:$
········print('hey')$
$
········|
But I think that
if True:$
····if True:$
········print('hey')$
$
····|
is more intuitive.
We can hack the previously created function so that it checks for a `:` character, in order to consider both events described in the bullet points, and have the intended behavior for `python`:
(defun newline-and-indent-to-previous-indent-column ()
(interactive)
(if (= (preceding-char) 58)
(newline-and-indent)
(let ((column (current-indentation)))
(newline-and-indent)
(delete-horizontal-space t)
(indent-to column))))
This however is not ideal because every mode has its indentation rules that are adaptations of native `newline-and-indent`, which doesn't really take into account if the current and/or the previous line are only composed of whitespace. So I would have to adapt all the indentation functions for every mode If I wanted this behavior globally.
Maybe there is a function in Emacs that already does something like this and I'm unaware of it. So I wanted to know if it is indeed the case.
--------
**EDIT**: Sorry for changing the original post so much, but the original flow of reasoning was not ideal and I think would make people focus too much on the python example. The current version is more in line with how I envisioned the question in my head. I promise not to change it anymore, unless it is for typos and grammar.
eleven_cupfuls · 2022-09-28 17:29
The details of this behavior are generally up to the major mode; see `indent-line-function`, which is set to `python-indent-line-function` in `python-mode`. It's also worth noting that electric indent behavior may not be signficant in `python-mode`: see `electric-indent-inhibit`, which `python-mode` sets `t`.
From a quick glance, it doesn't look like Python's indent behavior has customizable settings. Indentation calculation is a bit intricate; there tend to be a good number of edge cases. But it might be hackable; I would start by looking at `python-indent-line`.
Vaddi3 · 2022-09-28 17:33
`electric-indent-inhibit` simply swaps what `C-j` and `RET` do. So that is not really important for the cases I'm describing.
kelthan · 2022-09-28 17:52
There is `newline-and-indent` and `reindent-then-newline-and-indent` which do the things you are asking for.
thetemp_ · 2022-09-28 18:08
>Consider that you are editing a python file. In other editors when you press the Return key, one of two things happens (unless the cursor is after a dedent statement):
>
>If the cursor is at the end of a line that doesn't have the header of control flow statements, function definitions, etc. Then pressing the Return key makes the cursor move one line down and go to the indent column of the previous line (the one you were before pressing the key).
>
>If the cursor is at the end of a line that has a header of a control flow statement or function definition, etc. Then pressing the Return key makes the cursor move one line down and increase the indentation level relative to the previous line.
>
>Current Emacs does not do this. If you have electric-indent-mode active, you can either move down and reset all the indentation with C-j or keep the indentation level with RET.
Not sure what version of Emacs you're running, but when I open it with `emacs -Q` on a fairly recent build of Emacs 29 and create a file called "test.py", it behaves exactly as described above (with `electric-indent-mode` enabled by default).
>The latter is a bit annoying when you have the following situation (character | represents the cursor):
So the cursor was previously on the line that has `print('hey')`, which line was indented to column 8. But in your depiction, the cursor is on the next line, at column 4.
In going from the `print('hey')` line to that depicted starting point, you would have had to have pressed "RET <backspace>" (inserting a new line with indentation and then dedenting from column 8 to column 4).
If I understand correctly, you would like to be able to press RET from that white-space-only line at column 4 and land on column 4 of the next line. But of course Emacs sets indentation based on the last line that had *non-white-space* characters, so it indents to column 8.
The obvious (but unsatisfying) solution would be to just wait to adjust the indentation until you're on the line where you'll be typing text. But what's done is done, and you don't want to have to do it again.
Another (probably still unsatisfying) solution would be to go ahead and type the text now that you've set the indentation and then do the equivalent of "C-a C-o" to insert the empty line above while keeping the indentation you've just set.
I don't think there's a built-in command to do that, but it might be something you could change by adjusting the settings for `python-mode`. Based on its docstring, `electric-indent-mode` uses rules set by the respective major modes. Alternatively, you could advise the `new-line-and-indent` function to work as you like, but that might have unintended effects.
thetemp_ · 2022-09-29 17:16
What is the bigger picture of what you're trying to accomplish? In my earlier answer, I thought you were trying to keep your new indentation column even though you didn't enter any non-whitespace on that line. The previous line became blank in your desired result for the Python example. That would be expected, because `electric-indent-mode` cleans up extraneous whitespace when you press RET. But you didn't seem to be complaining about that, only the reset indentation.
However, in your new `text-mode` example, it shows the whitespace remaining on the previous line. Are you trying to create a rectangular area where the whitespace on empty lines is preserved? Or are you trying to create a persistent indentation column where all new lines will begin? Or are you trying to get `electric-indent-mode` to set indentation of the next line based on the current line even when the current line has no non-whitespace characters?
We need to understand what you're trying to achieve in order to give meaningful help.
I think the command you wrote is likely the best result you'll get, as I suspect that no such built-in command exists.
Vaddi3 · 2022-09-29 18:40
> We need to understand what you're trying to achieve in order to give meaningful help.
Sorry, I only noticed that today, but I think I'm wrongly asking for two different things in the original post. This happened because Kate does not indent the previous line after newline, and I only noticed that later. So, in the original post only the python example is correct.
I did some more research and noticed that every editor has its own way of dealing with newline. I also noticed that except for Emacs, all of the editors use the same indentation rules regardless of they are editing a source file (e.g. python) or plain text.
Below I'm going to give examples of how `newline-and-indent` is done in some editors.
## Emacs
### Start
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')|
### Return + Backtab (or Return + Backspace)
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
$
⋅⋅⋅⋅⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
$
$
⋅⋅⋅⋅⋅⋅⋅⋅|
## sublimetext
### Start
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')|
### Return + Backtab (or Return + Backspace)
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
$
$
⋅⋅⋅⋅|
## vscode
### Start
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')|
### Return + Backtab (or Return + Backspace)
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅$
$
⋅⋅⋅⋅|
## kate/kwrite
### Start
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')|
### Return + Backtab (or Return + Backspace)
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅$
⋅⋅⋅⋅|
### + Return
if⋅True:$
⋅⋅⋅⋅if⋅True:$
⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$
⋅⋅⋅⋅$
⋅⋅⋅⋅$
⋅⋅⋅⋅|
I think that Sublime Text has the implementation that I prefer and is more intuitive.
With this in mind, my goal e maybe to adapt and redefine Emac's `newline` or `electric-indent-post-self-insert-function` so that it takes into account lines with whitespace, and ultimately behave like Sublime Text.
I think that if one of these functions is adapted, the change propagates to all the major modes' `newline` functions, so that I can get their indentation rules together with my whitespace one.
thetemp_ · 2022-09-29 23:16
Sounds like you're on the right track. Also, you should take a look through "indent.el", which has most of the functions regarding indentation. It also defines a variable named `indent-line-function` which controls which function is called to indent a line. The default value is `'indent-relative`, which is what causes the indentation to be based on on the last non-blank line. So if you write your own version of that function and set `indent-line-function` to your function, that may be all you have to do.
Vaddi3 · 2022-09-29 23:20
Thank you, I'm going to try that.