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:
Kim Silkebækken 2011-04-02 17:14:51 +02:00
commit 8ac570be59
2 changed files with 233 additions and 77 deletions

View File

@ -23,7 +23,8 @@ CONTENTS *easymotion-contents*
4.3 EasyMotion_shade_hl ............ |EasyMotion_shade_hl|
4.4 EasyMotion_do_shade ............ |EasyMotion_do_shade|
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|
6. Known bugs ......................... |easymotion-known-bugs|
7. Contributing ....................... |easymotion-contributing|
@ -167,7 +168,51 @@ this, see |easymotion-custom-mappings| for customizing the default mappings.
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
with existing mappings. All custom mappings follow the same variable format: >

View File

@ -46,6 +46,7 @@
\ , 'shade_hl' : 'EasyMotionShade'
\ , 'do_shade' : 1
\ , 'do_mapping' : 1
\ , 'grouping' : 1
\ })
" }}}
" Default highlighting {{{
@ -197,45 +198,178 @@
return char
endfunction " }}}
" }}}
" Core functions {{{
" Create key index {{{
function! s:CreateIndex(chars) " {{{
let index_to_key = {}
let key_to_index = {}
" 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 idx = 0
for char in split(a:chars, '\zs')
let index_to_key[idx] = char
let key_to_index[char] = idx
let groups = {}
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
return [index_to_key, key_to_index]
endfunction "}}}
let [s:index_to_key, s:key_to_index] = s:CreateIndex(g:EasyMotion_keys)
let level += 1
endwhile
" }}}
function! s:PromptUser(groups) "{{{
let single_group = len(a:groups) == 1
let targets_len = single_group ? len(a:groups[0]) : len(a:groups)
" 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 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 single_group && targets_len == 1
let group_values = values(a:groups)
if len(group_values) == 1
redraw
return a:groups[0][0]
return group_values[0]
endif
" }}}
" Prepare marker lines {{{
let lines = {}
let hl_coords = []
let current_group = 0
for group in a:groups
let element = 0
for [coords, target_key] in items(s:CreateCoordKeyDict(a:groups))
let [line_num, col_num] = split(coords, ',')
for [line_num, col_num] in group
" Add original line and marker line
if ! has_key(lines, line_num)
let current_line = getline(line_num)
@ -243,28 +377,21 @@
let lines[line_num] = { 'orig': current_line, 'marker': current_line }
endif
let marker_char = s:index_to_key[single_group ? element : current_group]
if strlen(lines[line_num]['marker']) > 0
" Replace hard tab with spaces
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
" 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
" 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
" Add highlighting coordinates
call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c')
let element += 1
endfor
let current_group += 1
endfor
let lines_items = items(lines)
@ -279,12 +406,8 @@
redraw
" Get target/group character {{{
if single_group
call s:Prompt('Target character')
else
call s:Prompt('Group character')
endif
" Get target character {{{
call s:Prompt('Target key')
let char = s:GetChar()
" }}}
@ -307,17 +430,19 @@
endif
" }}}
" 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'
endif
" }}}
if single_group
let target = a:groups[char]
if type(target) == 3
" Return target coordinates
return a:groups[0][s:key_to_index[char]]
return target
else
" Prompt for target character
return s:PromptUser([a:groups[s:key_to_index[char]]])
" Prompt for new target character
return s:PromptUser(target)
endif
endfunction "}}}
function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{
@ -356,24 +481,10 @@
throw 'No matches'
endif
" }}}
" Split targets into key groups {{{
let groups_len = len(s:index_to_key)
let groups = []
let i = 0
while i < targets_len
call add(groups, targets[i : i + groups_len - 1])
let GroupingFn = function('s:GroupingAlgorithm' . s:grouping_algorithms[g:EasyMotion_grouping])
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 {{{
if g:EasyMotion_do_shade
let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c'