Insert Mode on Steroids

Motivation

Modal editing is great, but when people say they just want to enter their editor and start typing I think I understand what they mean. I don’t think the deal is pressing i. Rather, I’d say is the lack of support for insert mode. Just look at the number of mappings of the rest of the modes and the number of mappings of insert mode. Even writing in the prompt is easier (for those that know they keybindings, which is not my case :sweat_smile:). Kakoune forces mode switching even for the simplest tasks, like moving a word behind to change something.

In my opinion, Kakoune is sublime for editing code projects where you basically make small changes everywhere, but sometimes you want to write a lot of new text/code.

I will write here my customization to tackle some of this issues. Feel free to comment with additions and reviews (criticism is accepted).

Also, a lot of the code is actually not mine. I’ll try to link to the original source.

General Shortcuts

Return to normal mode

Most of us find the esc key to be far way from our fingers, specially if you keep your hands more close to the lower keys to reach ctrl and alt. That’s why people tend to map key combinations like jd to escape insert mode. I feel it’s better to map a ctrl-key combination and use it everywhere: to escape insert mode, to escape prompt mode, to toogle the completition menu off when it’s disturbing, etc.
I’ve started to use <c-q> but I’m looking to change it because you get used to pressing it and it closes most programs.

TODO: looking for a new combination

Edit file

For me, editing files or changing between them feels more like an action than a command I want to type (that’s why we have ga). That’s why it makes sense to map a combination. I’d use <c-o> because that’s what most programs use, but it’s already used in Kakoune. That’s why I decided to map <c-e> (ctrl-edit) to the edit command and <c-E> to the buffer command.

Close buffer

The same thing applies for closing buffers, specially draft buffers. I’d rather not type : db and press enter. To maintain compatibility with browsers and most text editors, I mapped <c-w> to this functionality.

Save file

What about saving a file? It’s <c-s> in most programs, but in Kakoune <c-s> is saving selections. I decided I rarely need to save my selections manually (even less by using phantom-selections). Therefore, I mapped <c-s> to save and created a shortcut in user mode to save selections.

Open prompt in insert

<c-p> (ctrl-prompt) instead of <a-;>:

Comment Lines

Other editors like Visual Studio Code use the mapping <c-/> for this. Unfortunately, we cannot map it in terminals. For the moment, I’m using # in normal mode but I’d be glad to have a key combination that could also be used in insert mode.
TODO: Look for a new key combination

<tab>/<s-tab> for completition

Then again, we do use tab everywhere for completition, so I think we should use it here too. Additionally, I like to be able to close the completition menu with esc or the mapping that you usually use for this purpose.

Check @mawww’s kakrc, from where I took the implementation for this.

Chart and code

Combination Mode(s) Action
<c-q> Not normal Return to normal mode (esc)
<c-e> Insert/Normal Edit File
<c-b> Insert/Normal Change to buffer
<c-w> Insert/Normal Close buffer
<c-s> Insert/Normal Save file
<c-p> Insert Open Prompt
# Normal Comment Lines
<tab>/<s-tab> Insert Completition selection
<esc>/<c-q> Insert Toggle completition menu
# General Short-cuts and custom mappings
# ––––––––––––––––––––––––––––––––––––––
# <c-q> - Return to normal mode
map global insert <c-q> <esc>
map global prompt <c-q> <esc>
# <c-e> - Edit file, <c-w> - Close buffer
map global normal <c-e> ': edit '
map global insert <c-e> '<esc>: edit '
map global normal <c-E> ': buffer '
map global insert <c-E> '<esc>: buffer '
map global normal <c-w> ': db<ret>'
map global insert <c-w> '<esc>: db<ret>'
# <c-s> - Save file
map global normal <c-s> ': w<ret>'
map global insert <c-s> '<a-;>: w<ret>'
map global  user  <c-s> '<c-s>'
# <c-p> - Open prompt in insert
map global insert <c-p> '<a-;>:'
# <#> Comments lines
map global normal '#' ': comment-line<ret>'
# <tab>/<s-tab> for completion selection
hook global InsertCompletionShow .* %{
    map window insert <tab>   '<c-n>'
    map window insert <s-tab> '<c-p>'
    map window insert <esc>   '<c-o>'
    map window insert <c-q>   '<c-o>'
}
hook global InsertCompletionHide .* %{
    unmap window insert <tab>   '<c-n>'
    unmap window insert <s-tab> '<c-p>'
    unmap window insert <esc>   '<c-o>'
    unmap window insert <c-q>   '<c-o>'
}

Improved Movement

Better home movement

When coding, I feel like most of the time we’d rather move to the the first non-blank character than to the beginning of the line (gi), but sometimes you do want to move to the beginning of the line. My approach is, <home> behaves as gi unless you are already in the first non-blank character, in which case it behaves as gh. Same functionality for <s-home>.

Ctrl-arrow to move between words and paragraphs

This is something most browsers and apps allow in their text fields. In Kakoune we shouldn’t need them because we have w/b and ]p/[p, but in reality, I believe it is very comfortable to be able to do it without leaving insert mode.

Chart and Code

Combination Mode(s) Action
<home>/<s-home> Insert/Normal Improved home movement
<c-left>/<c-right> Insert/Normal Move around a word
<c-down>/<c-up> Insert/Normal Move around paragraph
# Movement
# ––––––––––––––––––––––––––––––––––––––
# Home moves/expand to the begining of line/non blank depending on position
define-command -override -hidden home-expansion %{
    # Run each selection independently so that the
    # test does not just remove failing selections.
    eval -itersel %{ 
        try %{
            exec -draft %{ <a-h><a-k>\A\h+.\z<ret> } # check that the preceeding characters are horizontal whitespaces
            exec Gh # if the previous line does not fail, go to begining of line
        } catch %{
            exec Gi # if it failed, go to first non blank character
        }
    }
}
map global insert <home>      '<esc>: home-expansion<ret>;i'
# If you want to implement the following section, use this instead:
# map global insert <s-home>    '<esc>: home-expansion<ret>: enter-user-mode fixsel'
map global insert <s-home>    '<esc>: home-expansion<ret>i'
map global normal <home>      ': home-expansion<ret>;'
map global normal <s-home>    ': home-expansion<ret>'

# Ctrl for moving objects in insert mode
map global insert <c-left>    '<a-;>b<a-;>;'
map global insert <c-right>   '<a-;>w<a-;>;'
map global insert <c-up>      '<a-;>[p<a-;>;'
map global insert <c-down>    '<a-;>]p<a-;>;'
map global normal <c-left>    'b;'
map global normal <c-right>   'w;'
map global normal <c-up>      '[p;'
map global normal <c-down>    ']p;'

Insert Mode Selections

Just as much as we want to move inside insert mode, we’d like to be able to select things and do quick fixes. It’s pretty easy to implement <c-s-left> to select a word to the left, but we cannot do anything to selections while we are in insert mode. For me, quick fixes means deleting, yanking, changing or surrounding text.

I decided I could achieve that functionality via a new user mode. I’ve called it fixsel (fix selection) because I like mode names with 6 letters for my scripts (like insert and normal).

Entering fixsel mode

The combinations <ctrl-s-arrow> behave just like <ctrl-arrow> but they add a word/paragraph to the current selection and enter fixsel mode (which falls back to normal mode for complex tasks).

The same applies for <s-arrow> but with a single character.

It is also possible to continue expanding the selection in fixsel mode.

fixsel mappings

In the new mode, you can insert(i), append(a), change(c), yank(y) and delete(d) as usual. There’s also a mapping that will result familiar for newcomers: <backspace> behaves as c. Deleting a word you mistyped is as “natural” (ofc, this is subjective) as pressing <c-s-left> to select it and pressing <backspace>, after which you can continue editing.

In addition, we have some already mentioned mapping to surround the selection: ", ', ( and {. They depend on the plugin kakoune-surround by @h-youhei.

Chart and code

Combination Mode(s) Action
<home>/<s-home> Insert/Normal Improved home movement
<c-s-left>/<c-s-right> Insert/Normal Select around a word and enter quickfix
<c-down>/<c-up> Insert/Normal Select around paragraph and enter quickfix
<backspace> Fixsel Delete selection and edit (c)
"/'/(/{ Fixsel Surround selection
# Selection
# ––––––––––––––––––––––––––––––––––––––
declare-user-mode fixsel
map global fixsel i 'i'
map global fixsel a 'a'
map global fixsel c 'c'
map global fixsel <backspace> 'c'
map global fixsel y 'y'
map global fixsel d 'd'
map global fixsel m 'm'

# map global fixsel H 'H'
# map global fixsel L 'L'
# map global fixsel K 'K'
# map global fixsel J 'J'
map global fixsel <s-left>  'H: enter-user-mode fixsel<ret>'
map global fixsel <s-right> 'L: enter-user-mode fixsel<ret>'
map global fixsel <s-up>    'K: enter-user-mode fixsel<ret>'
map global fixsel <s-down>  'J: enter-user-mode fixsel<ret>'
# map global fixsel B 'B'
# map global fixsel W 'W'
# map global fixsel { '{'
# map global fixsel } '}'
map global fixsel <c-s-left>  'B: enter-user-mode fixsel<ret>'
map global fixsel <c-s-right> 'W: enter-user-mode fixsel<ret>'
map global fixsel <c-s-up>    '{p: enter-user-mode fixsel<ret>'
map global fixsel <c-s-down>  '}p: enter-user-mode fixsel<ret>'
map global fixsel <s-home>    ': home-expansion<ret>: enter-user-mode fixsel<ret>'
map global fixsel <s-end>     'Gl: enter-user-mode fixsel<ret>'
map global fixsel <"> ': surround<ret>"' -docstring 'surround with ""'
map global fixsel <'> ": surround<ret>'" -docstring "surround with ''"
map global fixsel <(> ': surround<ret>(' -docstring 'surround with ()'
map global fixsel <{> ': surround<ret>{' -docstring 'surround with {}'

map global insert <s-left>    '<esc>H: enter-user-mode fixsel<ret>'
map global insert <s-right>   '<esc>L: enter-user-mode fixsel<ret>'
map global insert <s-up>      '<esc>K: enter-user-mode fixsel<ret>'
map global insert <s-down>    '<esc>J: enter-user-mode fixsel<ret>'
map global insert <c-s-left>  '<esc>B: enter-user-mode fixsel<ret>'
map global insert <c-s-right> '<esc>W: enter-user-mode fixsel<ret>'
map global insert <c-s-up>    '<esc>{p: enter-user-mode fixsel<ret>'
map global insert <c-s-down>  '<esc>}p: enter-user-mode fixsel<ret>'
map global normal <c-s-left>  'B: enter-user-mode fixsel<ret>'
map global normal <c-s-right> 'W: enter-user-mode fixsel<ret>'
map global normal <c-s-up>    '{p: enter-user-mode fixsel<ret>'
map global normal <c-s-down>  '}p: enter-user-mode fixsel<ret>'

Alternative Implementation

If you’d prefer to select things and jump to normal mode instead of entering a special mode, it’s very easy to adapt the code above.

Powerful Functionality

Sublime’s and Visual Studio Code <ctrl-d>

Thanks to @andreyorst, we can enjoy the functionality in Kakoune as well. Have a look.

Move line’s above or below

Check @alexherbo2’s plugin. I suggest you map the command to <a-down> and <a-up>.

Kakoune phantom selections

Sublime’s <c-d> mapping became very popular. I think @occivink’s phantom selections plugin can make Kakoune very appealing.

I’ve found very useful to loop and modify individual selections with <tab>. We already discussed about it in another blog post. <c-g> is my new favourite mapping along with <c-d>.

Automatically grouping insert modifications in undo groups

TODO: I’d like to implement a hook that would group modifications into a single undo group after some time (every 5s?). This would also favor big inserts.

Conclusion

I think these modifications make Kakoune feel more similar to other programs without compromising modal editing and Kakoune’s view. I would also claim that it makes easier to work in insert mode, but everyone has their own tastes.

The part I am the least comfortable with is the new mode fixsel. I continue to think in ways of making this behave better.

7 Likes

Thanks for this thorough exploration of what a beef up insert mode can look like. There are lot of good things here.

I’m starting to pare down my keyboard mappings to move to a 40% keyboard, and haven’t figured out what to do with Esc either. A colleague I worked with for a while had jk mapped to Esc in Vim. It worked for years until he had to type Dijkstra. :slight_smile:

At the time I looked it up - while there is one word with jk, there are no words with kj. I’m thinking about using q+' (held at the same time), but still looking for ideas.

t the time I looked it up - while there is one word with jk , there are no words with kj . I’m thinking about using q + ' (held at the same time), but still looking for ideas.

I’d like to be able to press it using only my left hand. Sometimes I work using my mouse and I like to be able to control things with my left hand. Perhaps you’ve noticed it in the rest of the shortcuts. I don’t know if this is a bad habit though. I’ll try to make my right hand used to pressing shortcuts starting with looping through the history with <c-i> and <c-o>.

I’ve been thinking of using <c-a>. It’s used almost everywhere, including browsers, to select all the text, but we have % for that.

Ohh, and I like to have a shortcut over a combination of keys because then I can map it to different things and make use of the precedence of scopes. For example, to hide the completition menu when it appears.

Maybe not applicable on a 40%, but I swap caps lock and esc at the os level. Makes a huge difference in ease of returning to normal mode.

I swap Caps Lock and Ctrl, Ctrl with Win (my kb is manufactured in 1986 and does not have it), and R Shift with Esc.

I use Ctrl so often (much more than Esc) and Caps Lock is right next to pinky finger, so losing it to Esc is not a good option. R Shift on the other hand is a really good fit for Esc, because I rarely use it for capitalizing or shortcuts.

What do you guys think of having an insert user mode? This could include:

  • Commenting a line
  • Indenting/deindenting

I thought that perhaps it could be merged with the fixsel mode and renamed to usert. I don’t like to have multiple user modes unless they are very specific (related to git, for instance), but a normal user mode and an insert user mode seems reasonable.

I don’t know if it could be interesting to pack this into a plugin.

This helps so much with modal editing. I do one better, and overload Caps with Esc and Ctrl.
I use xcape on Linux:

# ~/.xprofile
# Set capslock as Esc when alone, and Ctrl when with another key
setxkbmap -option 'caps:ctrl_modifier'
xcape -e 'Caps_Lock=Escape'

and dual-key-remap on Windows (for Kakoune on WSL).

What would be the activation key? IMO it’d be simpler to just map things to Ctrl or Alt combinations. And for anything more complicated, it’s easier to just press Esc to return to normal mode. For me, pressing Esc (Caps) with my pinky after I type has become so ingrained that I do it even when I’m using other programs.

Well, I was thinking it could activate alone on selection, like fixsel, but additionally you’d be able to enter with a shortcut. This allows for quick selection and surrounding/deleting, for instance. But I thought it could be useful to add comment options and more.

I came up with the following implementation. It only groups changes after intervals of at least %opt{grouping_delay} seconds (though the precision could be bigger) and following the insertion of whitespace.

# Automatic group of changes in insert mode
declare-option -hidden int last_grouping
declare-option int grouping_delay 4
hook global ModeChange "push:.*:insert" %{
    set buffer last_grouping %sh{ date +%s }
}
hook global InsertChar "\s" %{ eval %sh{
    if [ $(($kak_opt_last_grouping + $kak_opt_grouping_delay)) -le $(date +%s) ]; then
        printf "set buffer last_grouping $(date +%s)\n"
        printf "exec <c-u>\n"
    fi
}}

EDIT: I didn’t notice at first, but the script misbehaves with the keys o and O, because <c-u> gets executed in normal mode instead of insert mode. I could set the hook each time the insert mode is intered.

I use the default <c-[> as escape, with caps as control.

1 Like