Skip to content

Commit

Permalink
Merge pull request #242 from infokiller/multi-key-target-maps
Browse files Browse the repository at this point in the history
Add support for multi-keys targets prefixes.
  • Loading branch information
wellle authored Dec 8, 2019
2 parents be30977 + 8577fdc commit 8d6ff29
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 34 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,19 @@ let g:targets_aiAI = 'aiAI'
```

Controls the normal mode operator mode maps that get created for In Pair (`i`),
A Pair (`a`), Inside Pair (`I`), and Around Pair (`A`). Required to be a 4
character long list. Use a space to deactivate a mode.
A Pair (`a`), Inside Pair (`I`), and Around Pair (`A`). Required to be either a
string or a list with 4 characters/elements.

Use a space to deactivate a mode. If you want to use multiple keys, for example
`<Space>a` instead of `A`, you must use a list.

In contrast to `g:targets_nl`, special keys must not be escaped with a
backslash. For example, use `"<Space>"`
or `'<Space>'`, **not** `"\<Space>"`. Example for configuring `g:targets_aiAI`:

```vim
let g:targets_aiAI = ['<Space>a', '<Space>i', '<Space>A', '<Space>I']
```

### g:targets_mapped_aiAI

Expand All @@ -557,6 +568,8 @@ example if you want to map `k` to `i` and use `k` as `i` in targets mappings,
you need to NOT map `k` to `i` in operator pending mode, and set
`g:targets_aiAI = 'akAI'` and `g:targets_mapped_aiAI = 'aiAI'`.

Has the same format as `g:targets_aiAI`.

For more details see issue #213 and don't hesitate to comment there or open a
new issue if you need assistance.

Expand All @@ -576,7 +589,18 @@ for the last, you could set:
let g:targets_nl = 'nN'
```

Required to be a 2 character long list. Use a space to deactivate a direction.
Required to be either a string or a list with 2 characters/elements.

Use a space to deactivate a mode. If you want to use multiple keys, for example
`<Space>n` instead of `n`, you must use a list.

In contrast to `g:targets_aiAI`, special keys must be escaped with a backslash.
For example, use `"\<Space>"`, **not** `"<Space>"` nor `'<Space>'`. Example for
configuring `g:targets_nl`:

```vim
let g:targets_nl = ["\<Space>n", "\<Space>l"]
```

### g:targets_seekRanges

Expand Down
4 changes: 2 additions & 2 deletions autoload/health/targets.vim
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ function! health#targets#check() abort
let conflicts = 0

for trigger in targets#mappings#list()
for ai in split(g:targets_aiAI, '\zs')
for ai in g:targets_aiAI
let conflicts += s:check(trigger, ai . trigger)
for nl in split(g:targets_nl, '\zs')
for nl in g:targets_nl
let conflicts += s:check(trigger, ai . nl . trigger)
endfor
endfor
Expand Down
50 changes: 41 additions & 9 deletions autoload/targets.vim
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,36 @@ function! targets#e(mapmode, modifier, original)
return a:original
endif

let char1 = nr2char(getchar())
let [trigger, which, chars] = [char1, 'c', char1]
for i in range(2)
if g:targets_nl[i] ==# trigger
" trigger was which, get another char for trigger
let char2 = nr2char(getchar())
let [trigger, which, chars] = [char2, 'nl'[i], chars . char2]
let [nKeys, lKeys] = g:targets_nl

let pending = ''
while 1
let pending .= s:getKeyAsStr()

if pending ==# nKeys
let which = 'n'
let trigger = s:getKeyAsStr()
let typed = a:original . pending . trigger
break
endif
endfor

let typed = a:original . chars
if pending ==# lKeys
let which = 'l'
let trigger = s:getKeyAsStr()
let typed = a:original . pending . trigger
break
endif

if s:hasPrefix(nKeys, pending) || s:hasPrefix(lKeys, pending)
continue
endif

let which = 'c'
let trigger = pending
let typed = a:original . pending
break
endwhile

if empty(s:getFactories(trigger))
return typed
endif
Expand All @@ -63,6 +81,20 @@ function! targets#e(mapmode, modifier, original)
return "@(targets)"
endfunction

function! s:getKeyAsStr()
" getchar returns an int for a regular character, and a string for a special
" key (such as "\<Left>").
let getcharOutput = getchar()
if type(getcharOutput) == type(0)
return nr2char(getcharOutput)
endif
return getcharOutput
endfunction

function! s:hasPrefix(str, prefix)
return a:str[:len(a:prefix)-1] ==# a:prefix
endfunction

" gets called via the @(targets) mapping from above
function! targets#do()
execute s:call
Expand Down
28 changes: 24 additions & 4 deletions doc/targets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -524,9 +524,18 @@ Available options: ~
Default:
let g:targets_aiAI = 'aiAI' ~

Controls the normal mode operator mode maps that get created for In Pair (i),
A Pair (a), Inside Pair (I), and Around Pair (A). Required to be a 4 character
long list. Use a space to deactivate a mode.
Controls the normal mode operator mode maps that get created for In Pair (`i`),
A Pair (`a`), Inside Pair (`I`), and Around Pair (`A`). Required to be either a
string or a list with 4 characters/elements.

Use a space to deactivate a mode. If you want to use multiple keys, for example
`<Space>a` instead of `A`, you must use a list.

In contrast to `g:targets_nl`, special keys must not be escaped with a
backslash. For example, use `"<Space>"`
or `'<Space>'`, not `"\<Space>"`. Example for configuring `g:targets_aiAI`:

let g:targets_aiAI = ['<Space>a', '<Space>i', '<Space>A', '<Space>I'] ~

------------------------------------------------------------------------------
*g:targets_mapped_aiAI*
Expand All @@ -539,6 +548,8 @@ example if you want to map `k` to `i` and use `k` as `i` in targets mappings,
you need to NOT map `k` to `i` in operator pending mode, and set
`g:targets_aiAI = 'akAI'` and `g:targets_mapped_aiAI = 'aiAI'`

Has the same format as `g:targets_aiAI`.

For more details see issue #213 and don't hesitate to comment there or open a
new issue if you need assistance.

Expand All @@ -553,7 +564,16 @@ search for the last, you could set:

let g:targets_nl = 'nN' ~

Required to be a 4 character long list. Use a space to deactivate a direction.
Required to be either a string or a list with 2 characters/elements.

Use a space to deactivate a mode. If you want to use multiple keys, for example
`<Space>n` instead of `n`, you must use a list.

In contrast to `g:targets_aiAI`, special keys must be escaped with a backslash.
For example, use `"\<Space>"`, not `"<Space>"` nor `'<Space>'`. Example for
configuring `g:targets_nl`:

let g:targets_nl = ["\<Space>n", "\<Space>l"] ~

------------------------------------------------------------------------------
*g:targets_seekRanges*
Expand Down
43 changes: 27 additions & 16 deletions plugin/targets.vim
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,26 @@ function! s:addAllMappings()
" this is somewhat ugly, but we still need these nl values inside of the
" expression mapping and don't want to have this legacy fallback in two
" places. similarly we reuse g:targets_aiAI in the health check
let g:targets_nl = get(g:, 'targets_nl', get(g:, 'targets_nlNL', 'nl')[0:1]) " legacy fallback
let g:targets_aiAI = get(g:, 'targets_aiAI', 'aiAI')
let mapped_aiAI = get(g:, 'targets_mapped_aiAI', g:targets_aiAI)
let [s:n, s:l] = split(g:targets_nl, '\zs')
let [s:a, s:i, s:A, s:I] = split(g:targets_aiAI, '\zs')
let [s:ma, s:mi, s:mA, s:mI] = split(mapped_aiAI, '\zs')
let g:targets_nl = s:getKeysAsList(get(g:, 'targets_nl', get(g:, 'targets_nlNL', 'nl')[0:1])) " legacy fallback
let g:targets_aiAI = s:getKeysAsList(get(g:, 'targets_aiAI', 'aiAI'))
let mapped_aiAI = s:getKeysAsList(get(g:, 'targets_mapped_aiAI', g:targets_aiAI))
let [s:n, s:l] = g:targets_nl
let [s:a, s:i, s:A, s:I] = g:targets_aiAI
let [s:ma, s:mi, s:mA, s:mI] = mapped_aiAI

" if possible, create only a few expression mappings to speed up loading times
if v:version >= 704 || (v:version == 703 && has('patch338'))
" if possible, create only a few expression mappings to speed up loading times
silent! execute 'omap <expr> <unique>' s:i "targets#e('o', 'i', '" . s:mi . "')"
silent! execute 'omap <expr> <unique>' s:a "targets#e('o', 'a', '" . s:ma . "')"
silent! execute 'omap <expr> <unique>' s:I "targets#e('o', 'I', '" . s:mI . "')"
silent! execute 'omap <expr> <unique>' s:A "targets#e('o', 'A', '" . s:mA . "')"

silent! execute 'xmap <expr> <unique>' s:i "targets#e('x', 'i', '" . s:mi . "')"
silent! execute 'xmap <expr> <unique>' s:a "targets#e('x', 'a', '" . s:ma . "')"
silent! execute 'xmap <expr> <unique>' s:I "targets#e('x', 'I', '" . s:mI . "')"
silent! execute 'xmap <expr> <unique>' s:A "targets#e('x', 'A', '" . s:mA . "')"
for [modifier, map_lhs, map_rhs] in [
\ ['i', s:i, s:mi],
\ ['a', s:a, s:ma],
\ ['I', s:I, s:mI],
\ ['A', s:A, s:mA]]
" See https://github.com/wellle/targets.vim/pull/242#issuecomment-557931274
if map_lhs != '' && map_lhs != ' '
silent! execute printf("omap <expr> <unique> %s targets#e('o', '%s', '%s')", map_lhs, modifier, map_rhs)
silent! execute printf("xmap <expr> <unique> %s targets#e('o', '%s', '%s')", map_lhs, modifier, map_rhs)
endif
endfor

" #209: The above mappings don't use <silent> for better visual
" feedback on `!ip` (when we pass back control to Vim). To be silent
Expand All @@ -49,6 +51,15 @@ function! s:addAllMappings()
endif
endfunction

function! s:getKeysAsList(keys)
" if it's already an array, no need to split it.
if type(a:keys) == type([])
return a:keys
endif
" otherwise, it's a string and will be split by char.
return split(a:keys, '\zs')
endfunction

call s:addAllMappings()

let &cpoptions = s:save_cpoptions
Expand Down

0 comments on commit 8d6ff29

Please sign in to comment.