cascade.nvim · Editing · vimdoc
:help cascade
Context-aware lists & cycling for Neovim
doc/cascade.txt — rendered from the plugin's own vimdoc
*cascade.txt* Context-aware lists & cycling for Neovim *cascade.nvim*
CONTENTS
1. Introduction ............................. |cascade-introduction| 2. Setup .................................... |cascade-setup| 3. Keymaps .................................. |cascade-keymaps| 4. Configuration ............................ |cascade-config| 5. Lists domain ............................. |cascade-lists| 6. Cycle domain ............................. |cascade-cycle| 7. Sequence domain .......................... |cascade-sequence| 8. Transpose domain ......................... |cascade-transpose| 9. Health ................................... |cascade-health| 10. lib.nvim integration ..................... |cascade-lib|
1. INTRODUCTION
cascade.nvim applies a single pattern to four feature domains:
detect the context under the cursor -> advance it one step -> fall back to
the native key when nothing matches.
- The lists domain continues markdown/text/tex/norg lists, renumbers them,
toggles checkboxes, cycles marker types and indents items. It is filetype
scoped.
- The cycle domain rotates the word under the cursor (true/false, on/off,
...) and defers to native |CTRL-A|/|CTRL-X| on numeric tokens. It is global
by default.
- The sequence domain renumbers the ordinal tokens (1., a), II.) inside
a Visual selection, whatever precedes them -- numbered headlines, inline
numbers in prose. It is global and filetype-agnostic.
- The transpose domain swaps a character or a word (or a same-line
selection) with its left/right neighbor. It is global and
filetype-agnostic.
Requires Neovim 0.9+. No Treesitter, no autocommand-per-keystroke; all work is
triggered by explicit keys, guarded with pcall, and uses one context object per
action.
2. SETUP
*cascade.setup()*
require("cascade").setup({
keymaps = { preset = true },
})
Calling setup() merges your options over the defaults and defines the:Cascadeuser command. Withkeymaps.preset = trueit additionally binds the default keys (see |cascade-keymaps|) directly onto the facade actions — no<Plug>indirection. setup() is safe to call once.
3. KEYMAPS
Every action is a plain function on thecascademodule, so you can bind any keys you like with a normal|vim.keymap.set()|: cr insert continue list / delete empty o normal open continued item below O normal open continued item above toggle_checkbox normal toggle / cycle checkbox bullet_toggle / _visual n / x toggle "-" bullet (no marker required) star_toggle / _visual n / x toggle "*" bullet (no marker required) number_toggle / _visual n / x toggle "1." marker (no marker required) checkbox_toggle / _visual n / x toggle "- [ ]" checkbox (no marker required) cycle_type_next normal cycle list marker type forward cycle_type_prev normal cycle list marker type backward cycle_word_next normal cycle word / native increment cycle_word_prev normal cycle word / native decrement increment normal cycle word / native increment / native line-down (+'s own meaning) otherwise decrement normal cycle word / native decrement / native line-up (-'s own meaning) otherwise cycle_pick normal pick a cycle-group value via vim.ui.select cycle_char_next normal step the char under the cursor through the alphabet, inside a word too cycle_char_prev normal the same, backward indent / indent_visual n / x indent + level-aware renumber (normal- mode count = N lines from cursor) dedent / dedent_visual n / x dedent + level-aware renumber (normal- mode count = N lines from cursor) indent_levels normal indent the current line by N levels (old count meaning ofindent) dedent_levels normal dedent the current line by N levels (old count meaning ofdedent) move_up / move_up_visual n / x move line/selection up + renumber move_down / move_down_visual n / x move line/selection down + renumber renumber normal renumber the ordered block renumber_selection visual renumber the ordinal tokens inside the selection rotate_form_next / _visual n / x rotate block/selection forms rotate_form_prev / _visual n / x rotate forms backward shift_level_next / _prev n shift the whole ordered level by 1 (<C-S-y> / <C-S-x>, <leader>c+ / c-); <C-y>/<C-x> ON a marker shift the item and the later siblings (no renumber) sort / sort_visual n / x sort block/selection A-Z reverse / reverse_visual n / x reverse block/selection order strip_checkbox / _visual n / x remove checkboxes swap_right / swap_left normal swap char with right/left neighbor (count = swap N times) swap_right_visual / swap_left_visual x swap selection with neighbor char (count = swap N times) swap_word_right / swap_word_left normal swap word with right/left neighbor word (count = swap N times) swap_word_right_visual / swap_word_left_visual x swap selection with neighbor word (count = swap N times) The checkbox, bullet-toggle, number-toggle, checkbox-toggle, cycle-type, cycle-word, swap-right/left (normal), swap-word-right/left (normal), rotate-form (normal) and sort (normal) actions are dot-repeatable (|.|). rotate-form and sort have separate normal and visual functions: normal mode operates on the contiguous list block at the cursor, visual mode on the selected lines. Dot-repeat is native (operatorfunc+g@l), not atpope/vim-repeatdependency. If vim-repeat happens to be installed, cascade also calls itsrepeat#setalongside the native trick, purely as optional interop --.works identically either way. Unliketoggle_checkbox/cycle_type_next, which only ever advance an existing marker (they no-op on a plain line),bullet_toggle,star_toggle,number_toggleandcheckbox_togglealso work without one: they insert the marker from scratch, and toggle it back off again on a second press. Their_visualvariants apply this independently to every non-blank line in a Visual or Visual-line selection — each line keeps deciding its own fate from its own current state, same as pressing the key on it individually; a blank line in the selection is left untouched. USER COMMANDS *cascade-commands* One command, :Cascade <subcommand> (built via lib.nvim.bindings.usercmd.composer, with <Tab> completion). The list transforms below are|:command-range|aware: without a range they act on the list block at the cursor; with a range (e.g. visual:'<,'>) on the selected lines. The threecyclesubcommands edit configuration rather than text and take no range. Bang attaches to the VERB, not the subcommand: :Cascade! rotate (not :Cascade rotate!). *:Cascade* :Cascade cycle list *:Cascade-cycle-list* list the cycle groups in effect for this buffer: the globalcycle.groupspluscycle.per_filetype[ft]. Pack contents are not listed;|:checkhealth|reports those, together with any collisions between them. :Cascade cycle add {values} *:Cascade-cycle-add* add a cycle group for this session from comma-separated values, e.g. :Cascade cycle add on,off,maybe. The whole tail is taken, so a value may contain spaces. Values are trimmed and de-duplicated; fewer than two distinct values is refused. Deliberately NOT persisted -- the config file stays the source of truth for groups worth keeping. :Cascade cycle remove {value} *:Cascade-cycle-remove* remove the first runtime group containing {value}; any member of a group identifies it. :Cascade rotate [next|prev] *:Cascade-rotate* rotate list form forward/backward; :Cascade! rotate rotates backward. :Cascade sort *:Cascade-sort* sort the list A-Z; :Cascade! sort sorts Z-A. :Cascade reverse *:Cascade-reverse* reverse the order of the list items. :Cascade strip *:Cascade-strip* remove checkboxes (markers are kept). :Cascade indent [n] *:Cascade-indent* indent line/range by n levels (default 1) and renumber; e.g. :'<,'>Cascade indent 2. :Cascade dedent [n] *:Cascade-dedent* dedent line/range by n levels and renumber. :Cascade renumber [all|selection] *:Cascade-renumber* renumber the ordered list block at the cursor (or the given range); :Cascade renumber all sweeps every list block in the buffer instead, each numbered independently; :Cascade renumber selection renumbers the ordinal tokens inside the range's lines rather than the list markers (see |cascade-sequence|). PRESET *cascade-preset* Withkeymaps.preset = true: Global: <C-y> -> cycle_word_next <C-x> -> cycle_word_prev + -> increment - -> decrement <leader>cp -> cycle_pick <C-M-y> -> cycle_char_next (also <leader>cy) <C-M-x> -> cycle_char_prev (also <leader>cY) <leader>cR -> renumber_selection (Visual mode only) Buffer-local inlists.filetypes: i <CR> -> cr i <M-CR> -> cr_literal n o -> o n O -> O n <leader>cx -> toggle_checkbox n <A--> -> bullet_toggle x <A--> -> bullet_toggle_visual n <A-*> -> star_toggle x <A-*> -> star_toggle_visual n <A-0> -> number_toggle x <A-0> -> number_toggle_visual n <A-c> -> checkbox_toggle x <A-c> -> checkbox_toggle_visual n <leader>ct -> cycle_type_next n <leader>cT -> cycle_type_prev n <leader>cr -> renumber n <leader>cl -> rotate_form_next x <leader>cl -> rotate_form_next_visual n <leader>cL -> rotate_form_prev x <leader>cL -> rotate_form_prev_visual n <leader>cs -> sort x <leader>cs -> sort_visual n <leader>cv -> reverse x <leader>cv -> reverse_visual n <leader>cX -> strip_checkbox x <leader>cX -> strip_checkbox_visual Global (every filetype): n <A-Right> -> indent x <A-Right> -> indent_visual n <A-Left> -> dedent x <A-Left> -> dedent_visual n <leader><A-Right> -> indent_levels n <leader><A-Left> -> dedent_levels i <A-Right> -> <C-t> (native insert indent) i <A-Left> -> <C-d> (native insert dedent) n <A-Up> -> move_up x <A-Up> -> move_up_visual n <A-Down> -> move_down x <A-Down> -> move_down_visual i <A-Up> -> :m .-2 + == (native insert move) i <A-Down> -> :m .+1 + == (native insert move) n <leader><Right> -> swap_right n <leader><Left> -> swap_left x <leader><Right> -> swap_right_visual x <leader><Left> -> swap_left_visual n <leader><C-Right> -> swap_word_right n <leader><C-Left> -> swap_word_left x <leader><C-Right> -> swap_word_right_visual x <leader><C-Left> -> swap_word_left_visual <Tab>/<S-Tab> are intentionally left out of the preset to avoid clashing with completion plugins; bind them yourself tocascade.indent/cascade.dedentif you want them.
4. CONFIGURATION
Defaults (excerpt -- full reference below and in cascade.config.DEFAULTS):
{
lists = {
enable = true,
features = { -- toggle each feature individually
continue = true, checkbox = true, cycle_type = true,
rotate = true, sort = true, reverse = true, strip = true,
indent = true, move = true,
bullet_toggle = true, number_toggle = true, checkbox_toggle = true,
},
filetypes = { "markdown", "markdown.mdx", "text", "tex", "norg", ... },
types = { "unordered", "digit" },
unordered_markers = { "-", "*", "+" },
per_filetype_patterns = {},
cycle = { "-", "*", "+", "1.", "a)", "I." },
forms = { "1.", "1. [ ]", "- [ ]", "-" },
checkbox = { states = { " ", "x", "~" } },
continue = { delete_empty = true, hanging_indent = true },
renumber = { enable = true, on = { "edit", "save" }, blank_break = 0 },
precision = "off",
precision_nodes = {},
},
cycle = {
enable = true,
features = { word = true, date = true, letter = true, char = true },
filetypes = nil,
number_fallback = true,
packs = { "en", "de", "dev" },
groups = { { "==", "!=" }, ... },
per_filetype = {},
},
sequence = {
enable = true,
start = "keep",
types = { "digit", "ascii", "roman" },
},
transpose = {
enable = true,
features = { char = true, word = true },
},
keymaps = { preset = false },
debug = false,
}
Field reference
lists.enable (boolean) Master switch for the list domain.
lists.features (table) Per-feature on/off switches. Keys: continue,
checkbox, bullet_toggle, number_toggle,
checkbox_toggle, cycle_type, rotate, sort,
reverse, strip, indent, move. A disabled
feature does not run and the preset does not
bind its keys; native keys stay native.
Missing key = enabled. bullet_toggle also
gates <A-*> (star_toggle) — same key, one
more bullet shape.
lists.filetypes (string[]) Prose/markup filetypes the list keys attach
to. Actions no-op on lines without a marker, so
a broad default is safe (markdown/text/tex/
norg/org/rst/asciidoc/typst/quarto/pandoc/
vimwiki/gitcommit/mail). The word/number cycle
is a separate, global domain (cycle.filetypes).
lists.types (string[]) Enabled marker kinds, in detection order:
"unordered", "digit", "ascii", "roman".
ascii/roman are opt-in (letters are ambiguous).
lists.unordered_markers (string[]) Accepted bullet characters.
lists.per_filetype_patterns (table<string,string[]>) Custom, non-incrementing
marker patterns per filetype, tried before
types -- e.g. LaTeX's \item, which isn't
any of the built-in kinds. Each pattern needs
exactly two Lua-pattern captures: the marker
token, then the rest of the line after the
required separating whitespace:
per_filetype_patterns = {
tex = { "^(\\item)%s(.*)$" },
},
A match is always an "unordered"-style
marker (fixed token, never renumbered); an
ordered custom marker should use types
instead.
lists.cycle (string[]) Marker templates for cycle-type (single line).
Convention: digits => digit, a/A => alpha,
i/I => roman, any other single char => bullet.
lists.forms (string[]) Form-rotation templates for block/visual
transforms. A form is a cycle template plus an
optional [ ] suffix, e.g. "1.", "1. [ ]",
"- [ ]", "-".
lists.checkbox.states (string[]) Ordered states cycled in [ ], default
{ " ", "x", "~" } (open -> done -> in
progress). Normally one character; a longer
state (e.g. an emoji) is also accepted but
must be listed here to be recognized on
parse.
lists.continue.delete_empty (boolean) <CR> on an empty bullet removes it.
lists.continue.hanging_indent (boolean) Sets buffer-local 'formatlistpat' (from
types/unordered_markers) and adds n to
'formatoptions' on the configured list
filetypes, so native gq/auto-wrap
hang-indents a wrapped item under its text
instead of back at the margin. Default true;
set false to leave both options alone.
lists.renumber (table) When ordered lists are auto-renumbered.
.enable (boolean) master switch; false = only
manual :Cascade renumber.
.on (string[]) any of "edit" (right after an
edit) and "save" (BufWritePre, whole
buffer).
.blank_break (integer) consecutive blank
lines that end a block. 0 (default)
= any blank line breaks it; raise to
1 for the "loose list" reading.
A boolean is also accepted:
true = { "edit", "save" },
false = {}.
A non-marker, non-blank line (a wrapped
continuation paragraph or note) never
breaks the sequence, regardless of its own
indent — Markdown "lazy continuation": no
blank line separating it means it belongs
to the item above. A blank line, by
contrast, ends the block: the next list
starts a fresh sequence with its own start
offset. Set .blank_break = 1 to tolerate a
single blank line inside one block (two or
more still break it).
lists.precision ("off"|"treesitter") Default "off": cascade is a pure
line-scan plugin everywhere (no Treesitter).
"treesitter" additionally skips
single-cursor list actions (continuation,
toggles, single-line indent, ...) when the
cursor sits inside a configured "skip" node
-- by default, a markdown/norg fenced code
block, so a line that only looks like a
marker inside one (a shell - flag, a
Python # 1. note) isn't treated as a real
list item. Falls back safely to "off"
behavior (via pcall) if no Treesitter
parser is installed for the buffer's
filetype -- this is opt-in precision, never
a hard dependency. Range/whole-buffer
operations (visual shifts, :Cascade
commands, save-time renumber-all) aren't
covered: "inside a skip node" isn't
well-defined for an arbitrary range.
lists.precision_nodes (table<string,string[]>) Per-filetype skip-node type
overrides for precision = "treesitter";
falls back to
cascade.core.treesitter.default_skip_nodes
(markdown/markdown.mdx: fenced code blocks;
norg: verbatim tags).
cycle.enable (boolean) Master switch for the cycle domain.
cycle.features (table) Per-feature switches. Keys: word, date,
letter, char. Missing key = enabled.
cycle.filetypes (string[]|nil) Restrict to these fts; nil = all.
cycle.number_fallback (boolean) Use native <C-a>/<C-x> on numeric tokens
(via <C-y>/<C-x> or +/-). false only skips
that; off a number and a cyclable word, the
pressed key still falls back to its own
native meaning either way.
cycle.packs (string[]) Built-in group bundles to enable, in
precedence order. See |cascade-cycle-packs|.
cycle.groups (string[][]) Your own cycle groups; first match wins,
wrap-around. Checked BEFORE packs.
A group entry made purely of punctuation
(e.g. "==", "&&", "<") is matched by literal
position instead of 'iskeyword', so operator
flips work out of the box: "==" <-> "!=",
"&&" <-> "||", "<" <-> ">", "+" <-> "-".
cycle.per_filetype (table) ft -> extra groups, merged after the globals.
CYCLE PACKS *cascade-cycle-packs*
cycle.packs switches whole bundles of word groups on by name instead of
pasting them into cycle.groups. Each pack is one file under
lua/cascade/cycle/packs/ -- read one to see its contents, or copy it as a
template for your own.
en (default) true/false, on/off, yes/no, show/hide, start/stop, ...
de (default) wahr/falsch, ja/nein, ein/aus, oben/unten, ...
dev (default) dev/stage/prod, todo/doing/done, low/medium/high,
draft/review/final, alpha/beta/rc/stable,
debug/info/warn/error, get/post/put/patch/delete,
xs/sm/md/lg/xl
es fr it pt nl ru the same vocabulary per language, opt-in
Precedence runs most specific to least: cycle.groups, then
cycle.per_filetype[ft], then cycle.packs in the order you list them. A
word belongs only to the FIRST group containing it, so { "en", "es" } makes
no cycle to yes while { "es", "en" } makes it cycle to sí.
cycle = {
packs = { "de", "en", "dev", "fr" },
groups = { { "wahr", "vielleicht", "falsch" } }, -- beats every pack
}
The default{ "en", "de", "dev" }is collision-free;|:checkhealth|cascade reports the words your own combination makes unreachable.packs = {}disables all of them and leaves only your owngroups. Scripts without word separators (Chinese, Japanese) are deliberately not shipped: cascade takes the token under the cursor with\k\+, and'iskeyword's@class matches every alphabetic character, so an unspaced run of CJK is captured as one token rather than a word and never matches a group entry. Cyrillic (ru) is unaffected -- it uses spaces.cycle_pick(preset: <leader>cp) shows every entry of the cursor's cycle group via|vim.ui.select()|and replaces it with whichever one you pick, instead of only stepping forward/backward one at a time. cascade calls the plainvim.ui.select()API, so it's Telescope-backed automatically if you havetelescope-ui-select.nvim(ordressing.nvim,fzf-lua's register, ...) registered as its handler -- no extra cascade-side dependency needed. On an ISO date (YYYY-MM-DD) under the cursor, +/- (and <C-y>/<C-x>) step the year/month/day segment the cursor is on, normalizing through Lua'sos.time/os.date-- so incrementing the last day of a month rolls into the next one instead of producing an invalid date. Native <C-a>/<C-x> can't do this: it only sees the numeric island touching the cursor and misreads the "-" before it as a minus sign.
2024-01-31 + (cursor on "31") -> 2024-02-01
2024-01-31 - (cursor on "2024") -> 2023-01-31
Setcycle.features.date = falseto turn this off and fall back to the native (date-unaware) behavior. On a single a-z/A-Z letter under the cursor -- not part of any configuredcycle.groupsentry, or that would already have matched -- +/- (and <C-y>/<C-x>) step it through the alphabet, wrapping at the boundary and keeping its case:
a <C-y> -> b z <C-y> -> a
A <C-x> -> Z B <C-x> -> A
A count steps N places at once:3<C-y>on "a" gives "d". Setcycle.features.letter = falseto turn this off and fall back to the pressed key's own native meaning. IN-WORD CHAR CYCLE *cascade-cycle-char* <C-M-y>/<C-M-x> step the single character under the cursor through the alphabet -- wrapping, case preserved -- wherever it sits, the middle of a word included:
cat <C-M-y> (cursor on "a") -> cbt
caT <C-M-y> (cursor on "T") -> caU
czt <C-M-y> (cursor on "z") -> cat
a 3<C-M-y> -> d
This is a separate key on purpose, not another link in the <C-y> chain. <C-y> reads the whole keyword under the cursor and looks it up in the cycle groups; on a word no group contains, it hands the keypress back to its native meaning. So a lone "a" cycles (the letter feature sees a one-character token) while the "a" inside "cat" does not -- the token is "cat". Extending that chain with a final "nothing matched, so step the character" step would rewrite text on every unknown word, exactly where a no-op is expected. Off an a-z/A-Z byte -- a digit, punctuation, past the end of the line, a multi-byte character -- these keys are a silent no-op: unlike <C-y> and +/- they have no native meaning of their own to fall back to. A count jumps N places in one edit. Setcycle.features.char = falseto turn the feature off. This action is bound to TWO keys -- <C-M-y> and <leader>cy (and <C-M-x> / <leader>cY) -- the only one in cascade that is. Ctrl+Alt+letter is nearly universal: terminals encode Alt as an ESC prefix (|:map-alt-keys|) and 0x19 (<C-y>) is a byte every terminal sends, soESC 0x19arrives and Nvim reassembles it. Two gaps remain: a terminal with "Alt sends Escape" off, and a layout where AltGr IS Ctrl+Alt (German and most European layouts) on a combination carrying a third-level character -- AltGr+q gives "@" and no key event reaches the application at all. Neither is detectable, and cascade does not try. Nvim asks the terminal whether it speaks "CSI u" (|tui-csiu|) but exposes the answer to no Lua API, and that answer describes the terminal rather than the path a key takes through tmux, ssh and the keyboard layout. A self-test is impossible for a simpler reason:|nvim_feedkeys()|and|nvim_input()|inject BELOW the terminal's input decoder, so Nvim pressing its own key always succeeds -- including on a terminal that could never have sent it. Binding both costs one key and settles it. Drop either the ordinary way:
keymaps = { globals = { cycle_char_next = "<C-M-y>" } }
sequence.enable (boolean) Master switch for the sequence domain
(renumbering inside a selection).
sequence.start ("keep"|"one") "keep" (default) takes the sequence's
start value from the first hit, like the
list renumberer does; "one" always restarts
at 1/a/i. Any other value degrades to
"keep".
sequence.types (string[]) Kinds tried, in order, to classify the
FIRST hit -- which then locks the kind for
the rest of the run: "digit", "ascii",
"roman". The default reads a single letter
as ascii before roman (a)b)c) being the
commoner case); put "roman" first for
i./ii./iii. sequences. An empty or
malformed value degrades to the default.
transpose.enable (boolean) Master switch for the transpose domain.
transpose.features (table) Per-feature switches. Keys: char, word.
Missing key = enabled.
keymaps.preset (boolean) Bind the default key set on setup.
debug (boolean) Debug logging at cascade's central
decision points: dispatch.try's handler
chain (detect/advance) and lists_active()'s
gate (why an action did or didn't run —
disabled feature, unwritable buffer, wrong
filetype, inside a Treesitter skip node).
Bridges to lib.nvim.logger (one cached
"cascade" instance -- inspect with
:LibLogger or its ring/file sinks) when
installed, else falls back to vim.notify
at DEBUG level. Default false; even the
check is a single cheap boolean read when
off.
Validation
Options are checked before they are merged. An unknown key -- top-level, or one level intolists/cycle/sequence/transpose/strings-- is ignored with a did-you-mean hint (keymapsis exempt: its own keys are action names, not a fixed schema). A non-table value for an option table (lists = false) falls back to that table's default instead of replacing it wholesale, and a few specific values several call sites index unconditionally (lists.filetypes,lists.checkbox,lists.continue,cycle.filetypes) get the same fallback for a wrong type. Everything ignored or degraded is listed again under|:checkhealth|cascade. See |cascade-health|.
5. LISTS DOMAIN
A list item is<indent><marker><delim?> <text>with an optional[x]checkbox right after the marker. Ordered markers (digit/ascii/roman) are incremented on continuation; renumber rewrites a contiguous block at one indent level, preserving the first item's start value and case. Examples:
1. first -> <CR> -> 2. (cursor here)
- [ ] task -> checkbox -> - [x] task
a) alpha -> <CR> -> b)
IV) roman -> <CR> -> V)
bullet_toggle/star_toggle/number_toggle/checkbox_togglework even without an existing marker (they insert one), unlike the actions above:
plain line -> bullet_toggle -> - plain line
plain line -> star_toggle -> * plain line
plain line -> checkbox_toggle -> - [ ] plain line -> (again) -> - [x] plain line -> (again) -> - [~] plain line -> (again) -> plain line
Their _visual variants apply the same toggle independently to every
non-blank line of a Visual or Visual-line selection.
Indent / dedent renumber every nesting level independently: a deeper level
starts at 1, returning to a shallower level continues it, and the level a line
leaves closes its gap. The base level keeps its first item's start offset.
1. top 1. top
1. a (>>) 1. a
2. b 2. b
3. c 1. c (new sub-level resets to 1)
4. d 3. d (gap closed: 4 -> 3)
5. e 4. e (5 -> 4)
2. bot 2. bot
Indenting/dedenting a single line (normal-mode>>/<<) carries its subtree along: any deeper-indented lines directly following it (nested children, or the item's own wrapped continuation text) shift by the same amount.
1. top 1. top
1. item (>>) 1. item
1. x 1. x
2. y 2. y
2. sibling 1. sibling (gap closed: 2 -> 1)
Onindent/dedent,v:countmeans "how many LINES" (starting at the cursor), not "how many levels" — with no count (or 1) it shifts the current line (+ its subtree, above) by one level;N>1shifts that many consecutive lines by one level each:
1. one 1. one
2. two 2>> 1. two (2 lines, one level each)
3. three 2. three
To shift a single line by N levels instead (the old count meaning ofindent/dedent), useindent_levels/dedent_levels(<leader><A-Right>/<leader><A-Left>in the preset). A blank line between items ends the block; the next list starts a fresh sequence with its own start offset. Raiselists.renumber.blank_breakto tolerate blanks inside one block. Renumbering *already-existing* text —M.all(on save), and the explicit:Cascade renumber/:Cascade renumber allcommands — behaves slightly differently for nested levels: instead of resetting a nested level to1the first time it's seen, it keeps that level's own first marker value, so a deliberately authored non-1 start (e.g. a sub-list starting at "5.") survives a save. Only the live indent/outdent/move actions reset a freshly-touched nested level to 1.
6. CYCLE DOMAIN
The keyword token under the cursor (respecting'iskeyword') is matched case-insensitively against the cycle groups; the replacement preserves the original capitalization (lower / UPPER / Capital). Numeric tokens are skipped so that native|CTRL-A|/|CTRL-X|can handle them whennumber_fallbackis on.
7. SEQUENCE DOMAIN
Renumbers the ordinal tokens inside a *selection*, whatever precedes them -- the cases the list domain structurally cannot see, because its marker parser requires the number to be the line's very first token: - a numbered Markdown headline (### 2. two), and - plain inline numbers in prose, possibly selected mid-line (text with only a 4. item and a 5. one ...). Both are the same operation: scan the selected text for ordinal tokens in order of appearance and rewrite them sequentially, independent of filetype. So this is its own domain, not an extension of the list renumberer. A candidate is an alphanumeric run followed by "." or ")". Two boundary rules keep it out of ordinary prose: the run is matched greedily (the "1" inv1.2is never seen on its own), and the delimiter must be followed by whitespace or end-of-text, so decimals (3.14) and abbreviations (e.g.) don't qualify. The FIRST hit decides the kind (digit / ascii / roman, tried in the order ofsequence.types) and locks it: tokens of the other kinds are then skipped rather than folded into the same sequence. The delimiter is kept per hit, so a mixed./)selection keeps its shapes -- only the number is replaced.
### 7. a (selected) ### 7. a
### 2. b <leader>cR ### 8. b ("keep": first hit sets 7)
### 9. c ### 9. c (outside the selection)
renumber_selectionis Visual mode only, on purpose: an Ex-command range (:'<,'>) is always linewise in Vim and would discard the columns a mid-line or multi-line charwise selection needs. A charwise (v) selection -- same-line or spanning several lines -- is rewritten in place and reselected on its new bounds (the text can widen,9.->10.); text before the selection's start and after its end is always left untouched, on either boundary line. Linewise (V), or a selection with no column bounds at all (blockwise), is treated as a whole-line range and reselected linewise. Multi-line charwise bridges directly tolib.nvim.selection.chars_multiline/reselect_chars_multiline-- see |lib.nvim-selection-chars_multiline|. |:Cascade-renumber|selectionis the Ex-command pendant for the linewise case; it complements the list-marker branch rather than replacing it.
8. TRANSPOSE DOMAIN
Swaps a character (swap_right/swap_left), or a same-line selection (swap_right_visual/swap_left_visual), with the single character immediately to its right or left. UTF-8 safe — a multibyte character (e.g. "ä") moves as one unit rather than being torn apart — and, unlike the classicxp/xhPtrick, it never touches the unnamed register. Examples:
ab -> swap_right -> ba (cursor follows to "a")
xyzw -> select "yz", swap_right -> xwyz
v:count1repeats the swap that many times in one keypress — e.g.2<leader><Right>moves the char (or selection) two positions over, the same net effect as pressing the key twice:
abcde 2<leader><Right> -> bcade (cursor follows to "a")
Works the same way for the visual variant, re-anchoring the selection to the shifted text after each step. Stops early (partial application, not an error) if a swap fails before the count is exhausted — e.g. it reaches the line boundary. No-op at a line boundary (nothing to swap with) or, for the visual variant, across multiple lines — there is no single well-defined neighbor there. On a no-op the visual selection is restored (gv); on success the swapped text stays selected (re-anchored to its new position, not the original columns).swap_word_right/swap_word_left(and their_visualcounterparts) work the same way one level up: the word under the cursor (or a same-line selection) swaps with the neighboring'iskeyword'word instead of a single character. The separator between the two (whitespace, punctuation, ...) moves as an untouched block — only the two words trade places. Examples:
foo bar -> swap_word_right -> bar foo (cursor follows to "bar")
foo,bar -> swap_word_right -> bar,foo (comma gap kept as-is)
Same no-op rules as the char variant: nothing to the right/left on the line, across multiple lines, or when the cursor/selection isn't touching a word. Both the char and word swaps, normal and visual, honorv:count: a bare call swaps once,N<leader><Right>/N<leader><C-Right>(etc.) repeats the swapNtimes, dragging the char/word/selectionNpositions over. It stops early (rather than erroring) if it hits a line boundary or runs out of neighbors beforeNswaps are done.
9. HEALTH
*:checkhealth-cascade*
:checkhealth cascade
Reports the Neovim version, per-domain status, lib.nvim availability (required — gates the :Cascade command) and basic configuration sanity.
10. LIB.NVIM INTEGRATION
cascade.nvim REQUIRESlib.nvim: the :Cascade command is built on lib.nvim.bindings.usercmd.composer and fails to load without it.lib.notifyandlib.augroupremain soft-guarded — used when present, native API fallback otherwise (see lua/cascade/util/lib.lua).