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.

I’m very new to Kakoune, so forgive me if these are dumb questions.

Isn’t there a way to do this natively in Kakoune without resorting to scripts?

Also, it appears your script has Bash code in it. If using scripts is the only way to go, is there a shell-agnostic way of accomplishing the same thing for those of us who don’t use Bash or maybe have Bash on some machines and Fish on others?

It depends what you mean by “this”. Repeating a character is easy, repeating a character from within insert mode with a friendly prompt and error checking (as this script does) is not.

Also, note that Kakoune is designed to make scripts easy and natural, rather than being a last resort. Writing a script is what you reach for first, trying to do something entirely within Kakoune is an exercise in esoteric programming.

Note that Kakoune doesn’t use bash or fish. It always uses the system’s POSIX-compatible shell (usually /bin/sh) regardless of what shell you use interactively. The POSIX shell isn’t as fancy or friendly as other shells, but it’s guaranteed to be present on every POSIX-compatible OS.

As Screwtapello pointed out, the script you are referring to is not written in Bash but in POSIX shell :wink:. Notice for example the use of traditional single brackets ([ ... ]) instead of Bash’s double brackets ([[ ... ]]).

This script is actually written as part of a custom Kakoune command (repeat-char) that has a %sh{ ... } string inside it.

As stated in :doc expansions,

EXPANSIONS

While parsing a command (see :doc command-parsing),
Kakoune recognises certain patterns and will replace them with their
associated value before executing the command. These patterns are called
expansions.

A %sh{ ... } string is called a shell expansion:

Shell expansions

Expansions with the type sh are executed as shell-scripts, and whatever
the script prints to standard output replaces the expansion. For example,
the command echo %sh{date} will echo the output of the date command.

Now, within the POSIX-shell scripts that you execute from Kakoune, you are free to call any tool that you want, and if you look into the source code of some plugins:

https://kakoune.org/plugins.html

you’ll see that some of them call awk, perl, python, ruby, etc.

It depends what you mean by “this”. Repeating a character is easy, repeating a character from within insert mode with a friendly prompt and error checking (as this script does) is not.

Thanks for your reply.

What I meant by “this” was a native, built-in way to repeat a character in Kakoune. For example, you can enter 10 asterisks in Vim by typing 10i*. Is that possible in Kakoune or do you have to rely on scripts for that functionality (forgive me if that is a dumb question, but I’ve searched all over the Kakoune universe and haven’t found an answer yet).

Since #4041, we can create overlapping selections with the + command and Alt + + to merge them.

Example – Insert 80 asterisks:

80+i*<esc><a-+>
4 Likes

TIL, neato!

Thanks!