18f51f0699
EasyMotion#SelectLinesPaste() & EasyMotion#SelectPhrasePaste() conflict with vim default 'p' function. If enable this function, you cannot execute vim default 'p' smoothly
1001 lines
27 KiB
VimL
1001 lines
27 KiB
VimL
" EasyMotion - Vim motions on speed!
|
||
"
|
||
" Author: Kim Silkebækken <kim.silkebaekken+vim@gmail.com>
|
||
" Source repository: https://github.com/Lokaltog/vim-easymotion
|
||
|
||
" Default configuration functions {{{
|
||
function! EasyMotion#InitOptions(options) " {{{
|
||
for [key, value] in items(a:options)
|
||
if ! exists('g:EasyMotion_' . key)
|
||
exec 'let g:EasyMotion_' . key . ' = ' . string(value)
|
||
endif
|
||
endfor
|
||
" Reset Migemo Dictionary
|
||
let s:migemo_dicts = {}
|
||
endfunction " }}}
|
||
function! EasyMotion#InitHL(group, colors) " {{{
|
||
let group_default = a:group . 'Default'
|
||
|
||
" Prepare highlighting variables
|
||
let guihl = printf('guibg=%s guifg=%s gui=%s', a:colors.gui[0], a:colors.gui[1], a:colors.gui[2])
|
||
if !exists('g:CSApprox_loaded')
|
||
let ctermhl = &t_Co == 256
|
||
\ ? printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm256[0], a:colors.cterm256[1], a:colors.cterm256[2])
|
||
\ : printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm[0], a:colors.cterm[1], a:colors.cterm[2])
|
||
else
|
||
let ctermhl = ''
|
||
endif
|
||
|
||
" Create default highlighting group
|
||
execute printf('hi default %s %s %s', group_default, guihl, ctermhl)
|
||
|
||
" Check if the hl group exists
|
||
if hlexists(a:group)
|
||
redir => hlstatus | exec 'silent hi ' . a:group | redir END
|
||
|
||
" Return if the group isn't cleared
|
||
if hlstatus !~ 'cleared'
|
||
return
|
||
endif
|
||
endif
|
||
|
||
" No colors are defined for this group, link to defaults
|
||
execute printf('hi default link %s %s', a:group, group_default)
|
||
endfunction " }}}
|
||
function! EasyMotion#InitMappings(motions) "{{{
|
||
for motion in keys(a:motions)
|
||
call EasyMotion#InitOptions({ 'mapping_' . motion : g:EasyMotion_leader_key . motion })
|
||
endfor
|
||
|
||
if g:EasyMotion_do_mapping
|
||
for [motion, fn] in items(a:motions)
|
||
if empty(g:EasyMotion_mapping_{motion})
|
||
continue
|
||
endif
|
||
|
||
silent exec 'nnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>'
|
||
silent exec 'onoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>'
|
||
silent exec 'vnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :<C-U>call EasyMotion#' . fn.name . '(1, ' . fn.dir . ')<CR>'
|
||
endfor
|
||
endif
|
||
endfunction "}}}
|
||
|
||
function! EasyMotion#InitSpecialMappings(motions) "{{{
|
||
for motion in keys(a:motions)
|
||
call EasyMotion#InitOptions({ 'special_mapping_' . motion : g:EasyMotion_leader_key . motion })
|
||
endfor
|
||
|
||
if g:EasyMotion_do_mapping
|
||
for [motion, fn] in items(a:motions)
|
||
if empty(g:EasyMotion_special_mapping_{motion})
|
||
continue
|
||
endif
|
||
|
||
if g:EasyMotion_special_{fn.flag}
|
||
silent exec 'onoremap <silent> ' . g:EasyMotion_special_mapping_{motion} . ' :call EasyMotion#' . fn.name . '()<CR>'
|
||
silent exec 'nnoremap <silent> v' . g:EasyMotion_special_mapping_{motion} . ' :call EasyMotion#' . fn.name . '()<CR>'
|
||
silent exec 'nnoremap <silent> y' . g:EasyMotion_special_mapping_{motion} . ' :call EasyMotion#' . fn.name . 'Yank()<CR>'
|
||
endif
|
||
endfor
|
||
endif
|
||
endfunction "}}}
|
||
|
||
" }}}
|
||
" Motion functions {{{
|
||
function! EasyMotion#F(visualmode, direction) " {{{
|
||
let char = s:GetSearchChar(a:visualmode)
|
||
|
||
let re = escape(char, '.$^~')
|
||
|
||
if g:EasyMotion_use_migemo
|
||
if ! has_key(s:migemo_dicts, &l:encoding)
|
||
let s:migemo_dicts[&l:encoding] = s:load_migemo_dict()
|
||
endif
|
||
if re =~# '^\a$'
|
||
let re = s:migemo_dicts[&l:encoding][re]
|
||
endif
|
||
endif
|
||
|
||
if empty(char)
|
||
return
|
||
endif
|
||
|
||
if g:EasyMotion_smartcase && char =~# '\v\U'
|
||
let re = '\c' . re
|
||
else
|
||
let re = '\C' . re
|
||
endif
|
||
|
||
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
|
||
endfunction " }}}
|
||
function! EasyMotion#S(visualmode, direction) " {{{
|
||
let char = s:GetSearchChar(a:visualmode)
|
||
|
||
if empty(char)
|
||
return
|
||
endif
|
||
|
||
let re = escape(char, '.$^~')
|
||
|
||
if g:EasyMotion_use_migemo
|
||
if ! has_key(s:migemo_dicts, &l:encoding)
|
||
let s:migemo_dicts[&l:encoding] = s:load_migemo_dict()
|
||
endif
|
||
if re =~# '^\a$'
|
||
let re = s:migemo_dicts[&l:encoding][re]
|
||
endif
|
||
endif
|
||
|
||
if g:EasyMotion_smartcase && char =~# '\v\U'
|
||
let re = '\c' . re
|
||
else
|
||
let re = '\C' . re
|
||
endif
|
||
|
||
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
|
||
endfunction " }}}
|
||
function! EasyMotion#T(visualmode, direction) " {{{
|
||
let char = s:GetSearchChar(a:visualmode)
|
||
|
||
if empty(char)
|
||
return
|
||
endif
|
||
|
||
let re = escape(char, '.$^~')
|
||
|
||
if g:EasyMotion_use_migemo
|
||
if ! has_key(s:migemo_dicts, &l:encoding)
|
||
let s:migemo_dicts[&l:encoding] = s:load_migemo_dict()
|
||
endif
|
||
if re =~# '^\a$'
|
||
let re = s:migemo_dicts[&l:encoding][re]
|
||
endif
|
||
endif
|
||
|
||
if a:direction == 1
|
||
" backward
|
||
let re = re . '\zs.'
|
||
else
|
||
" forward
|
||
let re = '.\ze' . re
|
||
endif
|
||
|
||
if g:EasyMotion_smartcase && char =~# '\v\U'
|
||
let re = '\c' . re
|
||
else
|
||
let re = '\C' . re
|
||
endif
|
||
|
||
|
||
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1))
|
||
endfunction " }}}
|
||
|
||
function! EasyMotion#WB(visualmode, direction) " {{{
|
||
call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', '')
|
||
endfunction " }}}
|
||
function! EasyMotion#WBW(visualmode, direction) " {{{
|
||
call s:EasyMotion('\(\(^\|\s\)\@<=\S\|^$\)', a:direction, a:visualmode ? visualmode() : '', '')
|
||
endfunction " }}}
|
||
function! EasyMotion#E(visualmode, direction) " {{{
|
||
call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1))
|
||
endfunction " }}}
|
||
function! EasyMotion#EW(visualmode, direction) " {{{
|
||
call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1))
|
||
endfunction " }}}
|
||
|
||
function! EasyMotion#JK(visualmode, direction) " {{{
|
||
if g:EasyMotion_startofline
|
||
call s:EasyMotion('^\(\w\|\s*\zs\|$\)', a:direction, a:visualmode ? visualmode() : '', '')
|
||
else
|
||
let prev_column = getpos('.')[2] - 1
|
||
call s:EasyMotion('^.\{,' . prev_column . '}\zs\(.\|$\)', a:direction, a:visualmode ? visualmode() : '', '')
|
||
endif
|
||
endfunction " }}}
|
||
function! EasyMotion#Search(visualmode, direction) " {{{
|
||
call s:EasyMotion(@/, a:direction, a:visualmode ? visualmode() : '', '')
|
||
endfunction " }}}
|
||
|
||
function! EasyMotion#SelectLines() "{{{
|
||
let orig_pos = [line('.'), col('.')]
|
||
|
||
call s:EasyMotion('^\(\w\|\s*\zs\|$\)', 2, '', '', 0, 0, 1)
|
||
if g:EasyMotion_cancelled
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
return ''
|
||
else
|
||
let pos1 = [line('.'), col('.')]
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
call s:EasyMotion('^\(\w\|\s*\zs\|$\)', 2, '', '', pos1[0], 1, 1)
|
||
if g:EasyMotion_cancelled
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
return ''
|
||
else
|
||
normal! V
|
||
keepjumps call cursor(pos1[0], pos1[1])
|
||
endif
|
||
endif
|
||
endfunction "}}}
|
||
function! EasyMotion#SelectLinesYank() "{{{
|
||
let orig_pos = [line('.'), col('.')]
|
||
call EasyMotion#SelectLines()
|
||
normal y
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
"normal p
|
||
endfunction "}}}
|
||
|
||
function! EasyMotion#SelectPhrase() "{{{
|
||
let chars = s:GetSearchChar2(0)
|
||
if empty(chars)
|
||
return
|
||
endif
|
||
|
||
let orig_pos = [line('.'), col('.')]
|
||
|
||
if g:EasyMotion_smartcase && chars[0] =~# '\v\U' || chars[1] =~# '\v\U'
|
||
let re = '\c'
|
||
else
|
||
let re = '\C'
|
||
endif
|
||
|
||
let re = re . escape(chars[0], '.$^~') . '\|' . escape(chars[1], '.$^~')
|
||
call s:EasyMotion(re, 2, '', '', 0, 0, 0, 0)
|
||
if g:EasyMotion_cancelled
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
return ''
|
||
else
|
||
let pos1 = [line('.'), col('.')]
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
call s:EasyMotion(re, 2, '', '', 0, 0, 0, pos1)
|
||
if g:EasyMotion_cancelled
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
return ''
|
||
else
|
||
normal! v
|
||
keepjumps call cursor(pos1[0], pos1[1])
|
||
endif
|
||
endif
|
||
endfunction "}}}
|
||
function! EasyMotion#SelectPhraseYank() "{{{
|
||
let orig_pos = [line('.'), col('.')]
|
||
|
||
call EasyMotion#SelectPhrase()
|
||
normal y
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
endfunction "}}}
|
||
|
||
" }}}
|
||
" Helper functions {{{
|
||
function! s:Message(message) " {{{
|
||
echo 'EasyMotion: ' . a:message
|
||
endfunction " }}}
|
||
function! s:Prompt(message) " {{{
|
||
echohl Question
|
||
echo a:message . ': '
|
||
echohl None
|
||
endfunction " }}}
|
||
function! s:VarReset(var, ...) " {{{
|
||
if ! exists('s:var_reset')
|
||
let s:var_reset = {}
|
||
endif
|
||
|
||
if a:0 == 0 && has_key(s:var_reset, a:var)
|
||
" Reset var to original value
|
||
call setbufvar("", a:var, s:var_reset[a:var])
|
||
elseif a:0 == 1
|
||
let new_value = a:0 == 1 ? a:1 : ''
|
||
|
||
" Store original value
|
||
let s:var_reset[a:var] = getbufvar("", a:var)
|
||
|
||
" Set new var value
|
||
call setbufvar("", a:var, new_value)
|
||
endif
|
||
endfunction " }}}
|
||
function! s:SetLines(lines, key) " {{{
|
||
try
|
||
" Try to join changes with previous undo block
|
||
undojoin
|
||
catch
|
||
endtry
|
||
|
||
for [line_num, line] in a:lines
|
||
call setline(line_num, line[a:key])
|
||
endfor
|
||
endfunction " }}}
|
||
function! s:GetChar() " {{{
|
||
let char = getchar()
|
||
|
||
if char == 27
|
||
" Escape key pressed
|
||
redraw
|
||
|
||
call s:Message('Cancelled')
|
||
|
||
return ''
|
||
endif
|
||
|
||
return nr2char(char)
|
||
endfunction " }}}
|
||
|
||
function! s:GetSearchChar2(visualmode) " {{{
|
||
|
||
let chars = []
|
||
for i in [1, 2]
|
||
redraw
|
||
|
||
call s:Prompt('Search for character ' . i)
|
||
let char = s:GetChar()
|
||
|
||
" Check that we have an input char
|
||
if empty(char)
|
||
" Restore selection
|
||
if ! empty(a:visualmode)
|
||
silent exec 'normal! gv'
|
||
endif
|
||
|
||
return ''
|
||
endif
|
||
call add(chars, char)
|
||
endfor
|
||
|
||
return chars
|
||
endfunction " }}}
|
||
function! s:GetSearchChar(visualmode) " {{{
|
||
call s:Prompt('Search for character')
|
||
|
||
let char = s:GetChar()
|
||
|
||
" Check that we have an input char
|
||
if empty(char)
|
||
" Restore selection
|
||
if ! empty(a:visualmode)
|
||
silent exec 'normal! gv'
|
||
endif
|
||
|
||
return ''
|
||
endif
|
||
|
||
return char
|
||
endfunction " }}}
|
||
|
||
function! s:load_migemo_dict() "{{{
|
||
let enc = &l:encoding
|
||
if enc ==# 'utf-8'
|
||
return EasyMotion#migemo#utf8#load_dict()
|
||
elseif enc ==# 'cp932'
|
||
return EasyMotion#migemo#cp932#load_dict()
|
||
elseif enc ==# 'euc-jp'
|
||
return EasyMotion#migemo#eucjp#load_dict()
|
||
else
|
||
let g:EasyMotion_use_migemo = 0
|
||
throw "Error: ".enc." is not supported. Migemo is made disabled."
|
||
endif
|
||
endfunction "}}}
|
||
" }}}
|
||
" Grouping algorithms {{{
|
||
let s:grouping_algorithms = {
|
||
\ 1: 'SCTree'
|
||
\ , 2: 'Original'
|
||
\ }
|
||
" Single-key/closest target priority tree {{{
|
||
" This algorithm tries to assign one-key jumps to all the targets closest to the cursor.
|
||
" It works recursively and will work correctly with as few keys as two.
|
||
function! s:GroupingAlgorithmSCTree(targets, keys)
|
||
" Prepare variables for working
|
||
let targets_len = len(a:targets)
|
||
let keys_len = len(a:keys)
|
||
|
||
let groups = {}
|
||
|
||
let keys = reverse(copy(a:keys))
|
||
|
||
" Semi-recursively count targets {{{
|
||
" We need to know exactly how many child nodes (targets) this branch will have
|
||
" in order to pass the correct amount of targets to the recursive function.
|
||
|
||
" Prepare sorted target count list {{{
|
||
" This is horrible, I know. But dicts aren't sorted in vim, so we need to
|
||
" work around that. That is done by having one sorted list with key counts,
|
||
" and a dict which connects the key with the keys_count list.
|
||
|
||
let keys_count = []
|
||
let keys_count_keys = {}
|
||
|
||
let i = 0
|
||
for key in keys
|
||
call add(keys_count, 0)
|
||
|
||
let keys_count_keys[key] = i
|
||
|
||
let i += 1
|
||
endfor
|
||
" }}}
|
||
|
||
let targets_left = targets_len
|
||
let level = 0
|
||
let i = 0
|
||
|
||
while targets_left > 0
|
||
" Calculate the amount of child nodes based on the current level
|
||
let childs_len = (level == 0 ? 1 : (keys_len - 1) )
|
||
|
||
for key in keys
|
||
" Add child node count to the keys_count array
|
||
let keys_count[keys_count_keys[key]] += childs_len
|
||
|
||
" Subtract the child node count
|
||
let targets_left -= childs_len
|
||
|
||
if targets_left <= 0
|
||
" Subtract the targets left if we added too many too
|
||
" many child nodes to the key count
|
||
let keys_count[keys_count_keys[key]] += targets_left
|
||
|
||
break
|
||
endif
|
||
|
||
let i += 1
|
||
endfor
|
||
|
||
let level += 1
|
||
endwhile
|
||
" }}}
|
||
" Create group tree {{{
|
||
let i = 0
|
||
let key = 0
|
||
|
||
call reverse(keys_count)
|
||
|
||
for key_count in keys_count
|
||
if key_count > 1
|
||
" We need to create a subgroup
|
||
" Recurse one level deeper
|
||
let groups[a:keys[key]] = s:GroupingAlgorithmSCTree(a:targets[i : i + key_count - 1], a:keys)
|
||
elseif key_count == 1
|
||
" Assign single target key
|
||
let groups[a:keys[key]] = a:targets[i]
|
||
else
|
||
" No target
|
||
continue
|
||
endif
|
||
|
||
let key += 1
|
||
let i += key_count
|
||
endfor
|
||
" }}}
|
||
|
||
" Finally!
|
||
return groups
|
||
endfunction
|
||
" }}}
|
||
" Original {{{
|
||
function! s:GroupingAlgorithmOriginal(targets, keys)
|
||
" Split targets into groups (1 level)
|
||
let targets_len = len(a:targets)
|
||
let keys_len = len(a:keys)
|
||
|
||
let groups = {}
|
||
|
||
let i = 0
|
||
let root_group = 0
|
||
try
|
||
while root_group < targets_len
|
||
let groups[a:keys[root_group]] = {}
|
||
|
||
for key in a:keys
|
||
let groups[a:keys[root_group]][key] = a:targets[i]
|
||
|
||
let i += 1
|
||
endfor
|
||
|
||
let root_group += 1
|
||
endwhile
|
||
catch | endtry
|
||
|
||
" Flatten the group array
|
||
if len(groups) == 1
|
||
let groups = groups[a:keys[0]]
|
||
endif
|
||
|
||
return groups
|
||
endfunction
|
||
" }}}
|
||
" Coord/key dictionary creation {{{
|
||
function! s:CreateCoordKeyDict(groups, ...)
|
||
" Dict structure:
|
||
" 1,2 : a
|
||
" 2,3 : b
|
||
let sort_list = []
|
||
let coord_keys = {}
|
||
let group_key = a:0 == 1 ? a:1 : ''
|
||
|
||
for [key, item] in items(a:groups)
|
||
let key = group_key . key "( ! empty(group_key) ? group_key : key)
|
||
"let key = ( ! empty(group_key) ? group_key : key)
|
||
|
||
if type(item) == 3
|
||
" Destination coords
|
||
|
||
" The key needs to be zero-padded in order to
|
||
" sort correctly
|
||
let dict_key = printf('%05d,%05d', item[0], item[1])
|
||
let coord_keys[dict_key] = key
|
||
|
||
" We need a sorting list to loop correctly in
|
||
" PromptUser, dicts are unsorted
|
||
call add(sort_list, dict_key)
|
||
else
|
||
" Item is a dict (has children)
|
||
let coord_key_dict = s:CreateCoordKeyDict(item, key)
|
||
|
||
" Make sure to extend both the sort list and the
|
||
" coord key dict
|
||
call extend(sort_list, coord_key_dict[0])
|
||
call extend(coord_keys, coord_key_dict[1])
|
||
endif
|
||
|
||
unlet item
|
||
endfor
|
||
|
||
return [sort_list, coord_keys]
|
||
endfunction
|
||
" }}}
|
||
" }}}
|
||
" Core functions {{{
|
||
function! s:PromptUser(groups, allows_repeat, fixed_column) "{{{
|
||
|
||
" If only one possible match, jump directly to it {{{
|
||
let group_values = values(a:groups)
|
||
|
||
if len(group_values) == 1
|
||
redraw
|
||
|
||
return group_values[0]
|
||
endif
|
||
" }}}
|
||
" Prepare marker lines {{{
|
||
let lines = {}
|
||
let hl_coords = []
|
||
let hl2_first_coords = [] " Highlight for two characters
|
||
let hl2_second_coords = [] " Highlight for two characters
|
||
|
||
let coord_key_dict = s:CreateCoordKeyDict(a:groups)
|
||
|
||
for dict_key in sort(coord_key_dict[0])
|
||
let target_key = coord_key_dict[1][dict_key]
|
||
let [line_num, col_num] = split(dict_key, ',')
|
||
|
||
let line_num = str2nr(line_num)
|
||
let col_num = str2nr(col_num)
|
||
|
||
" Add original line and marker line
|
||
if ! has_key(lines, line_num)
|
||
let current_line = getline(line_num)
|
||
|
||
let lines[line_num] = { 'orig': current_line, 'marker': current_line, 'mb_compensation': 0 }
|
||
|
||
endif
|
||
|
||
" Solve multibyte issues by matching the byte column
|
||
" number instead of the visual column
|
||
"echom lines[line_num]['mb_compensation']
|
||
let col_num -= lines[line_num]['mb_compensation']
|
||
"if col_num < 0
|
||
" let col_num = 0
|
||
"endif
|
||
|
||
" Compensate for byte difference between marker
|
||
" character and target character
|
||
"
|
||
" This has to be done in order to match the correct
|
||
" column; \%c matches the byte column and not display
|
||
" column.
|
||
let target_char_len = strdisplaywidth(matchstr(lines[line_num]['marker'], '\%' . col_num . 'c.'))
|
||
let target_key_len = strdisplaywidth(target_key)
|
||
|
||
|
||
let target_line_byte_len = strlen(lines[line_num]['marker'])
|
||
|
||
let target_char_byte_len = strlen(matchstr(lines[line_num]['marker'], '\%' . col_num . 'c.'))
|
||
|
||
if a:fixed_column
|
||
let firstS = match(lines[line_num]['marker'], '\S')
|
||
if firstS >= 4
|
||
let leftText = strpart(lines[line_num]['marker'], 0, firstS - 3)
|
||
else
|
||
let leftText = ''
|
||
endif
|
||
|
||
if firstS >= 1
|
||
let rightText = strpart(lines[line_num]['marker'], firstS - 1)
|
||
elseif firstS == 0
|
||
let rightText = ' ' . lines[line_num]['marker']
|
||
else
|
||
let rightText = ''
|
||
endif
|
||
|
||
if target_key_len < 2
|
||
let text = ' ' . target_key
|
||
call add(hl_coords, '\%' . line_num . 'l\%2c')
|
||
else
|
||
let text = target_key
|
||
call add(hl2_first_coords, '\%' . line_num . 'l\%1c')
|
||
call add(hl2_second_coords, '\%' . line_num . 'l\%2c')
|
||
endif
|
||
let lines[line_num]['marker'] = text . ' ' . lines[line_num]['marker']
|
||
else
|
||
if strlen(lines[line_num]['marker']) > 0
|
||
" Substitute marker character if line length > 0
|
||
|
||
let c = 0
|
||
while c < target_key_len && c < 2
|
||
if strlen(lines[line_num]['marker']) >= col_num + c
|
||
" Substitute marker character if line length > 0
|
||
if c == 0
|
||
let lines[line_num]['marker'] = substitute(
|
||
\ lines[line_num]['marker'],
|
||
\ '\%' . (col_num + c) . 'c.',
|
||
\ strpart(target_key, c, 1) . repeat(' ', target_char_len - 1),
|
||
\ '')
|
||
else
|
||
let lines[line_num]['marker'] = substitute(
|
||
\ lines[line_num]['marker'],
|
||
\ '\%' . (col_num + c) . 'c.',
|
||
\ strpart(target_key, c, 1),
|
||
\ '')
|
||
endif
|
||
else
|
||
let lines[line_num]['marker'] = lines[line_num]['marker'] . strpart(target_key, c, 1)
|
||
endif
|
||
let c += 1
|
||
endwhile
|
||
else
|
||
" Set the line to the marker character if the line is empty
|
||
let lines[line_num]['marker'] = target_key
|
||
endif
|
||
endif
|
||
|
||
" Add highlighting coordinates
|
||
|
||
if !a:fixed_column
|
||
if target_key_len == 1
|
||
call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
|
||
else
|
||
call add(hl2_first_coords, '\%' . line_num . 'l\%' . (col_num) . 'c')
|
||
call add(hl2_second_coords, '\%' . line_num . 'l\%' . (col_num + 1) . 'c')
|
||
endif
|
||
endif
|
||
|
||
" Add marker/target lenght difference for multibyte
|
||
" compensation
|
||
let lines[line_num]['mb_compensation'] += (target_line_byte_len - strlen(lines[line_num]['marker']) )
|
||
endfor
|
||
|
||
let lines_items = items(lines)
|
||
" }}}
|
||
" Highlight targets {{{
|
||
if len(hl_coords) > 0
|
||
let target_hl_id = matchadd(g:EasyMotion_hl_group_target, join(hl_coords, '\|'), 1)
|
||
endif
|
||
if len(hl2_second_coords) > 0
|
||
let target_hl2_second_id = matchadd(g:EasyMotion_hl2_second_group_target, join(hl2_second_coords, '\|'), 1)
|
||
endif
|
||
if len(hl2_first_coords) > 0
|
||
let target_hl2_first_id = matchadd(g:EasyMotion_hl2_first_group_target, join(hl2_first_coords, '\|'), 1)
|
||
endif
|
||
|
||
" }}}
|
||
try
|
||
" Set lines with markers
|
||
call s:SetLines(lines_items, 'marker')
|
||
|
||
redraw
|
||
|
||
" Get target character {{{
|
||
call s:Prompt('Target key')
|
||
|
||
let char = s:GetChar()
|
||
" }}}
|
||
finally
|
||
" Restore original lines
|
||
call s:SetLines(lines_items, 'orig')
|
||
|
||
" Un-highlight targets {{{
|
||
if exists('target_hl_id')
|
||
call matchdelete(target_hl_id)
|
||
endif
|
||
if exists('target_hl2_first_id')
|
||
call matchdelete(target_hl2_first_id)
|
||
endif
|
||
if exists('target_hl2_second_id')
|
||
call matchdelete(target_hl2_second_id)
|
||
endif
|
||
" }}}
|
||
|
||
redraw
|
||
endtry
|
||
|
||
" Check if we have an input char {{{
|
||
if empty(char)
|
||
throw 'Cancelled'
|
||
endif
|
||
" }}}
|
||
" Check if the input char is valid {{{
|
||
if a:allows_repeat && char == '.'
|
||
return g:old_target
|
||
else
|
||
if ! has_key(a:groups, char)
|
||
throw 'Invalid target'
|
||
endif
|
||
" }}}
|
||
|
||
let target = a:groups[char]
|
||
|
||
if type(target) == 3
|
||
" Return target coordinates
|
||
return target
|
||
else
|
||
" Prompt for new target character
|
||
return s:PromptUser(target, a:allows_repeat, a:fixed_column)
|
||
endif
|
||
endif
|
||
endfunction "}}}
|
||
|
||
function! s:EasyMotion(regexp, direction, visualmode, mode, ...) " {{{
|
||
" For SelectLines(), to highlight previous selected line
|
||
let hlcurrent = a:0 >= 1 ? a:1 : 0
|
||
" For SelectLines(), to allows '.' to repeat the previously pressed
|
||
" character
|
||
let allows_repeat = a:0 >= 2 ? a:2 : 0
|
||
" For SelectLines(), a flag to display character only at the beginning
|
||
" of the line
|
||
let fixed_column = a:0 >= 3 ? a:3 : 0
|
||
|
||
let hlchar = a:0 >= 4 ? a:4 : 0
|
||
|
||
let orig_pos = [line('.'), col('.')]
|
||
let targets = []
|
||
|
||
try
|
||
" Reset properties {{{
|
||
call s:VarReset('&scrolloff', 0)
|
||
call s:VarReset('&modified', 0)
|
||
call s:VarReset('&modifiable', 1)
|
||
call s:VarReset('&readonly', 0)
|
||
call s:VarReset('&spell', 0)
|
||
call s:VarReset('&virtualedit', '')
|
||
call s:VarReset('&foldmethod', 'manual')
|
||
" }}}
|
||
" Find motion targets {{{
|
||
" Setup searchpos args {{{
|
||
let search_direction = (a:direction >= 1 ? 'b' : '')
|
||
let search_stopline = line(a:direction >= 1 ? 'w0' : 'w$')
|
||
let search_at_cursor = fixed_column ? 'c' : ''
|
||
"}}}
|
||
|
||
" Handle visual mode {{{
|
||
if ! empty(a:visualmode)
|
||
" Decide at where visual mode start {{{
|
||
normal! gv
|
||
let c_pos = [line("."),col(".")]
|
||
let v_start = [line("'<"),col("'<")]
|
||
let v_end = [line("'>"),col("'>")]
|
||
|
||
let vmode = mode(1)
|
||
if match('Vv',vmode) < 0
|
||
throw 'Unkown visual mode:'.vmode
|
||
elseif vmode ==# 'V' "line-wise Visual
|
||
" Line-wise Visual {{{
|
||
if v_start[0] == v_end[0]
|
||
if search_direction == ''
|
||
let v_pos = v_start
|
||
elseif search_direction == 'b'
|
||
let v_pos = v_end
|
||
else
|
||
throw 'Unkown search_direction'
|
||
endif
|
||
else
|
||
if c_pos[0] == v_start[0]
|
||
let v_pos = v_end
|
||
|
||
elseif c_pos[0] == v_end[0]
|
||
let v_pos = v_start
|
||
endif
|
||
endif
|
||
"}}}
|
||
else
|
||
" Character-wise or Block-wise Visual"{{{
|
||
if c_pos == v_start
|
||
let v_pos = v_end
|
||
elseif c_pos == v_end
|
||
let v_pos = v_start
|
||
else
|
||
throw 'Unkown c_pos'
|
||
endif
|
||
"}}}
|
||
endif
|
||
"}}}
|
||
" Reselect visual text {{{
|
||
keepjumps call cursor(v_pos)
|
||
exec "normal! " . a:visualmode
|
||
keepjumps call cursor(c_pos)
|
||
"}}}
|
||
" Update orig_pos {{{
|
||
let orig_pos = v_pos
|
||
" }}}
|
||
endif
|
||
" }}}
|
||
|
||
" Construct match dict {{{
|
||
while 1
|
||
" Note: searchpos() has side effect which call jump cursor position.
|
||
" You can disable this side effect by add 'n' flags,
|
||
" but in this case, it's better to allows jump side effect.
|
||
let pos = searchpos(a:regexp, search_direction . search_at_cursor, search_stopline)
|
||
let search_at_cursor = ''
|
||
|
||
" Reached end of search range
|
||
if pos == [0, 0]
|
||
break
|
||
endif
|
||
|
||
" Skip folded lines
|
||
if foldclosed(pos[0]) != -1 && (g:EasyMotion_skipfoldedline == 1 || pos[0] != foldclosed(pos[0]))
|
||
continue
|
||
endif
|
||
|
||
call add(targets, pos)
|
||
endwhile
|
||
"}}}
|
||
|
||
" Handle direction == 2"{{{
|
||
" Reconstruct match dict
|
||
if a:direction == 2
|
||
if ! empty(a:visualmode)
|
||
keepjumps call cursor(c_pos[0], c_pos[1])
|
||
else
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
endif
|
||
let targets2 = []
|
||
while 1
|
||
let pos = searchpos(a:regexp, '', line('w$'))
|
||
if pos == [0, 0]
|
||
break
|
||
endif
|
||
|
||
" Skip folded lines {{{
|
||
if foldclosed(pos[0]) != -1 && (g:EasyMotion_skipfoldedline == 1 || pos[0] != foldclosed(pos[0]))
|
||
continue
|
||
endif
|
||
"}}}
|
||
|
||
call add(targets2, pos)
|
||
endwhile
|
||
" Merge match target dict"{{{
|
||
let t1 = 0
|
||
let t2 = 0
|
||
let targets3 = []
|
||
while t1 < len(targets) || t2 < len(targets2)
|
||
if t1 < len(targets)
|
||
call add(targets3, targets[t1])
|
||
let t1 += 1
|
||
endif
|
||
if t2 < len(targets2)
|
||
call add(targets3, targets2[t2])
|
||
let t2 += 1
|
||
endif
|
||
endwhile
|
||
let targets = targets3
|
||
"}}}
|
||
endif
|
||
"}}}
|
||
|
||
" Handle no match"{{{
|
||
let targets_len = len(targets)
|
||
if targets_len == 0
|
||
throw 'No matches'
|
||
endif
|
||
"}}}
|
||
" }}}
|
||
|
||
let GroupingFn = function('s:GroupingAlgorithm' . s:grouping_algorithms[g:EasyMotion_grouping])
|
||
let groups = GroupingFn(targets, split(g:EasyMotion_keys, '\zs'))
|
||
|
||
" Shade inactive source {{{
|
||
if g:EasyMotion_do_shade
|
||
let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'
|
||
|
||
if a:direction == 1
|
||
" Backward
|
||
let shade_hl_re = '\%'. line('w0') .'l\_.*' . shade_hl_pos
|
||
elseif a:direction == 0
|
||
" Forward
|
||
let shade_hl_re = shade_hl_pos . '\_.*\%'. line('w$') .'l'
|
||
elseif a:direction == 2
|
||
" Both directions"
|
||
let shade_hl_re = '\%'. line('w0') .'l\_.*\%'. line('w$') .'l'
|
||
endif
|
||
if !fixed_column
|
||
let shade_hl_id = matchadd(g:EasyMotion_hl_group_shade, shade_hl_re, 0)
|
||
endif
|
||
endif
|
||
if hlcurrent != 0
|
||
let shade_hl_line_id = matchadd(g:EasyMotion_hl_line_group_shade, '\%'. hlcurrent .'l.*', 1)
|
||
endif
|
||
if !empty(hlchar)
|
||
let shade_hl_line_id = matchadd(g:EasyMotion_hl_line_group_shade, '\%'. hlchar[0] .'l\%' . hlchar[1] .'c' , 2)
|
||
endif
|
||
" }}}
|
||
|
||
" Prompt user for target group/character"{{{
|
||
let coords = s:PromptUser(groups, allows_repeat, fixed_column)
|
||
let g:old_target = coords
|
||
"}}}
|
||
|
||
" Update selection {{{
|
||
if ! empty(a:visualmode)
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
|
||
exec 'normal! ' . a:visualmode
|
||
endif
|
||
" }}}
|
||
" Handle operator-pending mode {{{
|
||
if a:mode == 'no'
|
||
" This mode requires that we eat one more
|
||
" character to the right if we're using
|
||
" a forward motion
|
||
if a:direction != 1
|
||
let coords[1] += 1
|
||
endif
|
||
endif
|
||
" }}}
|
||
|
||
" Update cursor position"{{{
|
||
call cursor(orig_pos[0], orig_pos[1])
|
||
let mark_save = getpos("'e")
|
||
call setpos("'e", [bufnr('%'), coords[0], coords[1], 0])
|
||
execute 'normal! `e'
|
||
call setpos("'e", mark_save)
|
||
|
||
call s:Message('Jumping to [' . coords[0] . ', ' . coords[1] . ']')
|
||
let g:EasyMotion_cancelled = 0
|
||
"}}}
|
||
catch
|
||
redraw
|
||
|
||
" Show exception message
|
||
call s:Message(v:exception)
|
||
|
||
" Restore original cursor position/selection {{{
|
||
if ! empty(a:visualmode)
|
||
silent exec 'normal! gv'
|
||
keepjumps call cursor(c_pos[0], c_pos[1])
|
||
else
|
||
keepjumps call cursor(orig_pos[0], orig_pos[1])
|
||
endif
|
||
" }}}
|
||
let g:EasyMotion_cancelled = 1
|
||
finally
|
||
" Restore properties {{{
|
||
call s:VarReset('&scrolloff')
|
||
call s:VarReset('&modified')
|
||
call s:VarReset('&modifiable')
|
||
call s:VarReset('&readonly')
|
||
call s:VarReset('&spell')
|
||
call s:VarReset('&virtualedit')
|
||
call s:VarReset('&foldmethod')
|
||
" }}}
|
||
" Remove shading {{{
|
||
if g:EasyMotion_do_shade && exists('shade_hl_id') && (!fixed_column)
|
||
call matchdelete(shade_hl_id)
|
||
endif
|
||
if (hlcurrent || !empty(hlchar)) && exists('shade_hl_line_id')
|
||
call matchdelete(shade_hl_line_id)
|
||
endif
|
||
" }}}
|
||
endtry
|
||
endfunction " }}}
|
||
" }}}
|
||
|
||
" vim: fdm=marker:noet:ts=4:sw=4:sts=4
|