# [`readline-vim`][]

Upgrade [Readline][] from [vi][] to [Vim][].

[`readline-vim`][] is a [Python][] script that generates [Readline][] macros
(4658 of them) that provide [Vim][] [motions][], [operators][] and [text
objects][] (the ones that are useful on a single line) as well as functionality
from the popular [`vim-surround`][].

[`readline-vim`]: https://git.rcrnstn.net/rcrnstn/readline-vim
[Readline]: https://en.wikipedia.org/wiki/Readline
[vi]: https://en.wikipedia.org/wiki/vi
[Vim]: https://en.wikipedia.org/wiki/Vim_(text_editor)
[Python]: https://www.python.org
[motions]: https://vimhelp.org/motion.txt.html
[operators]: https://vimhelp.org/motion.txt.html#operator
[text objects]: https://vimhelp.org/motion.txt.html#text-objects
[`vim-surround`]: https://github.com/tpope/vim-surround

## Background

[Readline][] is a library that provides line-editing for interactive
command-line programs. It is used in a great many places, including shells such
as [Bash][] (where it originated) and [REPL][]s such as [Python][]'s or
[Octave][]'s. Using [`rlwrap`][], Readline can even be used with programs that
don't have built-in support for it, such as [MATLAB][] (as in `rlwrap -a matlab
-nodesktop -nosplash`). There are bindings in many languages, so if you're a
developer you can easily use it in your own projects. These factors make it
fairly ubiquitous.

Readline can be configured with an [`inputrc`][] file, usually placed at
[`~/.inputrc`][], which supports an [`$include`][] directive useful for
including other files, e.g. from `~/.inputrc.d/`.

Readline uses [Emacs][]-like key bindings by default. If you're a Vim fan
though, you probably use its vi mode, activated by `set editing-mode vi` in the
configuration or through some mechanism in the containing program, such as
Bash's `set -o vi`.

If you *are* a Vim fan though, this has the potential of landing you in a sort
of [uncanny valley][] since (Readline's) vi (mode) does not include the many
improvements contained in Vim that you have come to rely on, mainly [text
objects][] and the related plugins. This is especially bad if you're using
Readline inside Vim's own [`:terminal`][]. [`readline-vim`][] aims to fix this.

[Bash]: https://en.wikipedia.org/wiki/Bash_(Unix_shell)
[REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
[Octave]: https://en.wikipedia.org/wiki/GNU_Octave
[`rlwrap`]: https://manpages.debian.org/rlwrap/rlwrap.1.html
[MATLAB]: https://en.wikipedia.org/wiki/MATLAB
[`inputrc`]: https://www.mankier.com/3/readline#Initialization_File
[`~/.inputrc`]: https://www.mankier.com/3/readline#Files-~/.inputrc
[`$include`]: https://www.mankier.com/3/readline#Initialization_File-Conditional_Constructs
[Emacs]: https://en.wikipedia.org/wiki/Emacs
[uncanny valley]: https://en.wikipedia.org/wiki/Uncanny_valley
[`:terminal`]: https://vimhelp.org/terminal.txt.html#:terminal

## Usage

Clone the repository

```sh
git clone https://git.rcrnstn.net/rcrnstn/readline-vim
cd readline-vim
```

and either append the output to your `~/.inputrc`

```sh
./readline-vim >> ~/.inputrc
```

or use a separate file and include it (recommended)

```sh
mkdir -p ~/.inputrc.d
./readline-vim >| ~/.inputrc.d/readline-vim.inputrc
```

```sh
cat >> ~/.inputrc << 'EOF'

## `readline-vim`
$include ~/.inputrc.d/readline-vim.inputrc
EOF
```

### Use vi mode by default

```sh
cat >> ~/.inputrc << 'EOF'

## Use vi editing mode
set editing-mode vi
EOF
```

### Indicate mode with cursor shape

```sh
cat >> ~/.inputrc << 'EOF'

## Indicate mode with cursor shape
set show-mode-in-prompt On
$if term=linux
    # https://www.kernel.org/doc/Documentation/admin-guide/vga-softcursor.rst
    set vi-cmd-mode-string \1\e[?8c\2
    set vi-ins-mode-string \1\e[?0c\2
    set emacs-mode-string  \1\e[?0c\2
$else
    # https://vt100.net/docs/vt510-rm/DECSCUSR
    # http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
    set vi-cmd-mode-string \1\e[1 q\2
    set vi-ins-mode-string \1\e[5 q\2
    set emacs-mode-string  \1\e[5 q\2
$endif
EOF
```

## Features

-   Motions
    -   vi (built-in)
        -   Word (upper and lower case versions)
            -   `w` *word beginning, forwards*
            -   `e` *word end, forwards*
            -   `b` *word beginning, backwards*
        -   Other
            -   `h`, `l` *left, right*
            -   `0`, `^`, `$` *line beginning, first non-whitespace character,
                end*.
            -   `|` *(first) column*
            -   `;`, `,` *next, previous searched character*
            -   `%` *matching pair character*
    -   Vim
        -   Word (upper and lower case versions)
            -   `ge` *word end, backwards*
        -   Other
            -   `g_` *last non-whitespace character*
            -   `)`, `(` *sentence, forwards, backwards* (only works with
                sentences after ".")
-   Operators
    -   vi
        -   `d` *delete*
        -   `c` *change*
        -   `y` *[yank][]*
    -   [`vim-surround`][]
        -   `ds` *delete surroundings*
        -   `cs` *change surroundings*
        -   `ys` *add surroundings ("you surround")*
-   Text objects
    -   Motions (vi and Vim)
    -   Implicit line (repeat last character of operator)
    -   Vim words (`a` *an* and `i` *inner*, upper and lower case versions)
        -   `w` *word*
    -   Vim pairs (`a` *an* and `i` *inner* versions)
        -   Distinct
            -   Matching
                -   `(`, `)`, `b`
                -   `{`, `}`, `B`
                -   `[`, `]`
            -   Other
                -   `<`, `>`
        -   Nondistinct
            -   Quotes
                -   `"`, `'`, `` ` ``
            -   Punctuation
                -   `#`, `$`, `%`, `&`, `@`, `\\`, `^` `_`, `|`, `~`
                -   `.`, `,`, `:`, `;`, `!`, `?`
                -   `+`, `-`, `*`, `/`, `=`

[yank]: https://en.wiktionary.org/wiki/yank

## Bugs

Some macros clobber the `a`, `b` and/or `c` marks.

Only forward motions work as expected with the surround operators.

Most known bugs exhibit themselves at the end, and sometimes the beginning, of
line. This is sometimes unavoidable, sometimes a trade-off to produce correct
behavior elsewhere, mostly single-character words and/or whitespace:

-   `ge`/`gE` motions: Jumps past last word on line, does not jump past first
    word on line.
-   word/WORD text objects: Always excludes the last character on line.
-   Implicit line text object (as used in e.g. `yss)`): Includes whitespace at
    the beginning and end of line.

## Implementation notes

This is an honest attempt to solve this problem "optimally". However, Readline
macros are not [Turing complete][Turing completeness] (in contrast to e.g.
VimScript) which makes this kind of hard (and more fun :smile:).

Testing suggests that marks are not supported by all applications when combined
with operators in macros. No workaround has been found.

Counts used in macros have to come before the operator. That is, `2cl` works
but `c2l` does not.

Macros are what Vim calls recursive, i.e. they can make use of other macros in
their expansion. To use Readline commands (such as `upcase-word`) they need to
be bound to a key sequence (if no binding exists already) before being used in
macros.

[Turing completeness]: https://en.wikipedia.org/wiki/Turing_completeness

## Related projects

-   [`bash-surround`][]

    Defines the macros manually and therefore "only" provides 234 of them,
    missing some functionality.

    The instructions are Bash centric, although it uses general Readline
    macros.

-   [Athame][]

    Patches Readline to route keystrokes through an actual Vim process. Also
    provides patches for [Zsh][]. This means that whatever your local Vim
    supports, Athame supports (including plugins)!

    Can be a little unreliable if your local Vim has a ton of plugins,
    especially if they interact with the terminal in non-obvious ways.

-   [Zsh][]

    Has built-in [text objects][Zsh text objects] and [surround][Zsh surround]
    support. It even has a Vim-like [Visual mode][Zsh Visual mode]. The popular
    [Oh My Zsh][] Zsh configuration framework extends this with its
    [`vi-mode`][Oh My Zsh `vi-mode`] plugin. There are also several other
    plugins:

    -   [`softmoth/zsh-vim-mode`][]
    -   [`jeffreytse/zsh-vi-mode`][]

-   [`thlorenz/readline-vim`][]

    Exclusive to [Node.js][] [`readline`][Node.js `readline`] and only defines
    vi bindings, not Vim bindings.

[`bash-surround`]: https://github.com/liloman/bash-surround
[Athame]: https://github.com/ardagnir/athame
[Zsh]: https://en.wikipedia.org/wiki/Zsh
[Zsh text objects]: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Text-Objects
[Zsh surround]: https://sourceforge.net/p/zsh/code/ci/master/tree/Functions/Zle/surround
[Zsh Visual mode]: http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#index-visual_002dmode
[Oh My Zsh]: https://ohmyz.sh
[Oh My Zsh `vi-mode`]: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/vi-mode
[`softmoth/zsh-vim-mode`]: https://github.com/softmoth/zsh-vim-mode
[`jeffreytse/zsh-vi-mode`]: https://github.com/jeffreytse/zsh-vi-mode
[`thlorenz/readline-vim`]: https://github.com/thlorenz/readline-vim
[Node.js]: https://nodejs.org
[Node.js `readline`]: https://nodejs.org/api/readline.html#readline_readline

## License

Licensed under the [ISC License][], see the [`LICENSE`](LICENSE) file.

[ISC License]: https://choosealicense.com/licenses/isc/
