Cycle text within predefined candidates.
yes
→no
→yes
January
→February
→March
trUe
→faLse
keep case by default"
→'
can handle non-keywords- 可
是
→ 可否
multibyte is fine Rails Metal
→Thrash
→Technical Death
handle multi-words by visual selection, or smart auto search<em>
important</em>
→<strong>
important</strong>
tag pairs cycle together (even across lines)「
quoted」
→『
quoted』
special pairs cycle together"
‗"
→^
‗^
→“
‗”
the two sides can be identical (must has only 1 char currently){ :one => 'two' }
→{ one: 'two' }
now supports pattern replace like switch.vimfoo-bar-baz
→FOO_BAR_BAZ
→fooBarBaz
naming conventioncovid-19-mk2
→covid_19Mk2
→Covid_19Mk2
alternative number handling民國 40
→昭和 26
→พ.ศ. 2495
→1951
cycle calendar era systems with "year" option
The selection UI is telescope.nvim and sound is setup by vim-noise.
(This video has audio)
vim-cycle.mp4
Sound credit:
- Complete and unavailable sounds were generated by jsfxr.
- Conflict UI sound was edited from source xeno-canto.org/559593, CC BY-NC-SA 4.0.
let g:cycle_no_mappings = 1
let g:cycle_max_conflict = 14
let g:cycle_select_ui = 'telescope'
let g:cycle_conflict_ui = 'confirm'
nmap <silent> <LocalLeader>a <Plug>CycleNext
vmap <silent> <LocalLeader>a <Plug>CycleNext
nmap <silent> <Leader>a <Plug>CyclePrev
vmap <silent> <Leader>a <Plug>CyclePrev
nmap <silent> <LocalLeader>ga <Plug>CycleSelect
vmap <silent> <LocalLeader>ga <Plug>CycleSelect
let g:cycle_filetype_links = {
\ 'ghmarkdown': 'markdown',
\ 'typescriptreact': 'jsx',
\ }
let g:cycle_default_groups = [
\ [['true', 'false']],
\ [['yes', 'no']],
\ [['on', 'off'], 'match_word'],
\ [['+', '-']],
\ [['>', '<']],
\ [['==', '!='], { 'cond': function('s:not_lua_context') }],
\ [['0', '1']],
\ [['and', 'or']],
\ [['asc', 'desc']],
\ [['是', '否']],
\ [['在', '再']],
\ [[',', '。', '、']],
\ [['✓', '✗', '◯', '✕', '✔', '✘', '⭕', '✖']],
\ [['lat', 'lon'], 'match_word'],
\ [['latitude', 'longitude']],
\ [['ancestor', 'descendant']],
\ [['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
\ 'Friday', 'Saturday'], ['hard_case', {'name': 'Days'}]],
\ [['":"', "':'"], 'sub_pairs'],
\ [['(:)', '(:)', '「:」', '『:』'], 'sub_pairs'],
\ [['民國', '令和', '平成', '昭和', '大正', '明治', 'พ.ศ.', 'CE'], 'year'],
\ ]
" For fileType "lua" only
let g:cycle_default_groups_for_lua = [
\ [['==', '~=']],
\ ]
" For fileType "plaintex" only
let g:cycle_default_groups_for_plaintex = [
\ [
\ '\left(:\right)',
\ '\mleft(:\mright)',
\ '\Bigl(:\Bigr)',
\ '(:)',
\ ],
\ 'sub_pairs', 'hard_case', 'match_case'
\ ]
" Note (:) must be put at the end. The search runs in sequence,
" an earlier ( match can short-circuit the following items.
" For filetype "ruby" only
" This uses "regex" option to works on patterns
" 1. cycles: :bar => bar:
" 2. cycles: "foo" 'foo' :foo
let g:cycle_default_groups_for_ruby = [
\ [[':\(\k\+\)\s*=>\s*', '\<\(\k\+\): '],
\ #{regex: ['\1: ', ':\1 => '], name: 'ruby hash style'}],
\ [['"\(\k\+\%([?!]\)\=\)"',
\ '''\(\k\+\%([?!]\)\=\)''',
\ ':\(\k\+\%([?!]\)\=\)\@>\%(\s*=>\)\@!'
\ ], #{regex: ['''\1''', ':\1', '"\1"\2']}]
\ ]
" For "jsx" only (not real filetype, but linked from typescriptreact)
let g:cycle_default_groups_for_jsx = [
\ [['\v\="(.+)"', '\v\=''(.+)''', '\v\=\{`\$@!(.+)`}'], #{regex: ['=''\1''', '={`\1`}', '="\1"']}],
\ [['={`${\(.\+\)}`}', '={`\@!\(.\+\)}'], #{regex: ['={\1}', '={`${\1}`}']}],
\ ]
" For fileType "markdown" only
let g:cycle_default_groups_for_markdown = [
\ [['^\(\s*\)- \[ \] ', '^\(\s*\)- \[x\] '],
\ #{regex: ['\1- [x] ', '\1- [ ] '], name: 'markdown_task_item'}],
\ ]
" For HTML, but here just blindly add to global groups
let g:cycle_default_groups += [
\ [['h1', 'h2', 'h3', 'h4'], 'sub_tag'],
\ [['ul', 'ol'], 'sub_tag'],
\ [['em', 'strong', 'small'], 'sub_tag'],
\ ]
let g:cycle_default_groups += [
\ [['日', '一', '二', '三', '四', '五', '六']],
\ [['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
\ 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']],
\ [['January', 'February', 'March', 'April', 'May', 'June', 'July',
\ 'August', 'September', 'October', 'November', 'December']],
\ [['portrait', 'landscape']],
\ ]
" Set another mapping for certain groups
let s:tx_groups = [
\ [['snake_case', 'kebab-case', 'camelCase_1',
\ 'PascalCase_1', 'SCREAMING_SNAKE_CASE'], 'naming'],
\ ]
function! s:cycle_tx_groups() abort " {{{
let groups = s:tx_groups
" ... can do more dynamic handling to make your groups
" must translate to internal format
let parsed = map(deepcopy(groups), {i, g -> cycle#parse_group(g)})
let parsed = flatten(parsed)
return #{groups: parsed}
endfunction " }}}
nnoremap <silent> <LocalLeader>x <Cmd>call Cycle('w', 1, v:count1, <SID>cycle_tx_groups())<CR>
vnoremap <silent> <LocalLeader>x <Cmd>call Cycle('v', 1, v:count1, <SID>cycle_tx_groups())<CR>
nnoremap <silent> <LocalLeader>gx <Cmd>call CycleSelect('w', <SID>cycle_tx_groups())<CR>
vnoremap <silent> <LocalLeader>gx <Cmd>call CycleSelect('v', <SID>cycle_tx_groups())<CR>
-
SwapIt by Michael Brown
Original ideas of special features including visual multi-words, xml tag pairs, omni-complete cycling. -
Cycle.vim by Zef
Yes, there is already a plugin named 'Cycle'. Maybe I have to rename mine. -
switch.vim by AndrewRadev
Supports more complicated patterns like ruby:a => 'b'
toa: 'b'
, which is generally unable to achieve by alternative projects. -
vim-clurin by syngan
Another early implementation, seems to have custom pattern and replace function features, but lacks documentation. -
toggle.vim by Timo Teifel
Maybe the very first plugin that introduced this idea.
-
In 繁體中文:
- 開發背景 (2011)
- 簡易使用說明 (2011)
- Group 設定簡介 (2011)
- 支援 pattern 搜尋、自訂轉換 (2025)
Run the test scripts, will clone vim-themis and execute it.
make test
Or directly run:
./test/run.sh
THEMIS_VIM=nvim ./test/run.sh
./test/run.sh --reporter dot --target 'respects .match_case"'
./test/run.sh --help