Skip to content

Commit c249186

Browse files
committed
Implement MultipleCursorsFind command to add multiple cursors based on regexp
This closes #20
1 parent bff9b50 commit c249186

File tree

6 files changed

+96
-8
lines changed

6 files changed

+96
-8
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 1.10 (04/17/2013)
2+
3+
Bugfixes:
4+
- `O` works now in normal mode. This fixes #24
5+
- Turn on `lazyredraw` during multicursor mode to prevent the sluggish screen redraws
6+
7+
Features:
8+
- Add command **MultipleCursorsFind** to add multiple virtual cursors using regexp. This closes #20
9+
110
## 1.9 (04/17/2013)
211

312
Bugfixes:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
### Do it backwards too! This is not just a replay of the above gif :)
1313
![Example3](assets/example3.gif?raw=true)
1414

15+
### Add multiple cursors using regexes
16+
![Example4](assets/example4.gif?raw=true)
17+
1518
To see what keystrokes are used for the above example, see [this issue](https://github.com/terryma/vim-multiple-cursors/issues/12).
1619

1720
## Features
@@ -33,6 +36,8 @@ Two additional keys are also mapped:
3336
- `Ctrl-p` in Visual mode will remove the current virtual cursor and go back to the previous virtual cursor location. This is useful if you are trigger happy with `Ctrl-n` and accidentally went too far.
3437
- `Ctrl-x` in Visual mode will remove the current virtual cursor and skip to the next virtual cursor location. This is useful if you don't want the current selection to be a candidate to operate on later.
3538

39+
You can also add multiple cursors using a regular expression. The command `MultipleCursorsFind` accepts a range and a pattern, and it will create a virtual cursor at the end of every match within the range. If no range is passed in, then it defaults to the entire buffer.
40+
3641
**NOTE:** The plugin is still somewhat buggy, if at any time you have lingering cursors on screen, you can press `Ctrl-n` in Normal mode and it will remove all prior cursors before starting a new one.
3742

3843
## Mapping

assets/example4.gif

532 KB
Loading

autoload/multiple_cursors.vim

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,61 @@ function! multiple_cursors#skip()
156156
call s:wait_for_user_input('v')
157157
endfunction
158158

159+
" Search for pattern between the start and end line number. For each match, add
160+
" a virtual cursor at the end and start multicursor mode
161+
" This function is called from a command. User commands in Vim do not support
162+
" passing in column ranges. If the user selects a block of text in visual mode,
163+
" but not visual line mode, we only want to match patterns within the actual
164+
" visual selection. We get around this by checking the last visual selection and
165+
" see if its start and end lines match the input. If so, we assume that the user
166+
" did a normal visual selection and we use the '< and '> marks to define the
167+
" region instead of start and end from the method parameter.
168+
function! multiple_cursors#find(start, end, pattern)
169+
let s:cm.saved_winview = winsaveview()
170+
let s:cm.start_from_find = 1
171+
if visualmode() ==# 'v' && a:start == line("'<") && a:end == line("'>")
172+
let pos1 = s:pos("'<")
173+
let pos2 = s:pos("'>")
174+
else
175+
let pos1 = [a:start, 1]
176+
let pos2 = [a:end, col([a:end, '$'])]
177+
endif
178+
call cursor(pos1)
179+
let first = 1
180+
while 1
181+
if first
182+
" First search starts from the current position
183+
let match = search(a:pattern, 'cW')
184+
let first = 0
185+
else
186+
let match = search(a:pattern, 'W')
187+
endif
188+
if !match
189+
break
190+
endif
191+
let left = s:pos('.')
192+
call search(a:pattern, 'ceW')
193+
let right = s:pos('.')
194+
if s:compare_pos(right, pos2) > 0
195+
break
196+
endif
197+
call s:cm.add(right, [left, right])
198+
" Redraw here forces the cursor movement to be updated. This prevents the
199+
" jerky behavior when doing any action once the cursors are added. But it
200+
" also slows down adding the cursors dramatically. We need to a better
201+
" solution here
202+
" redraw
203+
endwhile
204+
if s:cm.is_empty()
205+
call winrestview(s:cm.saved_winview)
206+
echohl ErrorMsg | echo 'No match found' | echohl None
207+
return
208+
else
209+
echohl Normal | echo 'Added '.s:cm.size().' cursor'.(s:cm.size()>1?'s':'')
210+
call s:wait_for_user_input('v')
211+
endif
212+
endfunction
213+
159214
"===============================================================================
160215
" Cursor class
161216
"===============================================================================
@@ -248,9 +303,12 @@ function! s:CursorManager.new()
248303
let obj.saved_settings = {
249304
\ 'virtualedit': &virtualedit,
250305
\ 'cursorline': &cursorline,
306+
\ 'lazyredraw': &lazyredraw,
251307
\ }
252308
" We save the window view when multicursor mode is entered
253309
let obj.saved_winview = []
310+
" Track whether we started multicursor mode from calling multiple_cursors#find
311+
let obj.start_from_find = 0
254312
return obj
255313
endfunction
256314

@@ -263,8 +321,9 @@ function! s:CursorManager.reset(restore_view) dict
263321
endif
264322

265323
" If the cursor moved, just restoring the view could get confusing, let's
266-
" put the cursor at where the user left it
267-
if !self.is_empty()
324+
" put the cursor at where the user left it. Only do this if we didn't start
325+
" from find mode
326+
if !self.is_empty() && !self.start_from_find
268327
call cursor(self.get(0).position)
269328
endif
270329
endif
@@ -282,6 +341,7 @@ function! s:CursorManager.reset(restore_view) dict
282341
let self.current_index = -1
283342
let self.starting_index = -1
284343
let self.saved_winview = []
344+
let self.start_from_find = 0
285345
call self.restore_user_settings()
286346

287347
" FIXME(terryma): Doesn't belong here
@@ -435,17 +495,24 @@ endfunction
435495
" virtualedit needs to be set to onemore for updates to work correctly
436496
" cursorline needs to be turned off for the cursor highlight to work on the line
437497
" where the real vim cursor is
498+
" lazyredraw needs to be turned on to prevent jerky screen behavior with many
499+
" cursors on screen
438500
function! s:CursorManager.initialize() dict
439501
let &virtualedit = "onemore"
440502
let &cursorline = 0
441-
let self.saved_winview = winsaveview()
503+
let &lazyredraw = 1
504+
" We could have already saved the view from multiple_cursors#find
505+
if !self.start_from_find
506+
let self.saved_winview = winsaveview()
507+
endif
442508
endfunction
443509

444510
" Restore user settings.
445511
function! s:CursorManager.restore_user_settings() dict
446512
if !empty(self.saved_settings)
447513
let &virtualedit = self.saved_settings['virtualedit']
448514
let &cursorline = self.saved_settings['cursorline']
515+
let &lazyredraw = self.saved_settings['lazyredraw']
449516
endif
450517
endfunction
451518

@@ -540,6 +607,8 @@ endfunction
540607
" Mode change: Normal -> Visual
541608
" Cursor change: Set to end of region
542609
" TODO: Refactor this and s:update_visual_markers
610+
" FIXME: By using m` we're destroying the user's jumplist. We should use a
611+
" different mark and use :keepjump
543612
function! s:select_in_visual_mode(region)
544613
if a:region[0] == a:region[1]
545614
normal! v
@@ -609,6 +678,7 @@ function! s:highlight_region(region)
609678
let s1 = '\%'.s[0][0].'l.\%>'.s[0][1].'v.*'
610679
let s2 = '\%'.s[1][0].'l.*\%<'.s[1][1].'v..'
611680
let pattern = s1.'\|'.s2
681+
" More than two lines
612682
if (s[1][0] - s[0][0] > 1)
613683
let pattern = pattern.'\|\%>'.s[0][0].'l\%<'.s[1][0].'l'
614684
endif
@@ -627,11 +697,6 @@ function! s:revert_mode(from, to)
627697
if a:to ==# 'n' && a:from ==# 'i'
628698
stopinsert
629699
endif
630-
if a:to ==# 'n' && a:from ==# 'v'
631-
" TODO(terryma): Hmm this would cause visual to normal mode to break.
632-
" Strange
633-
" call s:exit_visual_mode()
634-
endif
635700
endfunction
636701

637702
" Consume all the additional character the user typed between the last

doc/multiple_cursors.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ CTRL-X in Visual mode will remove the current virtual cursor and skip to the
6161
next virtual cursor location. This is useful if you don't want the current
6262
selection to be a candidate to operate on later.
6363

64+
You can also add multiple cursors using a regular expression. The command
65+
*MultipleCursorsFind* accepts a range and a pattern, and it will create a
66+
virtual cursor at the end of every match within the range. If no range is
67+
passed in, then it defaults to the entire buffer.
68+
6469
NOTE: The plugin is still somewhat buggy, if at any time you have
6570
lingering cursors on screen, you can press CTRL-N in Normal mode and it will
6671
remove all prior cursors before starting a new one.

plugin/multiple_cursors.vim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,9 @@ if exists('g:multi_cursor_next_key')
5353
\' :<C-u>call multiple_cursors#new("v")<CR>'
5454
endif
5555

56+
" Commands
57+
command! -nargs=1 -range=% MultipleCursorsFind
58+
\ call multiple_cursors#find(<line1>, <line2>, <q-args>)
59+
5660
let &cpo = s:save_cpo
5761
unlet s:save_cpo

0 commit comments

Comments
 (0)