NORMAL ~/wkd/p/cascade/help :set skin=modern utf-8

cascade.txt

Context-aware lists & cycling for Neovim — cascade.nvim

doc/cascade.txt — rendered from the plugin's own vimdoc

*cascade.txt*                          Context-aware lists & cycling for Neovim
                                                                *cascade.nvim*

CONTENTS *cascade-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-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*

                                                              *cascade.setup()*
    require("cascade").setup({
      keymaps = { preset = true },
    })
Calling setup() merges your options over the defaults and defines the
:Cascade user command. With keymaps.preset = true it additionally binds
the default keys (see |cascade-keymaps|) directly onto the facade actions — no
<Plug> indirection. setup() is safe to call once.

3. KEYMAPS *cascade-keymaps*

Every action is a plain function on the cascade module, 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 of indent)
    dedent_levels                normal  dedent the current line by N levels
                                          (old count meaning of dedent)
    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 a tpope/vim-repeat
dependency. If vim-repeat happens to be installed, cascade also calls its
repeat#set alongside the native trick, purely as optional interop -- .
works identically either way.

Unlike toggle_checkbox/cycle_type_next, which only ever advance an
existing marker (they no-op on a plain line), bullet_toggle, star_toggle,
number_toggle and checkbox_toggle also work without one: they insert the
marker from scratch, and toggle it back off again on a second press. Their
_visual variants 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 three cycle subcommands 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
                    global cycle.groups plus cycle.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*

With keymaps.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 in lists.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 to cascade.indent/cascade.dedent if
you want them.

4. CONFIGURATION *cascade-config*

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 own groups.

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
plain vim.ui.select() API, so it's Telescope-backed automatically if you
have telescope-ui-select.nvim (or dressing.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's
os.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
Set cycle.features.date = false to 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 configured
cycle.groups entry, 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". Set
cycle.features.letter = false to 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. Set cycle.features.char = false to 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, so ESC 0x19 arrives 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 into lists/cycle/sequence/transpose/strings -- is ignored
with a did-you-mean hint (keymaps is 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 *cascade-lists*

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_toggle work 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)
On indent/dedent, v:count means "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>1 shifts 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 of
indent/dedent), use indent_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. Raise lists.renumber.blank_break to
tolerate blanks inside one block.

Renumbering *already-existing* text — M.all (on save), and the explicit
:Cascade renumber/:Cascade renumber all commands — behaves slightly
differently for nested levels: instead of resetting a nested level to 1 the
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 *cascade-cycle*

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 when number_fallback is on.

7. SEQUENCE DOMAIN *cascade-sequence*

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" in v1.2
is 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 of
sequence.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_selection is 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 to lib.nvim.selection.chars_multiline/
reselect_chars_multiline -- see |lib.nvim-selection-chars_multiline|.

|:Cascade-renumber| selection is the Ex-command pendant for the linewise
case; it complements the list-marker branch rather than replacing it.

8. TRANSPOSE DOMAIN *cascade-transpose*

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 classic
xp/xhP trick, it never touches the unnamed register.

Examples:
    ab            ->  swap_right  ->  ba   (cursor follows to "a")
    xyzw          ->  select "yz", swap_right  ->  xwyz
v:count1 repeats 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 _visual counterparts) 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, honor v:count: a bare
call swaps once, N<leader><Right>/N<leader><C-Right> (etc.) repeats the
swap N times, dragging the char/word/selection N positions over. It stops
early (rather than erroring) if it hits a line boundary or runs out of
neighbors before N swaps are done.

9. HEALTH *cascade-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-lib*

cascade.nvim REQUIRES lib.nvim: the :Cascade command is built on
lib.nvim.bindings.usercmd.composer and fails to load without it. lib.notify
and lib.augroup remain soft-guarded — used when present, native API
fallback otherwise (see lua/cascade/util/lib.lua).

keys

j / k
next / previous line
gg / G
first / last line
⏎
open the line under the cursor
/
search the plugins
:
command line — Tab completes
:help x
vimdoc of a plugin (:e x = plugin page)
:ls · :log · :stack
plugin list · activity stream · dependency graph
:colo x
colorscheme
:set skin=
modern | tui
?
this help
esc
close