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.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: >

View File

@ -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'