]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/autoload/EasyMotion.vim
7c79dd81c194d7f09b79af28d23b04c6fe340f71
[rbdr/dotfiles] / vim / autoload / EasyMotion.vim
1 " EasyMotion - Vim motions on speed!
2 "
3 " Author: Kim Silkebækken <kim.silkebaekken+vim@gmail.com>
4 " Source repository: https://github.com/Lokaltog/vim-easymotion
5
6 " Default configuration functions {{{
7 function! EasyMotion#InitOptions(options) " {{{
8 for [key, value] in items(a:options)
9 if ! exists('g:EasyMotion_' . key)
10 exec 'let g:EasyMotion_' . key . ' = ' . string(value)
11 endif
12 endfor
13 endfunction " }}}
14 function! EasyMotion#InitHL(group, colors) " {{{
15 let group_default = a:group . 'Default'
16
17 " Prepare highlighting variables
18 let guihl = printf('guibg=%s guifg=%s gui=%s', a:colors.gui[0], a:colors.gui[1], a:colors.gui[2])
19 if !exists('g:CSApprox_loaded')
20 let ctermhl = &t_Co == 256
21 \ ? printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm256[0], a:colors.cterm256[1], a:colors.cterm256[2])
22 \ : printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm[0], a:colors.cterm[1], a:colors.cterm[2])
23 else
24 let ctermhl = ''
25 endif
26
27 " Create default highlighting group
28 execute printf('hi default %s %s %s', group_default, guihl, ctermhl)
29
30 " Check if the hl group exists
31 if hlexists(a:group)
32 redir => hlstatus | exec 'silent hi ' . a:group | redir END
33
34 " Return if the group isn't cleared
35 if hlstatus !~ 'cleared'
36 return
37 endif
38 endif
39
40 " No colors are defined for this group, link to defaults
41 execute printf('hi default link %s %s', a:group, group_default)
42 endfunction " }}}
43 function! EasyMotion#InitMappings(motions) "{{{
44 for motion in keys(a:motions)
45 call EasyMotion#InitOptions({ 'mapping_' . motion : g:EasyMotion_leader_key . motion })
46 endfor
47
48 if g:EasyMotion_do_mapping
49 for [motion, fn] in items(a:motions)
50 if empty(g:EasyMotion_mapping_{motion})
51 continue
52 endif
53
54 silent exec 'nnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>'
55 silent exec 'onoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>'
56 silent exec 'vnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :<C-U>call EasyMotion#' . fn.name . '(1, ' . fn.dir . ')<CR>'
57 endfor
58 endif
59 endfunction "}}}
60 " }}}
61 " Motion functions {{{
62 function! EasyMotion#F(visualmode, direction) " {{{
63 let char = s:GetSearchChar(a:visualmode)
64
65 if empty(char)
66 return
67 endif
68
69 let re = '\C' . escape(char, '.$^~')
70
71 call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
72 endfunction " }}}
73 function! EasyMotion#T(visualmode, direction) " {{{
74 let char = s:GetSearchChar(a:visualmode)
75
76 if empty(char)
77 return
78 endif
79
80 if a:direction == 1
81 let re = '\C' . escape(char, '.$^~') . '\zs.'
82 else
83 let re = '\C.' . escape(char, '.$^~')
84 endif
85
86 call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
87 endfunction " }}}
88 function! EasyMotion#WB(visualmode, direction) " {{{
89 call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', '')
90 endfunction " }}}
91 function! EasyMotion#WBW(visualmode, direction) " {{{
92 call s:EasyMotion('\(\(^\|\s\)\@<=\S\|^$\)', a:direction, a:visualmode ? visualmode() : '', '')
93 endfunction " }}}
94 function! EasyMotion#E(visualmode, direction) " {{{
95 call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1))
96 endfunction " }}}
97 function! EasyMotion#EW(visualmode, direction) " {{{
98 call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1))
99 endfunction " }}}
100 function! EasyMotion#JK(visualmode, direction) " {{{
101 call s:EasyMotion('^\(\w\|\s*\zs\|$\)', a:direction, a:visualmode ? visualmode() : '', '')
102 endfunction " }}}
103 function! EasyMotion#Search(visualmode, direction) " {{{
104 call s:EasyMotion(@/, a:direction, a:visualmode ? visualmode() : '', '')
105 endfunction " }}}
106 " }}}
107 " Helper functions {{{
108 function! s:Message(message) " {{{
109 echo 'EasyMotion: ' . a:message
110 endfunction " }}}
111 function! s:Prompt(message) " {{{
112 echohl Question
113 echo a:message . ': '
114 echohl None
115 endfunction " }}}
116 function! s:VarReset(var, ...) " {{{
117 if ! exists('s:var_reset')
118 let s:var_reset = {}
119 endif
120
121 let buf = bufname("")
122
123 if a:0 == 0 && has_key(s:var_reset, a:var)
124 " Reset var to original value
125 call setbufvar(buf, a:var, s:var_reset[a:var])
126 elseif a:0 == 1
127 let new_value = a:0 == 1 ? a:1 : ''
128
129 " Store original value
130 let s:var_reset[a:var] = getbufvar(buf, a:var)
131
132 " Set new var value
133 call setbufvar(buf, a:var, new_value)
134 endif
135 endfunction " }}}
136 function! s:SetLines(lines, key) " {{{
137 try
138 " Try to join changes with previous undo block
139 undojoin
140 catch
141 endtry
142
143 for [line_num, line] in a:lines
144 call setline(line_num, line[a:key])
145 endfor
146 endfunction " }}}
147 function! s:GetChar() " {{{
148 let char = getchar()
149
150 if char == 27
151 " Escape key pressed
152 redraw
153
154 call s:Message('Cancelled')
155
156 return ''
157 endif
158
159 return nr2char(char)
160 endfunction " }}}
161 function! s:GetSearchChar(visualmode) " {{{
162 call s:Prompt('Search for character')
163
164 let char = s:GetChar()
165
166 " Check that we have an input char
167 if empty(char)
168 " Restore selection
169 if ! empty(a:visualmode)
170 silent exec 'normal! gv'
171 endif
172
173 return ''
174 endif
175
176 return char
177 endfunction " }}}
178 " }}}
179 " Grouping algorithms {{{
180 let s:grouping_algorithms = {
181 \ 1: 'SCTree'
182 \ , 2: 'Original'
183 \ }
184 " Single-key/closest target priority tree {{{
185 " This algorithm tries to assign one-key jumps to all the targets closest to the cursor.
186 " It works recursively and will work correctly with as few keys as two.
187 function! s:GroupingAlgorithmSCTree(targets, keys)
188 " Prepare variables for working
189 let targets_len = len(a:targets)
190 let keys_len = len(a:keys)
191
192 let groups = {}
193
194 let keys = reverse(copy(a:keys))
195
196 " Semi-recursively count targets {{{
197 " We need to know exactly how many child nodes (targets) this branch will have
198 " in order to pass the correct amount of targets to the recursive function.
199
200 " Prepare sorted target count list {{{
201 " This is horrible, I know. But dicts aren't sorted in vim, so we need to
202 " work around that. That is done by having one sorted list with key counts,
203 " and a dict which connects the key with the keys_count list.
204
205 let keys_count = []
206 let keys_count_keys = {}
207
208 let i = 0
209 for key in keys
210 call add(keys_count, 0)
211
212 let keys_count_keys[key] = i
213
214 let i += 1
215 endfor
216 " }}}
217
218 let targets_left = targets_len
219 let level = 0
220 let i = 0
221
222 while targets_left > 0
223 " Calculate the amount of child nodes based on the current level
224 let childs_len = (level == 0 ? 1 : (keys_len - 1) )
225
226 for key in keys
227 " Add child node count to the keys_count array
228 let keys_count[keys_count_keys[key]] += childs_len
229
230 " Subtract the child node count
231 let targets_left -= childs_len
232
233 if targets_left <= 0
234 " Subtract the targets left if we added too many too
235 " many child nodes to the key count
236 let keys_count[keys_count_keys[key]] += targets_left
237
238 break
239 endif
240
241 let i += 1
242 endfor
243
244 let level += 1
245 endwhile
246 " }}}
247 " Create group tree {{{
248 let i = 0
249 let key = 0
250
251 call reverse(keys_count)
252
253 for key_count in keys_count
254 if key_count > 1
255 " We need to create a subgroup
256 " Recurse one level deeper
257 let groups[a:keys[key]] = s:GroupingAlgorithmSCTree(a:targets[i : i + key_count - 1], a:keys)
258 elseif key_count == 1
259 " Assign single target key
260 let groups[a:keys[key]] = a:targets[i]
261 else
262 " No target
263 continue
264 endif
265
266 let key += 1
267 let i += key_count
268 endfor
269 " }}}
270
271 " Finally!
272 return groups
273 endfunction
274 " }}}
275 " Original {{{
276 function! s:GroupingAlgorithmOriginal(targets, keys)
277 " Split targets into groups (1 level)
278 let targets_len = len(a:targets)
279 let keys_len = len(a:keys)
280
281 let groups = {}
282
283 let i = 0
284 let root_group = 0
285 try
286 while root_group < targets_len
287 let groups[a:keys[root_group]] = {}
288
289 for key in a:keys
290 let groups[a:keys[root_group]][key] = a:targets[i]
291
292 let i += 1
293 endfor
294
295 let root_group += 1
296 endwhile
297 catch | endtry
298
299 " Flatten the group array
300 if len(groups) == 1
301 let groups = groups[a:keys[0]]
302 endif
303
304 return groups
305 endfunction
306 " }}}
307 " Coord/key dictionary creation {{{
308 function! s:CreateCoordKeyDict(groups, ...)
309 " Dict structure:
310 " 1,2 : a
311 " 2,3 : b
312 let sort_list = []
313 let coord_keys = {}
314 let group_key = a:0 == 1 ? a:1 : ''
315
316 for [key, item] in items(a:groups)
317 let key = ( ! empty(group_key) ? group_key : key)
318
319 if type(item) == 3
320 " Destination coords
321
322 " The key needs to be zero-padded in order to
323 " sort correctly
324 let dict_key = printf('%05d,%05d', item[0], item[1])
325 let coord_keys[dict_key] = key
326
327 " We need a sorting list to loop correctly in
328 " PromptUser, dicts are unsorted
329 call add(sort_list, dict_key)
330 else
331 " Item is a dict (has children)
332 let coord_key_dict = s:CreateCoordKeyDict(item, key)
333
334 " Make sure to extend both the sort list and the
335 " coord key dict
336 call extend(sort_list, coord_key_dict[0])
337 call extend(coord_keys, coord_key_dict[1])
338 endif
339
340 unlet item
341 endfor
342
343 return [sort_list, coord_keys]
344 endfunction
345 " }}}
346 " }}}
347 " Core functions {{{
348 function! s:PromptUser(groups) "{{{
349 " If only one possible match, jump directly to it {{{
350 let group_values = values(a:groups)
351
352 if len(group_values) == 1
353 redraw
354
355 return group_values[0]
356 endif
357 " }}}
358 " Prepare marker lines {{{
359 let lines = {}
360 let hl_coords = []
361 let coord_key_dict = s:CreateCoordKeyDict(a:groups)
362
363 for dict_key in sort(coord_key_dict[0])
364 let target_key = coord_key_dict[1][dict_key]
365 let [line_num, col_num] = split(dict_key, ',')
366
367 let line_num = str2nr(line_num)
368 let col_num = str2nr(col_num)
369
370 " Add original line and marker line
371 if ! has_key(lines, line_num)
372 let current_line = getline(line_num)
373
374 let lines[line_num] = { 'orig': current_line, 'marker': current_line, 'mb_compensation': 0 }
375 endif
376
377 " Compensate for byte difference between marker
378 " character and target character
379 "
380 " This has to be done in order to match the correct
381 " column; \%c matches the byte column and not display
382 " column.
383 let target_char_len = strlen(matchstr(lines[line_num]['marker'], '\%' . col_num . 'c.'))
384 let target_key_len = strlen(target_key)
385
386 " Solve multibyte issues by matching the byte column
387 " number instead of the visual column
388 let col_num -= lines[line_num]['mb_compensation']
389
390 if strlen(lines[line_num]['marker']) > 0
391 " Substitute marker character if line length > 0
392 let lines[line_num]['marker'] = substitute(lines[line_num]['marker'], '\%' . col_num . 'c.', target_key, '')
393 else
394 " Set the line to the marker character if the line is empty
395 let lines[line_num]['marker'] = target_key
396 endif
397
398 " Add highlighting coordinates
399 call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
400
401 " Add marker/target lenght difference for multibyte
402 " compensation
403 let lines[line_num]['mb_compensation'] += (target_char_len - target_key_len)
404 endfor
405
406 let lines_items = items(lines)
407 " }}}
408 " Highlight targets {{{
409 let target_hl_id = matchadd(g:EasyMotion_hl_group_target, join(hl_coords, '\|'), 1)
410 " }}}
411
412 try
413 " Set lines with markers
414 call s:SetLines(lines_items, 'marker')
415
416 redraw
417
418 " Get target character {{{
419 call s:Prompt('Target key')
420
421 let char = s:GetChar()
422 " }}}
423 finally
424 " Restore original lines
425 call s:SetLines(lines_items, 'orig')
426
427 " Un-highlight targets {{{
428 if exists('target_hl_id')
429 call matchdelete(target_hl_id)
430 endif
431 " }}}
432
433 redraw
434 endtry
435
436 " Check if we have an input char {{{
437 if empty(char)
438 throw 'Cancelled'
439 endif
440 " }}}
441 " Check if the input char is valid {{{
442 if ! has_key(a:groups, char)
443 throw 'Invalid target'
444 endif
445 " }}}
446
447 let target = a:groups[char]
448
449 if type(target) == 3
450 " Return target coordinates
451 return target
452 else
453 " Prompt for new target character
454 return s:PromptUser(target)
455 endif
456 endfunction "}}}
457 function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{
458 let orig_pos = [line('.'), col('.')]
459 let targets = []
460
461 try
462 " Reset properties {{{
463 call s:VarReset('&scrolloff', 0)
464 call s:VarReset('&modified', 0)
465 call s:VarReset('&modifiable', 1)
466 call s:VarReset('&readonly', 0)
467 call s:VarReset('&spell', 0)
468 call s:VarReset('&virtualedit', '')
469 " }}}
470 " Find motion targets {{{
471 let search_direction = (a:direction == 1 ? 'b' : '')
472 let search_stopline = line(a:direction == 1 ? 'w0' : 'w$')
473
474 while 1
475 let pos = searchpos(a:regexp, search_direction, search_stopline)
476
477 " Reached end of search range
478 if pos == [0, 0]
479 break
480 endif
481
482 " Skip folded lines
483 if foldclosed(pos[0]) != -1
484 continue
485 endif
486
487 call add(targets, pos)
488 endwhile
489
490 let targets_len = len(targets)
491 if targets_len == 0
492 throw 'No matches'
493 endif
494 " }}}
495
496 let GroupingFn = function('s:GroupingAlgorithm' . s:grouping_algorithms[g:EasyMotion_grouping])
497 let groups = GroupingFn(targets, split(g:EasyMotion_keys, '\zs'))
498
499 " Shade inactive source {{{
500 if g:EasyMotion_do_shade
501 let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'
502
503 if a:direction == 1
504 " Backward
505 let shade_hl_re = '\%'. line('w0') .'l\_.*' . shade_hl_pos
506 else
507 " Forward
508 let shade_hl_re = shade_hl_pos . '\_.*\%'. line('w$') .'l'
509 endif
510
511 let shade_hl_id = matchadd(g:EasyMotion_hl_group_shade, shade_hl_re, 0)
512 endif
513 " }}}
514
515 " Prompt user for target group/character
516 let coords = s:PromptUser(groups)
517
518 " Update selection {{{
519 if ! empty(a:visualmode)
520 keepjumps call cursor(orig_pos[0], orig_pos[1])
521
522 exec 'normal! ' . a:visualmode
523 endif
524 " }}}
525 " Handle operator-pending mode {{{
526 if a:mode == 'no'
527 " This mode requires that we eat one more
528 " character to the right if we're using
529 " a forward motion
530 if a:direction != 1
531 let coords[1] += 1
532 endif
533 endif
534 " }}}
535
536 " Update cursor position
537 call cursor(orig_pos[0], orig_pos[1])
538 mark '
539 call cursor(coords[0], coords[1])
540
541 call s:Message('Jumping to [' . coords[0] . ', ' . coords[1] . ']')
542 catch
543 redraw
544
545 " Show exception message
546 call s:Message(v:exception)
547
548 " Restore original cursor position/selection {{{
549 if ! empty(a:visualmode)
550 silent exec 'normal! gv'
551 else
552 keepjumps call cursor(orig_pos[0], orig_pos[1])
553 endif
554 " }}}
555 finally
556 " Restore properties {{{
557 call s:VarReset('&scrolloff')
558 call s:VarReset('&modified')
559 call s:VarReset('&modifiable')
560 call s:VarReset('&readonly')
561 call s:VarReset('&spell')
562 call s:VarReset('&virtualedit')
563 " }}}
564 " Remove shading {{{
565 if g:EasyMotion_do_shade && exists('shade_hl_id')
566 call matchdelete(shade_hl_id)
567 endif
568 " }}}
569 endtry
570 endfunction " }}}
571 " }}}
572
573 " vim: fdm=marker:noet:ts=4:sw=4:sts=4