reddit / emacs

Followup to "Newline behavior like other editors"

Vaddi3 · 2022-10-07 00:20
Last week I submitted a [post](https://www.reddit.com/r/emacs/comments/xqhbkt/newline_behavior_like_other_editors/) asking if Emacs had a native way of changing the behavior of the `newline` function so that it could work the way some other text editors do. After interacting with some redditors and doing some more research I wrote [this comment](https://www.reddit.com/r/emacs/comments/xqhbkt/newline_behavior_like_other_editors/iqef2fk/) where I concluded that the way Sublime Text handles line composed of whitespace is the most intuitive for me, at least when editing Python source files. Which can be summarized as: If I'm editing a buffer with the following Python code if⋅True:$ ⋅⋅⋅⋅if⋅True:$ ⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$ ⋅⋅⋅⋅| where `⋅` represents a space, `$` represents a newline and `|` represents the cursor, if I press Return I want Emacs to end up like if⋅True:$ ⋅⋅⋅⋅if⋅True:$ ⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$ $ ⋅⋅⋅⋅| instead of if⋅True:$ ⋅⋅⋅⋅if⋅True:$ ⋅⋅⋅⋅⋅⋅⋅⋅print('2x⋅True')$ $ ⋅⋅⋅⋅⋅⋅⋅⋅| After reading the source code of `newline` and all the functions that are called by it I decided that redefining the function ` electric-indent-post-self-insert-function` would be the easiest way to achieve my goal. So I did it in the following way: (defun electric-indent-post-self-insert-function () "Function that `electric-indent-mode' adds to `post-self-insert-hook'. This indents if the hook `electric-indent-functions' returns non-nil, or if a member of `electric-indent-chars' was typed; but not in a string or comment." ;; FIXME: This reindents the current line, but what we really want instead is ;; to reindent the whole affected text. That's the current line for simple ;; cases, but not all cases. We do take care of the newline case in an ;; ad-hoc fashion, but there are still missing cases such as the case of ;; electric-pair-mode wrapping a region with a pair of parens. ;; There might be a way to get it working by analyzing buffer-undo-list, but ;; it looks challenging. (let (pos) (when (and electric-indent-mode ;; Don't reindent while inserting spaces at beginning of line. (or (not (memq last-command-event '(?\s ?\t))) (save-excursion (skip-chars-backward " \t") (not (bolp)))) (setq pos (electric--after-char-pos)) (save-excursion (goto-char pos) (let ((act (or (run-hook-with-args-until-success 'electric-indent-functions last-command-event) (memq last-command-event electric-indent-chars)))) (not (memq act '(nil no-indent)))))) ;; If we error during indent, silently give up since this is an ;; automatic action that the user didn't explicitly request. ;; But we don't want to suppress errors from elsewhere in *this* ;; function, hence the `condition-case' and `throw' (Bug#18764). (catch 'indent-error ;; For newline, we want to reindent both lines and basically ;; behave like reindent-then-newline-and-indent (whose code we ;; hence copied). (let ( (at-newline (<= pos (line-beginning-position))) (whitespace-indentation nil) ;; new variable introduced ) (when at-newline (let ((before (copy-marker (1- pos) t))) (save-excursion (unless (or (memq indent-line-function electric-indent-functions-without-reindent) electric-indent-inhibit) ;; Don't reindent the previous line if the ;; indentation function is not a real one. (goto-char before) (condition-case-unless-debug () (indent-according-to-mode) (error (throw 'indent-error nil))) ) (unless (eq electric-indent-inhibit 'electric-layout-mode) ;; Unless we're operating under ;; `electric-layout-mode' (Bug#35254), the goal here ;; will be to remove the trailing whitespace after ;; reindentation of the previous line because that ;; may have (re)introduced it. (goto-char before) (when (looking-back "^[[:space:]]*$") (setq whitespace-indentation (current-indentation))) ;;keep the indentation of an whitespace line before it is deleted ;; We were at EOL in marker `before' before the call ;; to `indent-according-to-mode' but after we may ;; not be (Bug#15767). (when (and (eolp)) (delete-horizontal-space t) ) )))) (unless (and electric-indent-inhibit (not at-newline)) (condition-case-unless-debug () (if whitespace-indentation (indent-to whitespace-indentation) (indent-according-to-mode)) (error (throw 'indent-error nil))))))))) All I did was add this 2 lines (whitespace-indentation nil) (when (looking-back "^[[:space:]]*$") (setq whitespace-indentation (current-indentation))) and this conditional (if whitespace-indentation (indent-to whitespace-indentation) (indent-according-to-mode)) As this is my first time writing Elisp and messing with the source code of Emacs, I'm not really certain of what I'm doing. So I want to ask for suggestions on how to improve these changes, in order to avoid unwanted behaviors and bugs. Thanks
Vaddi3 · 2022-10-07 00:23
I'm tagging u/thetemp_ because you seemed interested last week.
lebensterben · 2022-10-07 00:32
what emacs does is two things, make a new line and move the cursor to an inferred level of indentation. in your example, after making the newline there is neither lexical nor syntactical information to dictate emacs to change the indentation level. so it chose the same indentation level of the line with print expression. what sublime does is utterly wrong, though convenient for you.
Vaddi3 · 2022-10-07 00:37
I agree. But I only want to change the behavior when the cursor is at a line of whitespace. I'm leaving all the other situations for `(indent-according-to-mode)` to handle, which gives better results compared to other editors.
patrick_thomson · 2022-10-07 01:24
I think “utterly wrong” is a real stretch here. There is information that emacs can use: the fact that, as OP mentioned out, the point is indented 4 spaces should inform the heuristics used. I agree with OP that most modern editors are right to do this and it’s a shame that python-mode doesn’t.
lebensterben · 2022-10-07 01:29
the point is in a line with only whitespace. but whitespace in this case is syntactically meaningless to python. they are correctly ignored.
patrick_thomson · 2022-10-07 01:34
The fact that this particular whitespace doesn’t affect program semantics is totally orthogonal to whether or not emacs should aggressively indent at this point.
lebensterben · 2022-10-07 01:44
it't not orthogonal. it is the most critical part in determining indentation level instead. and neither is emacs "aggressively" indenting. Emacs is actually very conservative that it keeps the same indentation level as the last non-empty line.
patrick_thomson · 2022-10-07 01:53
Please explain to me why, in a indentation-sensitive language, the current position of the point is not critical to the editing/indentation experience.
lebensterben · 2022-10-07 01:58
I've explained already. Lines with only whitespaces have no syntactical meaning.
patrick_thomson · 2022-10-07 02:02
But you haven’t explained why that fact outweighs the very real indicator of user intent that is the position of the cursor.
lebensterben · 2022-10-07 02:09
it's syntactically meaningless. and why do you expect a program to guess user intent? are you sure every user has the same intent in this situation?
patrick_thomson · 2022-10-07 02:14
We’re not getting anywhere. Have a good day.
hibbelig · 2022-10-07 09:15
What an editor should do should not be *solely* driven by the question whether it's syntactically meaningful. For example, spell-checking in a comment is useful to the user even though it doesn't change the execution of the program.
weelyat · 2022-10-07 14:32
What is the next action for the programmer in your scenario?