From a0c2b760feb45b19ec5736367f6ceada72704246 Mon Sep 17 00:00:00 2001 From: haya14busa Date: Tue, 21 Jan 2014 11:10:12 +0900 Subject: [PATCH] Implement bidirectional t motion and fix exclusive & inclusive flag Implement: bidirectional t including within line, two-key, multi find motion. Fix: misuse of exclusive -> inclusive Fix: bidirectional find motion inclusive flag Add: test case for bidirectional t motion close #6 --- autoload/EasyMotion.vim | 111 ++++++++++++++++++++++++++++++---------- plugin/EasyMotion.vim | 6 +++ t/easymotion_spec.vim | 62 +++++++++++++++++++++- 3 files changed, 150 insertions(+), 29 deletions(-) diff --git a/autoload/EasyMotion.vim b/autoload/EasyMotion.vim index 4fffa70..2c37593 100644 --- a/autoload/EasyMotion.vim +++ b/autoload/EasyMotion.vim @@ -49,6 +49,8 @@ function! EasyMotion#reset() \ 'within_line' : 0, \ 'dot_repeat' : 0, \ 'regexp' : 0, + \ 'bd_t' : 0, + \ 'find_bd' : 0, \ } let s:current = { \ 'is_operator' : 0, @@ -61,20 +63,39 @@ endfunction "}}} " Motion Functions: {{{ " -- Find Motion ------------------------- function! EasyMotion#S(num_strokes, visualmode, direction) " {{{ - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + if a:direction == 1 + let is_inclusive = 0 + else + " Handle bi-direction later + 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) if s:handleEmpty(re, a:visualmode) | return | endif - call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_exclusive) + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#T(num_strokes, visualmode, direction) " {{{ - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + if a:direction == 1 + let is_inclusive = 0 + else + " Handle bi-direction later + 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) if s:handleEmpty(re, a:visualmode) | return | endif - let re = a:direction == 1 ? '\('.re.'\)\zs.' : '.\ze\('.re.'\)' - call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_exclusive) + if a:direction == 2 + let s:flag.bd_t = 1 + elseif a:direction == 1 + let re = '\('.re.'\)\zs.' + else + let re = '.\ze\('.re.'\)' + endif + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} " -- Word Motion ------------------------- function! EasyMotion#WB(visualmode, direction) " {{{ + "FIXME: inconsistent with default vim motion let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', 0) endfunction " }}} @@ -84,13 +105,13 @@ function! EasyMotion#WBW(visualmode, direction) " {{{ endfunction " }}} function! EasyMotion#E(visualmode, direction) " {{{ let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 - call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_exclusive) + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 + call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#EW(visualmode, direction) " {{{ let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 - call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_exclusive) + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 + call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} " -- JK Motion --------------------------- function! EasyMotion#JK(visualmode, direction) " {{{ @@ -123,31 +144,43 @@ function! EasyMotion#JumpToAnywhere(visualmode, direction) " {{{ endfunction " }}} " -- Line Motion ------------------------- function! EasyMotion#SL(num_strokes, visualmode, direction) " {{{ - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + if a:direction == 1 + let is_inclusive = 0 + else + " Handle bi-direction later + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 + endif + let s:flag.find_bd = a:direction == 2 ? 1 : 0 let s:flag.within_line = 1 let re = s:findMotion(a:num_strokes) if s:handleEmpty(re, a:visualmode) | return | endif - call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_exclusive) + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#TL(num_strokes, visualmode, direction) " {{{ - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + if a:direction == 1 + let is_inclusive = 0 + else + " Handle bi-direction later + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 + endif + let s:flag.find_bd = a:direction == 2 ? 1 : 0 let s:flag.within_line = 1 let re = s:findMotion(a:num_strokes) if s:handleEmpty(re, a:visualmode) | return | endif let re = a:direction == 1 ? '\('.re.'\)\zs.' : '.\ze\('.re.'\)' - call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_exclusive) + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#WBL(visualmode, direction) " {{{ let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 let s:flag.within_line = 1 call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', 0) endfunction " }}} function! EasyMotion#EL(visualmode, direction) " {{{ let s:flag.within_line = 1 let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 - call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_exclusive) + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 + call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#LineAnywhere(visualmode, direction) " {{{ let s:flag.within_line = 1 @@ -310,9 +343,9 @@ function! EasyMotion#Repeat(visualmode) " {{{ let direction = s:previous.direction let s:flag.within_line = s:previous.line_flag let s:current.is_operator = mode(1) ==# 'no' ? 1: 0 - let is_exclusive = mode(1) ==# 'no' ? 1 : 0 + let is_inclusive = mode(1) ==# 'no' ? 1 : 0 - call s:EasyMotion(re, direction, a:visualmode ? visualmode() : '', is_exclusive) + call s:EasyMotion(re, direction, a:visualmode ? visualmode() : '', is_inclusive) endfunction " }}} function! EasyMotion#DotRepeat(visualmode) " {{{ " Repeat previous motion with previous targets @@ -323,13 +356,13 @@ function! EasyMotion#DotRepeat(visualmode) " {{{ let re = s:dot_repeat.regexp let direction = s:dot_repeat.direction - let is_exclusive = s:dot_repeat.is_exclusive + let is_inclusive = s:dot_repeat.is_inclusive let s:flag.within_line = s:dot_repeat.line_flag let s:current.is_operator = 1 for cnt in range(v:count1) let s:flag.dot_repeat = 1 " s:EasyMotion() always call reset - silent call s:EasyMotion(re, direction, 0, is_exclusive) + silent call s:EasyMotion(re, direction, 0, is_inclusive) endfor endfunction " }}} function! EasyMotion#NextPrevious(visualmode, direction) " {{{ @@ -1082,7 +1115,7 @@ function! s:DotPromptUser(groups) "{{{ return s:PromptUser(target, a:allows_repeat, a:fixed_column) endif endfunction "}}} -function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ +function! s:EasyMotion(regexp, direction, visualmode, is_inclusive, ...) " {{{ " For Special Function {{{ " For SelectLines(), to highlight previous selected line let hlcurrent = a:0 >= 1 ? a:1 : 0 @@ -1108,7 +1141,7 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ let s:previous['regexp'] = a:regexp let s:previous['direction'] = a:direction let s:previous['line_flag'] = s:flag.within_line == 1 ? 1 : 0 - let s:previous['is_exclusive'] = a:is_exclusive + let s:previous['is_inclusive'] = a:is_inclusive let s:previous['operator'] = v:operator endif " To avoid side effect of overwriting buffer for tpope/repeat @@ -1152,12 +1185,20 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ endif " }}} + " Handle bi-directional t motion {{{ + if s:flag.bd_t == 1 + let regexp = '\('.a:regexp.'\)\zs.' + else + let regexp = a:regexp + 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 pos = searchpos(regexp, search_direction . search_at_cursor, search_stopline) let search_at_cursor = '' " Reached end of search range @@ -1180,6 +1221,11 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ "}}} " Handle bidirection "{{{ + " For bi-directional t motion {{{ + if s:flag.bd_t == 1 + let regexp = '.\ze\('.a:regexp.'\)' + endif + "}}} " Reconstruct match dict if a:direction == 2 " Forward @@ -1196,7 +1242,7 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ let search_stopline = !empty(a:visualmode) ? c_pos[0] : orig_pos[0] endif while 1 - let pos = searchpos(a:regexp, '', search_stopline) + let pos = searchpos(regexp, '', search_stopline) " Reached end of search range if pos == [0, 0] break @@ -1316,7 +1362,7 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ " support dot repeat {{{ " Use visual mode to emulate dot repeat normal! v - if s:dot_repeat.is_exclusive == 0 + if s:dot_repeat.is_inclusive == 0 if s:dot_repeat.direction == 0 "Forward let coords[1] -= 1 elseif s:dot_repeat.direction == 1 "Backward @@ -1334,8 +1380,17 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ exec 'normal! ' . cmd "}}} else - " Handle operator-pending mode {{{ - if a:is_exclusive == 1 + " Handle inclusive & exclusive {{{ + " Overwrite inclusive flag for special case + let is_exclusive = 0 + if s:flag.find_bd == 1 + " for bi-directional s(f) & t + let is_backward = EasyMotion#helper#is_greater_coords(orig_pos, coords) < 0 + if is_backward != 0 + let is_exclusive = 1 + endif + endif + if a:is_inclusive == 1 && is_exclusive == 0 " Exclusive motion requires that we eat one more " character to the right if we're using " a forward motion @@ -1366,7 +1421,7 @@ function! s:EasyMotion(regexp, direction, visualmode, is_exclusive, ...) " {{{ let s:dot_repeat.regexp = a:regexp let s:dot_repeat.direction = a:direction let s:dot_repeat.line_flag = s:flag.within_line == 1 ? 1 : 0 - let s:dot_repeat.is_exclusive = a:is_exclusive + let s:dot_repeat.is_inclusive = a:is_inclusive let s:dot_repeat.operator = v:operator "}}} silent! call repeat#set("\(easymotion-dotrepeat)") diff --git a/plugin/EasyMotion.vim b/plugin/EasyMotion.vim index 069cf17..5e20001 100644 --- a/plugin/EasyMotion.vim +++ b/plugin/EasyMotion.vim @@ -162,33 +162,39 @@ call s:find_motion_map_helper({ \ 's' : {'fnc': 'S' , 'cnt': 1, 'direction': 2}, \ 't' : {'fnc': 'T' , 'cnt': 1, 'direction': 0}, \ 'T' : {'fnc': 'T' , 'cnt': 1, 'direction': 1}, + \ 'bd-t' : {'fnc': 'T', 'cnt': 1, 'direction': 2}, \ 'fl' : {'fnc': 'SL', 'cnt': 1, 'direction': 0}, \ 'Fl' : {'fnc': 'SL', 'cnt': 1, 'direction': 1}, \ 'sl' : {'fnc': 'SL', 'cnt': 1, 'direction': 2}, \ 'tl' : {'fnc': 'TL', 'cnt': 1, 'direction': 0}, \ 'Tl' : {'fnc': 'TL', 'cnt': 1, 'direction': 1}, + \ 'bd-tl' : {'fnc': 'TL', 'cnt': 1, 'direction': 2}, \ \ 'f2' : {'fnc': 'S' , 'cnt': 2, 'direction': 0}, \ 'F2' : {'fnc': 'S' , 'cnt': 2, 'direction': 1}, \ 's2' : {'fnc': 'S' , 'cnt': 2, 'direction': 2}, \ 't2' : {'fnc': 'T' , 'cnt': 2, 'direction': 0}, \ 'T2' : {'fnc': 'T' , 'cnt': 2, 'direction': 1}, + \ 'bd-t2' : {'fnc': 'T', 'cnt': 2, 'direction': 2}, \ 'fl2' : {'fnc': 'SL', 'cnt': 2, 'direction': 0}, \ 'Fl2' : {'fnc': 'SL', 'cnt': 2, 'direction': 1}, \ 'sl2' : {'fnc': 'SL', 'cnt': 2, 'direction': 2}, \ 'tl2' : {'fnc': 'TL', 'cnt': 2, 'direction': 0}, \ 'Tl2' : {'fnc': 'TL', 'cnt': 2, 'direction': 1}, + \ 'bd-tl2' : {'fnc': 'TL', 'cnt': 2, 'direction': 2}, \ \ 'fn' : {'fnc': 'S' , 'cnt': -1, 'direction': 0}, \ 'Fn' : {'fnc': 'S' , 'cnt': -1, 'direction': 1}, \ 'sn' : {'fnc': 'S' , 'cnt': -1, 'direction': 2}, \ 'tn' : {'fnc': 'T' , 'cnt': -1, 'direction': 0}, \ 'Tn' : {'fnc': 'T' , 'cnt': -1, 'direction': 1}, + \ 'bd-tn' : {'fnc': 'T', 'cnt': -1, 'direction': 2}, \ 'fln' : {'fnc': 'SL', 'cnt': -1, 'direction': 0}, \ 'Fln' : {'fnc': 'SL', 'cnt': -1, 'direction': 1}, \ 'sln' : {'fnc': 'SL', 'cnt': -1, 'direction': 2}, \ 'tln' : {'fnc': 'TL', 'cnt': -1, 'direction': 0}, \ 'Tln' : {'fnc': 'TL', 'cnt': -1, 'direction': 1}, + \ 'bd-tln' : {'fnc': 'TL', 'cnt': -1, 'direction': 2}, \ }) "}}} diff --git a/t/easymotion_spec.vim b/t/easymotion_spec.vim index 07bf8e9..9f8bec0 100644 --- a/t/easymotion_spec.vim +++ b/t/easymotion_spec.vim @@ -1,7 +1,7 @@ "============================================================================= " FILE: t/easymotion_spec.vim " AUTHOR: haya14busa -" Last Change: 20 Jan 2014. +" Last Change: 21 Jan 2014. " Test: https://github.com/kana/vim-vspec " Refer: https://github.com/rhysd/clever-f.vim " Description: EasyMotion test with vim-vspec @@ -55,6 +55,10 @@ describe 'Default settings' Expect maparg('(easymotion-T)', 'n') ==# ':call EasyMotion#T(1,0,1)' Expect maparg('(easymotion-T)', 'o') ==# ':call EasyMotion#T(1,0,1)' Expect maparg('(easymotion-T)', 'v') ==# ':call EasyMotion#T(1,1,1)' + " bd-t + Expect maparg('(easymotion-bd-t)', 'n') ==# ':call EasyMotion#T(1,0,2)' + Expect maparg('(easymotion-bd-t)', 'o') ==# ':call EasyMotion#T(1,0,2)' + Expect maparg('(easymotion-bd-t)', 'v') ==# ':call EasyMotion#T(1,1,2)' " sl Expect maparg('(easymotion-sl)', 'n') ==# ':call EasyMotion#SL(1,0,2)' Expect maparg('(easymotion-sl)', 'o') ==# ':call EasyMotion#SL(1,0,2)' @@ -75,6 +79,10 @@ describe 'Default settings' Expect maparg('(easymotion-Tl)', 'n') ==# ':call EasyMotion#TL(1,0,1)' Expect maparg('(easymotion-Tl)', 'o') ==# ':call EasyMotion#TL(1,0,1)' Expect maparg('(easymotion-Tl)', 'v') ==# ':call EasyMotion#TL(1,1,1)' + " bd-tl + Expect maparg('(easymotion-bd-tl)', 'n') ==# ':call EasyMotion#TL(1,0,2)' + Expect maparg('(easymotion-bd-tl)', 'o') ==# ':call EasyMotion#TL(1,0,2)' + Expect maparg('(easymotion-bd-tl)', 'v') ==# ':call EasyMotion#TL(1,1,2)' "}}} " Two Char Find Motion: {{{ @@ -102,6 +110,10 @@ describe 'Default settings' Expect maparg('(easymotion-sl2)', 'n') ==# ':call EasyMotion#SL(2,0,2)' Expect maparg('(easymotion-sl2)', 'o') ==# ':call EasyMotion#SL(2,0,2)' Expect maparg('(easymotion-sl2)', 'v') ==# ':call EasyMotion#SL(2,1,2)' + " bd-t2 + Expect maparg('(easymotion-bd-t2)', 'n') ==# ':call EasyMotion#T(2,0,2)' + Expect maparg('(easymotion-bd-t2)', 'o') ==# ':call EasyMotion#T(2,0,2)' + Expect maparg('(easymotion-bd-t2)', 'v') ==# ':call EasyMotion#T(2,1,2)' " fl2 Expect maparg('(easymotion-fl2)', 'n') ==# ':call EasyMotion#SL(2,0,0)' Expect maparg('(easymotion-fl2)', 'o') ==# ':call EasyMotion#SL(2,0,0)' @@ -118,6 +130,10 @@ describe 'Default settings' Expect maparg('(easymotion-Tl2)', 'n') ==# ':call EasyMotion#TL(2,0,1)' Expect maparg('(easymotion-Tl2)', 'o') ==# ':call EasyMotion#TL(2,0,1)' Expect maparg('(easymotion-Tl2)', 'v') ==# ':call EasyMotion#TL(2,1,1)' + " bd-tl2 + Expect maparg('(easymotion-bd-tl2)', 'n') ==# ':call EasyMotion#TL(2,0,2)' + Expect maparg('(easymotion-bd-tl2)', 'o') ==# ':call EasyMotion#TL(2,0,2)' + Expect maparg('(easymotion-bd-tl2)', 'v') ==# ':call EasyMotion#TL(2,1,2)' "}}} " Multi Char Find Motion: {{{ @@ -141,6 +157,10 @@ describe 'Default settings' Expect maparg('(easymotion-Tn)', 'n') ==# ':call EasyMotion#T(-1,0,1)' Expect maparg('(easymotion-Tn)', 'o') ==# ':call EasyMotion#T(-1,0,1)' Expect maparg('(easymotion-Tn)', 'v') ==# ':call EasyMotion#T(-1,1,1)' + " bd-tn + Expect maparg('(easymotion-bd-tn)', 'n') ==# ':call EasyMotion#T(-1,0,2)' + Expect maparg('(easymotion-bd-tn)', 'o') ==# ':call EasyMotion#T(-1,0,2)' + Expect maparg('(easymotion-bd-tn)', 'v') ==# ':call EasyMotion#T(-1,1,2)' " sln Expect maparg('(easymotion-sln)', 'n') ==# ':call EasyMotion#SL(-1,0,2)' Expect maparg('(easymotion-sln)', 'o') ==# ':call EasyMotion#SL(-1,0,2)' @@ -161,6 +181,10 @@ describe 'Default settings' Expect maparg('(easymotion-Tln)', 'n') ==# ':call EasyMotion#TL(-1,0,1)' Expect maparg('(easymotion-Tln)', 'o') ==# ':call EasyMotion#TL(-1,0,1)' Expect maparg('(easymotion-Tln)', 'v') ==# ':call EasyMotion#TL(-1,1,1)' + " bd-tln + Expect maparg('(easymotion-bd-tln)', 'n') ==# ':call EasyMotion#TL(-1,0,2)' + Expect maparg('(easymotion-bd-tln)', 'o') ==# ':call EasyMotion#TL(-1,0,2)' + Expect maparg('(easymotion-bd-tln)', 'v') ==# ':call EasyMotion#TL(-1,1,2)' "}}} end @@ -1134,4 +1158,40 @@ describe 'EasyMotion regexp' end "}}} +" bi-directional t motion {{{ +describe 'bi-directional t motion' + before + new + let g:EasyMotion_keys = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + let g:EasyMotion_use_regexp = 1 + map t (easymotion-bd-t) + call EasyMotion#init() + call AddLine('poge1 2huga 3hiyo 4poyo') + " 12345678901234567890123 + end + + after + let g:EasyMotion_use_regexp = 0 + close! + end + + " provide bidirectional motion with one key mapping {{{ + it 'provide bidirectional motion with one key mapping' + normal! 0 + let l = line('.') + Expect CursorPos() == [l,1,'p'] + normal thb + Expect CursorPos() == [l,13,'3'] + + normal! h + Expect CursorPos() == [l,12,' '] + + normal thb + Expect CursorPos() == [l,9,'u'] + + end + "}}} +end +"}}} + " vim: fdm=marker:et:ts=4:sw=4:sts=4