linty-org/readline.nvim

github github
editing-support
star 64
stars
alert-circle 0
open issues
users 2
subscribers
git-branch 0
forks
CREATED

2022-05-18

UPDATED

5 days ago


readline.nvim

Readline motions and deletions in Neovim.

Introduction

The Readline text-editing shortcuts let you do things like move the cursor or delete text by a word or a line at a time. The Readline shortcuts are enabled by default all over the place: for example, in the major shells (Bash, Zsh, fish), in programming language REPLs, and even in macOS. So you should probably use them if you aren't already!

This plugin adds support for Readline text-editing shortcuts in Neovim in Insert mode, as well as in Command-line mode (when entering a command with :, when searching with / or ?, etc.).

Quick start

Install readline.nvim using vim-plug with Plug 'linty-org/readline.nvim', or use your favorite Neovim plugin manager. Requires at least Neovim 0.7.

Then add the following code block to your init.lua to enable some of the most-useful Readline shortcuts in Insert and Command-line mode, for moving or deleting by a word at a time, or to beginning/end-of-line.

local readline = require 'readline'
vim.keymap.set('!', '<M-f>', readline.forward_word)
vim.keymap.set('!', '<M-b>', readline.backward_word)
vim.keymap.set('!', '<C-a>', readline.beginning_of_line)
vim.keymap.set('!', '<C-e>', readline.end_of_line)
vim.keymap.set('!', '<M-d>', readline.kill_word)
vim.keymap.set('!', '<M-BS>', readline.backward_kill_word)
vim.keymap.set('!', '<C-w>', readline.unix_word_rubout)
vim.keymap.set('!', '<C-k>', readline.kill_line)
vim.keymap.set('!', '<C-u>', readline.backward_kill_line)

Design

This plugin does not create any keyboard mappings. Instead, it provides Lua functions that implement the Readline motion and deletion commands in Insert and Command-line mode. Once you have these functions, it's easy to create mappings in your Neovim config implementing whatever subset of the Readline shortcuts you actually want. There are some sample configs below that you can copy-paste from, including an opinionated one if you just want to copy-paste something and be done.

Some of the Readline default shortcuts conflict with Vim defaults: for example, <C-t> in Readline swaps the last two characters, and <C-t> in Vim's Insert mode indents the current line. So, while usually I think plugin authors should just set good defaults, in this case I think there should be some user choice about which mappings to use.

Some Readline commands, for example <C-f> to move the cursor forward one character, have identical or nearly-identical Vim equivalents, and don't need to be implemented using Lua functions. They can be bound like so: vim.keymap.set('!', '<C-f>', '<Right>').

Supported Readline commands

Readline command RL shortcut readline.nvim function / Vim command
kill-line C-k kill_line
backward-kill-line C-u backward_kill_line
kill-word M-d kill_word
backward-kill-word M-BS backward_kill_word
unix-word-rubout C-w unix_word_rubout
delete-char C-d <Delete>
backward-delete-char C-h <BS>
beginning-of-line C-a beginning_of_line
back-to-indentation M-m back_to_indentation
end-of-line C-e end_of_line
forward-word M-f forward_word
backward-word M-b backward_word
forward-char C-f <Right>
backward-char C-b <Left>
next-line C-n <Down>
previous-line C-p <Up>
transpose-chars C-t Ask if desired
transpose-words M-t Ask if desired
quoted-insert C-v <C-v>
yank (called put in Vim) C-y Ask if desired
yank-pop M-y Ask if desired
undo C-_ Ask if desired
upcase-word M-u Ask if desired
downcase-word M-l Ask if desired
capitalize-word M-c Ask if desired

References: GNU docs for moving, text, and killing; and Readline on Wikipedia.

Supported Emacs commands

Emacs command Emacs shortcut readline.nvim function
back-to-indentation M-m back_to_indentation

Do-what-I-mean commands

The following functions can be bound like vim.keymap.set('!', '<C-a>', require 'readline'.dwim_beginning_of_line). Inspired by mwim.el.

  • dwim_beginning_of_line
    • Cycle the cursor through the following locations:
      • (Used only if the current line is a single-line comment with no other code, for example a line that begins with // in C or JavaScript, with # in Python, or with -- in Lua.) The first character after the comment leader.
      • The first non-whitespace character on the line.
      • Column 0.
    • For example, calling this function repeatedly would move the cursor (|) like this:
          # Hello, wo|rld!
          # |Hello, world!
          |# Hello, world!
      |   # Hello, world!
          # |Hello, world!
      
  • dwim_backward_kill_line
    • Similar to dwim_beginning_of_line, but kills instead of moving the cursor, and does not cycle. If the cursor is to the right of the comment leader on an EOL-comment line, kills the comment text left of the cursor, then the comment leader, then the whitespace left of the cursor, then rolls to the previous line.

Configuring word characters

Readline motions that operate a-word-at-a-time (forward-word, etc.) advance the cursor until arriving at a "word character", then advance until arriving at the first non-word character. Deletion-by-word works similarly. The default set of word characters in Readline consists of letters and digits.

What should count as a "word character" can be context-dependent: for example, in C, one may wish to take the word characters to be the alphanumeric characters (abc…zABC…Z0123…9) and the underscore (_) – those characters that can be used in C variable/symbol names.

This plugin allows you to configure what counts as a word character in two ways:

  • You can set the buffer variable readline_word_chars to a string consisting of the word characters for the current buffer.
    • readline.alphanum is provided for convenience, and consists of the characters abc…zABC…Z0123…9.
    • For example, vim.b.readline_word_chars = readline.alphanum .. '_-' will cause alphanumerics, hyphens, and underscores to be treated as word characters in the current buffer.
  • You can set readline.word_chars.YOUR_FILETYPE = YOUR_STRING.
    • For example, readline.word_chars.bash = readline.alphanum .. '_-' will cause alphanumerics, hyphens, and underscores to be treated as word characters when filetype=bash.
    • The value of vim.b.readline_word_chars takes precedence in a buffer (if it's set).

If neither of the above is set, we fall back to readline.default_word_chars, which you can set to whatever you like, but which by default is equal to 'abc…zABC…Z0123…9'.

Sample configs

Opinionated

This is the same as the quick-start config above. This just enables some of the most-useful Readline shortcuts, for moving or deleting by a word at a time, or to beginning/end-of-line.

local readline = require 'readline'
vim.keymap.set('!', '<M-f>', readline.forward_word)
vim.keymap.set('!', '<M-b>', readline.backward_word)
vim.keymap.set('!', '<C-a>', readline.beginning_of_line)
vim.keymap.set('!', '<C-e>', readline.end_of_line)
vim.keymap.set('!', '<M-d>', readline.kill_word)
vim.keymap.set('!', '<M-BS>', readline.backward_kill_word)
vim.keymap.set('!', '<C-w>', readline.unix_word_rubout)
vim.keymap.set('!', '<C-k>', readline.kill_line)
vim.keymap.set('!', '<C-u>', readline.backward_kill_line)

Personally, I have OS-level custom keyboard mappings for the arrow keys, <BS>, and <Delete> to make them easier to type, so I don't need the CTRL-key Readline versions of those commands.

Maximal

This config creates Neovim mappings for all of the default Readline text-editing shortcuts, minus ones readline.nvim doesn't (yet) have support for, for example transpose-chars and capitalize-word. You can copy-paste this block into your init.lua and delete the mappings you don't want.

local readline = require 'readline'
vim.keymap.set('!', '<C-k>', readline.kill_line)
vim.keymap.set('!', '<C-u>', readline.backward_kill_line)
vim.keymap.set('!', '<M-d>', readline.kill_word)
vim.keymap.set('!', '<M-BS>', readline.backward_kill_word)
vim.keymap.set('!', '<C-w>', readline.unix_word_rubout)
vim.keymap.set('!', '<C-d>', '<Delete>')  -- delete-char
vim.keymap.set('!', '<C-h>', '<BS>')      -- backward-delete-char
vim.keymap.set('!', '<C-a>', readline.beginning_of_line)
vim.keymap.set('!', '<C-e>', readline.end_of_line)
vim.keymap.set('!', '<M-f>', readline.forward_word)
vim.keymap.set('!', '<M-b>', readline.backward_word)
vim.keymap.set('!', '<C-f>', '<Right>') -- forward-char
vim.keymap.set('!', '<C-b>', '<Left>')  -- backward-char
vim.keymap.set('!', '<C-n>', '<Down>')  -- next-line
vim.keymap.set('!', '<C-p>', '<Up>')    -- previous-line

Similar plugins