Merge branch 'feature/5-grouping-algorithms' into develop
* feature/5-grouping-algorithms: Add docs Make everything work with the new tree structured targets Create function for creating a coord/key lookup dict Make sure that the key count list is sorted Add grouping algorithms Closes #5.
This commit is contained in:
commit
8ac570be59
@ -23,7 +23,8 @@ CONTENTS *easymotion-contents*
|
|||||||
4.3 EasyMotion_shade_hl ............ |EasyMotion_shade_hl|
|
4.3 EasyMotion_shade_hl ............ |EasyMotion_shade_hl|
|
||||||
4.4 EasyMotion_do_shade ............ |EasyMotion_do_shade|
|
4.4 EasyMotion_do_shade ............ |EasyMotion_do_shade|
|
||||||
4.5 EasyMotion_do_mapping .......... |EasyMotion_do_mapping|
|
4.5 EasyMotion_do_mapping .......... |EasyMotion_do_mapping|
|
||||||
4.6 Custom mappings ................ |easymotion-custom-mappings|
|
4.6 EasyMotion_grouping ............ |EasyMotion_grouping|
|
||||||
|
4.7 Custom mappings ................ |easymotion-custom-mappings|
|
||||||
5. License ............................ |easymotion-license|
|
5. License ............................ |easymotion-license|
|
||||||
6. Known bugs ......................... |easymotion-known-bugs|
|
6. Known bugs ......................... |easymotion-known-bugs|
|
||||||
7. Contributing ....................... |easymotion-contributing|
|
7. Contributing ....................... |easymotion-contributing|
|
||||||
@ -167,7 +168,51 @@ this, see |easymotion-custom-mappings| for customizing the default mappings.
|
|||||||
Default: 1
|
Default: 1
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
4.6 Custom mappings *easymotion-custom-mappings*
|
4.6 EasyMotion_grouping *EasyMotion_grouping*
|
||||||
|
|
||||||
|
When there are too many possible targets on the screen, the results have to be
|
||||||
|
grouped. This configuration option lets you change which grouping algorithm
|
||||||
|
you want to use. There are two grouping algorithms available:
|
||||||
|
|
||||||
|
* Single-key priority (value: 1)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
This algorithm prioritizes single-key jumps for the targets closest to
|
||||||
|
the cursor and only groups the last jump targets to maximize the amount
|
||||||
|
of single-key jumps.
|
||||||
|
|
||||||
|
This algorithm works recursively and will work with as few keys as two.
|
||||||
|
|
||||||
|
Example (with |EasyMotion_keys| = "abcdef"): >
|
||||||
|
|
||||||
|
x x x x x x x x x
|
||||||
|
<
|
||||||
|
The |w| motion is triggered: >
|
||||||
|
|
||||||
|
a b c d e f f f f
|
||||||
|
^ ^ ^ ^ ^ Direct jump to target
|
||||||
|
^ ^ ^ ^ Enter group "f"
|
||||||
|
<
|
||||||
|
* Original (value: 2)
|
||||||
|
--------
|
||||||
|
|
||||||
|
This is the original algorithm which always groups all targets if there
|
||||||
|
are too many possible motion targets.
|
||||||
|
|
||||||
|
Example (with |EasyMotion_keys| = "abcdef"): >
|
||||||
|
|
||||||
|
x x x x x x x x x
|
||||||
|
<
|
||||||
|
The |w| motion is triggered: >
|
||||||
|
|
||||||
|
a a a a a a b b b
|
||||||
|
^ ^ ^ ^ ^ ^ Enter group "a"
|
||||||
|
^ ^ ^ Enter group "b"
|
||||||
|
|
||||||
|
Default: 1
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
4.7 Custom mappings *easymotion-custom-mappings*
|
||||||
|
|
||||||
EasyMotion allows you to customize all default mappings to avoid conflicts
|
EasyMotion allows you to customize all default mappings to avoid conflicts
|
||||||
with existing mappings. All custom mappings follow the same variable format: >
|
with existing mappings. All custom mappings follow the same variable format: >
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
\ , 'shade_hl' : 'EasyMotionShade'
|
\ , 'shade_hl' : 'EasyMotionShade'
|
||||||
\ , 'do_shade' : 1
|
\ , 'do_shade' : 1
|
||||||
\ , 'do_mapping' : 1
|
\ , 'do_mapping' : 1
|
||||||
|
\ , 'grouping' : 1
|
||||||
\ })
|
\ })
|
||||||
" }}}
|
" }}}
|
||||||
" Default highlighting {{{
|
" Default highlighting {{{
|
||||||
@ -197,45 +198,178 @@
|
|||||||
return char
|
return char
|
||||||
endfunction " }}}
|
endfunction " }}}
|
||||||
" }}}
|
" }}}
|
||||||
" Core functions {{{
|
" Grouping algorithms {{{
|
||||||
" Create key index {{{
|
let s:grouping_algorithms = {
|
||||||
function! s:CreateIndex(chars) " {{{
|
\ 1: 'SCTree'
|
||||||
let index_to_key = {}
|
\ , 2: 'Original'
|
||||||
let key_to_index = {}
|
\ }
|
||||||
|
" 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 idx = 0
|
let groups = {}
|
||||||
for char in split(a:chars, '\zs')
|
|
||||||
let index_to_key[idx] = char
|
|
||||||
let key_to_index[char] = idx
|
|
||||||
|
|
||||||
let idx += 1
|
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
|
endfor
|
||||||
|
|
||||||
return [index_to_key, key_to_index]
|
let level += 1
|
||||||
endfunction "}}}
|
endwhile
|
||||||
|
|
||||||
let [s:index_to_key, s:key_to_index] = s:CreateIndex(g:EasyMotion_keys)
|
|
||||||
" }}}
|
" }}}
|
||||||
function! s:PromptUser(groups) "{{{
|
" Create group tree {{{
|
||||||
let single_group = len(a:groups) == 1
|
let i = 0
|
||||||
let targets_len = single_group ? len(a:groups[0]) : len(a:groups)
|
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 coord_keys = {}
|
||||||
|
let group_key = a:0 == 1 ? a:1 : ''
|
||||||
|
|
||||||
|
for [key, item] in items(a:groups)
|
||||||
|
let key = ( ! empty(group_key) ? group_key : key)
|
||||||
|
|
||||||
|
if type(item) == 3
|
||||||
|
" Destination coords
|
||||||
|
let coord_keys[join(item, ',')] = key
|
||||||
|
else
|
||||||
|
" Item is a dict (has children)
|
||||||
|
call extend(coord_keys, s:CreateCoordKeyDict(item, key))
|
||||||
|
endif
|
||||||
|
|
||||||
|
unlet item
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return coord_keys
|
||||||
|
endfunction
|
||||||
|
" }}}
|
||||||
|
" }}}
|
||||||
|
" Core functions {{{
|
||||||
|
function! s:PromptUser(groups) "{{{
|
||||||
" If only one possible match, jump directly to it {{{
|
" If only one possible match, jump directly to it {{{
|
||||||
if single_group && targets_len == 1
|
let group_values = values(a:groups)
|
||||||
|
|
||||||
|
if len(group_values) == 1
|
||||||
redraw
|
redraw
|
||||||
|
|
||||||
return a:groups[0][0]
|
return group_values[0]
|
||||||
endif
|
endif
|
||||||
" }}}
|
" }}}
|
||||||
" Prepare marker lines {{{
|
" Prepare marker lines {{{
|
||||||
let lines = {}
|
let lines = {}
|
||||||
let hl_coords = []
|
let hl_coords = []
|
||||||
let current_group = 0
|
|
||||||
|
|
||||||
for group in a:groups
|
for [coords, target_key] in items(s:CreateCoordKeyDict(a:groups))
|
||||||
let element = 0
|
let [line_num, col_num] = split(coords, ',')
|
||||||
|
|
||||||
for [line_num, col_num] in group
|
|
||||||
" Add original line and marker line
|
" Add original line and marker line
|
||||||
if ! has_key(lines, line_num)
|
if ! has_key(lines, line_num)
|
||||||
let current_line = getline(line_num)
|
let current_line = getline(line_num)
|
||||||
@ -243,28 +377,21 @@
|
|||||||
let lines[line_num] = { 'orig': current_line, 'marker': current_line }
|
let lines[line_num] = { 'orig': current_line, 'marker': current_line }
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let marker_char = s:index_to_key[single_group ? element : current_group]
|
|
||||||
|
|
||||||
if strlen(lines[line_num]['marker']) > 0
|
if strlen(lines[line_num]['marker']) > 0
|
||||||
" Replace hard tab with spaces
|
" Replace hard tab with spaces
|
||||||
if match(lines[line_num]['marker'], '\%' . col_num . 'c\t') != -1
|
if match(lines[line_num]['marker'], '\%' . col_num . 'c\t') != -1
|
||||||
let marker_char .= repeat(' ', string(&tabstop) - strlen(marker_char))
|
let target_key .= repeat(' ', string(&tabstop) - strlen(target_key))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" Substitute marker character if line length > 0
|
" Substitute marker character if line length > 0
|
||||||
let lines[line_num]['marker'] = substitute(lines[line_num]['marker'], '\%' . col_num . 'c.', marker_char, '')
|
let lines[line_num]['marker'] = substitute(lines[line_num]['marker'], '\%' . col_num . 'c.', target_key, '')
|
||||||
else
|
else
|
||||||
" Set the line to the marker character if the line is empty
|
" Set the line to the marker character if the line is empty
|
||||||
let lines[line_num]['marker'] = marker_char
|
let lines[line_num]['marker'] = target_key
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" Add highlighting coordinates
|
" Add highlighting coordinates
|
||||||
call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
|
call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
|
||||||
|
|
||||||
let element += 1
|
|
||||||
endfor
|
|
||||||
|
|
||||||
let current_group += 1
|
|
||||||
endfor
|
endfor
|
||||||
|
|
||||||
let lines_items = items(lines)
|
let lines_items = items(lines)
|
||||||
@ -279,12 +406,8 @@
|
|||||||
|
|
||||||
redraw
|
redraw
|
||||||
|
|
||||||
" Get target/group character {{{
|
" Get target character {{{
|
||||||
if single_group
|
call s:Prompt('Target key')
|
||||||
call s:Prompt('Target character')
|
|
||||||
else
|
|
||||||
call s:Prompt('Group character')
|
|
||||||
endif
|
|
||||||
|
|
||||||
let char = s:GetChar()
|
let char = s:GetChar()
|
||||||
" }}}
|
" }}}
|
||||||
@ -307,17 +430,19 @@
|
|||||||
endif
|
endif
|
||||||
" }}}
|
" }}}
|
||||||
" Check if the input char is valid {{{
|
" Check if the input char is valid {{{
|
||||||
if ! has_key(s:key_to_index, char) || s:key_to_index[char] >= targets_len
|
if ! has_key(a:groups, char)
|
||||||
throw 'Invalid target'
|
throw 'Invalid target'
|
||||||
endif
|
endif
|
||||||
" }}}
|
" }}}
|
||||||
|
|
||||||
if single_group
|
let target = a:groups[char]
|
||||||
|
|
||||||
|
if type(target) == 3
|
||||||
" Return target coordinates
|
" Return target coordinates
|
||||||
return a:groups[0][s:key_to_index[char]]
|
return target
|
||||||
else
|
else
|
||||||
" Prompt for target character
|
" Prompt for new target character
|
||||||
return s:PromptUser([a:groups[s:key_to_index[char]]])
|
return s:PromptUser(target)
|
||||||
endif
|
endif
|
||||||
endfunction "}}}
|
endfunction "}}}
|
||||||
function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{
|
function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{
|
||||||
@ -356,24 +481,10 @@
|
|||||||
throw 'No matches'
|
throw 'No matches'
|
||||||
endif
|
endif
|
||||||
" }}}
|
" }}}
|
||||||
" Split targets into key groups {{{
|
|
||||||
let groups_len = len(s:index_to_key)
|
|
||||||
let groups = []
|
|
||||||
let i = 0
|
|
||||||
|
|
||||||
while i < targets_len
|
let GroupingFn = function('s:GroupingAlgorithm' . s:grouping_algorithms[g:EasyMotion_grouping])
|
||||||
call add(groups, targets[i : i + groups_len - 1])
|
let groups = GroupingFn(targets, split(g:EasyMotion_keys, '\zs'))
|
||||||
|
|
||||||
let i += groups_len
|
|
||||||
endwhile
|
|
||||||
" }}}
|
|
||||||
" Too many groups; only display the first ones {{{
|
|
||||||
if len(groups) > groups_len
|
|
||||||
call s:Message('Only displaying the first matches')
|
|
||||||
|
|
||||||
let groups = groups[0 : groups_len - 1]
|
|
||||||
endif
|
|
||||||
" }}}
|
|
||||||
" Shade inactive source {{{
|
" Shade inactive source {{{
|
||||||
if g:EasyMotion_do_shade
|
if g:EasyMotion_do_shade
|
||||||
let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'
|
let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user