vim-easymotion/autoload/EasyMotion.vim
YggdrasiI 8a4f00b3a0 -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.
2016-02-08 19:31:57 +01:00

1635 lines
56 KiB
VimL

scriptencoding utf-8
" EasyMotion - Vim motions on speed!
"
" Author: Kim Silkebækken <kim.silkebaekken+vim@gmail.com>
" haya14busa <hayabusa1419@gmail.com>
" Source: https://github.com/easymotion/vim-easymotion
"=============================================================================
" Saving 'cpoptions' {{{
let s:save_cpo = &cpo
set cpo&vim
" }}}
let s:TRUE = !0
let s:FALSE = 0
let s:DIRECTION = { 'forward': 0, 'backward': 1, 'bidirection': 2}
" Init: {{{
let s:loaded = s:FALSE
function! EasyMotion#init()
if s:loaded
return
endif
let s:loaded = s:TRUE
call EasyMotion#highlight#load()
" Store previous motion info
let s:previous = {}
" Store previous operator-pending motion info for '.' repeat
let s:dot_repeat = {}
" Prepare 1-key Migemo Dictionary
let s:migemo_dicts = {}
let s:EasyMotion_is_active = 0
call EasyMotion#reset()
" Anywhere regular expression: {{{
let re = '\v' .
\ '(<.|^$)' . '|' .
\ '(.>|^$)' . '|' .
\ '(\l)\zs(\u)' . '|' .
\ '(_\zs.)' . '|' .
\ '(#\zs.)'
" 1. word
" 2. end of word
" 3. CamelCase
" 4. after '_' hoge_foo
" 5. after '#' hoge#foo
let g:EasyMotion_re_anywhere = get(g:, 'EasyMotion_re_anywhere', re)
" Anywhere regular expression within line:
let re = '\v' .
\ '(<.|^$)' . '|' .
\ '(.>|^$)' . '|' .
\ '(\l)\zs(\u)' . '|' .
\ '(_\zs.)' . '|' .
\ '(#\zs.)'
let g:EasyMotion_re_line_anywhere = get(g:, 'EasyMotion_re_line_anywhere', re)
"}}}
" For other plugin
let s:EasyMotion_is_cancelled = 0
" 0 -> Success
" 1 -> Cancel
let g:EasyMotion_ignore_exception = 0
return ""
endfunction
"}}}
" Reset: {{{
function! EasyMotion#reset()
let s:flag = {
\ 'within_line' : 0,
\ 'dot_repeat' : 0,
\ 'regexp' : 0,
\ 'bd_t' : 0,
\ 'find_bd' : 0,
\ 'linewise' : 0,
\ 'count_dot_repeat' : 0,
\ }
" regexp: -> regular expression
" This value is used when multi input find motion. If this values is
" 1, input text is treated as regexp.(Default: escaped)
" bd_t: -> bi-directional 't' motion
" This value is used to re-define regexp only for bi-directional 't'
" motion
" find_bd: -> bi-directional find motion
" This value is used to recheck the motion is inclusive or exclusive
" because 'f' & 't' forward find motion is inclusive, but 'F' & 'T'
" backward find motion is exclusive
" count_dot_repeat: -> dot repeat with count
" https://github.com/easymotion/vim-easymotion/issues/164
let s:current = {
\ 'is_operator' : 0,
\ 'is_search' : 0,
\ 'dot_repeat_target_cnt' : 0,
\ 'dot_prompt_user_cnt' : 0,
\ 'changedtick' : 0,
\ }
" \ 'start_position' : [],
" \ 'cursor_position' : [],
" is_operator:
" Store is_operator value first because mode(1) value will be
" changed by some operation.
" dot_* :
" These values are used when '.' repeat for automatically
" select marker/label characters.(Using count avoid recursive
" prompt)
" changedtick:
" :h b:changedtick
" This value is used to avoid side effect of overwriting buffer text
" which will change b:changedtick value. To overwrite g:repeat_tick
" value(defined tpope/vim-repeat), I can avoid this side effect of
" conflicting with tpope/vim-repeat
" start_position:
" Original, start cursor position.
" cursor_position:
" Usually, this values is same with start_position, but in
" visualmode and 'n' key motion, this value could be different.
return ""
endfunction "}}}
" Motion Functions: {{{
" -- Find Motion -------------------------
" Note: {{{
" num_strokes:
" The number of input characters. Currently provide 1, 2, or -1.
" '-1' means no limit.
" visualmode:
" Vim script couldn't detect the function is called in visual mode by
" mode(1), so tell whether it is in visual mode by argument explicitly
" direction:
" 0 -> forward
" 1 -> backward
" 2 -> bi-direction (handle forward & backward at the same time) }}}
function! EasyMotion#S(num_strokes, visualmode, direction) " {{{
if a:direction == 1
let is_inclusive = 0
else
" Note: Handle bi-direction later because 'f' motion is inclusive but
" 'F' motion is exclusive
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
endif
let s:flag.find_bd = a:direction == 2 ? 1 : 0
let re = s:findMotion(a:num_strokes, a:direction)
if s:handleEmpty(re, a:visualmode) | return | endif
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#OverwinF(num_strokes) " {{{
let re = s:findMotion(a:num_strokes, s:DIRECTION.bidirection)
call EasyMotion#reset()
if re isnot# ''
return EasyMotion#overwin#move(re)
endif
endfunction "}}}
function! EasyMotion#T(num_strokes, visualmode, direction) " {{{
if a:direction == 1
let is_inclusive = 0
else
" Note: Handle bi-direction later because 't' motion is inclusive but
" 'T' motion is exclusive
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
endif
let s:flag.find_bd = a:direction == 2 ? 1 : 0
let re = s:findMotion(a:num_strokes, a:direction)
if s:handleEmpty(re, a:visualmode) | return | endif
if a:direction == 2
let s:flag.bd_t = 1
elseif a:direction == 1
let re = s:convert_t_regexp(re, 1) " backward
else
let re = s:convert_t_regexp(re, 0) " forward
endif
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive)
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- Word Motion -------------------------
function! EasyMotion#WB(visualmode, direction) " {{{
"FIXME: inconsistent with default vim motion
"FIXED: -> EasyMotion#WBK()
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#WBW(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
" Note: Previous regex for all directions was '\(\(^\|\s\)\@<=\S\|^$\)'
let l:regex_without_file_ends = '\v(^|\s)\zs\S|^$'
let l:regex = l:regex_without_file_ends
\ . (a:direction == 1 ? '' : '|%$')
\ . (a:direction == 0 ? '' : '|%^')
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#WBK(visualmode, direction) " {{{
" vim's iskeyword style word motion
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
" Note: Previous regex for all directions was '\(\(\<\|\>\|\s\)\@<=\S\|^$\)'
let l:regex_without_file_ends = '\v<|^\S|\s\zs\S|>\zs\S|^$'
let l:regex = l:regex_without_file_ends
\ . (a:direction == 1 ? '' : '|%$')
\ . (a:direction == 0 ? '' : '|%^')
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#E(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#EW(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
" Note: Previous regex for all directions was '\(\S\(\s\|$\)\|^$\)'
" Note: The stopping positions for 'E' and 'gE' differs. Thus, the regex
" for direction==2 cannot be the same in both directions. This will be
" ignored.
let l:regex_stub = '\v\S(\s|$)'
let l:regex = l:regex_stub
\ . (a:direction == 0 ? '' : '|^$|%^')
\ . (a:direction == 1 ? '' : '|%$')
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#EK(visualmode, direction) " {{{
" vim's iskeyword style word motion
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
" Note: Previous regex for all directions was '\(\S\(\>\|\<\|\s\)\@=\|^$\)'
" Note: The stopping positions for 'e' and 'ge' differs. Thus, the regex
" for direction==2 cannot be the same in both directions. This will be
" ignored.
let l:regex_stub = '\v.\ze>|\S\ze\s*$|\S\ze\s|\k\zs>\S\ze|\S<'
let l:regex = l:regex_stub
\ . (a:direction == 0 ? '' : '|^$|%^')
\ . (a:direction == 1 ? '' : '|%$')
call s:EasyMotion(l:regex, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- JK Motion ---------------------------
function! EasyMotion#JK(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let s:flag.linewise = 1
if g:EasyMotion_startofline
call s:EasyMotion('^\(\w\|\s*\zs\|$\)', a:direction, a:visualmode ? visualmode() : '', 0)
else
let vcol = EasyMotion#helper#vcol('.')
let pattern = printf('^.\{-}\zs\(\%%<%dv.\%%>%dv\|$\)', vcol + 1, vcol)
call s:EasyMotion(pattern, a:direction, a:visualmode ? visualmode() : '', 0)
endif
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#Sol(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let s:flag.linewise = 1
call s:EasyMotion('^\(.\|$\)', a:direction, a:visualmode ? visualmode() : '', '')
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#Eol(visualmode, direction) " {{{
let s:flag.linewise = 1
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
call s:EasyMotion('\(\w\|\s*\zs\|.\|^\)$', a:direction, a:visualmode ? visualmode() : '', '')
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- Search Motion -----------------------
function! EasyMotion#Search(visualmode, direction, respect_direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let search_direction = a:respect_direction ?
\ (a:direction == 1 ? v:searchforward : 1-v:searchforward) :
\ (a:direction)
call s:EasyMotion(@/, search_direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- JumpToAnywhere Motion ---------------
function! EasyMotion#JumpToAnywhere(visualmode, direction) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
call s:EasyMotion( g:EasyMotion_re_anywhere, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- Line Motion -------------------------
function! EasyMotion#SL(num_strokes, visualmode, direction) " {{{
let s:flag.within_line = 1
call EasyMotion#S(a:num_strokes, a:visualmode, a:direction)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#TL(num_strokes, visualmode, direction) " {{{
let s:flag.within_line = 1
call EasyMotion#T(a:num_strokes, a:visualmode, a:direction)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#WBL(visualmode, direction) " {{{
let s:flag.within_line = 1
call EasyMotion#WBK(a:visualmode, a:direction)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#EL(visualmode, direction) " {{{
let s:flag.within_line = 1
call EasyMotion#EK(a:visualmode, a:direction)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#LineAnywhere(visualmode, direction) " {{{
let s:flag.within_line = 1
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let re = g:EasyMotion_re_line_anywhere
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', 0)
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- User Motion -------------------------
let s:config = {
\ 'pattern': '',
\ 'visualmode': s:FALSE,
\ 'direction': s:DIRECTION.forward,
\ 'inclusive': s:FALSE,
\ 'accept_cursor_pos': s:FALSE,
\ 'overwin': s:FALSE
\ }
function! s:default_config() abort
let c = copy(s:config)
let m = mode(1)
let c.inclusive = m ==# 'no' ? s:TRUE : s:FALSE
return c
endfunction
function! EasyMotion#go(...) abort
let c = extend(s:default_config(), get(a:, 1, {}))
if c.overwin
return EasyMotion#overwin#move(c.pattern)
else
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
call s:EasyMotion(c.pattern, c.direction, c.visualmode ? visualmode() : '', c.inclusive, c)
return s:EasyMotion_is_cancelled
endif
endfunction
function! EasyMotion#User(pattern, visualmode, direction, inclusive, ...) " {{{
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
let is_inclusive = mode(1) ==# 'no' ? a:inclusive : 0
let re = a:pattern
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive, get(a:, 1, {}))
return s:EasyMotion_is_cancelled
endfunction " }}}
" -- Repeat Motion -----------------------
function! EasyMotion#Repeat(visualmode) " {{{
" Repeat previous motion with previous targets
if !has_key(s:previous, 'regexp')
call s:Message("Previous targets doesn't exist")
let s:EasyMotion_is_cancelled = 1
return s:EasyMotion_is_cancelled
endif
let re = s:previous.regexp
let direction = s:previous.direction
let s:flag.within_line = s:previous.line_flag
let s:flag.bd_t = s:previous.bd_t_flag
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
" FIXME: is_inclusive value is inappropriate but handling this value is
" difficult and priorities is low because this motion maybe used usually
" as a 'normal' motion.
let is_inclusive = mode(1) ==# 'no' ? 1 : 0
call s:EasyMotion(re, direction, a:visualmode ? visualmode() : '', is_inclusive)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#DotRepeat() " {{{
let cnt = v:count1 " avoid overwriting
" Repeat previous '.' motion with previous targets and operator
if !has_key(s:dot_repeat, 'regexp')
call s:Message("Previous motion doesn't exist")
let s:EasyMotion_is_cancelled = 1
return s:EasyMotion_is_cancelled
endif
let re = s:dot_repeat.regexp
let direction = s:dot_repeat.direction
let is_inclusive = s:dot_repeat.is_inclusive
for i in range(cnt)
" s:EasyMotion() always call reset s:flag & s:current
let s:flag.dot_repeat = 1
let s:flag.within_line = s:dot_repeat.line_flag
let s:flag.bd_t = s:dot_repeat.bd_t_flag
let s:current.is_operator = 1
let s:flag.count_dot_repeat = (i > 0 ? 1 : 0)
silent call s:EasyMotion(re, direction, 0, is_inclusive)
endfor
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#NextPrevious(visualmode, direction) " {{{
" Move next/previous destination using previous motion regexp
let cnt = v:count1 " avoid overwriting
if !has_key(s:previous, 'regexp')
call s:Message("Previous targets doesn't exist")
let s:EasyMotion_is_cancelled = 1
return s:EasyMotion_is_cancelled
endif
let re = s:previous.regexp
let search_direction = (a:direction == 1 ? 'b' : '')
if g:EasyMotion_move_highlight
call EasyMotion#highlight#attach_autocmd()
call EasyMotion#highlight#add_highlight(re, g:EasyMotion_hl_move)
endif
if ! empty(a:visualmode)
" FIXME: blink highlight
silent exec 'normal! gv'
endif
" Mark jump-list
if cnt > 1
" Consider Next/Previous motions as jump motion :h jump-motion
" Note: It should add jumplist even if the count isn't given
" considering vim's default behavior of `n` & `N`, but just
" I don't want to do it without the count. Should I add a
" option?
normal! m`
endif
" Jump
" @vimlint(EVL102, 1, l:_)
for _ in range(cnt)
keepjumps call searchpos(re, search_direction)
endfor
normal! zv
call EasyMotion#reset()
" -- Activate EasyMotion ----------------- {{{
let s:EasyMotion_is_active = 1
call EasyMotion#attach_active_autocmd() "}}}
return s:EasyMotion_is_cancelled
endfunction " }}}
" }}}
" Helper Functions: {{{
" -- Message -----------------------------
function! s:Message(message) " {{{
if g:EasyMotion_verbose
echo 'EasyMotion: ' . a:message
else
" Make the current message dissapear
echo ''
" redraw
endif
endfunction " }}}
function! s:Prompt(message) " {{{
echohl Question
echo a:message . ': '
echohl None
endfunction " }}}
function! s:Throw(message) "{{{
throw 'EasyMotion: ' . a:message
endfunction "}}}
" -- Save & Restore values ---------------
function! s:SaveValue() "{{{
if ! s:current.is_search
call EasyMotion#helper#VarReset('&scrolloff', 0)
endif
call EasyMotion#helper#VarReset('&modified', 0)
call EasyMotion#helper#VarReset('&modifiable', 1)
call EasyMotion#helper#VarReset('&readonly', 0)
call EasyMotion#helper#VarReset('&spell', 0)
call EasyMotion#helper#VarReset('&virtualedit', '')
" if &foldmethod !=# 'expr'
call EasyMotion#helper#VarReset('&foldmethod', 'manual')
" endif
endfunction "}}}
function! s:RestoreValue() "{{{
call EasyMotion#helper#VarReset('&scrolloff')
call EasyMotion#helper#VarReset('&modified')
call EasyMotion#helper#VarReset('&modifiable')
call EasyMotion#helper#VarReset('&readonly')
call EasyMotion#helper#VarReset('&spell')
call EasyMotion#helper#VarReset('&virtualedit')
" if &foldmethod !=# 'expr'
call EasyMotion#helper#VarReset('&foldmethod')
" endif
endfunction "}}}
function! s:turn_off_hl_error() "{{{
let s:error_hl = EasyMotion#highlight#capture('Error')
call EasyMotion#highlight#turn_off(s:error_hl)
let s:matchparen_hl = EasyMotion#highlight#capture('MatchParen')
call EasyMotion#highlight#turn_off(s:matchparen_hl)
endfunction "}}}
function! s:turn_on_hl_error() "{{{
if exists('s:error_hl')
call EasyMotion#highlight#turn_on(s:error_hl)
unlet s:error_hl
endif
if exists('s:matchparen_hl')
call EasyMotion#highlight#turn_on(s:matchparen_hl)
unlet s:matchparen_hl
endif
endfunction "}}}
" -- Draw --------------------------------
function! s:SetLines(lines, key) " {{{
for [line_num, line] in a:lines
keepjumps call setline(line_num, line[a:key])
endfor
endfunction " }}}
" -- Get characters from user input ------
function! s:GetChar(...) abort "{{{
let mode = get(a:, 1, 0)
while 1
" Workaround for https://github.com/osyo-manga/vital-over/issues/53
try
let char = call('getchar', a:000)
catch /^Vim:Interrupt$/
let char = 3 " <C-c>
endtry
if char == 27 || char == 3
" Escape or <C-c> key pressed
redraw
call s:Message('Cancelled')
return ''
endif
" Workaround for the <expr> mappings
if string(char) !=# "\x80\xfd`"
return mode == 1 ? !!char
\ : type(char) == type(0) ? nr2char(char) : char
endif
endwhile
endfunction "}}}
" -- Find Motion Helper ------------------
function! s:findMotion(num_strokes, direction) "{{{
" Find Motion: S,F,T
let s:current.is_operator = mode(1) ==# 'no' ? 1: 0
" store cursor pos because 'n' key find motion could be jump to offscreen
let s:current.original_position = [line('.'), col('.')]
let s:current.is_search = a:num_strokes == -1 ? 1: 0
let s:flag.regexp = a:num_strokes == -1 ? 1 : 0 " TODO: remove?
if g:EasyMotion_add_search_history && a:num_strokes == -1
let s:previous['input'] = @/
else
let s:previous['input'] = get(s:previous, 'input', '')
endif
let input = EasyMotion#command_line#GetInput(
\ a:num_strokes, s:previous.input, a:direction)
let s:previous['input'] = input
" Check that we have an input char
if empty(input)
return ''
endif
let re = s:convertRegep(input)
if g:EasyMotion_add_search_history && a:num_strokes == -1
let history_re = substitute(re, '\\c\|\\C', '', '')
let @/ = history_re "For textobject: 'gn'
call histadd('search', history_re)
endif
return re
endfunction "}}}
function! s:convertRegep(input) "{{{
" 1. regexp
" 2. migemo
" 3. smartsign
" 4. smartcase
let re = s:should_use_regexp() ? a:input : s:escape_regexp_char(a:input)
" Convert space to match only start of spaces
if re ==# ' '
let re = '\s\+'
endif
if s:should_use_migemo(a:input)
let re = s:convertMigemo(re)
endif
if s:should_use_smartsign(a:input)
let re = s:convertSmartsign(a:input)
endif
let case_flag = EasyMotion#helper#should_case_sensitive(
\ a:input, s:current.is_search) ? '\c' : '\C'
let re = case_flag . re
return re
endfunction "}}}
function! s:convertMigemo(re) "{{{
let re = a:re
if len(re) > 1
" System cmigemo
return EasyMotion#cmigemo#getMigemoPattern(re)
endif
" EasyMotion migemo one key dict
if ! has_key(s:migemo_dicts, &l:encoding)
let s:migemo_dicts[&l:encoding] = EasyMotion#helper#load_migemo_dict()
endif
if re =~# '^\a$'
let re = get(s:migemo_dicts[&l:encoding], re, a:re)
endif
return re
endfunction "}}}
function! s:convertSmartsign(chars) "{{{
" Convert given chars to smartsign string
" Example: 12 -> [1!][2@]
" a] -> a[]}]
" Load smartsign dictionary
let smart_dict = s:load_smart_dict()
" Prepare converted string
let converted_str = ''
" Get `upper_sign` for each given chars
" Split chars into list
for char in split(a:chars, '\zs')
let upper_sign = s:get_escaped_group_char(smart_dict, char)
if upper_sign ==# ''
let converted_str .= s:escape_regexp_char(char)
else
" [1!]
let converted_str .= '[' . char . upper_sign . ']'
endif
endfor
return converted_str
endfunction "}}}
function! s:get_escaped_group_char(dict, char) "{{{
" Get escaped char from given dictionary
" return '' if char is not find
" Used inside `[]`
return escape(get(a:dict, a:char, ''), '^')
endfunction "}}}
function! s:escape_regexp_char(char) "{{{
return escape(a:char, '.$^~\[]*')
endfunction "}}}
function! s:convertSmartcase(re, char) "{{{
let re = a:re
if a:char =~# '\U' "nonuppercase
return '\c' . re
else "uppercase
return '\C' . re
endif
endfunction "}}}
function! s:should_use_regexp() "{{{
return g:EasyMotion_use_regexp == 1 && s:flag.regexp == 1
endfunction "}}}
function! s:should_use_migemo(char) "{{{
if ! g:EasyMotion_use_migemo || match(a:char, '\A') != -1
return 0
endif
" TODO: use direction to improve
if s:flag.within_line == 1
let first_line = line('.')
let end_line = line('.')
else
let first_line = line('w0')
let end_line = line('w$')
endif
" Skip folded line and check if text include multibyte characters
for line in range(first_line, end_line)
if EasyMotion#helper#is_folded(line)
continue
endif
if EasyMotion#helper#include_multibyte_char(getline(line)) == 1
return 1
endif
endfor
return 0
endfunction "}}}
function! s:should_use_smartsign(char) "{{{
" Smartsign Dictionary exists?
" \A: non-alphabetic character
" Do not use smartsign for n-key find search motions
if (exists('g:EasyMotion_use_smartsign_us') ||
\ exists('g:EasyMotion_use_smartsign_jp')) &&
\ match(a:char, '\A') != -1 &&
\ exists('s:current.is_search') && s:current.is_search == 0
return 1
else
return 0
endif
endfunction "}}}
function! s:convert_t_regexp(re, direction) "{{{
if a:direction == 0 "forward
return '\_.\ze\('.a:re.'\)'
elseif a:direction == 1 "backward
return '\('.a:re.'\)\@<=\_.'
endif
endfunction "}}}
" -- Handle Visual Mode ------------------
function! s:GetVisualStartPosition(c_pos, v_start, v_end, search_direction) "{{{
let vmode = mode(1)
if vmode !~# "^[Vv\<C-v>]"
call s:Throw('Unkown visual mode:'.vmode)
endif
if vmode ==# 'V' "line-wise Visual
" Line-wise Visual {{{
if a:v_start[0] == a:v_end[0]
if a:search_direction == ''
return a:v_start
elseif a:search_direction == 'b'
return a:v_end
else
call s:throw('Unkown search_direction')
endif
else
if a:c_pos[0] == a:v_start[0]
return a:v_end
elseif a:c_pos[0] == a:v_end[0]
return a:v_start
endif
endif
"}}}
else
" Character-wise or Block-wise Visual"{{{
if a:c_pos == a:v_start
return a:v_end
elseif a:c_pos == a:v_end
return a:v_start
endif
" virtualedit
if a:c_pos[0] == a:v_start[0]
return a:v_end
elseif a:c_pos[0] == a:v_end[0]
return a:v_start
elseif EasyMotion#helper#is_greater_coords(a:c_pos, a:v_start) == 1
return a:v_end
else
return a:v_start
endif
"}}}
endif
endfunction "}}}
" -- Others ------------------------------
function! s:is_cmdwin() "{{{
return bufname('%') ==# '[Command Line]'
endfunction "}}}
function! s:should_use_wundo() "{{{
" wundu cannot use in command-line window and
" unless undolist is not empty
return ! s:is_cmdwin() && undotree().seq_last != 0
endfunction "}}}
function! s:handleEmpty(input, visualmode) "{{{
" if empty, reselect and return 1
if empty(a:input)
if ! empty(a:visualmode)
silent exec 'normal! gv'
endif
let s:EasyMotion_is_cancelled = 1 " Cancel
return 1
endif
return 0
endfunction "}}}
function! s:load_smart_dict() "{{{
if exists('g:EasyMotion_use_smartsign_us')
return g:EasyMotion#sticky_table#us
elseif exists('g:EasyMotion_use_smartsign_jp')
return g:EasyMotion#sticky_table#jp
else
return {}
endif
endfunction "}}}
function! EasyMotion#attach_active_autocmd() "{{{
" Reference: https://github.com/justinmk/vim-sneak
augroup plugin-easymotion-active
autocmd!
autocmd InsertEnter,WinLeave,BufLeave <buffer>
\ let s:EasyMotion_is_active = 0
\ | autocmd! plugin-easymotion-active * <buffer>
autocmd CursorMoved <buffer>
\ autocmd plugin-easymotion-active CursorMoved <buffer>
\ let s:EasyMotion_is_active = 0
\ | autocmd! plugin-easymotion-active * <buffer>
augroup END
endfunction "}}}
function! EasyMotion#is_active() "{{{
return s:EasyMotion_is_active
endfunction "}}}
function! EasyMotion#activate(is_visual) "{{{
let s:EasyMotion_is_active = 1
call EasyMotion#attach_active_autocmd()
call EasyMotion#highlight#add_highlight(s:previous.regexp,
\ g:EasyMotion_hl_move)
call EasyMotion#highlight#attach_autocmd()
if a:is_visual == 1
normal! gv
endif
endfunction "}}}
function! s:restore_cursor_state(visualmode) "{{{
" -- Restore original cursor position/selection
if ! empty(a:visualmode)
silent exec 'normal! gv'
keepjumps call cursor(s:current.cursor_position)
else
keepjumps call cursor(s:current.original_position)
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
"let key = ( ! empty(group_key) ? group_key : key)
if type(item) == type([]) " List
" 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) "{{{
" Recursive
let group_values = values(a:groups)
" -- If only one possible match, jump directly to it {{{
if len(group_values) == 1
if mode(1) ==# 'no'
" Consider jump to first match
" NOTE: matchstr() handles multibyte characters.
let s:dot_repeat['target'] = matchstr(g:EasyMotion_keys, '^.')
endif
redraw
return group_values[0]
endif
" }}}
" -- Prepare marker lines ---------------- {{{
let lines = {}
let coord_key_dict = s:CreateCoordKeyDict(a:groups)
let prev_col_num = 0
for dict_key in sort(coord_key_dict[0])
" NOTE: {{{
" let g:EasyMotion_keys = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
" Perform <Plug>(easymotion-w)
"
" lines[line_num]['orig']:
" Lorem ipsum dolor sit amet consectetur adipisicing
"
" {target_char}:
" {L}orem {i}psum {d}olor {s}it {a}met {c}onsectetur {a}dipisicing
"
" lines[line_num]['marker'], {marker_chars}:
" {A}orem {B}psum {C}olor {D}it {E}met {F}onsectetur {G}dipisicing
" 2-key-combo: {marker_chars} could be 1 or 2 chars like {AB}
"
" }}}
" Prepare original line and marker line {{{
let [line_num, col_num] = split(dict_key, ',')
let line_num = str2nr(line_num)
let col_num = str2nr(col_num)
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,
\ }
" mb_compensation -> multibyte compensation
let prev_col_num = 0
endif "}}}
" Multibyte Compensation: {{{
" Solve multibyte issues by matching the byte column
" number instead of the visual column
" 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 col_num = max([prev_col_num + 1,
\ col_num - lines[line_num]['mb_compensation']])
let prev_col_num = col_num
"}}}
" Prepare marker characters {{{
let marker_chars = coord_key_dict[1][dict_key]
let marker_chars_len = EasyMotion#helper#strchars(marker_chars)
"}}}
" Replace {target} with {marker} & Highlight {{{
let col_add = 0 " Column add byte length
" Disable two-key-combo feature?
let marker_max_length = g:EasyMotion_disable_two_key_combo == 1
\ ? 1 : 2
for i in range(min([marker_chars_len, marker_max_length]))
let marker_char = split(marker_chars, '\zs')[i]
" EOL {{{
if strlen(lines[line_num]['marker']) < col_num + col_add
" Append marker chars if target is EOL
let lines[line_num]['marker'] .= ' '
endif "}}}
let target_col_regexp = '\%' . (col_num + col_add) . 'c.'
let target_char = matchstr(lines[line_num]['marker'],
\ target_col_regexp)
let space_len = strdisplaywidth(target_char)
\ - strdisplaywidth(marker_char)
" Substitute marker character
let substitute_expr = marker_char . repeat(' ', space_len)
let lines[line_num]['marker'] = substitute(
\ lines[line_num]['marker'],
\ target_col_regexp,
\ escape(substitute_expr,'&'),
\ '')
" Highlight targets {{{
let _hl_group =
\ (marker_chars_len == 1) ? g:EasyMotion_hl_group_target
\ : (i == 0) ? g:EasyMotion_hl2_first_group_target
\ : g:EasyMotion_hl2_second_group_target
if exists('*matchaddpos')
call EasyMotion#highlight#add_pos_highlight(
\ line_num, col_num + col_add, _hl_group)
else
call EasyMotion#highlight#add_highlight(
\ '\%' . line_num . 'l' . target_col_regexp,
\ _hl_group)
endif
"}}}
" Add marker/target length difference for multibyte compensation
let lines[line_num]['mb_compensation'] +=
\ strlen(target_char) - strlen(substitute_expr)
" Shift column
let col_add += strlen(marker_char)
endfor
"}}}
endfor
let lines_items = items(lines)
" }}}
" -- Put labels on targets & Get User Input & Restore all {{{
" Save undo tree {{{
let s:undo_file = tempname()
if s:should_use_wundo()
execute "wundo" s:undo_file
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()
"}}}
" Convert uppercase {{{
if g:EasyMotion_use_upper == 1 && match(g:EasyMotion_keys, '\l') == -1
let char = toupper(char)
endif "}}}
" Jump first target when Enter or Space key is pressed "{{{
if (char ==# "\<CR>" && g:EasyMotion_enter_jump_first == 1) ||
\ (char ==# "\<Space>" && g:EasyMotion_space_jump_first == 1)
" NOTE: matchstr() is multibyte aware.
let char = matchstr(g:EasyMotion_keys, '^.')
endif "}}}
" For dot repeat {{{
if mode(1) ==# 'no'
" Store previous target when operator pending mode
if s:current.dot_prompt_user_cnt == 0
" Store
let s:dot_repeat['target'] = char
else
" Append target chars
let s:dot_repeat['target'] .= char
endif
endif "}}}
finally
" Restore original lines
call s:SetLines(lines_items, 'orig')
" Un-highlight targets {{{
call EasyMotion#highlight#delete_highlight(
\ g:EasyMotion_hl_group_target,
\ g:EasyMotion_hl2_first_group_target,
\ g:EasyMotion_hl2_second_group_target,
\ )
" }}}
" Restore undo tree {{{
if s:should_use_wundo() && filereadable(s:undo_file)
silent execute "rundo" s:undo_file
call delete(s:undo_file)
unlet s:undo_file
else
" Break undo history (undobreak)
let old_undolevels = &undolevels
set undolevels=-1
keepjumps call setline('.', getline('.'))
let &undolevels = old_undolevels
unlet old_undolevels
" FIXME: Error occur by GundoToggle for undo number 2 is empty
keepjumps call setline('.', getline('.'))
endif "}}}
redraw
endtry "}}}
" -- Check if we have an input char ------ {{{
if empty(char)
call s:Throw('Cancelled')
endif
" }}}
" -- Check if the input char is valid ---- {{{
if ! has_key(a:groups, char)
call s:Throw('Invalid target')
endif
" }}}
let target = a:groups[char]
if type(target) == type([])
" Return target coordinates
return target
else
" Prompt for new target character
let s:current.dot_prompt_user_cnt += 1
return s:PromptUser(target)
endif
endfunction "}}}
function! s:DotPromptUser(groups) "{{{
" Get char from previous target
let char = s:dot_repeat.target[s:current.dot_repeat_target_cnt]
" For dot repeat target chars
let s:current.dot_repeat_target_cnt += 1
let target = a:groups[char]
if type(target) == type([])
" Return target coordinates
return target
else
" Prompt for new target character
return s:PromptUser(target)
endif
endfunction "}}}
function! s:EasyMotion(regexp, direction, visualmode, is_inclusive, ...) " {{{
let config = extend(s:default_config(), get(a:, 1, {}))
" Store s:current original_position & cursor_position {{{
" current cursor pos.
let s:current.cursor_position = [line('.'), col('.')]
" original start position. This value could be changed later in visual
" mode
let s:current.original_position =
\ get(s:current, 'original_position', s:current.cursor_position)
"}}}
let win_first_line = line('w0') " visible first line num
let win_last_line = line('w$') " visible last line num
" Store the target positions list
" e.g. targets = [ [line, col], [line2, col2], ...]
let targets = []
" Store info for Repeat motion {{{
if s:flag.dot_repeat != 1
" Store Regular Expression
let s:previous['regexp'] = a:regexp
let s:previous['direction'] = a:direction
let s:previous['operator'] = v:operator
" Note: 'is_inclusive' value could be changed later when
" bi-directional find motion depend on 'true' direction the cursor
" will move.
let s:previous['is_inclusive'] = a:is_inclusive
" For special motion flag
let s:previous['line_flag'] = s:flag.within_line
let s:previous['bd_t_flag'] = s:flag.bd_t " bi-directional t motion
endif "}}}
" To avoid side effect of overwriting buffer for tpope/repeat
" store current b:changedtick. Use this value later
let s:current.changedtick = b:changedtick
try
" -- Reset properties -------------------- {{{
" Save original value and set new value
call s:SaveValue()
call s:turn_off_hl_error()
" }}}
" Setup searchpos args {{{
let search_direction = (a:direction == 1 ? 'b' : '')
let search_stopline = a:direction == 1 ? win_first_line : win_last_line
if s:flag.within_line == 1
let search_stopline = s:current.original_position[0]
endif
"}}}
" Handle visual mode {{{
if ! empty(a:visualmode)
" Decide at where visual mode start {{{
normal! gv
let v_start = [line("'<"),col("'<")] " visual_start_position
let v_end = [line("'>"),col("'>")] " visual_end_position
let v_original_pos = s:GetVisualStartPosition(
\ s:current.cursor_position, v_start, v_end, search_direction)
"}}}
" Reselect visual text {{{
keepjumps call cursor(v_original_pos)
exec "normal! " . a:visualmode
keepjumps call cursor(s:current.cursor_position)
"}}}
" Update s:current.original_position
" overwrite original start position
let s:current.original_position = v_original_pos
endif "}}}
" Handle bi-directional t motion {{{
if s:flag.bd_t == 1
let regexp = s:convert_t_regexp(a:regexp, 0) "forward
else
let regexp = a:regexp
endif
"}}}
" Handle dot repeat with count
if s:flag.count_dot_repeat
let cursor_char = EasyMotion#helper#get_char_by_coord(s:current.cursor_position)
if cursor_char =~# regexp
call add(targets, s:current.cursor_position)
endif
endif
" Construct match dict {{{
" Note: searchpos() has side effect which 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
" to gathering matched targets coordinates.
let pos = searchpos(regexp, search_direction . (config.accept_cursor_pos ? 'c' : ''), search_stopline)
let num_jumpmarks = 0
while l:num_jumpmarks < g:EasyMotion_maximal_jumpmarks
" Reached end of search range
if pos == [0, 0]
break
endif
" Skip folded lines {{{
if EasyMotion#helper#is_folded(pos[0])
if search_direction ==# 'b'
" FIXME: Hmm... I should use filter()
" keepjumps call cursor(foldclosed(pos[0]), 0)
else
keepjumps call cursor(foldclosedend(pos[0]+1), 0)
endif
else
call add(targets, pos)
endif
"}}}
let pos = searchpos(regexp, search_direction, search_stopline)
let l:num_jumpmarks += 1
endwhile
"}}}
" Handle bidirection "{{{
" For bi-directional t motion {{{
if s:flag.bd_t == 1
let regexp = s:convert_t_regexp(a:regexp, 1) "backward
endif
"}}}
" Reconstruct match dict
if a:direction == 2
" Backward
" Jump back cursor_position
keepjumps call cursor(s:current.cursor_position[0],
\ s:current.cursor_position[1])
let l:num_jumpmarks = 0
let targets2 = []
if s:flag.within_line == 0
let search_stopline = win_first_line
else
let search_stopline = s:current.cursor_position[0]
endif
while l:num_jumpmarks < g:EasyMotion_maximal_jumpmarks
" TODO: refactoring
let pos = searchpos(regexp, 'b', search_stopline)
let l:num_jumpmarks += 1
" Reached end of search range
if pos == [0, 0]
break
endif
" Skip folded lines {{{
if EasyMotion#helper#is_folded(pos[0])
" keepjumps call cursor(foldclosedend(pos[0]+1), 0)
continue
endif
"}}}
call add(targets2, pos)
endwhile
" Merge match target dict"{{{
let t1 = 0 " forward
let t2 = 0 " backward
let targets3 = []
while t1 < len(targets) || t2 < len(targets2)
" Forward -> Backward -> F -> B -> ...
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
call s:Throw('No matches')
endif
"}}}
" Attach specific key as marker to gathered matched coordinates
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 && targets_len != 1 && s:flag.dot_repeat != 1
if a:direction == 1 " Backward
let shade_hl_re = s:flag.within_line
\ ? '^.*\%#'
\ : '\%'. win_first_line .'l\_.*\%#'
elseif a:direction == 0 " Forward
let shade_hl_re = s:flag.within_line
\ ? '\%#.*$'
\ : '\%#\_.*\%'. win_last_line .'l'
else " Both directions
let shade_hl_re = s:flag.within_line
\ ? '^.*\%#.*$'
\ : '\_.*'
endif
call EasyMotion#highlight#add_highlight(
\ shade_hl_re, g:EasyMotion_hl_group_shade)
if g:EasyMotion_cursor_highlight
let cursor_hl_re = '\%#'
call EasyMotion#highlight#add_highlight(cursor_hl_re,
\ g:EasyMotion_hl_inc_cursor)
endif
endif
" }}}
" -- Jump back before prompt for visual scroll {{{
" Because searchpos() change current cursor position and
" if you just use cursor(s:current.cursor_position) to jump back,
" current line will become middle of line window
if ! empty(a:visualmode)
keepjumps call winrestview({'lnum' : s:current.cursor_position[0], 'topline' : win_first_line})
else
" for adjusting cursorline
keepjumps call cursor(s:current.cursor_position)
endif
"}}}
" -- Prompt user for target group/character {{{
if s:flag.dot_repeat != 1
let coords = s:PromptUser(groups)
else
let coords = s:DotPromptUser(groups)
endif
"}}}
" -- Update cursor position -------------- {{{
" First, jump back cursor to original position
keepjumps call cursor(s:current.original_position)
" Consider EasyMotion as jump motion :h jump-motion
normal! m`
" Update selection for visual mode {{{
if ! empty(a:visualmode)
exec 'normal! ' . a:visualmode
endif
" }}}
" For bi-directional motion, checking again whether the motion is
" inclusive is necessary. This value will might be updated later
let is_inclusive_check = a:is_inclusive
" For bi-directional motion, store 'true' direction for dot repeat
" to handling inclusive/exclusive motion
if a:direction == 2
let true_direction =
\ EasyMotion#helper#is_greater_coords(
\ s:current.original_position, coords) > 0 ?
\ 0 : 1
" forward : backward
else
let true_direction = a:direction
endif
if s:flag.dot_repeat == 1
" support dot repeat {{{
" Use visual mode to emulate dot repeat
normal! v
" Deal with exclusive {{{
if s:dot_repeat.is_inclusive == 0
" exclusive
if s:dot_repeat.true_direction == 0 "Forward
let coords[1] -= 1
elseif s:dot_repeat.true_direction == 1 "Backward
" Shift visual selection to left by making cursor one key
" left.
normal! hoh
endif
endif "}}}
" Jump to destination
keepjumps call cursor(coords[0], coords[1])
" Execute previous operator
let cmd = s:dot_repeat.operator
if s:dot_repeat.operator ==# 'c'
let cmd .= getreg('.')
endif
exec 'normal! ' . cmd
"}}}
else
" Handle inclusive & exclusive {{{
" Overwrite inclusive flag for special case {{{
if s:flag.find_bd == 1 && true_direction == 1
" Note: For bi-directional find motion s(f) & t
" If true_direction is backward, the motion is 'exclusive'
let is_inclusive_check = 0 " overwrite
let s:previous.is_inclusive = 0 " overwrite
endif "}}}
if is_inclusive_check
" Note: {{{
" Inclusive motion requires that we eat one more
" character to the right by forcing the motion to inclusive
" if we're using a forward motion because
" > :h exclusive
" > Note that when using ':' any motion becomes characterwise
" > exclusive.
" and EasyMotion use ':'
" See: h: o_v }}}
normal! v
endif " }}}
if s:current.is_operator && s:flag.linewise
" TODO: Is there better solution?
" Maike it linewise
normal! V
endif
" Adjust screen especially for visual scroll & offscreen search {{{
" Otherwise, cursor line will move middle line of window
keepjumps call winrestview({'lnum' : win_first_line, 'topline' : win_first_line})
" Jump to destination
keepjumps call cursor(coords[0], coords[1])
" To avoid side effect of overwriting buffer {{{
" for tpope/vim-repeat
" See: :h b:changedtick
if exists('g:repeat_tick')
if g:repeat_tick == s:current.changedtick
let g:repeat_tick = b:changedtick
endif
endif "}}}
endif
" Set tpope/vim-repeat {{{
if s:current.is_operator == 1 &&
\ !(v:operator ==# 'y' && match(&cpo, 'y') == -1)
" Store previous info for dot repeat {{{
let s:dot_repeat.regexp = a:regexp
let s:dot_repeat.direction = a:direction
let s:dot_repeat.line_flag = s:flag.within_line
let s:dot_repeat.is_inclusive = is_inclusive_check
let s:dot_repeat.operator = v:operator
let s:dot_repeat.bd_t_flag = s:flag.bd_t " Bidirectional t motion
let s:dot_repeat.true_direction = true_direction " Check inclusive
"}}}
silent! call repeat#set("\<Plug>(easymotion-dotrepeat)")
endif "}}}
" Highlight all the matches by n-key find motions {{{
if s:current.is_search == 1 && s:current.is_operator == 0 && g:EasyMotion_add_search_history
" It seems let &hlsearch=&hlsearch doesn't work when called
" in script, so use :h feedkeys() instead.
" Ref: :h v:hlsearch
" FIXME: doesn't work with `c` operator
call EasyMotion#helper#silent_feedkeys(
\ ":let &hlsearch=&hlsearch\<CR>",
\ 'hlsearch', 'n')
endif "}}}
call s:Message('Jumping to [' . coords[0] . ', ' . coords[1] . ']')
let s:EasyMotion_is_cancelled = 0 " Success
"}}}
catch /^EasyMotion:.*/
redraw
" Show exception message
" The verbose option will take precedence
if g:EasyMotion_verbose == 1 && g:EasyMotion_ignore_exception != 1
echo v:exception
endif
let s:previous['regexp'] = a:regexp
" -- Activate EasyMotion ----------------- {{{
let s:EasyMotion_is_active = 1
call EasyMotion#attach_active_autocmd() "}}}
call s:restore_cursor_state(a:visualmode)
let s:EasyMotion_is_cancelled = 1 " Cancel
catch
call s:Message(v:exception . ' : ' . v:throwpoint)
call s:restore_cursor_state(a:visualmode)
let s:EasyMotion_is_cancelled = 1 " Cancel
finally
" -- Restore properties ------------------ {{{
call s:RestoreValue()
call s:turn_on_hl_error()
call EasyMotion#reset()
" }}}
" -- Remove shading ---------------------- {{{
call EasyMotion#highlight#delete_highlight()
" }}}
if s:EasyMotion_is_cancelled == 0 " Success
" -- Landing Highlight ------------------- {{{
if g:EasyMotion_landing_highlight
call EasyMotion#highlight#add_highlight(a:regexp,
\ g:EasyMotion_hl_move)
call EasyMotion#highlight#attach_autocmd()
endif "}}}
" -- Activate EasyMotion ----------------- {{{
let s:EasyMotion_is_active = 1
call EasyMotion#attach_active_autocmd() "}}}
endif
endtry
endfunction " }}}
"}}}
" }}}
call EasyMotion#init()
" Restore 'cpoptions' {{{
let &cpo = s:save_cpo
unlet s:save_cpo
" }}}
" vim: fdm=marker:et:ts=4:sw=4:sts=4