Smooth motion animations for Neovim. Provides 60fps fluid animations for cursor movement, word navigation, text objects, and viewport scrolling — all in one plugin.
Motion coverage — animates every standard Vim motion:
| Category | Motions |
|---|---|
| Basic | h, j, k, l, 0, $ |
| Word | w, b, e, W, B, E |
| Find/Till | f, F, t, T |
| Text Objects | {, }, (, ), % |
| Line Jumps | gg, G, | |
| Search | n, N |
| Screen Lines | gj, gk |
| Viewport | <C-d>, <C-u>, <C-f>, <C-b>, zz, zt, zb |
- Works in Normal and Visual modes with count prefixes (position motions
zz/zt/zbare Normal mode only) - Separate duration and easing for cursor vs scroll animations
- Performance mode with automatic large file detection
- Object pooling (up to 10 reusable animation objects) to reduce garbage collection pressure
- Extensible — define your own keymaps via the orchestrator API, or register custom motions and traits through the registry
- Neovim >= 0.8
The plugin auto-calls require("whisk").setup() on load. To disable this and call setup manually, set g:whisk_auto_setup = 0 before the plugin loads.
{
"josstei/whisk.nvim",
event = "VeryLazy",
opts = {},
}Plug 'josstei/whisk.nvim'require("whisk").setup()All options with their defaults:
require("whisk").setup({
cursor = {
duration = 150,
easing = "ease-out",
enabled = true,
},
scroll = {
duration = 200,
easing = "ease-in-out",
enabled = true,
},
keymaps = {
cursor = true,
scroll = true,
},
performance = {
enabled = false,
disable_syntax_during_scroll = true,
ignore_events = { "WinScrolled", "CursorMoved", "CursorMovedI" },
reduce_frame_rate = false,
frame_rate_threshold = 60, -- not currently read by any code path
auto_enable_on_large_files = true,
large_file_threshold = 5000,
},
})Easing options: linear, ease-in, ease-out, ease-in-out
| Command | Description |
|---|---|
:WhiskEnable |
Enable all animations |
:WhiskDisable |
Disable all animations |
:WhiskToggle |
Toggle all animations |
:WhiskEnableCursor |
Enable cursor animations |
:WhiskDisableCursor |
Disable cursor animations |
:WhiskEnableScroll |
Enable scroll animations |
:WhiskDisableScroll |
Disable scroll animations |
:WhiskPerformanceEnable |
Enable performance mode |
:WhiskPerformanceDisable |
Disable performance mode |
:WhiskPerformanceToggle |
Toggle performance mode |
local whisk = require("whisk")
whisk.enable()
whisk.disable()
whisk.toggle()
whisk.enable_cursor()
whisk.disable_cursor()
whisk.enable_scroll()
whisk.disable_scroll()
whisk.toggle_performance()
whisk.reset() -- tear down keymaps, stop animations, clear registries, remove lifecycle autocmdslocal performance = require("whisk.performance")
performance.enable()
performance.disable()
performance.is_active()
performance.get_current_fps()
performance.get_frame_interval()
performance.should_ignore_event(event)For custom keymaps, use the orchestrator directly:
local orchestrator = require("whisk.engine.orchestrator")
orchestrator.execute("basic_j", { count = 5, direction = "j" })
orchestrator.execute("word_w", { count = 3, direction = "w" })
orchestrator.execute("find_f", { char = "x", count = 1, direction = "f" })| Category | IDs |
|---|---|
| Basic | basic_h, basic_j, basic_k, basic_l, basic_0, basic_$ |
| Word | word_w, word_b, word_e, word_W, word_B, word_E |
| Find | find_f, find_F, find_t, find_T |
| Text Object | text_object_{, text_object_}, text_object_(, text_object_), text_object_% |
| Line | line_gg, line_G, line_| |
| Search | search_n, search_N |
| Screen | screen_gj, screen_gk |
| Scroll | scroll_ctrl_d, scroll_ctrl_u, scroll_ctrl_f, scroll_ctrl_b, position_zz, position_zt, position_zb |
require("whisk").setup({
keymaps = { cursor = false, scroll = false },
})
local orchestrator = require("whisk.engine.orchestrator")
vim.keymap.set("n", "j", function()
orchestrator.execute("basic_j", { count = vim.v.count1, direction = "j" })
end)require("whisk").setup({
cursor = { duration = 100, easing = "linear" },
scroll = { duration = 400, easing = "ease-out" },
})require("whisk").setup({
cursor = { duration = 100, easing = "linear" },
scroll = { duration = 150, easing = "linear" },
performance = { enabled = true },
})When enabled, performance mode:
- Disables syntax highlighting when
disable_syntax_during_scrollis set (default: on) - Optionally reduces frame rate from 60fps to 30fps (
reduce_frame_rate = true) - Auto-enables on
BufEnter/BufWinEnterfor files larger thanlarge_file_thresholdlines (default: 5000) - Exposes a configurable
ignore_eventslist (default:WinScrolled,CursorMoved,CursorMovedI) for callers to check viashould_ignore_event()
Toggle at runtime with :WhiskPerformanceToggle or require("whisk").toggle_performance().
- If a motion category is disabled, whisk falls back to native
normal!motion behavior. - When a new motion starts while any of its traits are already animating, all active animations complete instantly at their final positions before the new animation begins (domination).
- Animations are automatically cancelled when the buffer is deleted (
BufDelete), the window is closed (WinClosed), or the buffer is left (BufLeave). ggwith a count (e.g.,5gg) goes to line N.|with a count (e.g.,5|) goes to column N.
| Feature | whisk.nvim | neoscroll.nvim | vim-smoothie |
|---|---|---|---|
| Cursor movement | Yes | No | No |
| Scroll movement | Yes | Yes | Yes |
| Word navigation | Yes | No | No |
| Find/Till | Yes | No | No |
| Text objects | Yes | No | No |
| Search navigation | Yes | No | No |
| Visual mode | Yes | Scroll only | Scroll only |
| Count prefixes | Yes | Scroll only | Scroll only |
whisk.nvim was previously published as nvim-luxmotion. A deprecation shim is included:
require("luxmotion")forwards torequire("whisk")with a warning.- All
:LuxMotion*commands forward to their:Whisk*equivalents. g:luxmotion_auto_setupforwards tog:whisk_auto_setup.
Update your config to use whisk directly — the shim will be removed in a future release.
MIT — see LICENSE.