Refactoring
to
maintainability

Adding insert lines to Emacs

2020-04-07
Emacs

I tried evil-mode, twice. The second time I was more prepared. Still, I found too many issues with keymaps, with the buffers where it should be active or not, keybinding clashes with every single package that I have installed … configuration in general.

I do consider the vim keybindings slightly superior to Emacs, and still have it activated on VsCode, for example (LiveShare is too good, sadly, to ignore). But the annoyances on Emacs where too much. And although I am fine with trying things and adapting Emacs to my liking, there is only so much time I can dedicate to actually make a single package working.

So I went back to “vanilla” Emacs bindings.

The missing functionality

But quickly I discovered that I was missing something: o and O. Which in vi/vim creates a line below the point, or above the point, respectively.

Although they were not 100% perfect (I am not aware of a modified to not move the point), they did a good job.

One thing that I discovered long time ago is that I will start writing some code and then realized that I needed to leave an empty blank line above or below the code that I was writing. o and O are not perfect because they change you into INSERT mode and change the point. And sometimes you want to create the line but keep the point where you were, or remain in NORMAL mode.

In Emacs you have open-line, binded to C-o. Which basically is using Return in the middle of a line for any other editor. It creates a new line, moves everything after the point to the new line, and then moves the point to the first character of the new line. This is not what I wanted.

You could use C-a RET and C-e RET. The former is going to the beginning of the line, add new line above and keep the point on the line you were. The latter is going to the end of the line, adding a new line below, and moving the point to the new line. I dislike inconsistency.

So, time to investigate how to replicate the vim functionality on Emacs.

The initial solution

The beginning of my solution comes from this answer from Basil: insert-line-above-below on the Emacs StackExchange.

(defun insert-line-below ()
  "Insert an empty line below the current line."
  (interactive)
  (save-excursion
    (end-of-line)
    (open-line 1)))

Investigating a bit what we have here is:

if you change the (end-of-line) to (end-of-line 0) you can create a function for inserting a line above, as passing a numeric argument to end-of-line makes you go to the end of the line that is argument - 1. So for an argument of 2, it will go to the end of the line below, and for an argument of 0 it will go to the end of the line above.

Ok, so now I have defined two functions, one for inserting a line above, one a line below. In both cases the point will remain where it was. And unlike C-o (or return on most editors) it will not break the current line.

I just give them keybindings and I am sorted

(global-set-key (kbd "C-c M-n") 'insert-line-above)

(global-set-key (kbd "C-c n") 'insert-line-below)

Take into account that the keybindings chosen are based on my other keybindings (C-c a, which was an option, is part of my ag keybindings). And the n does remind me of “new line”. Maybe I should change the name of the functions.

The improved solution

What if I want to move the point to the new line? We know that C-a RET doesn’t do the job. I could create separate functions, but I don’t like that. very much, especially as we have in Emacs the awesome universal argument C-u.

Well, not complete awesome, because the implementation is strange (probably I am missing some history here).

The universal argument (or prefix argument) is used to modify behaviour, usually through the passing of a numeric argument (e.g., C-u 6 C-p will go 6 lines up).

On this case I am going to use the presence of the universal argument on its own to modify if I want to move to the newly created line or not.

The modified code is

(defun insert-line-above (universal)
  "Insert an empty line above the current line.
The behaviour change if you pass the default UNIVERSAL argument.  Without it, a new line 
above the current one will be created, but the point will not change its location.  With 
the default UNIVERSAL argument, the point will change to the beginning of the new line created."
  (interactive "P")
  (if (equal universal '(4))
      (progn
        (end-of-line 0)
        (open-line 1)
        (forward-line))
    (save-excursion
      (end-of-line 0)
      (open-line 1))))

The first two things that you will discover is that now we have a parameter in our list (universal), and that the call to interactive now has a parameter "P". Both things are related.

"P" tells elisp that the function will receive a parameter with the value of the universal argument. By the way, this value is also stored on current-prefix-arg, but using it as above is the recommended way (still haven’t found why).

On the if statement we compare the parameter universal against '(4). That’s it, the list that has only 4 within. This is one of the quirkiness of the universal argument. If you want to read a bit more about the strange implementation, you can go to this gnu page.

If you have used C-u before calling the function then instead of using save-excursion it will use progn and will call forward-line as the last command. progn allows you to call multiple functions and returns the result of the last call. It is used for side effects (like do in Clojure). As additional information save-excursion will execute the body inside a progn. forward-line is the programmatic way (that’s it, is not an interactive function) to go to the next line.

So now, if I call C-c M-n I can create a new line above the current one without changing my position, and if I call C-u C-c M-n I create a new line above and then go to it.


Return to Index

Unless otherwise stated, all posts are my own and not associated with any other particular person or company.

For all code - MIT license
All other text - Creative Commons Attribution-ShareAlike 4.0 International License
Creative Commons BY-SA license image