Skip to content

Commit 8a4f00b

Browse files
committed
-Update regex in EasyMotion#WBW ('W' and 'B' movement)
-Update regex in EasyMotion#EW ('E' and 'gE' movement) - Add global option EasyMotion_maximal_jumpmarks. It limits the number of jump targets for the motion. If the search goes in both directions, they will be handled separately. The option was just added to speed up the automated tests in t/compare_movements_spec.vim.
1 parent 37042d3 commit 8a4f00b

File tree

3 files changed

+145
-35
lines changed

3 files changed

+145
-35
lines changed

autoload/EasyMotion.vim

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,12 @@ function! EasyMotion#WB(visualmode, direction) " {{{
181181
endfunction " }}}
182182
function! EasyMotion#WBW(visualmode, direction) " {{{
183183
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
184-
call s:EasyMotion('\(\(^\|\s\)\@<=\S\|^$\)', a:direction, a:visualmode ? visualmode() : '', 0)
184+
" Note: Previous regex for all directions was '\(\(^\|\s\)\@<=\S\|^$\)'
185+
let l:regex_without_file_ends = '\v(^|\s)\zs\S|^$'
186+
let l:regex = l:regex_without_file_ends
187+
\ . (a:direction == 1 ? '' : '|%$')
188+
\ . (a:direction == 0 ? '' : '|%^')
189+
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
185190
return s:EasyMotion_is_cancelled
186191
endfunction " }}}
187192
function! EasyMotion#WBK(visualmode, direction) " {{{
@@ -204,14 +209,32 @@ endfunction " }}}
204209
function! EasyMotion#EW(visualmode, direction) " {{{
205210
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
206211
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
207-
call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive)
212+
" Note: Previous regex for all directions was '\(\S\(\s\|$\)\|^$\)'
213+
" Note: The stopping positions for 'E' and 'gE' differs. Thus, the regex
214+
" for direction==2 cannot be the same in both directions. This will be
215+
" ignored.
216+
let l:regex_stub = '\v\S(\s|$)'
217+
let l:regex = l:regex_stub
218+
\ . (a:direction == 0 ? '' : '|^$|%^')
219+
\ . (a:direction == 1 ? '' : '|%$')
220+
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
208221
return s:EasyMotion_is_cancelled
209222
endfunction " }}}
210223
function! EasyMotion#EK(visualmode, direction) " {{{
211224
" vim's iskeyword style word motion
212225
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
213226
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
214-
call s:EasyMotion('\(\S\(\>\|\<\|\s\)\@=\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive)
227+
" Note: Previous regex for all directions was '\(\S\(\>\|\<\|\s\)\@=\|^$\)'
228+
" Note: The stopping positions for 'e' and 'ge' differs. Thus, the regex
229+
" for direction==2 cannot be the same in both directions. This will be
230+
" ignored.
231+
let l:regex_stub = '\v.\ze>|\S\ze\s*$|\S\ze\s|\k\zs>\S\ze|\S<'
232+
let l:regex = l:regex_stub
233+
\ . (a:direction == 0 ? '' : '|^$|%^')
234+
\ . (a:direction == 1 ? '' : '|%$')
235+
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
236+
237+
215238
return s:EasyMotion_is_cancelled
216239
endfunction " }}}
217240
" -- JK Motion ---------------------------
@@ -1289,7 +1312,8 @@ function! s:EasyMotion(regexp, direction, visualmode, is_inclusive, ...) " {{{
12891312
" but in this case, it's better to allows jump side effect
12901313
" to gathering matched targets coordinates.
12911314
let pos = searchpos(regexp, search_direction . (config.accept_cursor_pos ? 'c' : ''), search_stopline)
1292-
while 1
1315+
let num_jumpmarks = 0
1316+
while l:num_jumpmarks < g:EasyMotion_maximal_jumpmarks
12931317
" Reached end of search range
12941318
if pos == [0, 0]
12951319
break
@@ -1308,6 +1332,7 @@ function! s:EasyMotion(regexp, direction, visualmode, is_inclusive, ...) " {{{
13081332
endif
13091333
"}}}
13101334
let pos = searchpos(regexp, search_direction, search_stopline)
1335+
let l:num_jumpmarks += 1
13111336
endwhile
13121337
"}}}
13131338

@@ -1325,15 +1350,17 @@ function! s:EasyMotion(regexp, direction, visualmode, is_inclusive, ...) " {{{
13251350
keepjumps call cursor(s:current.cursor_position[0],
13261351
\ s:current.cursor_position[1])
13271352

1353+
let l:num_jumpmarks = 0
13281354
let targets2 = []
13291355
if s:flag.within_line == 0
13301356
let search_stopline = win_first_line
13311357
else
13321358
let search_stopline = s:current.cursor_position[0]
13331359
endif
1334-
while 1
1360+
while l:num_jumpmarks < g:EasyMotion_maximal_jumpmarks
13351361
" TODO: refactoring
13361362
let pos = searchpos(regexp, 'b', search_stopline)
1363+
let l:num_jumpmarks += 1
13371364
" Reached end of search range
13381365
if pos == [0, 0]
13391366
break

plugin/EasyMotion.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ let g:EasyMotion_command_line_key_mappings =
5151
\ get(g: , 'EasyMotion_command_line_key_mappings' , {})
5252
let g:EasyMotion_disable_two_key_combo =
5353
\ get(g: , 'EasyMotion_disable_two_key_combo' , 0)
54+
let g:EasyMotion_maximal_jumpmarks = get(g: , 'EasyMotion_maximal_jumpmarks' , 999)
5455

5556
"}}}
5657

t/compare_movements_spec.vim

Lines changed: 112 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,19 @@
2727

2828
" Setup {{{
2929
let s:root_dir = matchstr(system('git rev-parse --show-cdup'), '[^\n]\+')
30+
31+
" The consumed time depends from the length of the text and could be really high
32+
" on vimdoc pages. (See it 'Loop through Vim help buffer and compare movements')
33+
" Reduce this value to stop CompareMovements(...) before it reached the end of the
34+
" buffer.
35+
let s:maximal_number_of_compared_movments = 10000
3036
execute 'set' 'rtp +=./'.s:root_dir
3137
runtime! plugin/EasyMotion.vim
3238
" }}}
3339

3440
" Functions for Test {{{
3541
function! AddLine(str)
36-
put! =a:str
42+
put =a:str
3743
endfunction
3844

3945
function! CursorPos()
@@ -46,6 +52,7 @@ function TryNormal(str)
4652
exec 'normal ' . a:str
4753
catch /^Vim\%((\a\+)\)\=:E21/
4854
endtry
55+
return 0
4956
endfunction
5057

5158
let s:to_cursor = {}
@@ -55,7 +62,6 @@ endfunction
5562

5663
" Add metadata about failure.
5764
function! s:to_cursor.failure_message_for_should(actual, expected)
58-
return ''
5965
Expect a:actual[0] > 0
6066
Expect a:expected[0] > 0
6167
Expect a:actual[0] <= getpos('$')[1]
@@ -72,8 +78,10 @@ function! s:to_cursor.failure_message_for_should(actual, expected)
7278
let line2 = strpart(l:line2, 0, a:expected[1]-1)
7379
\ . ''
7480
\ . strpart(l:line2, a:expected[1])
81+
" Separation of both cases with \n would be nice, but
82+
" vim-vspec allow oneliners as return string, only.
7583
let l:msg = 'Line ' . string(a:actual[0]) . ": '" . l:line1
76-
\ . "', Line " . string(a:expected[0]) . ": '" . l:line2 . "'"
84+
\ . "',\x09\x09 Line " . string(a:expected[0]) . ": '" . l:line2 . "'\x0a"
7785
return l:msg
7886
endfunction
7987

@@ -86,37 +94,38 @@ function! CompareMovements(movement1, movement2, backward)
8694
" Loop through current buffer in both variants {{
8795
for [l:handler, l:list] in l:jumpmarks
8896
if a:backward == 1
89-
call cursor(getpos('$')[1:2])
97+
let l:last_line = line('$')
98+
let l:last_char = len(getline(l:last_line))
99+
call cursor(l:last_line, l:last_char)
90100
else
91101
call cursor([1,1])
92102
endif
93103

94104
let l:lastpos = [0,0]
95105

106+
" Centralize line. Otherwise, Easymotion functions aborts
107+
" at the end of the (virtual) window.
108+
call TryNormal('zz')
96109
call TryNormal(l:handler)
97110
let l:curpos = getpos(".")[1:2]
98111

99112
while l:lastpos != l:curpos
100113
let l:list += [l:curpos]
101114
let l:lastpos = l:curpos
115+
call TryNormal('zz')
102116
call TryNormal(l:handler)
103117
let l:curpos = getpos(".")[1:2]
118+
" Abort after a fixed number of steps.
119+
if len(l:list) > s:maximal_number_of_compared_movments
120+
break
121+
endif
104122
endwhile
105123
endfor
106124
" }}
107125

108126
" The resulting lists are stored in l:jumpmarks[*][1], now.
109127
let [l:cursor_positions1, l:cursor_positions2] = [ l:jumpmarks[0][1], l:jumpmarks[1][1] ]
110128

111-
" Debug output for this script
112-
let g:dbg_msg = printf("(CompareMovements) '%s' vs '%s'\<CR>Length of both lists: %d, %d\r Content of lists:\r%s\r\r%s",
113-
\ string(l:jumpmarks[0][0]),
114-
\ string(l:jumpmarks[1][0]),
115-
\ len(l:cursor_positions1), len(l:cursor_positions2),
116-
\ string(l:cursor_positions1),
117-
\ string(l:cursor_positions2))
118-
Expect g:dbg_msg == v:errmsg
119-
120129
if l:cursor_positions1 == l:cursor_positions2
121130
return 0
122131
endif
@@ -129,30 +138,61 @@ function! CompareMovements(movement1, movement2, backward)
129138
let l:index += 1
130139
endwhile
131140

132-
" Collision with begin or end of file
141+
" Collision with begin or end of file or while loop aborts to early.
133142
if a:backward == 1
134-
Expect join(['File begin reached after ', len(l:cursor_positions2), ' steps.'])
135-
\ == join(['File begin reached after ', len(l:cursor_positions1), ' steps.'])
143+
Expect join([a:movement2, ': File begin reached after ', len(l:cursor_positions2), ' steps.'])
144+
\ == join([a:movement1, ': File begin reached after ', len(l:cursor_positions1), ' steps.'])
136145
else
137-
Expect join(['File end reached after ', len(l:cursor_positions2), ' steps.'])
138-
\ == join(['File end reached after ', len(l:cursor_positions1), ' steps.'])
146+
Expect l:cursor_positions2[l:index-1] to_cursor l:cursor_positions1[l:index]
147+
Expect join([a:movement2, ': File end reached after ', len(l:cursor_positions2), ' steps.'])
148+
\ == join([a:movement1, ': File end reached after ', len(l:cursor_positions1), ' steps.'])
139149
endif
140150
" }}
141151

142152
return -1
143153
endfunction
154+
155+
" Hand crafted text with rare cases
156+
function! InsertTestText1()
157+
158+
" Blanks at document begin
159+
call AddLine('')
160+
call AddLine(' ')
161+
call AddLine('')
162+
163+
call AddLine('scriptencoding utf-8')
164+
165+
" '^\s*[not-\k]'-case
166+
call AddLine('!foo')
167+
call AddLine(' !bar')
168+
169+
call AddLine('<!{}>s! ')
170+
171+
" Blanks at document end
172+
call AddLine('')
173+
call AddLine(' ')
174+
call AddLine('')
175+
endfunction
176+
144177
"}}}
145178

146179
"Keyword word motion {{{
147180
describe 'Keyword word motion'
148181
before
149182
new
183+
resize 10
150184
nmap a <Nop>
151185
let g:EasyMotion_keys = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
186+
let g:EasyMotion_maximal_jumpmarks = 2 " Error for value 1 unanalyzed.
152187
nmap <Leader>w <Plug>(easymotion-iskeyword-w)
153188
nmap <Leader>b <Plug>(easymotion-iskeyword-b)
189+
nmap <Leader>e <Plug>(easymotion-iskeyword-e)
190+
nmap <Leader>ge <Plug>(easymotion-iskeyword-ge)
191+
nmap <Leader>W <Plug>(easymotion-W)
192+
nmap <Leader>B <Plug>(easymotion-B)
193+
nmap <Leader>E <Plug>(easymotion-E)
194+
nmap <Leader>gE <Plug>(easymotion-gE)
154195
call EasyMotion#init()
155-
156196
call vspec#customize_matcher('to_cursor', s:to_cursor)
157197
end
158198

@@ -161,25 +201,67 @@ describe 'Keyword word motion'
161201
end
162202

163203
it 'Simple test to check setup of this test'
204+
normal aa\<Esc>
205+
Expect getline(1) == ''
164206
call AddLine('word')
207+
Expect CompareMovements('w', 'w', 0) == 0
165208
Expect CompareMovements('w', '\wa', 0) == 0
166-
"Expect CompareMovements('b', '\ba', 1) == 0
209+
Expect CompareMovements('b', '\ba', 1) == 0
210+
Expect CompareMovements('e', '\ea', 0) == 0
211+
Expect CompareMovements('ge', '\gea', 1) == 0
212+
Expect CompareMovements('W', '\Wa', 0) == 0
213+
Expect CompareMovements('B', '\Ba', 1) == 0
214+
Expect CompareMovements('E', '\Ea', 0) == 0
215+
Expect CompareMovements('gE', '\gEa', 1) == 0
167216
end
168217

169-
"it 'Loop through hand crafted text with rare cases'
170-
" " Hand crafted text with rare cases
171-
" call AddLine('scriptencoding utf-8')
172-
" call AddLine('Test case [ ')
173-
" call AddLine('<!{}>s! ')
174-
" Expect CompareMovements('w', '\wa', 0) == 0
175-
" Expect CompareMovements('b', '\ba', 1) == 0
176-
"end
218+
it 'w'
219+
call InsertTestText1()
220+
Expect CompareMovements('w', '\wa', 0) == 0
221+
end
177222

223+
it 'b'
224+
call InsertTestText1()
225+
Expect CompareMovements('b', '\ba', 1) == 0
226+
end
227+
228+
it 'e'
229+
call InsertTestText1()
230+
Expect CompareMovements('e', '\ea', 0) == 0
231+
end
232+
233+
it 'ge'
234+
call InsertTestText1()
235+
Expect CompareMovements('ge', '\gea', 1) == 0
236+
end
237+
238+
it 'W'
239+
call InsertTestText1()
240+
Expect CompareMovements('W', 'W', 0) == 0
241+
end
242+
243+
it 'B'
244+
call InsertTestText1()
245+
Expect CompareMovements('B', 'B', 1) == 0
246+
end
247+
248+
it 'E'
249+
call InsertTestText1()
250+
Expect CompareMovements('E', 'E', 0) == 0
251+
end
252+
253+
it 'gE'
254+
call InsertTestText1()
255+
Expect CompareMovements('gE', 'gE', 1) == 0
256+
end
257+
258+
" Really time consuming test...
178259
"it 'Loop through Vim help buffer and compare movements'
179260
" help motion.txt
180261
" Expect expand('%:t') ==# 'motion.txt'
181-
" "Expect CompareMovements('w', '\wa', 0) == 0
182-
" "Expect CompareMovements('b', '\ba', 1) == 0
262+
" "Optional: Copy text into editable buffer
263+
" exec "normal! Gygg\<C-W>cP"
264+
" Expect CompareMovements('w', '\wa', 0) == 0
183265
"end
184266

185267
end

0 commit comments

Comments
 (0)