Repeating a character n times in Insert mode

If you want to insert a character (say, an asterisk, *) 10 times in your document, then in Vim’s command mode you can type:

10i*<esc>

Doing so from Vim’s Insert mode is more annoying because you need to move in and out of Insert mode twice:

<esc>10i*<esc>a

See:

Here is a kak script that you can put in your autoload directory and that allows you to repeat a character n times in Insert mode. You just need to put

repeat-char-enable

in your kakrc file to have access to the repeat-char command. I call this command in Insert mode with the <a-r> hotkey, but which hotkey you use can be configured by setting the option

repeat_char_hotkey

to whatever value you prefer.

A note on command execution: repeating a char could be done in fewer keystrokes by using a count and the user key followed by the on-key command, but I prefer the solution below because it gives more visual feedback, which is especially useful in the case of a hard-to-reach key (such as the = char on a Brazilian keyboard).

declare-option \
-docstring \
'hotkey (e.g., <a-r>) to repeat character in Insert mode' \
str repeat_char_hotkey <a-r>

define-command \
-docstring \
'allow repeating a character n times' \
repeat-char-enable %{
    hook -group repeat-char global InsertKey %opt(repeat_char_hotkey) %{
        repeat-char
    }
}

define-command \
-docstring \
'disable character-repetition command' \
repeat-char-disable %{
    remove-hooks global repeat-char
}

define-command \
-hidden \
repeat-char %{
    prompt "enter <count><char> (e.g., 10*):" \
    %[ evaluate-commands %sh[
        rule=$kak_text
        length=$(expr length "$rule")
        digit_plus_char=2
        if [ $length -lt $digit_plus_char ]; then
            printf 'fail "missing digits or character"'
            exit
        fi
        count=$(expr substr "$rule" 1 $((length-1)))
        char=$(expr substr  "$rule" $length 1)
        if ! expr "$count" : '[1-9][0-9]*$' >/dev/null; then
            printf 'fail "incorrect format"'
            exit
        fi
        seq=
        step=0
        while [ $step -lt $count ]; do
            seq=${seq}${char}
            step=$((step + 1))
        done
        #
        # A sequence of single quotes must be double-quoted. Any other sequence
        # is single-quoted to avoid parsing issues with ", %, or ;.
        if [ "$char" = "'" ]; then
            seq='"'${seq}'"'
        else
            seq="'"${seq}"'"
        fi
        #
        # '--': avoid interpreting a -... sequence as a switch.
        printf '%s' "exec -- $seq"
    ]]
}

I think you don’t need -override everywhere

what about this?

define-command repeat-char -params 1 %{ evaluate-commands -save-regs 'a' %{
    set-register "a" %arg{1}
    execute-keys -draft "%val{count}""aP"
}}

To call it like this 22:repeat-char -

The only problem with prompt version is that it doesn’t close itself automatically:

define-command repeat-char %{ evaluate-commands -save-regs 'a' %{
    prompt "enter char: " %{
        set-register "a" %val{text}
        execute-keys -draft "%val{count}""aP"
    }
}}

map global normal <a-r> :repeat-char<ret>

Thanks Andrey, you are right about the unneeded -override. (I put them everywhere because they allow me to source the same script again and again without “command already defined” errors while debugging.) I have edited my post to remove all of the overrides.

Your solutions are way more elegant than mine in terms of coding, but I still prefer mine in terms of actual use and comfort, for the reason I explained in the original post. I am quite happy to type 34g to go to line 34, for example – because in this case the count is followed by a single key that is easy to remember. In the repeat-char case, however, using a count means 1) typing the count (while glancing on the right to get visual feedback?), 2) typing the hotkey, and 3) typing the char I want to repeat.

Maybe I am just stupid or too old :innocent: but I really can’t stand it, I find that step 2) interrupts the flow (and I recall hating vim’s way of repeating a char for this very reason). In my solution, you first type the hotkey (<a-r> like “repeat”), forget about it, and then type the count and the char in succession. Much more comfortable in my view.

Now if you follow this line of thought, you’ll need the shell to parse the count+char input, so here we are with the loop and the execute-keys command. (I thought about using registers, as you did, but found it “too smart” to call eval -save-regs commands from the shell).

In any case, the best part of it is that all the solutions are available in this topic, so people will be able to select the one they like the most :slightly_smiling_face:

1 Like

I wish built-in i command to be consistent with o command, in other words pressing count i should behave same as pressing count o, with exception that all cursors should appear on the same line. Related issue #1106.

The difference between i and o is that as well as entering insert mode, o also inserts a newline character. 5o can have five cursors, because there are five newline characters for them to select.

How would you feel about 5i automatically inserting, say, five space characters, or "a5i inserting five copies of the a register (or five spaces if "a is empty)?

It’s not necessary to insert count space characters. There could be just one cursor expanding to count upon typing. Following command behaves like this:

define-command -hidden -params 1 count-insert %{
    execute-keys -with-hooks \;i.<esc>hd %arg{1} P %arg{1} Hs.<ret><a-space>c
}
map global user i %{:count-insert %val{count}<ret>} -docstring 'count insert'

Actually there is no problem with pasting 5 copies of empty register with "a5p. So let say 5i<c-r>a should behave same.