nvim-training: Efficient training of keybinds

January 13, 2026 ยท View on GitHub

License: GPL

This code implements a Neovim Plugin for training keybinds by providing 50+ different small tasks. GIF

Getting Started

Installation

Install it using the plugin manager of your choice. Lazy is tested, if any other fails, please open an issue. Pinning your local installation to a fixed version is encouraged. In Lazy, a possible setup might be:

local lazy = require("lazy")
local plugin_list = {
    -- Your various other plugins ..
    {"https://github.com/Weyaaron/nvim-training", pin= true, opts = {}}
    -- Support for configuration with opts is included, see below for the options
}
lazy.setup(plugin_list)

How to train

This plugin uses subcommands of Training to activate certain functions. All of these commands support completion, just use Tab and you will be fine. Currently, these are the available options:

NameSyntaxDescription
Start:Training Start [Scheduler] [Task-Collection A] [Task Collection B] ...Starts a session with the choosen scheduler and the choosen task collections. Both arguments are optional.
Stop:Training StopStops a session.
Analyze:Training AnalyzePrints some statistics about your progress.

The plugin aims to use scratch-buffers to avoid polluting the disk.

Philosophy

This plugin fills a gap I have noticed during interaction with vim: Knowing is half the battle, executing another entirely. This plugin aims to help with the latter. Basic familiarity with vim is assumed.

A training session consists of a series of tasks, each of which is small and easily repeatable. The plugin will recognize when a task is completed and automatically start the next one. This helps to work on a lot of tasks in a short amount of time.

I consider this project mature enough for daily use. I will expand it as I see fit, which might include months of inactivity. I will respond to issues quickly, including requests for new features.

I will attempt to ship breaking changes to public interfaces in such a way that they are done "all/many at once". I consider this to be the best option for minimizing disruptions.

Contributions

Some of the work on this plugin has been done under the supervision of Jonas und der Wolf. I am gratefull for this oppoturnity to contribute to open source on my own terms.

Known problems

Currently, logging and event storage for statistics is disabled. A fix/reeenabling them is under way. Furthermore, as of 2025-02, support for unit tests is in its infancy. It will take quite some more work to enable tests for all tasks/fix some of them. Some failing tests will be addet to keep momentum going.

Some stats of current tasks

  • Supported Tasks: 67 (For a full list, see below)
  • Supported Tasks-Collections: 5
  • Supported Schedulers: 3

Full Task List

Click to expand

All tasks

NameDescriptionTags
AppendCharInsert a char next to the cursor.append, change, insertion
ChangeFChange text using Fcchange, chair-wise, F, horizontal, left, operator
ChangefChange text using fcchange, chair-wise, f, horizontal, operator, right
ChangeLineChange the current line.cchange, line, lines, operator
ChangeTChange text using Tcchange, chair-wise, horizontal, left, operator, T
ChangetChange text using fcchange, chair-wise, horizontal, operator, right, t
ChangeWORDChange multiple WORDS.cchange, counter, horizontal, operator, text-object, WORD
CommentLineChange the current line into a single line comment.change, commenting, plugin
DecrementDecrement the value at the cursor.change, char, increment
DeleteCharDelete the current char.change, char
DeleteFDelete back to the previous char.chair-wise, deletion, F, horizontal, left, operator, register
DeletefDelete forward to the next char.chair-wise, deletion, f, horizontal, operator, register, right
DeleteInsideQuotesDelete inside the quotes.deletion, operator, quotes, register
DeleteLineDelete the current line.deletion, line
DeleteMatchDelete the current match.deletion, match, operator, register, register, text-object
DeleteSentenceDelete the textobject inner sentence.deletion, horizontal, operator, register, sentence, text-object
DeleteTDelete back to the next char.chair-wise, deletion, horizontal, left, operator, register, T
DeletetDelete to the next char.chair-wise, deletion, horizontal, operator, register, right, t
DeleteWORDDelete multiple WORDs.counter, deletion, horizontal, operator, register, text-object, WORD
DeleteWordDelete multiple words.counter, deletion, horizontal, operator, register, text-object, word
DeleteWordEndDelete using 'e'.deletion, end, operator, register, vertical, word_end
DeleteWORDEndDelete using 'E'.deletion, END, operator, register, vertical, WORD_end
IncrementIncrement the value at the cursor.change, char, increment
InsertAtStartOfLineInsert text at the start of the line.I, insert, line, start
InsertCharInsert a char at the current position.change, char
JoinLinesJoin the current line with the line below.change, J, join, line
MoveAbsoluteLineMove to the absolute line.line, movement, vertical
MoveCharsLeftMove left charwise.char, h, horizontal, movement
MoveCharsRightMove right charwise.horizontal, l, movement
MoveEndOfFileMove to the end the file.end, file, movement, vertical
MoveEndOfLineMove to the end of the current line.end, horizontal, line, movement
MoveFGo back to the last ocurrence of a char.chair-wise, F, horizontal, left, movement, operator
MovefFind the next char.chair-wise, f, horizontal, movement, operator, right
MoveLinesDownMove down multiple lines.horizontal, j, lines, movement, operator
MoveLinesUpMove multiple lines up.horizontal, k, lines, movement, operator
MoveMarkMove to a markmark, movement, vertical
MoveMatchMove to the current match.match, movement, operator, text-object
MoveoEnter and leave insert mode below the current line.change, insert_mode, linewise, o
MoveOEnter and leave insert mode above the current line.change, insert_mode, linewise, O
MoveStartOfFileMove to the start of the file.file, start, vertical
MoveStartOfLineMove to the start of the current line.line, movement, start
MoveTGo back next to the last ocurrence of a char.chair-wise, horizontal, left, movement, operator, T
MovetMove using t.chair-wise, horizontal, movement, operator, right, t
MoveWORDMove multiple WORDS.counter, horizontal, movement, operator, text-object, WORD
MoveWordMove multiple words.counter, horizontal, movement, operator, text-object, word
MoveWORDEndMove to the end of WORDs.END, movement, operator, vertical, WORD_end
MoveWordEndMove to the end of words.end, movement, operator, vertical, word_end
OpenWindowOpen a new window.window
PastePaste from a given register.Paste, register
pastePaste from a given register.paste, register
SearchBackwardSearch backwards.diagonal, movement, search
SearchForwardSearch forwards.forward, movement, search
SearchWordBackwardSearch backwards for the word at the cursor.backward, movement, search
SearchWordForwardSearch forwards for the word at the cursor.forward, movement, search
YankEndOfLineYank to the end of the current line.end, line, yank
YankfYank to the next char.chair-wise, f, horizontal, operator, register, right, yank
YankFYank back to the previous char.chair-wise, F, horizontal, left, operator, register, yank
YankInsideBlockYank inside the block.inside, operator, register, text-object, yank
YankInsideQuotesYank inside the quotes.operator, quotes, register, yank
YankLineYank a line into a register.line, lines, operator, register, vertical, yank
YankMatchYank the current match.match, operator, register, register, text-object, yank
YanktYank next to the next char.chair-wise, horizontal, operator, register, right, t, yank
YankTYank back next to the previous char.chair-wise, horizontal, left, operator, register, T, yank
YankWORDYank multiple WORDS.counter, horizontal, operator, register, text-object, WORD, yank
YankWordYank multiple words.counter, horizontal, operator, register, text-object, word, yank

Task-Collections

NameDescriptionDetails
AllAll currently supported tasksAll
CChangeTasks that use the 'change' operator.CChange
ChangeTasks that change the buffer but do not use the 'change' operator.Change
Custom-TasksTasks that require setup to work as intendet.Custom-Tasks
DeletionTasks involving deletionDeletion
FTasks involving FF
MovementTasks that move the cursor.Movement
RegisterTasks that may use registers.Register
SearchTasks involving searchSearch
TTasks involving TT
WORDWORD-based TasksWORD
WordWord-based TasksWord
YankingTasks that use the 'yank' operator.Yanking
fTasks involving ff
tTasks involving tt

Schedulers

NameDescriptionSupported Arguments
RandomSchedulerThe next task is chosen at random.-
RepeatUntilNSuccessSchedulerThe current task is repeated until n successes are reached.repetitions
RepeatNSchedulerA task is repeated n-times.repetitions

Configuration

A interface for configuration is provided. These are the default values. Simply copying then but leaving the defaults in is actually discouraged. Some of these names might change, and if you leave this in your config these changes are not propagated.

local training = require("nvim-training")
training.configure({ -- All of these options work for 'opts' of lazy as well.
	audio_feedback = true, -- Enables/Disables audio feedback, if enabled, requires the 'sox' package providing the 'play' command.
	counter_bounds = { 1, 5 }, --The outer bounds for counters used in some tasks. WARNING: A high value may result in glitchy behaviour.
	custom_collections = {}, -- A table of tables containing names of tasks, for details read on.
	enable_counters = true, -- Enables/Disables counters in tasks that support counters.
	enable_events = true, -- Enables/Disables events.
	enable_registers = false, -- Enables/Disables registers. Since this option complicates a lot of tasks, it is disabled by default.
	enable_repeat_on_failure = false, -- Enables/Disables wether the current task will be repeated (exactly) if it failed. Failures will be counted regardless.
	enable_highlights = true, --Enables/Disables highlights. Care is taken to ensure that tasks are possible without them.
	event_storage_directory_path= vim.fn.stdpath("data") .. "/nvim-training/", -- The path used to store events.
	logging_args = {
        enable_logging = true, --Enables/Disables logging entirely.
		log_directory_path = vim.fn.stdpath("log") .. "/nvim-training/",
		log_file_path = os.date("%Y-%m-%d") .. ".log",
		display_logs = false, --Enables/Disables wether messages with the level 'log' should be printed. WARNING: Enabling his produces a lot of noise, but might be usefull for developers.
		display_warnings = true, --Enables/Disables wether messages with the level 'warning' should be printed.
	},
	possible_marks_list = { "a", "b", "c", "r", "s", "t", "d", "n", "e" }, -- A list of possible marks.
	possible_register_list = { "a", "b", "c", "r", "s", "t", "d", "n", "e" }, -- A list of possible registers.
	scheduler_args = { repetitions = 5 }, --These args are used to configure all the available schedulers
	task_alphabet = "ABCDEFGabddefg,", -- The alphabet of targets used in tasks like f,t etc. WARNING: Chars that represent lua patterns (Including e.g. '.') are not a valid entry. This is not checked for.
})

Deprecated Configuration Options

  • disabled_tags: This is no longer used since it did not provide feedback and was used to hide unfinished features. This option will on longer have any effect.
  • disabled_collections: No longer used for the same reasons as above.

Custom Collections

To add a custom collection, please use its name as a key for a list of task names in the config, for example like this:

local training = require("nvim-training")
training.configure({
    -- .. your other configs ...
	custom_collections = { MyCollection = { "MoveWord", "MoveWORD"}}
})

You may provide as many collections as you wish, they will be available in autocompletion.

Goals

  • Ease of use. Starting a session should be seamless. The UI should not get in the way.
  • Fast and flow-inducing. There should be no waiting time/friction between tasks.
  • (Eventually) Community-driven. Adding new tasks is encouraged, both by providing the interfaces and the documentation required.
  • Customizable. Task should be switched on and off with ease, and the difficulty should be adjustable.

Non-Goals

  • Implement puzzles. A solution to the current task should be obvious and small, at most a few keystrokes on a vanilla setup.
  • Competing with others. Your progress matters to you, not to others.
  • Provide help/guides on how to solve a particular task. Basic familiarity with vim is assumed.
  • Constrain the user on how to solve a particular task.
  • Support for everyones personal setup. Some settings may mess up some tasks, support for these cases is limited. I try to accomodate about 80% of the users and will decide each upcoming case on its own.

On Contributions

Contributions are welcome! Any input is appreciated, be it a bug report, a feature request, or a pull request. Just open a issue and we shall get cooking :)

License

GPL