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-Up
to 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.txt
or directly copy-paste the command of Start vs result section of the desired exercise. - To display the solution click on
▸ Solution
or 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-Up
to add cursors verticallyleader-c
to add cursors verticallyQ
to remove unwanted cursors after selectionq
to skip cursor-region and go to next region.
to run single repeatR
to replace content of patternleader-\
to add cursor at positionleader-d
to duplicate regionsleader-t
to transpose- Rotate programmatly
S
to surroundC-n
in normal mode adds a word under cursor, keep pressing to find next occurrencen
orN
afterC-n
-
leader-/
for regex search g/
to expand each region by search regexC-n
in visual mode adds selecting chars, keep pressing to find next occurrenceC-n
in normal mode to add multiple regex-wordsC-n
case sensitive vs ignore-case vs smartcaseu
/C-r
to undo/redoleader-w
to toggle whole word searchleader-a
to align,[count]leader-<
to align by char,leader->
to align by regexleader-C
for case conversionleader-m
to merge regionsleader-s
to split regionsleader-minus
/leader-plus
to shrink/enlarge regionss
for select operatorm
for find operatorleader-f
to filter regionsleader-n
/leader-N
for numbering<M-S-Arrows>
to shift textleader-e
to transform regions with expressionleader-R
to 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
w
ofwordB1a
. Ctrl-Down
to add a cursor on the line belowCtrl-Down
again to add another cursor on the third line. Now multi-cursors are inw
ofwordB1a
,wordB10a
andwordB100a
. Note that steps 2 and 3 can be combined using counts, like2Ctrl-Down
.j
to move the multi-cursors down. I.e. to move the cursor towordB10a
towordB1000a
. Reference wiki/Quick-start:
To make things easier, since
hjkl
move all cursors, you can still use arrows and ctrl-arrows to move around, without moving the cursors as well.
e
goes to end of word. Read:h Q_tm
a_end
to add_end
after cursor position.:help vm-cursor-mode
states:
You can enter |insert-mode| with
i
,I
,a
,A
, and only from cursor mode also witho
andO
.
Esc
to exit insert modeEsc
to 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.def
Result
wordA wordB1a = abc.def
wordA wordB10a_end = abc.def
wordA wordB100a_end = abc.def
wordA wordB1000a_end = abc.def
A. (Neo)Vimdiff. Jump with ]e
or ]b
of diffchar.vim to next char-diff
cd exercises/add_cursors_vertically
vim -d start.txt result.txt
B. 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
w
ofwordB1
. C-v
enters Vim's native visual block mode (not Visual-Multi)jj
to visual-block select the next 2 chars below\\c
to create column-wise cursorsj
to move the multi-cursors down. I.e. to move the cursor towordB10a
towordB1000a
e
to go to the end of the worda_end
to add_end
at end of those wordsEsc
to exit insert modeEsc
to exit multi-cursor mode
Start
wordA wordB1a = abc.def
wordA wordB10a = abc.def
wordA wordB100a = abc.def
wordA wordB1000a = abc.def
Result
wordA wordB1a = abc.def
wordA wordB10a_end = abc.def
wordA wordB100a_end = abc.def
wordA wordB1000a_end = abc.def
Start 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-Down
to adds cursor in same column of line 2 ("b" of "banana")Ctrl-Down
to 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-arrow
to move to the "banana" line. Usearrows
to move around instead ofhjkl
, reference wiki/Quick-start:
To make things easier, since
hjkl
move 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.
Q
to delete that cursor ("Remove region")
Now edit the remaining cursors:
e
go to end of worda_end
to add_end
at 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"
v
andCtrl-n
to add cursorDown-arrow
twice or/cherry
v
andCtrl-n
to add cursore
go to end of worda_end
to add_end
at end of those words
Start
item = "apple" # Edit this
item = "banana" # Skip this
item = "cherry" # Edit this
Result
item = "apple_end" # Edit this
item = "banana" # Skip this
item = "cherry_end" # Edit this
Start 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-Down
to adds cursor in same column of line 2 ("b" of "banana")Ctrl-Down
to 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
Tab
to switch to "extend mode"Up-arrow
to move to the "banana" lineq
to 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
):
e
go to end of worda_end
to add_end
at 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 lines
Result
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 lines
Start 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
gg
to go to the first line (and first non-blank char)- Press
o
, insert textnew line
, thenEsc
to exit insert mode - Undo the change by pressing
u
, then pressgg
to go back - Press
2<C-Down>
- Finally press
.
Esc
to 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 witho
andO
.
Next alternative might be clearer.
gg
to go to the first line2C-Down
to add VM-cursors in next 2 lines- Press
o
, insert textnew line
, thenEsc
to exit insert mode. This step was previously achieved just with.
(pressing dot) Esc
to exit VM-mode
Start
item1 = "apple"
item2 = "banana"
item3 = "cherry"
Result
item1 = "apple"
new line
item2 = "banana"
new line
item3 = "cherry"
new line
Start vs result
cd exercises/dot_add_content
vim -d start.txt result.txt
Solution
gg
to go to the first line (and first non-blank char)- Press
2<C-Down>
- Press
dw
to remove the next word, here "item" - Press
.
to repeat, and remove the next word, here "=" Esc
to 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
gg
to go to the first line (and first non-blank char)- Press
f"l
to 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 (pressEnter
to confirm) - Enter a single space as replacement (press
Enter
to confirm) Esc
to 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.txt
Solution
gg
to 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>
Esc
to 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 cursorciw
and typefruit
to replace those words with "fruit"Esc
to exit insert modeEsc
to 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.txt
Bonus
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 lineEsc
to 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-Down
to adds cursor to 1st letter of "banana"Ctrl-Down
to adds cursor to 1st letter of "cherry"Tab
to switch to "extend mode"e
to select till end of word\\d
to duplicate tab-extension regionsEsc
to 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-Down
to adds cursor to 1st char of "banana"Ctrl-Down
to adds cursor to 1st char of "cherry"Tab
to switch to "extend mode"e
to select till end of word\\t
to transpose tab-extension regions. Notice rest of line didn't changeEsc
to 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
hjkl
move all cursors, you can still use arrows and ctrl-arrows to move around, without moving the cursors as well.
gg
to go to the first line (and first non-blank char)0
to go to start of line (regardless of whether it is a blank or not)f1
to move to 1st1
:call vm#commands#add_cursor_at_word(1, 1)
to add cursor-wordf2
to move to 1st2
:call vm#commands#add_cursor_at_word(1, 1)
to add cursor-wordj
to go 1 line down0
to go to start of linef3
to move to 1st3
:call vm#commands#add_cursor_at_word(1, 1)
to add cursor-wordf4
to 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
/2
to search for2
(and press Enter)- Same as in previous step
- Same as in previous step
- Same as in previous step
/3
to search for3
(and press Enter)- Same as in previous step
/4
to search for4
(and press Enter)- Same as in previous step
Tab
to 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
endfunction
Thus 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.txt
Recommended 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
endfunction
Thus 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-Down
to adds cursor to 1st char of "banana"Ctrl-Down
to adds cursor to 1st char of "cherry"Tab
to switch to "extend mode"e
to select till end of wordS"
to surround the transpose tab-extension regions with double quotes"
Esc
to exit multi-cursor mode
Start
item1 = apple
item2 = banana
item3 = cherry
Result
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-n
will 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-n
again, 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-n
to 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_
)q
to 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_end
to add_end
at end of those wordsEsc
to 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/N
will 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
n
to get the next occurrence,N
to 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-n
to 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 6N
to go to previous cursor (of line 4)Q
to 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_end
to add_end
at end of those wordsEsc
to 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
gg
to 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 regexessom
andsOm
. Alternative uses\(o\|O\)m
\\A
to select all VM-cursors, see:h vm-select-all
r~
to replace each char with~
Esc
to exit VM-mode
Start
som some some awesome som som sam
SOM SOME SOME AWESOME SOM SOM SAM
Som some sOme awesoMe sOm soM sam
Result
~~~ ~~~e ~~~e awe~~~e ~~~ ~~~ sam
SOM SOME SOME AWESOME SOM SOM SAM
Som ~~~e ~~~e awesoMe ~~~ soM sam
Start vs result
cd exercises/add_cursor_regex_search
vim -d start.txt result.txt
:help vm-slash
Solution
gg
and/age
C-n
\\A
to 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 commaq
to 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)q
to skip this VM-cursor (known age)n
to go to next match (current has unknown age)n
to go to next match (current has unknown age)Q
to remove this VM-cursor (known age)\\CU
to change the case of remaining regions (the "age" field, the equal and its value) to uppercase. Or just~
since selected chars are all undercase.Esc
to 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=Legolas
Result
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 vs result
cd exercises/regions_expand_to_regex_search
vim -d start.txt result.txt
Read :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
v
to enter visual-model
to select the next char (another "p" of "apple" of line 1)C-n
to enter cursor-modennnnn
to select next 5 "pp"-s, no matter if they are part of "apple" or not. Or directly\\A
to select all, see:h vm-select-all
\\CU
to convert to uppercaseEsc
to exit multi-cursor mode
Start
apple _apple apple_
new apple _apple_ old apple
Result
aPPle _aPPle aPPle_
new aPPle _aPPle_ old aPPle
Start vs result
cd exercises/add_chars_visual_selected
vim -d start.txt result.txt
Read :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-n
to 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)q
to skip current cursor and jump to next "apple", which it's last "apple" of same line 3Up-arrow
to move to previous line and same column, i.e. to a letter of last "cherry" of line 2C-n
to 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-n
or (B) just after currentC-n
we could pressNn
to 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 3q
to 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\\CU
to convert to uppercaseEsc
to 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_ melon
Result
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_ melon
Start 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 characters
For 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-n
to 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\\A
to select all, see:h vm-select-all
r~
to replace matches with "~~~~~". Read note belowEsc
to 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 APPLE
Result
~~~~~ APPLE _apple _APPLE _apple_ _APPLE_ ~~~~~ APPLE
~~~~~ APPLE _apple _APPLE _apple_ _APPLE_ ~~~~~ APPLE
Start vs result
cd exercises/case_sensitive
vim -d start.txt result.txt
Solution
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 APPLE
Result
~~~~~ ~~~~~ _apple _APPLE _apple_ _APPLE_ ~~~~~ ~~~~~
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
Start vs result
cd exercises/case_ignore
vim -d start.txt result.txt
Smart 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-n
we set cursor on first "APPLE" of line 1, so smart-case acts as case sensitive - After step (
C-n
) we press\\c
twice (to set smart-case)
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
Result
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-tutorial
See 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-n
to add cursor to "_apple" of line 1\\w
to 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 "~"Esc
to exit multi-cursor mode
Start
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
apple APPLE _apple _APPLE _apple_ _APPLE_ apple APPLE
Result
apple APPLE ~~~~~~ _APPLE ~~~~~~_ _APPLE_ apple APPLE
apple APPLE ~~~~~~ _APPLE ~~~~~~_ _APPLE_ apple APPLE
result 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 APPLE
Start vs result
cd exercises/toggle_whole_word_search
vim -d start.txt result.txt result_02.txt
leader-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
gg
to go to the first line (and first non-blank char)- 4
Ctrl-Down
to add a cursor on the next 4 lines below f,
to move each VM-cursor to the first commaw
to move them to start of next word\\a
to align them- Repeat the three previous steps twice more to align all fields
Esc
to exit multi-cursor mode
With [count]\\<
gg0
- 4
Ctrl-Down
f,
to move each VM-cursor to the first comma\\<a
to allign by next "a" char, i.e. first letter of next field "age"f,
to move each VM-cursor to next comma\\<h
to allign by next "h" char, i.e. first letter of next field "hobby"f,
to move each VM-cursor to next comma\\<b
to allign by next "b" char, i.e. first letter of next field "best_friend"Esc
Using \\>
gg0
- 4
Ctrl-Down
\\>
and typeage
as regex pattern. Then pressEnter
\\>
and typehobby
as regex pattern. Then pressEnter
\\>
and typebest_friend
as regex pattern. Then pressEnter
Esc
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=Legolas
Result
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 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
w
ofwordB1
. C-v
jj
\\c
c-n
. Notice how in 1st line it's selectedwordB1a
, in 2ndwordB10a
and in 3rdwordB100a
.\\C
- It asks
Case conversion: (u/U/C/t/c/P/s/S/-/./ )
. For example typeU
to 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.def
Result
wordA WORDB1A = abc.def
wordA WORDB10A = abc.def
wordA WORDB100A = abc.def
Start 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-n
to add cursor-word\<an\>
\\w
to toggle whole word search. Pattern now isan
without\<
nor\>
Right-arrow
to move to "n" of "nual" of line 1C-n
to add cursor-word\<nual\>
\\w
to toggle whole word search. Pattern now isnual
without\<
nor\>
- Press
n
till 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
\\CP
to convert to PascalCase (all words capitalized without spaces)Esc
to 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
\\m
to 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
\\CP
to convert to PascalCase (all words capitalized without spaces)Esc
to 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-n
to add cursor-word\<an\>
\\w
to toggle whole word search. Pattern now isan
without\<
nor\>
Right-arrow
to move to "n" of "nual" of line 1C-n
to add cursor-word\<nual\>
\\w
to toggle whole word search. Pattern now isnual
without\<
nor\>
- Press
n
till 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
\\s
to 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
\\CU
to 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
Esc
to 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-n
to add cursor-word\<an\>
\\w
to toggle whole word search. Pattern now isan
without\<
nor\>
Right-arrow
to move to "n" of "nual" of line 1C-n
to add cursor-word\<nual\>
\\w
to toggle whole word search. Pattern now isnual
without\<
nor\>
Right-arrow
to move to any char of "Berlinale" of line 1C-n
to add cursor-word- Press
n
till 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
\\CU
to convert to uppercaseEsc
to 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
gg
to go to the first line (and first non-blank char)/let
to findlet
and pressEnter
C-n
to add cursor8mj
to select all occurrences in the 8 lines belowc
and typeput
to change the text fromlet
toput
Esc
to exit insert modeEsc
to 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.k
Result
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.k
Start vs result
cd exercises/operator_find
vim -d start.txt result.txt
maf
to select all occurrences inside a function.
See respective GIF of wiki/5.-Operators.
:help vm-filter
See GIFs of wiki/5.-Operators
Solution
gg
to go to the first line (and first non-blank char)/apple
<C-n>
/banana
<C-n>
vii
to visual select lines of same indentation (with same parents indents), thus last 3 lines are not selected\\f
to selected all occurrences in the visual selection~
to toogle each char caseEsc
to 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 banana
Result
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 banana
Start 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
gg
to go to the first line (and first non-blank char)- 3
C-Down
\\N
prompts: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
Esc
to exit multi-cursor mode
Start
text
text
text
text
Result
1 - text
4 - text
7 - text
10 - text
Start vs result
cd exercises/numbering_prepend
vim -d start.txt result.txt
Solution
gg
to go to the first line (and first non-blank char)- 3
C-Down
\\n
prompts: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
Esc
to exit multi-cursor mode
Start
text
text
text
text
Result
text, 1000
text, 1100
text, 1200
text, 1300
Start 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.
gg
to go to the first line (and first non-blank char)- 4
Ctrl-Down
to add a cursor on the next 4 lines below f,
to move each VM-cursor to the first commaw
to move them to start of next wordTab
to enter extend modeE
to grow region till end of Word, reaching next commal
to 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
Tab
to toggle back to VM-column modeE
to go to end of Wordx
deletes last char (a comma that we shifted)
Add missing comma
gE
to go to end of previous Word, orBBE
. Read vim Text object motions help:h Q_tm
a,
to add a commaEsc
to exit instert mode
Remove spaces in between last two field-values that do not comply with the formatting
Tab
to enter extend modew
to extend regions to start of next word (last field)h
to shink from each region the last char (unselect first letter of last field)h
unselect rightmost space of the regionx
to delete regionsEsc
to 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=Legolas
Result
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=139
Start vs result
cd exercises/regions_shift
vim -d start.txt result.txt
:help vm-transform
Solution
VM-extended selection to all item
-s
gg
to go to the first line (and first non-blank char)C-n
to add current word as VM-cursor\\A
to add rest of matches as VM-cursor
Transform regions with expression to prepend [index]/[total]
, where index
starts at 1.
\\e
and type%i+1 ."/". %N ." ". %t
. Explained in:help vm-transform
Extended selection includes [index]/[total] item
but we want to limit it to total
.
gE
to shrink it to[index]/[total]
and Vim-cursor is at the last digit of[total]
. Read:h Q_tm
o
to 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]
E
to shrink the VM-extended selection to just the only digit of[total]
Multiply total by 1.5
\\e
and type%f * 1.5
Esc
to 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 witho
andO
.
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
gg
to go to the first line (and first non-blank char)C-n
\\A
to add
Remove regions of no colors
3\\R
to remove every 3 regions. To remove eachitem
region in lines that indicate quantity: lines 3, 6, 9,...q
to skip current region at first line. Vim-cursor and first VM-region is now at item of second line (item "red"
)\\R
to remove every other regions (to remove eachitem
region previous to fruit names)
Replace region content with proper field name
c
and typecolor
.Esc
to exit insert-modeEsc
to exit VM-mode
Add all items to VM-regions
gg
C-n
\\A
Remove regions of no quantity
q
to skip current region at first line\\R
to remove every other regions (to remove eachitem
region previous to fuit names)
Replace region content with proper field name
c
and typeqty
.Esc
to exit insert-modeEsc
to exit VM-mode
Alternative applying leader-e
(:help vm-transform
)
gg
,C-n
,\\A
\\e
and type%i%3-2 ? %t : 'qty'
Esc
to exit VM-modegg
,C-n
,\\A
q
to skip current region at first line\\e
and type%i%2 ? %t : 'color'
Esc
to 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.
gg
qa
to start recoding macro a0
go to first charE
to move cursor at end of fieldl
to go to next charr=
to replace the space with an equal signA,
to add a comma at end of lineEsc
to exit insert-modeq
to finish recording macro auu
to undo last two edits, to undo the macroqb
to start recoding macro b@a
to run macro aj
to go to next line@a
to run macro aj
to go to next line@a
to run macro ax
to delete last comma added2k
to go two lines upJJ
to join two linesj
to go to next lineq
to finish recording macro b6@b
to 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
gg
w
C-n
\\A
\\e
and type%i%3-2 ? %t : ''
Esc
to 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
gg
w
C-n
\\A
q
(which skips the current region and goes to next one, in second line)3\\R
to 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)\\R
to remove alternate [remaining] regionsx
to remove remaining regionsEsc
to 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 1
Result
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
Start vs result
cd exercises/regions_remove_every_n
vim -d start.txt result.txt
Do 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.json
Final 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.md
Tip: 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
\\z
to Run Normal, see:help vm-run-normal
and wiki/Commands-at-cursors#normal-commands\\v
to Run Visual, see:help vm-run-visual
and wiki/Commands-at-cursors#visual-commands\\x
to Run Ex, see:help vm-run-ex
and wiki/Commands-at-cursors#ex-commands\\@
to Run Macro see:help vm-run-macro
and wiki/Commands-at-cursors#macros\\Z
to Run Last Normal, no:help
found, 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\\V
to Run Last Visual, no:help
found, defined as:call b:VM_Selection.Edit.run_visual(g:Vm.last_visual[0], g:Vm.last_visual[1])
in autoload/vm/plugs.vim\\X
to Run Last Ex, no:help
found, 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-mode
M
, read:help vm-multiline-mode
and check GIF of wiki/4.-Motions-and-Modes\\L
to run"One Per Line"
:help vm-ex-commands
:help vm-registers
exercise 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.txt
script 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▸ Solution
or 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.