1
0

EasyMotion.vim 15 KB

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