Exercises to learn vim-visual-multi.
A complement to the official tutorial.
How to use
- Read doc/visual-multi.txt and practice with doc/vm-tutorial.
- Read the
C-Down/C-Upto add cursors vertically exercise to get familiar with VM (short for vim-visual-multi). This is the most beginners-focused exercise and has screenshots. - Learning by Doing. Go to an exercise and try to solve it:
- Make the left window buffer become identical to the right one applying VM commands:
cd <this_repo>/exercises/<exercise_name> && vim -d start.txt result.txtor directly copy-paste the command of Start vs result section of the desired exercise. - To display the solution click on
▸ Solutionor use:split steps.md.
- To solve the exercises more programmatly read the Transpose programmatly exercise as example.
- All collaboration is appreciated: contribute.
Avoid VM wiki, embrace :help vm-
Use :help vm-quick-reference or alike to read the up-to-date VM own documentation. The wiki will be kept for screenshots but consider it outdated for everything else.
leader- vs \\
leader- is your g:VM_leader (default \\)
- In section's headers it appears as
leader-instead of\\because one might not use the defaultg:VM_leader. - But the other way around in solutions to make it easier to understand (and apply) for Vim-beginners. Also to not confuse with Vim-mapleader (see
:help mapleader).
C-Down/C-Upto add cursors verticallyleader-cto add cursors verticallyQto remove unwanted cursors after selectionqto skip cursor-region and go to next region.to run single repeatRto replace content of patternleader-\to add cursor at positionleader-dto duplicate regionsleader-tto transpose- Rotate programmatly
Sto surroundC-nin normal mode adds a word under cursor, keep pressing to find next occurrencenorNafterC-n-
leader-/for regex search g/to expand each region by search regexC-nin visual mode adds selecting chars, keep pressing to find next occurrenceC-nin normal mode to add multiple regex-wordsC-ncase sensitive vs ignore-case vs smartcaseu/C-rto undo/redoleader-wto toggle whole word searchleader-ato align,[count]leader-<to align by char,leader->to align by regexleader-Cfor case conversionleader-mto merge regionsleader-sto split regionsleader-minus/leader-plusto shrink/enlarge regionssfor select operatormfor find operatorleader-fto filter regionsleader-n/leader-Nfor numbering<M-S-Arrows>to shift textleader-eto transform regions with expressionleader-Rto remove every n regions
Advance
Collaboration, ToDo-s & FAQs
:help vm-add-cursors
If C-Down/C-Up mappings don't work apply overwrite mappings.
Solution
- Cursor at
wofwordB1a. Ctrl-Downto add a cursor on the line belowCtrl-Downagain to add another cursor on the third line. Now multi-cursors are inwofwordB1a,wordB10aandwordB100a. Note that steps 2 and 3 can be combined using counts, like2Ctrl-Down.jto move the multi-cursors down. I.e. to move the cursor towordB10atowordB1000a. Reference wiki/Quick-start:
To make things easier, since
hjklmove all cursors, you can still use arrows and ctrl-arrows to move around, without moving the cursors as well.
egoes to end of word. Read:h Q_tma_endto add_endafter cursor position.:help vm-cursor-modestates:
You can enter |insert-mode| with
i,I,a,A, and only from cursor mode also withoandO.
Escto exit insert modeEscto exit multi-cursor mode
Bonus. In 4th step j moves the multi-cursors down. wiki/Quick-start#adding-cursors-vertically shows another example which uses k to move the multi-cursors up.
Start
wordA wordB1a = abc.def
wordA wordB10a = abc.def
wordA wordB100a = abc.def
wordA wordB1000a = abc.defResult
wordA wordB1a = abc.def
wordA wordB10a_end = abc.def
wordA wordB100a_end = abc.def
wordA wordB1000a_end = abc.defA. (Neo)Vimdiff. Jump with ]e or ]b of diffchar.vim to next char-diff
cd exercises/add_cursors_vertically
vim -d start.txt result.txtB. delta diff. For example
:help vm-mappings-visual
Do not confuse with leader-c of C-n case sensitive vs ignore-case vs smartcase.
Solution
- Cursor at
wofwordB1. C-venters Vim's native visual block mode (not Visual-Multi)jjto visual-block select the next 2 chars below\\cto create column-wise cursorsjto move the multi-cursors down. I.e. to move the cursor towordB10atowordB1000aeto go to the end of the worda_endto add_endat end of those wordsEscto exit insert modeEscto exit multi-cursor mode
Start
wordA wordB1a = abc.def
wordA wordB10a = abc.def
wordA wordB100a = abc.def
wordA wordB1000a = abc.defResult
wordA wordB1a = abc.def
wordA wordB10a_end = abc.def
wordA wordB100a_end = abc.def
wordA wordB1000a_end = abc.defStart vs result
cd exercises/add_cursors_vertically_leader_c
vim -d start.txt result.txt| Map | Action | Documentation |
|---|---|---|
| Tab | Switch between cursor and extend mode | :help vm-modes |
| n / N / q | Next/Previous/Skip | :help vm-find-next |
| Q | Remove region under cursor | :help vm-remove-region |
Solution
Add cursors to all target lines (including the one to skip):
- Cursor on line 1 letter "a" of "apple"
Ctrl-Downto adds cursor in same column of line 2 ("b" of "banana")Ctrl-Downto adds cursor in same column of line 3 ("c" of "cherry"). Note that steps 2 and 3 can be combined using counts, like2Ctrl-Down.
Remove the unwanted cursor:
Up-arrowto move to the "banana" line. Usearrowsto move around instead ofhjkl, reference wiki/Quick-start:
To make things easier, since
hjklmove all cursors, you can still use arrows and ctrl-arrows to move around, without moving the cursors as well.
Or use [ to goto previous cursor.
Qto delete that cursor ("Remove region")
Now edit the remaining cursors:
ego to end of worda_endto add_endat end of those words
Note. Add cursor on second line to finally remove it is absurd. Instead:
- Cursor on line 1 letter "a" of "apple"
vandCtrl-nto add cursorDown-arrowtwice or/cherryvandCtrl-nto add cursorego to end of worda_endto add_endat end of those words
Start
item = "apple" # Edit this
item = "banana" # Skip this
item = "cherry" # Edit thisResult
item = "apple_end" # Edit this
item = "banana" # Skip this
item = "cherry_end" # Edit thisStart vs result
cd exercises/remove_cursors_after_selection
vim -d start.txt result.txt| Map | Action | Documentation |
|---|---|---|
| n / N / q | Next/Previous/Skip | :help vm-find-next |
Solution
- Cursor on line 1 letter "a" of "apple"
Ctrl-Downto adds cursor in same column of line 2 ("b" of "banana")Ctrl-Downto adds cursor in same column of line 3 ("c" of "cherry"). Note that steps 2 and 3 can be combined using counts, like2Ctrl-Down.
q to skip cursor-region and go to next region
Tabto switch to "extend mode"Up-arrowto move to the "banana" lineqto remove that region but also jump to next region, i.e. if we are inb(of banana) we jump to next line that has ab.
Now edit the remaining cursors (and the new one added region by q):
ego to end of worda_endto add_endat end of those words
Start
item = "apple" # Edit this
item = "banana" # Skip this
item = "cherry" # Edit this
item = "apple" "banana" "cherry" # q will jump to a 'b' here if any
item = "banana" "cherry" "apple" # q will jump to a 'b' here if any and if not jumped to previous line
item = "cherry" "apple" "banana" # q will jump to a 'b' here if any and if not jumped to 2 previous linesResult
item = "apple_end" # Edit this
item = "banana" # Skip this
item = "cherry_end" # Edit this
item = "apple" "banana_end" "cherry" # q will jump to a 'b' here if any
item = "banana" "cherry" "apple" # q will jump to a 'b' here if any and if not jumped to previous line
item = "cherry" "apple" "banana" # q will jump to a 'b' here if any and if not jumped to 2 previous linesStart vs result
cd exercises/skip_cursor_and_go_to_next
vim -d start.txt result.txt:help vm-run-dot
Next exercises are very close to those of doc/vm-tutorial dot-section: vim <path>/vim-visual-multi/doc/vm-tutorial -c '/Dot \~'.
Solution
ggto go to the first line (and first non-blank char)- Press
o, insert textnew line, thenEscto exit insert mode - Undo the change by pressing
u, then pressggto go back - Press
2<C-Down> - Finally press
. Escto exit VM-mode
We did actually take advantage of the o mapping in cursor mode.
o mapping varies in different VM modes. :help vm-cursor-mode states:
You can enter |insert-mode| with
i,I,a,A, and only from cursor mode also withoandO.
Next alternative might be clearer.
ggto go to the first line2C-Downto add VM-cursors in next 2 lines- Press
o, insert textnew line, thenEscto exit insert mode. This step was previously achieved just with.(pressing dot) Escto exit VM-mode
Start
item1 = "apple"
item2 = "banana"
item3 = "cherry"Result
item1 = "apple"
new line
item2 = "banana"
new line
item3 = "cherry"
new lineStart vs result
cd exercises/dot_add_content
vim -d start.txt result.txtSolution
ggto go to the first line (and first non-blank char)- Press
2<C-Down> - Press
dwto remove the next word, here "item" - Press
.to repeat, and remove the next word, here "=" Escto exit VM-mode
Note that the . (dot command) ignores any count. As :h vm-run-dot states its aim is one single repeat.
Start
item1 = "apple"
item2 = "banana"
item3 = "cherry"Result
"apple"
"banana"
"cherry"Start vs result
cd exercises/dot_remove_content
vim -d start.txt result.txt:help vm-replace-pattern
Next exercises are almost identical to those of doc/vm-tutorial: vim <path>/vim-visual-multi/doc/vm-tutorial -c '/Replace in regions, Select Operator \~'.
Solution
ggto go to the first line (and first non-blank char)- Press
f"lto go inside the quotes - Press
3<C-Down>to create cursors - Press
<C-n>to select words - Press
R: this is the command to replace a pattern in each region. - Enter
_as pattern (pressEnterto confirm) - Enter a single space as replacement (press
Enterto confirm) Escto exit VM-mode
Start
param_table.AddNumber("num_control_buffers", options_.num_control_buffers);
param_table.AddNumber("control_buffer_size", options_.control_buffer_size);
param_table.AddNumber("num_payload_buffers", options_.num_payload_buffers);
param_table.AddNumber("payload_buffer_size", options_.payload_buffer_size);Result
param_table.AddNumber("num control buffers", options_.num_control_buffers);
param_table.AddNumber("control buffer size", options_.control_buffer_size);
param_table.AddNumber("num payload buffers", options_.num_payload_buffers);
param_table.AddNumber("payload buffer size", options_.payload_buffer_size);Start vs result
cd exercises/replace_pattern
vim -d start.txt result.txtSolution
ggto go to the first line (and first non-blank char)- Press
3<C-Down>to create cursors - Press
f"to go to the quotes - Press
si"to select inside the quotes. See:h vm-select-operator - Press
R, then a single space followed by<Return> - Enter
_as replacement, and again<Return> Escto exit VM-mode
Start
param_table.AddNumber("num control buffers", options_.num_control_buffers);
param_table.AddNumber("control buffer size", options_.control_buffer_size);
param_table.AddNumber("num payload buffers", options_.num_payload_buffers);
param_table.AddNumber("payload buffer size", options_.payload_buffer_size);Result
param_table.AddNumber("num_control_buffers", options_.num_control_buffers);
param_table.AddNumber("control_buffer_size", options_.control_buffer_size);
param_table.AddNumber("num_payload_buffers", options_.num_payload_buffers);
param_table.AddNumber("payload_buffer_size", options_.payload_buffer_size);Start vs result
cd exercises/replace_pattern_02
vim -d start.txt result.txt:help vm-add-cursor
Solution
- Cursor on any part of "cereal"
\\\to add cursor. See:help vm-add-cursor- Use arrows to move the cursor to any char "meat"
\\\to add cursor- Use arrows to move the cursor to any char "fish"
\\\to add cursorciwand typefruitto replace those words with "fruit"Escto exit insert modeEscto exit multi-cursor mode
Start
item1 = "apple", "green", "cereal"
item2 = "banana", "yellow", "meat"
item3 = "cherry", "red", "fish"Result
item1 = "apple", "green", "fruit"
item2 = "banana", "yellow", "fruit"
item3 = "cherry", "red", "fruit"Start vs result
cd exercises/add_cursor_at_position
vim -d start.txt result.txtBonus
Solution
If once I set all the cursors on any letter of last field ("cereal", "meat" and "fruit") I realize that instead of renaming them I want to just delete that field, then I can move that multicursors to previous , and delete rest of line:
- 1-8 Identical steps
F,move all cursors to previous ","d$delete all till end of lineEscto exit multi-cursor mode
Result
item1 = "apple", "green"
item2 = "banana", "yellow"
item3 = "cherry", "red"Start vs result
cd exercises/add_cursor_at_position_02
vim -d start.txt result.txt:help vm-duplicate
Solution
- Cursor on first letter of "apple"
Ctrl-Downto adds cursor to 1st letter of "banana"Ctrl-Downto adds cursor to 1st letter of "cherry"Tabto switch to "extend mode"eto select till end of word\\dto duplicate tab-extension regionsEscto exit multi-cursor mode
Start
item1 = "apple"
item2 = "banana"
item3 = "cherry"Result
item1 = "appleapple"
item2 = "bananabanana"
item3 = "cherrycherry"Start vs result
cd exercises/regions_duplicate
vim -d start.txt result.txt:help vm-transpose
See GIF of wiki/Special-commands#text-transposition.
Solution
- Cursor on first char of "apple"
Ctrl-Downto adds cursor to 1st char of "banana"Ctrl-Downto adds cursor to 1st char of "cherry"Tabto switch to "extend mode"eto select till end of word\\tto transpose tab-extension regions. Notice rest of line didn't changeEscto exit multi-cursor mode
Start
item1 = "apple"
item2 = "banana"
item3 = "cherry"Result of apply transpose once
item1 = "banana"
item2 = "cherry"
item3 = "apple"Result of apply transpose twice
item1 = "cherry"
item2 = "apple"
item3 = "banana"Start vs results
cd exercises/regions_transpose
vim -d start.txt result.txt result_apply_2-times.txt:help vm-transpose
Solution
Based on issue #53 let's have next example that we want to transpose programmatly.
[[1, 2]
[3, 4]]
WRONG. Next step 5 (f2) updates Vim's last search pattern to 2, which causes VM to drop the first selection (1) because it thinks you're now only working with 2.
f<char> is analogous to hjkl in wiki/Quick-start:
To make things easier, since
hjklmove all cursors, you can still use arrows and ctrl-arrows to move around, without moving the cursors as well.
ggto go to the first line (and first non-blank char)0to go to start of line (regardless of whether it is a blank or not)f1to move to 1st1:call vm#commands#add_cursor_at_word(1, 1)to add cursor-wordf2to move to 1st2:call vm#commands#add_cursor_at_word(1, 1)to add cursor-wordjto go 1 line down0to go to start of linef3to move to 1st3:call vm#commands#add_cursor_at_word(1, 1)to add cursor-wordf4to move to 1st4:call vm#commands#add_cursor_at_word(1, 1)to add cursor-word:call b:VM_Selection.Edit.transpose()to transpose
Equivalent (still wrong)
:execute 'normal gg0' | execute 'normal f1' | call vm#commands#add_cursor_at_word(1, 1)
: execute 'normal f2' | call vm#commands#add_cursor_at_word(1, 1)
:execute 'normal j0' | execute 'normal f3' | call vm#commands#add_cursor_at_word(1, 1)
: execute 'normal f4' | call vm#commands#add_cursor_at_word(1, 1)
:call b:VM_Selection.Edit.transpose()RIGHT
We could use arrows (or Ctrl-arrows), though search commands (/) are more convenient, read :h vm-slash and :h vm-regex-search. Another approach is to keep using f<char> but run \\<Space> before the first f-motion, read :h vm-mappings-toggle
- Same as in previous step
- Same as in previous step
- Same as in previous step
- Same as in previous step
/2to search for2(and press Enter)- Same as in previous step
- Same as in previous step
- Same as in previous step
/3to search for3(and press Enter)- Same as in previous step
/4to search for4(and press Enter)- Same as in previous step
Tabto switch to "extend mode":call b:VM_Selection.Edit.transpose()to transpose
Equivalent
:execute 'normal gg0' | execute 'normal f1' | call vm#commands#add_cursor_at_word(1, 1)
: execute "normal /2\<CR>" | call vm#commands#add_cursor_at_word(1, 1)
: execute "normal /3\<CR>" | call vm#commands#add_cursor_at_word(1, 1)
: execute "normal /4\<CR>" | call vm#commands#add_cursor_at_word(1, 1)
:call b:VM_Selection.Global.change_mode(1)
:call b:VM_Selection.Edit.transpose()Now let's make the testing text a little more complex, like:
[[1, 2, 3, 4]
[5, 6, 7, 8]]
Then the vim-steps to transpose could be next
function! MultiCursorCustomSearchAndAdd(start,end)
for number in range(a:start, a:end)
" Search for the current number
execute "normal! /" . number . "\<CR>"
" Add Multi-Cursor
call vm#commands#ctrln(v:count1)
endfor
endfunction
function! MultiCursorCustomTranspose(...)
" Set iter to 1 if no arg is passed
let l:iter = a:0 > 0 ? a:1 : 1
" Go to 1st line and 1st char
execute 'normal gg0'
" Add Multi-Cursors
call MultiCursorCustomSearchAndAdd(1,8)
" Transpose iter times
for number in range(1, l:iter)
call b:VM_Selection.Edit.transpose()
endfor
endfunctionThus if we run :call MultiCursorCustomTranspose() we get
[[4, 1, 2, 3]
[8, 5, 6, 7]]
Notice that we did not exit the Multi-Cursor mode (Esc), therefore we can further transpose it.
If then we tranpose once more (\\t or :call b:VM_Selection.Edit.transpose()) we achieve:
[[3, 4, 1, 2]
[7, 8, 5, 6]]
If then we tranpose again it becomes the desired result. Press Esc to exit the Multi-Cursor mode.
[[2, 3, 4, 1]
[6, 7, 8, 5]]
Note. If initially we run :call MultiCursorCustomTranspose(3) | execute "normal \<Esc>" we get this same result.
Finally if we transpose again we get the original result.
Start
[[1, 2, 3, 4]
[5, 6, 7, 8]]Result
[[2, 3, 4, 1]
[6, 7, 8, 5]]Start vs results
cd exercises/regions_transpose_02
vim -d start.txt result.txtRecommended to previous understand the transpose programmatly exercise.
There is no oficial :help . Proof me wrong please
cd <path>/vim-visual-multi/doc
rg rotate
autoload/vm/maps/all.vim says:
"Rotate": ['', 'n'],
...thus there is no default mapping to rotate.
autoload/vm/plugs.vim says:
nnoremap <silent> <Plug>(VM-Rotate) :call b:VM_Selection.Edit.rotate()<cr>Thus we can
- Run
:call b:VM_Selection.Edit.rotate() - Run
:execute "normal \<Plug>(VM-Rotate)" - Create a mapping, for example:
let g:VM_maps['Rotate'] = '\\B'. Try to use a key that is not already used. Searchleader-<key>in autoload/vm/maps/all.vim
Solution
The vim-steps to rotate could be next
function! MultiCursorCustomSearchAndAdd(start,end)
for number in range(a:start, a:end)
" Search for the current number
execute "normal! /" . number . "\<CR>"
" Add Multi-Cursor
call vm#commands#add_cursor_at_word(1, 1)
endfor
endfunction
function! MultiCursorCustomRotate(...)
" Set iter to 1 if no arg is passed
let l:iter = a:0 > 0 ? a:1 : 1
" Go to 1st line and 1st char
execute 'normal gg0'
" Add Multi-Cursors
call MultiCursorCustomSearchAndAdd(1,8)
" Switch to extend mode
call b:VM_Selection.Global.change_mode(1)
" Rotate iter times
for number in range(1, l:iter)
call b:VM_Selection.Edit.rotate()
endfor
endfunctionThus if we run :call MultiCursorCustomRotate() we get
[[2, 3, 4, 5]
[6, 7, 8, 1]]
Notice that we did not exit the Multi-Cursor mode (Esc), therefore we can further rotate it.
If then we rotate once more (:call b:VM_Selection.Edit.rotate()) we achieve:
[[3, 4, 5, 6]
[7, 8, 1, 2]]
If then we rotate again it becomes:
[[4, 5, 6, 7]
[8, 1, 2, 3]]
Rotate again to get
[[5, 6, 7, 8]
[1, 2, 3, 4]]
Rotate again to get
[[6, 7, 8, 1]
[2, 3, 4, 5]]
Rotate again to get
[[7, 8, 1, 2]
[3, 4, 5, 6]]
If then we rotate again it becomes the desired result. Press Esc to exit the Multi-Cursor mode.
[[8, 1, 2, 3]
[4, 5, 6, 7]]
Note. If initially we run :call MultiCursorCustomRotate(7) | execute "normal \<Esc>" we get this same result.
Finally if we rotate again we get the original result.
Start
[[1, 2, 3, 4]
[5, 6, 7, 8]]Result
[[8, 1, 2, 3]
[4, 5, 6, 7]]Start vs results
cd exercises/regions_rotate
vim -d start.txt result.txt
:help vm-mappings-buffer and :help vm-extend-mode say:
- vim-surround plugin is required
- This command is specific to extend-mode
:help vm-operators quote
|vim-surround| example:
ysiw(to enclose in parentheses
Solution
- Cursor on "a" of "apple"
Ctrl-Downto adds cursor to 1st char of "banana"Ctrl-Downto adds cursor to 1st char of "cherry"Tabto switch to "extend mode"eto select till end of wordS"to surround the transpose tab-extension regions with double quotes"Escto exit multi-cursor mode
Start
item1 = apple
item2 = banana
item3 = cherryResult
item1 = "apple"
item2 = "banana"
item3 = "cherry"Start vs result
cd exercises/regions_surround
vim -d start.txt result.txt:help vm-find-word. :help vm-mappings-qr summarizes it, but does not mention counts, quote:
Find Under <C-n> select the word under cursor
Find Subword Under <C-n> from visual mode, without word boundaries
Remember the avoid VM wiki section: the VM wiki will be kept for screenshots but consider it outdated for everything else. Though C-n content is up-to-date. wiki/Quick-start#select-word-or-subword-under-cursor quotes:
The basic mapping is C-n, it works from normal mode (selecting a whole word) or visual mode (selecting characters, without word boundaries)
C-nwill select a word with word boundaries, unless pressed on an existing selection. Also note that if you move to a different word with arrow keys and pressC-nagain, a new pattern will be added, and all of them will be searched when pressingn.
For programmatly solving, like in Transpose programmatly, notice autoload/vm/plugs.vim:
nnoremap <silent> <Plug>(VM-Find-Under) :<c-u>call vm#commands#ctrln(v:count1)<cr>
xnoremap <silent><expr> <Plug>(VM-Find-Subword-Under) <sid>Visual('under')
Solution
- Cursor on any char of "apple" of line 1
C-nto add cursor to "apple" of line 1n(orC-n) to add cursor of next "apple", which is on line 4 (line 2 is not recognized cause prefix_, line 3 is not recognized cause suffix_)qto skip current cursor and jump to next "apple", which it's on line 6 (line 5 is not recognized cause both prefix and suffix_)a_endto add_endat end of those wordsEscto exit multi-cursor mode
Start
item1 = "apple"
item2 = "_apple"
item3 = "apple_"
item4 = "new apple"
item5 = "_apple_"
item6 = "old apple"Result
item1 = "apple_end"
item2 = "_apple"
item3 = "apple_"
item4 = "new apple"
item5 = "_apple_"
item6 = "old apple_end"Start vs result
cd exercises/add_word_under_cursor
vim -d start.txt result.txt:help vm-find-word quote:
n find next
N find previous
:help vm-regex-search quote:
Pressing
n/Nwill then find the next occurrence of that pattern, rather than the word under cursor.
wiki/Quick-start#select-word-or-subword-under-cursor says:
Once VM is active, you can press
nto get the next occurrence,Nto get the previous one.
In previous section we combined C-n to add regions and q to skip and jump to next. Now we aim the same result using Q to remove regions and n/N to move to next/previous region.
Solution
- Cursor on any char of "apple" of line 1
C-nto add cursor to "apple" of line 1n(orC-n) to add cursor of next "apple", which is on line 4 (line 2 is not recognized cause prefix_, line 3 is not recognized cause suffix_)n(orC-n) to add cursor of line 6Nto go to previous cursor (of line 4)Qto remove that cursor. FYI: then the multi-cursor jumps to previous region, which is located on line 1, though in this MWE this is irrelevant.a_endto add_endat end of those wordsEscto exit multi-cursor mode
Start
item1 = "apple"
item2 = "_apple"
item3 = "apple_"
item4 = "new apple"
item5 = "_apple_"
item6 = "old apple"Result
item1 = "apple_end"
item2 = "_apple"
item3 = "apple_"
item4 = "new apple"
item5 = "_apple_"
item6 = "old apple_end"Start vs result
cd exercises/add_word_under_cursor_N_and_n
vim -d start.txt result.txt:help vm-regex-search
Solution
ggto go to the first line (and first non-blank char)\\/opens a prompt/- type
s[oO]m(and pressEnter) to add as VM-cursors the words that match the regexessomandsOm. Alternative uses\(o\|O\)m \\Ato select all VM-cursors, see:h vm-select-allr~to replace each char with~Escto exit VM-mode
Start
som some some awesome som som sam
SOM SOME SOME AWESOME SOM SOM SAM
Som some sOme awesoMe sOm soM samResult
~~~ ~~~e ~~~e awe~~~e ~~~ ~~~ sam
SOM SOME SOME AWESOME SOM SOM SAM
Som ~~~e ~~~e awesoMe ~~~ soM samStart vs result
cd exercises/add_cursor_regex_search
vim -d start.txt result.txt:help vm-slash
Solution
ggand/ageC-n\\Ato add all matches as VM-cursors. Vim cursor stays on 1st match (does not move)g/and type?,as regex. So just those with unkown age will expand their region till the?char followed by a commaqto skip current VM-cursor (of first line), since it has a known age asg/?helps visually to find out, and jump to next match (to 2nd line)qto skip this VM-cursor (known age)nto go to next match (current has unknown age)nto go to next match (current has unknown age)Qto remove this VM-cursor (known age)\\CUto change the case of remaining regions (the "age" field, the equal and its value) to uppercase. Or just~since selected chars are all undercase.Escto quit VM
Challenge.
After /age-C-n run /best-C-n, so \\A will match both regexes.
And g/?,\|?$ will also expand those fields including their values if they are unkown (?).
Figure out how to set any of those two fields in uppercase if its respective value is known. q, Q, n and N are still valid to use.
Solution should be:
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, AGE=55k??, hobby=smoking, BEST_FRIEND=__?
name=Legolas, AGE=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=Legolas
Start
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, age=55k??, hobby=smoking, best_friend=__?
name=Legolas, age=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=LegolasResult
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, AGE=55k??, hobby=smoking, best_friend=__?
name=Legolas, AGE=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=LegolasStart vs result
cd exercises/regions_expand_to_regex_search
vim -d start.txt result.txtRead :help commands of section C-n in normal mode adds a word under cursor, keep pressing to find next occurrence.
Solution
- Cursor at first "p" of "apple" of line 1
vto enter visual-modelto select the next char (another "p" of "apple" of line 1)C-nto enter cursor-modennnnnto select next 5 "pp"-s, no matter if they are part of "apple" or not. Or directly\\Ato select all, see:h vm-select-all\\CUto convert to uppercaseEscto exit multi-cursor mode
Start
apple _apple apple_
new apple _apple_ old appleResult
aPPle _aPPle aPPle_
new aPPle _aPPle_ old aPPleStart vs result
cd exercises/add_chars_visual_selected
vim -d start.txt result.txtRead :help commands of section C-n in normal mode adds a word under cursor, keep pressing to find next occurrence.
Solution
- Cursor on any char of "apple" of line 1
C-nto add cursor to "apple" of line 1n(orC-n) to add cursor of next "apple" (still on line 1), (if added prefix/suffix like "_" then it's not recognized as next regex-match)n(orC-n) to add cursor of next "apple" (first word of line 3)qto skip current cursor and jump to next "apple", which it's last "apple" of same line 3Up-arrowto move to previous line and same column, i.e. to a letter of last "cherry" of line 2C-nto add a NEW cursor to "cherry" of line 2. Notice that in this example we don't want to have the first "cherry" of that line, otherwise we could (A) go with arrows there before pressingC-nor (B) just after currentC-nwe could pressNnto also include it and return cursor position to current onen(orC-n) to add a NEW cursor of "apple" of "cherry". Which adds first "apple" of line 3qto skip current cursor and jump to next "apple" or "cherry", which it's last "apple" of same line 3 which we already had under cursor anyhown(orC-n) to add a NEW cursor of "apple" of "cherry". Which adds first "cherry" of line 4n(orC-n) to add a NEW cursor of "apple" of "cherry". Which adds fourth "cherry" of line 4\\CUto convert to uppercaseEscto exit multi-cursor mode
Start
apple _apple apple_ apple banana _banana banana_ banana
cherry _cherry cherry_ cherry melon _melon melon_ melon
apple _apple apple_ apple banana _banana banana_ banana
cherry _cherry cherry_ cherry melon _melon melon_ melonResult
APPLE _apple apple_ APPLE banana _banana banana_ banana
cherry _cherry cherry_ CHERRY melon _melon melon_ melon
apple _apple apple_ APPLE banana _banana banana_ banana
CHERRY _cherry cherry_ CHERRY melon _melon melon_ melonStart vs result
cd exercises/add_word_under_cursor_multiple_regex
vim -d start.txt result.txt:help g:VM_case_setting
doc/vm-tutorial explains it:
Press
\\c: this allows you to cycle the case setting of the current pattern. Press it until it becomes case insensitive.
and actually it show how to use it several times:
cd <path>/vim-visual-multi/doc
rg --smart-case '\\c' vm-tutorial
rg --case-sensitive '\\\\c' vm-tutorial # if shell interprets backslashes as escape charactersFor a programmatly approach check wiki/5.-Operators#smart-case-change.
Do not confuse with leader-c to add cursors vertically.
Case sensitive is default.
Solution
- Cursor on any char of first "apple" of line 1
C-nto add cursor to "apple" of line 1n(orC-n) to add cursor of next "apple" (still on line 1). Note that if added prefix/suffix like "_" then it's not recognized as next regex-match. Uppercase case sensitive also discards "APPLE" alike onesn(orC-n) to add cursor of next "apple" (first word of line 2)n(orC-n) to add cursor of next "apple" (pen-ultimate word of line 2). Or replace steps 3 to 5 with directly\\Ato select all, see:h vm-select-allr~to replace matches with "~~~~~". Read note belowEscto exit multi-cursor mode
Note. :help vm-cursor-mode states:
r replace single character
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLEResult
~~~~~ APPLE _apple _APPLE _apple_ _APPLE_ ~~~~~ APPLE
~~~~~ APPLE _apple _APPLE _apple_ _APPLE_ ~~~~~ APPLEStart vs result
cd exercises/case_sensitive
vim -d start.txt result.txtSolution
Same steps as in previous Case sensitive section, though after step (C-n) we press \\c once.
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLEResult
~~~~~ ~~~~~ _apple _APPLE _apple_ _APPLE_ ~~~~~ ~~~~~
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLEStart vs result
cd exercises/case_ignore
vim -d start.txt result.txtSmart Case: searches are case insensitive unless the search pattern contains an uppercase letter, in which case it becomes case sensitive.
For extend mode read :help vm-smart-case-change.
Solution
Same steps as in previous Case sensitive section, though:
- Before
C-nwe set cursor on first "APPLE" of line 1, so smart-case acts as case sensitive - After step (
C-n) we press\\ctwice (to set smart-case)
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLEResult
apple ~~~~~ _apple _APPLE _apple_ _APPLE_ apple ~~~~~
apple ~~~~~ _apple _APPLE _apple_ _APPLE_ apple ~~~~~Start vs result
cd exercises/case_smart
vim -d start.txt result.txt:help vm-undo-redo, :help g:VM_maps and tutorialrc repeat that:
To enable undo/redo (still experimental):
let g:VM_maps["Undo"] = 'u'
let g:VM_maps["Redo"] = '<C-r>'
Thus this mappings should be added.
doc/vm-tutorial shows how to use it several times:
cd <path>/vim-visual-multi/doc
rg --case-sensitive -e 'undo' -e 'redo' vm-tutorialSee also the GIF of wiki/Quick-start#undoredo.
:help vm-mappings-buffer quote:
Toggle Whole Word \w toggle whole word search
Solution
- Cursor on any char of first "_apple" of line 1
C-nto add cursor to "_apple" of line 1\\wto toggle whole word searchn(orC-n) to add cursor of next "_apple"-whole-word (first "apple" of line 1)n(orC-n) to add cursor of next "_apple"-whole-word (first "_apple" of line 2)n(orC-n) to add cursor of next "_apple"-whole-word (first "apple" of line 2)r~to replace each visual-cursor char with "~"Escto exit multi-cursor mode
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLEResult
apple APPLE ~~~~~~ _APPLE ~~~~~~_ _APPLE_ apple APPLE
apple APPLE ~~~~~~ _APPLE ~~~~~~_ _APPLE_ apple APPLEresult if step 3 (toggle whole word search) is skipped (and actually n is pressed just once cause no more matches in those 2 lines)
apple APPLE ~~~~~~ _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE ~~~~~~ _APPLE _apple_ _APPLE_ apple APPLEStart vs result
cd exercises/toggle_whole_word_search
vim -d start.txt result.txt result_02.txtleader-a to align the rightmost column, [count]leader-< to align by char, leader-> to align by regex
:help vm-align
Read the doc/vm-tutorial specific section: vim <path>/vim-visual-multi/doc/vm-tutorial -c '/Alignment \~'.
Here [count]\\< (to align with 1 char) and \\> (to align by regex pattern) are explained too.
See GIF of wiki/Special-commands#align.
Solution
ggto go to the first line (and first non-blank char)- 4
Ctrl-Downto add a cursor on the next 4 lines below f,to move each VM-cursor to the first commawto move them to start of next word\\ato align them- Repeat the three previous steps twice more to align all fields
Escto exit multi-cursor mode
With [count]\\<
gg0- 4
Ctrl-Down f,to move each VM-cursor to the first comma\\<ato allign by next "a" char, i.e. first letter of next field "age"f,to move each VM-cursor to next comma\\<hto allign by next "h" char, i.e. first letter of next field "hobby"f,to move each VM-cursor to next comma\\<bto allign by next "b" char, i.e. first letter of next field "best_friend"Esc
Using \\>
gg0- 4
Ctrl-Down \\>and typeageas regex pattern. Then pressEnter\\>and typehobbyas regex pattern. Then pressEnter\\>and typebest_friendas regex pattern. Then pressEnterEsc
Start
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, age=55k??, hobby=smoking, best_friend=__?
name=Legolas, age=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=LegolasResult
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, age=55k??, hobby=smoking, best_friend=__?
name=Legolas, age=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=LegolasStart vs result
cd exercises/align
vim -d start.txt result.txt:help vm-case-conversion explains:
| Letter in the prompt | Meaning | Description |
|---|---|---|
| u | lowercase | Converts all letters to lowercase. |
| U | UPPERCASE | Converts all letters to uppercase. |
| C | Capitalize | Capitalizes the first letter of each word. |
| t | Title Case | Capitalizes the first letter of each significant word. |
| c | camelCase | First word lowercase, subsequent words capitalized. |
| P | PascalCase | All words capitalized. |
| s | snake_case | All lowercase, words separated by underscores. |
| S | SNAKE_UPPERCASE | All uppercase, words separated by underscores. |
- |
dash-case | All lowercase, words separated by hyphens. |
. |
dot.case | All lowercase, words separated by dots. |
<space> |
space case | All lowercase, words separated by spaces. |
Solution
- Cursor at
wofwordB1. C-vjj\\cc-n. Notice how in 1st line it's selectedwordB1a, in 2ndwordB10aand in 3rdwordB100a.\\C- It asks
Case conversion: (u/U/C/t/c/P/s/S/-/./ ). For example typeUto set all to uppercase Esc
Note. To toggle upper-/lowercase of each char use directly ~. :help vm-cursor-mode states:
~ change case of single character
At least one regex (VM-cursor) already had at least one char in uppercase, thus ~ is no alternative here to convert matches to uppercase.
Start
wordA wordB1a = abc.def
wordA wordB10a = abc.def
wordA wordB100a = abc.defResult
wordA WORDB1A = abc.def
wordA WORDB10A = abc.def
wordA WORDB100A = abc.defStart vs result
cd exercises/case_conversion
vim -d start.txt result.txt:help vm-mappings-buffer quote:
Merge Regions \m merge overlapping regions
#242 says
\m (merge regions) works from normal mode, not from visual mode. It merges overlapping regions, not simply adjacent ones.
Here we will deal with regions_contents. They are:
- What text is currently selected/active in each region
- The actual editable content you can modify
Solution
- Cursor on any char of "an" of line 1
C-nto add cursor-word\<an\>\\wto toggle whole word search. Pattern now isanwithout\<nor\>Right-arrowto move to "n" of "nual" of line 1C-nto add cursor-word\<nual\>\\wto toggle whole word search. Pattern now isnualwithout\<nor\>- Press
ntill no more matches in those 3 lines :call b:VM_Selection.Funcs.regions_contents()produces (adding fields "Whole-word" and "line")
Index ID A B w l / L a / b --- Pattern --- --- Regions contents --- --- Whole-word --- --- line ---
0 1 15 16 2 1 / 1 15 / 16 \<an\> an an 1
1 2 22 25 4 1 / 1 22 / 25 \<nual\> nual nual 1
2 3 54 55 2 2 / 2 28 / 29 an an biannual 2
3 4 56 59 4 2 / 2 30 / 33 nual nual biannual 2
4 5 94 95 2 2 / 2 68 / 69 an an annual 2
5 6 96 99 4 2 / 2 70 / 73 nual nual annual 2
6 7 123 124 2 3 / 3 16 / 17 an an an 3
7 8 127 128 2 3 / 3 20 / 21 an an and 3
8 9 132 133 2 3 / 3 25 / 26 an an annual 3
9 10 134 137 4 3 / 3 27 / 30 nual nual annual 3
\\CPto convert to PascalCase (all words capitalized without spaces)Escto exit multi-cursor mode
Result
Word cursors: An and Nual
Sentece with words: "The biAnNual Berlinale is awesome, thats a 2 nAnNual event"
Word cursors: "An" And "AnNual"
Now instead after step 7 we apply next
\\mto merge regions. Or run:call b:VM_Selection.Global.merge_regions(). Note this command could be called:call b:VM_Selection.Funcs.regions_contents()produces (adding fields "Whole-word" and "line")
Index ID A B w l / L a / b --- Pattern --- --- Regions contents --- --- Whole-word --- --- line ---
0 11 15 16 2 1 / 1 15 / 16 an an an 1
1 12 22 25 4 1 / 1 22 / 25 nual nual nual 1
2 13 54 59 6 2 / 2 28 / 33 nual annual biannual 2
3 14 94 99 6 2 / 2 68 / 73 nual annual nannual 2
4 15 123 124 2 3 / 3 16 / 17 an an an 3
5 16 127 128 2 3 / 3 20 / 21 an an and 3
6 17 132 137 6 3 / 3 25 / 30 nual annual annual 3
\\CPto convert to PascalCase (all words capitalized without spaces)Escto exit multi-cursor mode
Start
Word cursors: an and nual
Sentece with words: "The biannual Berlinale is awesome, thats a 2 nannual event"
Word cursors: "an" and "annual"Result
Word cursors: An and Nual
Sentece with words: "The biAnnual Berlinale is awesome, thats a 2 nAnnual event"
Word cursors: "An" And "Annual"Start vs result
cd exercises/regions_merge
vim -d start.txt result.txt:help vm-subtract-pattern
:help vm-extend-mode indicates that this command is specific to extend-mode.
Solution
- Cursor on any char of "an" of line 1
C-nto add cursor-word\<an\>\\wto toggle whole word search. Pattern now isanwithout\<nor\>Right-arrowto move to "n" of "nual" of line 1C-nto add cursor-word\<nual\>\\wto toggle whole word search. Pattern now isnualwithout\<nor\>- Press
ntill no more matches in those 3 lines \\`(:help vm-mappings-buffer) and after prompt typei. Or directly:call b:VM_Selection.Funcs.regions_contents()produces (adding fields "Whole-word" and "line")
Index ID A B w l / L a / b --- Pattern --- --- Regions contents --- --- Whole-word --- --- line ---
0 1 15 16 2 1 / 1 15 / 16 \<an\> an an 1
1 2 22 25 4 1 / 1 22 / 25 \<nual\> nual nual 1
2 3 54 55 2 2 / 2 28 / 29 an an biannual 2
3 4 56 59 4 2 / 2 30 / 33 nual nual biannual 2
4 5 94 95 2 2 / 2 68 / 69 an an annual 2
5 6 96 99 4 2 / 2 70 / 73 nual nual annual 2
6 7 123 124 2 3 / 3 16 / 17 an an an 3
7 8 127 128 2 3 / 3 20 / 21 an an and 3
8 9 132 133 2 3 / 3 25 / 26 an an annual 3
9 10 134 137 4 3 / 3 27 / 30 nual nual annual 3
\\sto split regions. In prompt typen(and press Enter)
V-M 10 / 10 ['nual', 'an']
Pattern to remove > n
:call b:VM_Selection.Funcs.regions_contents()outputs next. Noticepattern-s are gone
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 27 15 15 1 1 / 1 15 / 15 a
1 28 23 25 3 1 / 1 23 / 25 ual
2 29 54 54 1 2 / 2 28 / 28 a
3 30 57 59 3 2 / 2 31 / 33 ual
4 31 94 94 1 2 / 2 68 / 68 a
5 32 97 99 3 2 / 2 71 / 73 ual
6 33 123 123 1 3 / 3 16 / 16 a
7 34 127 127 1 3 / 3 20 / 20 a
8 35 132 132 1 3 / 3 25 / 25 a
9 36 135 137 3 3 / 3 28 / 30 ual
\\CUto convert to uppercase:call b:VM_Selection.Funcs.regions_contents()echoes
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 27 15 15 1 1 / 1 15 / 15 A
1 28 23 25 3 1 / 1 23 / 25 UAL
2 29 54 54 1 2 / 2 28 / 28 A
3 30 57 59 3 2 / 2 31 / 33 UAL
4 31 94 94 1 2 / 2 68 / 68 A
5 32 97 99 3 2 / 2 71 / 73 UAL
6 33 123 123 1 3 / 3 16 / 16 A
7 34 127 127 1 3 / 3 20 / 20 A
8 35 132 132 1 3 / 3 25 / 25 A
9 36 135 137 3 3 / 3 28 / 30 UAL
Escto exit multi-cursor mode
Start
Word cursors: an and nual
Sentece with words: "The biannual Berlinale is awesome, thats a 2 nannual event"
Word cursors: "an" and "annual"Result
Word cursors: An and nUAL
Sentece with words: "The biAnnUAL Berlinale is awesome, thats a 2 nAnnUAL event"
Word cursors: "An" And "AnnUAL"Start vs result
cd exercises/regions_split
vim -d start.txt result.txt:help vm-mappings-buffer:
Shrink \\- reduce regions from the sides
Enlarge \\+ enlarge regions from the sides
autoload/vm/plugs.vim says:
nnoremap <silent> <Plug>(VM-Shrink) :call vm#commands#shrink_or_enlarge(1)<cr>
nnoremap <silent> <Plug>(VM-Enlarge) :call vm#commands#shrink_or_enlarge(0)<cr>Solution
- Cursor on any char of "an" of line 1
C-nto add cursor-word\<an\>\\wto toggle whole word search. Pattern now isanwithout\<nor\>Right-arrowto move to "n" of "nual" of line 1C-nto add cursor-word\<nual\>\\wto toggle whole word search. Pattern now isnualwithout\<nor\>Right-arrowto move to any char of "Berlinale" of line 1C-nto add cursor-word- Press
ntill no more matches in those 3 lines. Or use\\A, see:h vm-select-all :call b:VM_Selection.Funcs.regions_contents()produces (adding fields "Whole-word" and "line")
Index ID A B w l / L a / b --- Pattern --- --- Regions contents --- --- Whole-word --- --- line ---
0 1 15 16 2 1 / 1 15 / 16 \<an\> an an 1
1 2 19 22 4 1 / 1 19 / 22 \<nual\> nual nual 1
2 3 28 36 9 1 / 1 28 / 36 \<Berlinale\> Berlinale Berlinale 1
3 4 65 66 2 2 / 2 28 / 29 an an biannual 2
4 5 67 70 4 2 / 2 30 / 33 nual nual biannual 2
5 6 72 80 9 2 / 2 35 / 43 \<Berlinale\> Berlinale Berlinale 2
6 7 105 106 2 2 / 2 68 / 69 an an annual 2
7 8 107 110 4 2 / 2 70 / 73 nual nual annual 2
8 9 134 135 2 3 / 3 16 / 17 an an an 3
9 10 140 141 2 3 / 3 22 / 23 an an and 3
10 11 142 145 4 3 / 3 24 / 27 nual nual annual 3
11 12 148 149 2 3 / 3 30 / 31 an an annual 3
12 13 153 161 9 3 / 3 35 / 43 \<Berlinale\> Berlinale Berlinale 3
\\-to shrink regions:call b:VM_Selection.Funcs.regions_contents()outputs next. Notice how regions-contents of 1 or 2 chars long are no longer shrunk. "an" is not shrunk at all.
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 1 15 16 2 1 / 1 15 / 16 an an
1 2 20 21 2 1 / 1 20 / 21 \<nual\> ua
2 3 29 35 7 1 / 1 29 / 35 \<Berlinale\> erlinal
3 4 65 66 2 2 / 2 28 / 29 an an
4 5 68 69 2 2 / 2 31 / 32 nual ua
5 6 73 79 7 2 / 2 36 / 42 \<Berlinale\> erlinal
6 7 105 106 2 2 / 2 68 / 69 an an
7 8 108 109 2 2 / 2 71 / 72 nual ua
8 9 134 135 2 3 / 3 16 / 17 an an
9 10 140 141 2 3 / 3 22 / 23 an an
10 11 143 144 2 3 / 3 25 / 26 nual ua
11 12 148 149 2 3 / 3 30 / 31 an an
12 13 154 160 7 3 / 3 36 / 42 \<Berlinale\> erlinal
\\-to shrink regions:call b:VM_Selection.Funcs.regions_contents()outputs
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 1 15 16 2 1 / 1 15 / 16 an an
1 2 20 21 2 1 / 1 20 / 21 \<nual\> ua
2 3 30 34 5 1 / 1 30 / 34 \<Berlinale\> rlina
3 4 65 66 2 2 / 2 28 / 29 an an
4 5 68 69 2 2 / 2 31 / 32 nual ua
5 6 74 78 5 2 / 2 37 / 41 \<Berlinale\> rlina
6 7 105 106 2 2 / 2 68 / 69 an an
7 8 108 109 2 2 / 2 71 / 72 nual ua
8 9 134 135 2 3 / 3 16 / 17 an an
9 10 140 141 2 3 / 3 22 / 23 an an
10 11 143 144 2 3 / 3 25 / 26 nual ua
11 12 148 149 2 3 / 3 30 / 31 an an
12 13 155 159 5 3 / 3 37 / 41 \<Berlinale\> rlina
\\-to shrink regions:call b:VM_Selection.Funcs.regions_contents()outputs
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 1 15 16 2 1 / 1 15 / 16 an an
1 2 20 21 2 1 / 1 20 / 21 \<nual\> ua
2 3 31 33 3 1 / 1 31 / 33 \<Berlinale\> lin
3 4 65 66 2 2 / 2 28 / 29 an an
4 5 68 69 2 2 / 2 31 / 32 nual ua
5 6 75 77 3 2 / 2 38 / 40 \<Berlinale\> lin
6 7 105 106 2 2 / 2 68 / 69 an an
7 8 108 109 2 2 / 2 71 / 72 nual ua
8 9 134 135 2 3 / 3 16 / 17 an an
9 10 140 141 2 3 / 3 22 / 23 an an
10 11 143 144 2 3 / 3 25 / 26 nual ua
11 12 148 149 2 3 / 3 30 / 31 an an
12 13 156 158 3 3 / 3 38 / 40 \<Berlinale\> lin
\\-to shrink regions:call b:VM_Selection.Funcs.regions_contents()outputs. Notice how regions-contents of even chars are shrunk at most to a 2 chars-string (like "an" not shrunk at all or "nual" shrunk to "ua") while those with odd chars are shrunk to 1 char (like "Berlinale" shrunk to "i").
Index ID A B w l / L a / b --- Pattern --- --- Regions contents ---
0 1 15 16 2 1 / 1 15 / 16 an an
1 2 20 21 2 1 / 1 20 / 21 \<nual\> ua
2 3 32 32 1 1 / 1 32 / 32 \<Berlinale\> i
3 4 65 66 2 2 / 2 28 / 29 an an
4 5 68 69 2 2 / 2 31 / 32 nual ua
5 6 76 76 1 2 / 2 39 / 39 \<Berlinale\> i
6 7 105 106 2 2 / 2 68 / 69 an an
7 8 108 109 2 2 / 2 71 / 72 nual ua
8 9 134 135 2 3 / 3 16 / 17 an an
9 10 140 141 2 3 / 3 22 / 23 an an
10 11 143 144 2 3 / 3 25 / 26 nual ua
11 12 148 149 2 3 / 3 30 / 31 an an
12 13 157 157 1 3 / 3 39 / 39 \<Berlinale\> i
\\CUto convert to uppercaseEscto exit multi-cursor mode
Start
Word cursors: an, nual and Berlinale
Sentece with words: "The biannual Berlinale is awesome, thats a 2 nannual event"
Word cursors: "an", "annual" and "Berlinale"Result
Word cursors: ,AN nlUA and BerlInale
Sentece with words: "The binANlUA BerlInale is awesome, thats a 2 nnANlUA event"
Word cursors: ""AN, "nANlUA" dAN "BerlInale"Start vs result
cd exercises/regions_shrink
vim -d start.txt result.txt:help vm-select-operator
Check
:help vm-find-operator
m stands for matches
Solution
ggto go to the first line (and first non-blank char)/letto findletand pressEnterC-nto add cursor8mjto select all occurrences in the 8 lines belowcand typeputto change the text fromlettoputEscto exit insert modeEscto exit multi-cursor mode
Based on https://github.com/mg979/vim-visual-multi/wiki/Quick-start#find-operator.
Start
if went_back
let r.dir = 0
let r.a = new
let r.b = r.k
elseif went_forth
let r.dir = 1
let r.b = new
let r.a = r.kResult
if went_back
put r.dir = 0
put r.a = new
put r.b = r.k
elseif went_forth
put r.dir = 1
put r.b = new
put r.a = r.kStart vs result
cd exercises/operator_find
vim -d start.txt result.txtmaf to select all occurrences inside a function.
See respective GIF of wiki/5.-Operators.
:help vm-filter
See GIFs of wiki/5.-Operators
Solution
ggto go to the first line (and first non-blank char)/apple<C-n>/banana<C-n>viito visual select lines of same indentation (with same parents indents), thus last 3 lines are not selected\\fto selected all occurrences in the visual selection~to toogle each char caseEscto exit VM-cursor mode
Start
indent_level_0
indent_level_1
indent_level_2 apple banana cherry
indent_level_2 banana cherry apple
indent_level_2 cherry apple banana
indent_level_0
indent_level_1
indent_level_2 apple banana cherry
indent_level_2 banana cherry apple
indent_level_2 cherry apple bananaResult
indent_level_0
indent_level_1
indent_level_2 APPLE BANANA cherry
indent_level_2 BANANA cherry APPLE
indent_level_2 cherry APPLE BANANA
indent_level_0
indent_level_1
indent_level_2 apple banana cherry
indent_level_2 banana cherry apple
indent_level_2 cherry apple bananaStart vs result
cd exercises/add_cursor_filter_regions
vim -d start.txt result.txt:help vm-numbering
Expression syntax is: start=[count]/step/separator
Next exercises are almost identical to the :h vm-numbering ones.
See GIF of wiki/Special-commands#numbering.
Solution
ggto go to the first line (and first non-blank char)- 3
C-Down \\Nprompts:Expression > 1/1/. I.e. numbering starting in 1 with step of 1 and lacks the separator.- Increase the step to 3 and add a separator of
-(space-dash-space), i.e. the prompt should be:1/3/ -. PressEnter Escto exit multi-cursor mode
Start
text
text
text
textResult
1 - text
4 - text
7 - text
10 - textStart vs result
cd exercises/numbering_prepend
vim -d start.txt result.txtSolution
ggto go to the first line (and first non-blank char)- 3
C-Down \\nprompts:Expression > 1/1/- Make initial value 1000, increase the step to 100 and add a separator of
,(comma-space), i.e. the prompt should be:1000/100/,. PressEnter Escto exit multi-cursor mode
Start
text
text
text
textResult
text, 1000
text, 1100
text, 1200
text, 1300Start vs result
cd exercises/numbering_append
vim -d start.txt result.txt:help vm-shifting
<M-S-Arrows> where <M> is the Meta key (usually Alt in Linux and Windows).
See GIF of wiki/Special-commands#text-shifting
Solution
Purpose: set ages as rightmost field-value. Keep formatting: a comma and one space in between fied-values.
Shift age field-value (and its comma-space) to the right.
ggto go to the first line (and first non-blank char)- 4
Ctrl-Downto add a cursor on the next 4 lines below f,to move each VM-cursor to the first commawto move them to start of next wordTabto enter extend modeEto grow region till end of Word, reaching next commalto include also next char (space)<M-S-Right>to shift extended regions all the way to the right. Repeat as needed
Delete the rightmost comma
Tabto toggle back to VM-column modeEto go to end of Wordxdeletes last char (a comma that we shifted)
Add missing comma
gEto go to end of previous Word, orBBE. Read vim Text object motions help:h Q_tma,to add a commaEscto exit instert mode
Remove spaces in between last two field-values that do not comply with the formatting
Tabto enter extend modewto extend regions to start of next word (last field)hto shink from each region the last char (unselect first letter of last field)hunselect rightmost space of the regionxto delete regionsEscto exit multi-cursor mode
Remove trailing spaces
- Remove trailing withspaces. Run
:%s/\s\+$//
Start
name=Sam, age=38, hobby=gardening, best_friend=Frodo
name=Frodo, age=50, hobby=reading, best_friend=Sam
name=Gandalf, age=55k??, hobby=smoking, best_friend=__?
name=Legolas, age=3000?, hobby=wandering, best_friend=Gimli
name=Gimli, age=139, hobby=stonecraft, best_friend=LegolasResult
name=Sam, hobby=gardening, best_friend=Frodo, age=38
name=Frodo, hobby=reading, best_friend=Sam, age=50
name=Gandalf, hobby=smoking, best_friend=__?, age=55k??
name=Legolas, hobby=wandering, best_friend=Gimli, age=3000?
name=Gimli, hobby=stonecraft, best_friend=Legolas, age=139Start vs result
cd exercises/regions_shift
vim -d start.txt result.txt:help vm-transform
Solution
VM-extended selection to all item-s
ggto go to the first line (and first non-blank char)C-nto add current word as VM-cursor\\Ato add rest of matches as VM-cursor
Transform regions with expression to prepend [index]/[total], where indexstarts at 1.
\\eand type%i+1 ."/". %N ." ". %t. Explained in:help vm-transform
Extended selection includes [index]/[total] item but we want to limit it to total.
gEto shrink it to[index]/[total]and Vim-cursor is at the last digit of[total]. Read:h Q_tmoto jump the vim-cursor to start/end of visual-selection, read:h visual-change. It was at end (last [and here only] digit of[total]), so this map moves the vim-cursor to the first [and here only] digit of[index]Eto shrink the VM-extended selection to just the only digit of[total]
Multiply total by 1.5
\\eand type%f * 1.5Escto exit VM mode
Note. o mapping varies in different VM modes. :help vm-cursor-mode states:
You can enter |insert-mode| with
i,I,a,A, and only from cursor mode also withoandO.
Read Dot to add content exercise to see o mapping inserting text.
Start
item "apple"
item "banana"
item "cherry"Result
1/4.5 item "apple"
2/4.5 item "banana"
3/4.5 item "cherry"Start vs result
cd exercises/regions_transform_with_expression
vim -d start.txt result.txt:help vm-remove-every-n
See GIF of wiki/Special-commands#remove-every-n-regions.
Solution
Add all item-s to VM-regions
ggto go to the first line (and first non-blank char)C-n\\Ato add
Remove regions of no colors
3\\Rto remove every 3 regions. To remove eachitemregion in lines that indicate quantity: lines 3, 6, 9,...qto skip current region at first line. Vim-cursor and first VM-region is now at item of second line (item "red")\\Rto remove every other regions (to remove eachitemregion previous to fruit names)
Replace region content with proper field name
cand typecolor.Escto exit insert-modeEscto exit VM-mode
Add all items to VM-regions
ggC-n\\A
Remove regions of no quantity
qto skip current region at first line\\Rto remove every other regions (to remove eachitemregion previous to fuit names)
Replace region content with proper field name
cand typeqty.Escto exit insert-modeEscto exit VM-mode
Alternative applying leader-e (:help vm-transform)
gg,C-n,\\A\\eand type%i%3-2 ? %t : 'qty'Escto exit VM-modegg,C-n,\\Aqto skip current region at first line\\eand type%i%2 ? %t : 'color'Escto exit VM-mode
We achieved the following:
item "apple"
color "red"
qty 2
item "banana"
color "green"
qty 3
item "cherry"
color "red"
qty 20
item "melon"
color "yellow"
qty 1
item "mango"
color "green"
qty 1
item "kiwi"
color "yellow"
qty 4
item "watermelon"
color "dark green"
qty 1
All left is plain Vim. No VM related.
ggqato start recoding macro a0go to first charEto move cursor at end of fieldlto go to next charr=to replace the space with an equal signA,to add a comma at end of lineEscto exit insert-modeqto finish recording macro auuto undo last two edits, to undo the macroqbto start recoding macro b@ato run macro ajto go to next line@ato run macro ajto go to next line@ato run macro axto delete last comma added2kto go two lines upJJto join two linesjto go to next lineqto finish recording macro b6@bto run macro b six times
Note. With leader-e one transforms regions with expression. Its :help vm-transform states
%i%3-2 ? %t : '' will delete every third region
So remove a region differs from delete it. Let's try it. We have next initial contentin a file:
1 item
2 item
3 item
4 item
5 item
6 item
7 item
8 item
9 item
10 item
11 item
12 item
Steps to apply
ggwC-n\\A\\eand type%i%3-2 ? %t : ''Escto exit VM-mode
Result:
1 item
2 item
3
4 item
5 item
6
7 item
8 item
9
10 item
11 item
12
Alternative
ggwC-n\\Aq(which skips the current region and goes to next one, in second line)3\\Rto remove regions of lines current_line-1 + 3n, i.e. 2-1 + 3n = 4, 7, 10,...q(which skips the current region and goes to next one, in third line)\\Rto remove alternate [remaining] regionsxto remove remaining regionsEscto exit VM-mode
Start
item "apple"
item "red"
item 2
item "banana"
item "green"
item 3
item "cherry"
item "red"
item 20
item "melon"
item "yellow"
item 1
item "mango"
item "green"
item 1
item "kiwi"
item "yellow"
item 4
item "watermelon"
item "dark green"
item 1Result
item="apple", color="red", qty=2
item="banana", color="green", qty=3
item="cherry", color="red", qty=20
item="melon", color="yellow", qty=1
item="mango", color="green", qty=1
item="kiwi", color="yellow", qty=4
item="watermelon", color="dark green", qty=1Start vs result
cd exercises/regions_remove_every_n
vim -d start.txt result.txtDo not confuse with \\l described in :help vm-infoline.
:help vm-mappings-buffer says
Tools Menu \\` filter lines to buffer, etc
Alternative to \\` we can run :call vm#special#commands#menu(), defined in autoload/vm/special/commands.vim. This prompts the user with a menu of commands:
" - Show VM registers
i - Show regions info
f - Filter regions by pattern or expression
l - Filter lines with regions
r - Regions contents to buffer
q - Fill quickfix with regions lines
Q - Fill quickfix with regions positions and contents
We can also directly access each one with its respective command. For example to show regions info we can directly run :call b:VM_Selection.Funcs.regions_contents(), see practical use in leader-minus/leader-plus to shrink/enlarge regions.
Read also :help vm-ex-commands.
Read doc/vm-tutorial: vim <path>/vim-visual-multi/doc/vm-tutorial -c '/Some experiments \~'. Extract:
About <C-v> in insert mode: it's a special VM command that will paste the content of
the unnamed VM register, if this has been filled with something.
If you pressed <C-r>{register}, this would still work, but the pasted content would
be the same for all cursors, since it would use vim (and not VM) registers.
Read :help vm-mappings-default.
To see them run :echo g:Vm.
Let's make it more human-readable. Write that vim-variable in a file:
vim -c 'redir > Vm_content.txt | echo g:Vm | redir END | quit'Then replace \\ with for example a & so json.dump can handle it.
Finally we can revert that, replacing && with leader- (because jq cannot handle \\):
cat Vm_content.txt \
| sed 's/\\/\&/g' \
| python3 -c 'import sys, ast, json; print(json.dumps(ast.literal_eval(sys.stdin.read())))' \
| sed 's/&&/leader-/g' \
| jq . --indent 2 > Vm_content.json \
&& cat Vm_content.jsonFinal JSON where each leader- should be \\:
{
"last_ex": "",
"hi": {},
"registers": {
"\"": [],
"-": []
},
"finding": 0,
"extend_mode": 0,
"mappings_enabled": 0,
"select_motions": [
"h",
"j",
"k",
"l",
"w",
"W",
"b",
"B",
"e",
"E",
"ge",
"gE",
"BBW"
],
"unmaps": [
"silent! nunmap leader-A",
"",
"silent! nunmap <C-n>",
"",
"silent! nunmap <S-Left>",
"",
"",
"silent! nunmap <S-Right>",
"silent! xunmap leader-A",
"silent! xunmap leader-/",
"silent! nunmap <C-Up>",
"silent! xunmap <C-n>",
"",
"silent! xunmap leader-f",
"",
"silent! xunmap leader-a",
"silent! nunmap leader-&",
"",
"",
"",
"silent! nunmap leader-/",
"",
"silent! nunmap <C-Down>",
"",
"",
"silent! nunmap leader-gS",
"silent! xunmap leader-c"
],
"last_normal": "",
"last_visual": "",
"oldupdate": 0,
"leader": {
"visual": "leader-",
"default": "leader-",
"buffer": "leader-"
},
"buffer": 0,
"maps": {
"permanent": [
"nmap <nowait> leader-A <Plug>(VM-Select-All)",
"nmap <nowait> <C-n> <Plug>(VM-Find-Under)",
"nmap <nowait> <S-Left> <Plug>(VM-Select-h)",
"nmap <nowait> <S-Right> <Plug>(VM-Select-l)",
"xmap <nowait> leader-A <Plug>(VM-Visual-All)",
"xmap <nowait> leader-/ <Plug>(VM-Visual-Regex)",
"nmap <nowait> <C-Up> <Plug>(VM-Add-Cursor-Up)",
"xmap <nowait> <C-n> <Plug>(VM-Find-Subword-Under)",
"xmap <nowait> leader-f <Plug>(VM-Visual-Find)",
"xmap <nowait> leader-a <Plug>(VM-Visual-Add)",
"nmap <nowait> leader-& <Plug>(VM-Add-Cursor-At-Pos)",
"nmap <nowait> leader-/ <Plug>(VM-Start-Regex-Search)",
"nmap <nowait> <C-Down> <Plug>(VM-Add-Cursor-Down)",
"nmap <nowait> leader-gS <Plug>(VM-Reselect-Last)",
"xmap <nowait> leader-c <Plug>(VM-Visual-Cursors)"
],
"exit": "<Esc>",
"surround": "S",
"toggle": "leader-<Space>"
}
}:help vm-mappings-all. Also read :h vm-faq-mappings and related Vim-tags sections in doc/vm-faq.txt.
For kitty terminal the C-Down/C-Up mappings might not work. Overwrite them like in ./mappings/kitty-terminal.vim
" Override the default mappings
let g:VM_maps = {}
" Add undo/redo
let g:VM_maps["Undo"] = 'u'
let g:VM_maps["Redo"] = '<C-r>'
" Kitty's terminal doesn't properly handle C-Up/C-Down
" thus replace them with \\j/\\k
let g:VM_maps['Add Cursor Down'] = '\\j'
let g:VM_maps['Add Cursor Up'] = '\\k'- Clone this repo
git clone https://github.com/juanMarinero/vim-visual-multi-exercises
cd vim-visual-multi-exercises- Install the Node.js modules
nvm install # Installs version from .nvmrc
nvm use # Auto-switches to version from .nvmrc
npm install-
Edit
README_magic.md. For example add local content or remote content -
Run the markdown-magic - script and check the result
node run-magic.js \
&& vim README.mdTip: preview directly with markdown-preview.nvim
node run-magic.js \
&& vim README.md -c "MarkdownPreviewToggle"- Make a pull request
VM has many commands, next are some ToDo-s exercises to be added. The easiest and most practical ones are listed at the top:
- Cursor Operators as in wiki/5.-Operators#cursor-operators. Focus on describe limitations
\\gS, see:help vm-reselect-last:help vm-run-at-cursors\\zto Run Normal, see:help vm-run-normaland wiki/Commands-at-cursors#normal-commands\\vto Run Visual, see:help vm-run-visualand wiki/Commands-at-cursors#visual-commands\\xto Run Ex, see:help vm-run-exand wiki/Commands-at-cursors#ex-commands\\@to Run Macro see:help vm-run-macroand wiki/Commands-at-cursors#macros\\Zto Run Last Normal, no:helpfound, defined as:<C-u>call b:VM_Selection.Edit.run_normal(g:Vm.last_normal[0], {'count': v:count1, 'recursive': g:Vm.last_normal[1]})in autoload/vm/plugs.vim\\Vto Run Last Visual, no:helpfound, defined as:call b:VM_Selection.Edit.run_visual(g:Vm.last_visual[0], g:Vm.last_visual[1])in autoload/vm/plugs.vim\\Xto Run Last Ex, no:helpfound, defined as:<C-u>call b:VM_Selection.Edit.run_ex(g:Vm.last_ex)in autoload/vm/plugs.vim
\\<CR>, see:help vm-single-modeM, read:help vm-multiline-modeand check GIF of wiki/4.-Motions-and-Modes\\Lto run"One Per Line":help vm-ex-commands:help vm-registersexercise showing how VM-registers vary thanks to Tools Menu triggered with\\`(:help vm-mappings-buffer) and", or directly with:VMRegisters:help VMDebug,:help VMClear,:help VMSearch, etc.
Run :help vm-faqs or open doc/vm-faq.txt.
Markdowns cannot show scripts (nor remote nor local). markdown-magic comes to rescue, but it must be installed in a Node.js environment.
Q: And why not directly on a HTML script?
A: I believe markdowns are easier for humans to read and edit, making them better suited for collaboration. While HTML scripts with Node.js offer many features that Markdown lacks, I don't need [most of] those advanced capabilities. For this project Node.js should be limited to using markdown-magic, and perhaps a few additional modules in the future.
- For easier version control. See contribute section.
- To enable vimdiff, git diff, delta,... or (Neo)Vimdiff plugins like diffchar or fugitive on initial (
start.txt) vs final (result.txt) code. See next screenshots of delta example. - Learning by Doing feature. To challenge myself I can
vim -O start.txt result.txt, once I think that I achieved to makestart.txtscript identical toresult.txt(applying vim-visual-multi commands), then I can check it with:windo diffthis(OFF with:windo diffoff). To display the solution click on▸ Solutionor use:split steps.md.
The contribute section instructs you to run the following JavaScript-script:
const { markdownMagic } = require('markdown-magic');
const path = require('path');
const inputFile = path.join(__dirname, 'README_magic.md');
const outputFile = path.join(__dirname, 'README.md');
markdownMagic(inputFile, {
output: {
pathFormatter: () => outputFile,
applyTransformsToSource: false
}
}).then(() => {
console.log(`Processed content written to ${outputFile}`);
});Thus content of README_magic.md is processed by markdown-magic, which outputs the result to README.md.
Next non-existing --output flag could achieve the same effect, for example: npx markdown-magic --output README.md -- README_magic.md.
In summary, running npx markdown-magic README.md would overwrite README.md directly, making version control more difficult.
:help vm-troubleshooting- VM-issues
GPLv3 or later. This project is licensed under the GNU General Public License (GPL) version 3 or later. See the local copy LICENSE/GPLv3.
Portions of this project are derived (copied or adapted) from vim-visual-multi, which is licensed under the MIT License:
- Original MIT License
- Local copy in LICENSE/vim-visual-multi
The MIT-licensed portions retain their original terms, while the new work is distributed under GPLv3 or later.


