Now, you can move cursor even over/across window

- implement <Plug>(easymotion-overwin-f)
- implement <Plug>(easymotion-overwin-f2)
- TODO:
  - test
  - doc
  - care configuration
This commit is contained in:
haya14busa 2016-01-15 00:59:56 +09:00
parent 39abbf30a7
commit 00fc3281c3
13 changed files with 1526 additions and 22 deletions

View File

@ -138,6 +138,10 @@ function! EasyMotion#S(num_strokes, visualmode, direction) " {{{
call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', is_inclusive)
return s:EasyMotion_is_cancelled
endfunction " }}}
function! EasyMotion#OverwinF(num_strokes) " {{{
let re = s:findMotion(a:num_strokes, s:DIRECTION.bidirection)
return EasyMotion#overwin#move(re)
endfunction "}}}
function! EasyMotion#T(num_strokes, visualmode, direction) " {{{
if a:direction == 1
let is_inclusive = 0

View File

@ -150,6 +150,12 @@ endfunction "}}}
function! s:search.on_leave(cmdline) "{{{
if s:num_strokes == -1
call EasyMotion#highlight#delete_highlight(g:EasyMotion_hl_inc_search)
if g:EasyMotion_do_shade
call EasyMotion#highlight#delete_highlight(g:EasyMotion_hl_group_shade)
endif
endif
if g:EasyMotion_cursor_highlight
call EasyMotion#highlight#delete_highlight(g:EasyMotion_hl_inc_cursor)
endif
endfunction "}}}
function! s:search.on_char(cmdline) "{{{

View File

@ -0,0 +1,6 @@
let s:V = vital#of('easymotion')
let s:HitAHintMotion = s:V.import('HitAHint.Motion')
function! EasyMotion#overwin#move(pattern) abort
return s:HitAHintMotion.move(a:pattern)
endfunction

View File

@ -237,26 +237,16 @@ function! s:_build_module(sid) abort
for func in functions
let module[func] = function(prefix . func)
endfor
if has_key(module, '_vital_created')
call module._vital_created(module)
endif
let export_module = filter(copy(module), 'v:key =~# "^\\a"')
let s:loaded[a:sid] = get(g:, 'vital_debug', 0) ? module : export_module
if has_key(module, '_vital_loaded')
let V = vital#{s:self_version}#new()
if has_key(module, '_vital_depends')
let all = {}
let modules =
\ s:_concat(map(module._vital_depends(),
\ 's:expand_modules(v:val, all)'))
call call(V.load, modules, V)
endif
try
call module._vital_loaded(V)
catch
" FIXME: Show an error message for debug.
endtry
call module._vital_loaded(V)
endif
if !get(g:, 'vital_debug', 0)
call filter(module, 'v:key =~# "^\\a"')
endif
let s:loaded[a:sid] = module
return copy(module)
return copy(s:loaded[a:sid])
endfunction
if exists('+regexpengine')

View File

@ -225,7 +225,7 @@ endfunction
" similar to Haskell's Prelude.foldl1
function! s:foldl1(f, xs) abort
if len(a:xs) == 0
throw 'foldl1'
throw 'vital: Data.List: foldl1'
endif
return s:foldl(a:f, a:xs[0], a:xs[1:])
endfunction
@ -238,7 +238,7 @@ endfunction
" similar to Haskell's Prelude.fold11
function! s:foldr1(f, xs) abort
if len(a:xs) == 0
throw 'foldr1'
throw 'vital: Data.List: foldr1'
endif
return s:foldr(a:f, a:xs[-1], a:xs[0:-2])
endfunction
@ -264,7 +264,7 @@ endfunction
" Inspired by Ruby's with_index method.
function! s:with_index(list, ...) abort
let base = a:0 > 0 ? a:1 : 0
return s:zip(a:list, range(base, len(a:list)+base-1))
return map(copy(a:list), '[v:val, v:key + base]')
endfunction
" similar to Ruby's detect or Haskell's find.

View File

@ -0,0 +1,269 @@
let s:save_cpo = &cpo
set cpo&vim
let s:TRUE = !0
let s:FALSE = 0
function! s:set(...) abort
return call(s:set._new, a:000, s:set)
endfunction
function! s:frozenset(...) abort
return call(s:frozenset._new, a:000, s:frozenset)
endfunction
function! s:_hash_func(x) abort
return a:x
endfunction
let s:_base_set = {
\ '_is_set' : s:TRUE,
\ '_data' : {},
\ '_hash_func' : function('s:_hash_func')
\ }
function! s:_base_set._new(...) abort
let obj = deepcopy(self)
let xs = get(a:, 1, [])
let obj._hash_func = get(a:, 2, obj._hash_func)
call obj._set_data(xs)
return obj
endfunction
"" Return the union of two sets as a new set.
" (I.e. all elements that are in either set.)
function! s:_base_set.union(t) abort
let r = deepcopy(self)
call r._update(a:t)
return r
endfunction
let s:_base_set.or = s:_base_set.union
"" Return the intersection of two sets as a new set.
" (I.e. all elements that are in both sets.)
function! s:_base_set.intersection(t) abort
let t = self._to_set(a:t)
let [little, big] = self.len() <= t.len() ? [self, t] : [t, self]
return self._new(filter(copy(big.to_list()), 'little.in(v:val)'))
endfunction
let s:_base_set.and = s:_base_set.intersection
"" Return the symmetric difference of two sets as a new set.
" (I.e. all elements that are in exactly one of the sets.)
function! s:_base_set.symmetric_difference(t) abort
let t = self._to_set(a:t)
return self._new(filter(copy(self.to_list()), '!t.in(v:val)')
\ + filter(copy(t.to_list()), '!self.in(v:val)'))
endfunction
let s:_base_set.xor = s:_base_set.symmetric_difference
"" Return the difference of two sets as a new Set.
function! s:_base_set.difference(t) abort
let t = self._to_set(a:t)
return self._new(filter(copy(self.to_list()), '!t.in(v:val)'))
endfunction
let s:_base_set.sub = s:_base_set.difference
"" Report whether another set contains this set.
function! s:_base_set.issubset(t) abort
let t = self._to_set(a:t)
return self.len() > t.len() ? s:FALSE
\ : empty(filter(copy(self.to_list()), '!t.in(v:val)'))
endfunction
"" Report whether this set contains another set.
function! s:_base_set.issuperset(t) abort
let t = self._to_set(a:t)
return self.len() < t.len() ? s:FALSE
\ : empty(filter(copy(t.to_list()), '!self.in(v:val)'))
endfunction
" less than equal & greater than equal
let s:_base_set.le = s:_base_set.issubset
let s:_base_set.ge = s:_base_set.issuperset
" less than
function! s:_base_set.lt(t) abort
let t = self._to_set(a:t)
return self.len() < t.len() && self.issubset(t)
endfunction
" greater than
function! s:_base_set.gt(t) abort
let t = self._to_set(a:t)
return self.len() > t.len() && self.issuperset(t)
endfunction
function! s:_base_set.len() abort
return len(self._data)
endfunction
function! s:_base_set.to_list() abort
return values(self._data)
endfunction
function! s:_base_set._update(xs) abort
for X in (s:_is_set(a:xs) ? a:xs.to_list() : a:xs)
call self._add(X)
unlet X
endfor
endfunction
function! s:_base_set._add(x) abort
let key = self._hash(a:x)
if !has_key(self._data, key)
let self._data[key] = a:x
endif
endfunction
" Report whether an element is a member of a set.
function! s:_base_set.in(x) abort
return has_key(self._data, self._hash(a:x))
endfunction
function! s:_base_set._to_set(x) abort
return s:_is_set(a:x) ? a:x : self._new(a:x)
endfunction
function! s:_base_set._clear() abort
let self._data = {}
endfunction
function! s:_base_set._set_data(xs) abort
call self._clear()
call self._update(a:xs)
endfunction
function! s:_base_set._hash(x) abort
return string(self._hash_func(a:x))
endfunction
" frozenset: Immutable set class.
let s:frozenset = deepcopy(s:_base_set)
" Set: Mutable set class.
let s:set = deepcopy(s:_base_set)
" Update a set with the union of itself and another.
function! s:set.update(iterable) abort
call self._update(a:iterable)
endfunction
" Update a set with the union of itself and another.
function! s:set.ior(t) abort
call self.update(a:t)
return self
endfunction
" Update a set with the intersection of itself and another.
function! s:set.intersection_update(t) abort
let r = self.and(a:t).to_list()
call self.clear()
call self.update(r)
endfunction
" Update a set with the intersection of itself and another.
function! s:set.iand(t) abort
call self.intersection_update(a:t)
return self
endfunction
" Update a set with the symmetric difference of itself and another.
function! s:set.symmetric_difference_update(t) abort
let t = self._to_set(a:t)
if self is t
call self.clear()
return
endif
for X in t.to_list()
if self.in(X)
call self.remove(X)
else
call self._add(X)
endif
unlet X
endfor
endfunction
" Update a set with the symmetric difference of itself and another.
function! s:set.ixor(t) abort
call self.symmetric_difference_update(a:t)
return self
endfunction
" Remove all elements of another set from this set.
function! s:set.difference_update(t) abort
let t = self._to_set(a:t)
if self is t
call self.clear()
return
endif
for X in filter(t.to_list(), 'self.in(v:val)')
call self.remove(X)
unlet X
endfor
endfunction
" Remove all elements of another set from this set.
function! s:set.isub(t) abort
call self.difference_update(a:t)
return self
endfunction
" Remove all elements from this set.
function! s:set.clear() abort
call self._clear()
endfunction
"" Add an element to a set.
" This has no effect if the element is already present.
function! s:set.add(x) abort
return self._add(a:x)
endfunction
"" Remove an element from a set; it must be a member.
" If the element is not a member, throw Exception.
function! s:set.remove(e) abort
try
unlet self._data[self._hash(a:e)]
catch /^Vim\%((\a\+)\)\?:E716/
call s:_throw('the element is not a member')
endtry
endfunction
"" Remove an element from a set if it is a member.
" If the element is not a member, do nothing.
function! s:set.discard(e) abort
try
call self.remove(a:e)
catch /vital: Data.Set: the element is not a member/
" Do nothing
endtry
endfunction
" Remove and return an arbitrary set element.
function! s:set.pop() abort
try
let k = keys(self._data)[0]
catch /^Vim\%((\a\+)\)\?:E684/
call s:_throw('set is empty')
endtry
let v = self._data[k]
unlet self._data[k]
return v
endfunction
" Helper:
function! s:_is_set(x) abort
return type(a:x) is type({}) && get(a:x, '_is_set', s:FALSE)
endfunction
function! s:_throw(message) abort
throw 'vital: Data.Set: ' . a:message
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -0,0 +1,111 @@
" function() wrapper
if v:version > 703 || v:version == 703 && has('patch1170')
let s:_function = function('function')
else
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
let s:_s = '<SNR>' . s:_SID() . '_'
function! s:_function(fstr) abort
return function(substitute(a:fstr, 's:', s:_s, 'g'))
endfunction
endif
function! s:_assert(...) abort
return ''
endfunction
function! s:_vital_loaded(V) abort
if a:V.exists('Vim.PowerAssert')
let s:assert = a:V.import('Vim.PowerAssert').assert
else
let s:assert = s:_function('s:_assert')
endif
endfunction
" TERMS:
" key: A character to generate hint. e.g. a,b,c,d,e,f,...
" hint: A hint is a combination of keys. e.g. a,b,ab,abc,..
" s:create() assigns keys to each targets and generate hint dict.
" Example:
" let targets = [1, 2, 3, 4, 5, 6]
" echo s:label(targets, ['a', 'b', 'c'])
" " => {
" 'a': 1,
" 'b': {
" 'a': 2,
" 'b': 3
" },
" 'c': {
" 'a': 4,
" 'b': 5,
" 'c': 6
" }
" }
" Hint-to-target:
" a -> 1
" ba -> 2
" bb -> 3
" ca -> 4
" cb -> 5
" cc -> 6
" @param {list<T>} targets
" @param {list<string>} keys each key should be uniq
" @return Tree{string: (T|Tree)}
function! s:create(targets, keys) abort
exe s:assert('len(a:keys) > 1')
let groups = {}
let keys_count = reverse(s:_keys_count(len(a:targets), len(a:keys)))
let target_idx = 0
let key_idx = 0
for key_count in keys_count
if key_count > 1
" We need to create a subgroup
" Recurse one level deeper
let sub_targets = a:targets[target_idx : target_idx + key_count - 1]
let groups[a:keys[key_idx]] = s:create(sub_targets, a:keys)
elseif key_count == 1
" Assign single target key_idx
let groups[a:keys[key_idx]] = a:targets[target_idx]
else
" No target
continue
endif
let key_idx += 1
let target_idx += key_count
endfor
return groups
endfunction
" s:_keys_count() generates list which represents how many targets to be
" assigned to the key.
" If the count > 1, use tree recursively.
" Example:
" echo s:_keys_count(5, 3)
" " => [3, 1, 1]
" echo s:_keys_count(8, 3)
" " => [3, 3, 2]
" @param {number} target_len
" @param {number} keys_len
function! s:_keys_count(targets_len, keys_len) abort
exe s:assert('a:keys_len > 1')
let _keys_count = repeat([0], a:keys_len)
let is_first_level = 1
let targets_left_cnt = a:targets_len
while targets_left_cnt > 0
let cnt_to_add = is_first_level ? 1 : a:keys_len - 1
for i in range(a:keys_len)
let _keys_count[i] += cnt_to_add
let targets_left_cnt -= cnt_to_add
if targets_left_cnt <= 0
let _keys_count[i] += targets_left_cnt
break
endif
endfor
let is_first_level = 0
endwhile
exe s:assert('len(_keys_count) is# a:keys_len')
return _keys_count
endfunction

View File

@ -0,0 +1,623 @@
function! s:_vital_loaded(V) abort
let s:Hint = a:V.import('HitAHint.Hint')
let s:PHighlight = a:V.import('Palette.Highlight')
let s:Buffer = a:V.import('Vim.Buffer')
let s:Prelude = a:V.import('Prelude')
let s:Set = a:V.import('Data.Set')
endfunction
function! s:_vital_depends() abort
return [
\ 'HitAHint.Hint',
\ 'Palette.Highlight',
\ 'Vim.Buffer',
\ 'Prelude',
\ 'Data.Set',
\ ]
endfunction
let s:TRUE = !0
let s:FALSE = 0
let s:DIRECTION = {'forward': 0, 'backward': 1}
" s:move() moves cursor over/accross window with Hit-A-Hint feature like
" vim-easymotion
" @param {dict} config
function! s:move(pattern, ...) abort
let o = s:new_overwin(get(a:, 1, {}))
return o.pattern(a:pattern)
endfunction
function! s:move_f(...) abort
echo 'Target: '
let c = s:getchar()
return s:move(c, get(a:, 1, {}))
endfunction
function! s:move_f2() abort
echo 'Target: '
let c = s:getchar()
redraw
echo 'Target: ' . c
let c2 = s:getchar()
return s:move(s:Prelude.escape_pattern(c . c2), get(a:, 1, {}))
endfunction
let s:overwin = {
\ 'config': {
\ 'keys': 'asdghklqwertyuiopzxcvbnmfj;',
\ 'user_upper': s:FALSE,
\ 'highlight': {
\ 'shade': 'HitAHintShade',
\ 'target': 'HitAHintTarget',
\ },
\ }
\ }
function! s:_init_hl() abort
highlight HitAHintShade ctermfg=242 guifg=#777777
" highlight HitAHintTarget cterm=bold ctermfg=196 gui=bold guifg=#ff0000
" highlight HitAHintTarget term=standout ctermfg=81 gui=bold guifg=#66D9EF
highlight HitAHintTarget ctermfg=81 gui=bold guifg=#66D9EF
endfunction
call s:_init_hl()
augroup vital-hit-a-hint-motion-default-highlight
autocmd!
autocmd ColorScheme * call s:_init_hl()
augroup END
function! s:new_overwin(...) abort
let o = deepcopy(s:overwin)
call s:deepextend(o.config, get(a:, 1, {}))
return o
endfunction
function! s:overwin.pattern(pattern) abort
let winpos = self.select_winpos(self.gather_poses_overwin(a:pattern), self.config.keys)
if winpos is# -1
else
let [winnr_str, pos] = winpos
let winnr = str2nr(winnr_str)
if winnr is# winnr()
normal! m`
else
call s:move_to_win(winnr)
endif
call cursor(pos)
endif
endfunction
function! s:overwin.select_winpos(winnr2poses, keys) abort
return self.choose_prompt(s:create_hint_dict(a:winnr2poses, a:keys))
endfunction
function! s:create_hint_dict(winnr2poses, keys) abort
let wposes = s:winnr2poses_to_list(a:winnr2poses)
let hint_dict = s:Hint.create(wposes, a:keys)
return hint_dict
endfunction
" s:wpos_to_hint() returns dict whose key is position with window and whose
" value is the hints.
" @param Tree{string: ((winnr, (number,number))|Tree)} hint_dict
" @return {{winnr: {string: list<char>}}} poskey to hint for each window
" e.g.
" {
" '1': {
" '00168:00004': ['b', 'c', 'b'],
" '00174:00001': ['b', 'c', 'a'],
" '00188:00004': ['b', 'b'],
" '00190:00001': ['b', 'a'],
" '00191:00016': ['a', 'c'],
" '00192:00004': ['a', 'b'],
" '00195:00035': ['a', 'a']
" },
" '3': {
" '00168:00004': ['c', 'c', 'c'],
" '00174:00001': ['c', 'c', 'b'],
" '00188:00004': ['c', 'c', 'a'],
" '00190:00001': ['c', 'b'],
" '00191:00016': ['c', 'a'],
" '00192:00004': ['b', 'c', 'c']
" }
" }
function! s:create_win2pos2hint(hint_dict) abort
return s:_create_win2pos2hint({}, a:hint_dict)
endfunction
function! s:_create_win2pos2hint(dict, hint_dict, ...) abort
let prefix = get(a:, 1, [])
for [hint, v] in items(a:hint_dict)
if type(v) is# type({})
call s:_create_win2pos2hint(a:dict, v, prefix + [hint])
else
let [winnr, pos] = v
let a:dict[winnr] = get(a:dict, winnr, {})
let a:dict[winnr][s:pos2poskey(pos)] = prefix + [hint]
endif
unlet v
endfor
return a:dict
endfunction
" s:pos2poskey() convertes pos to poskey to use pos as dictionary keys and
" sort pos correctly.
" @param {(number,number)} pos
" @return string
" e.g. [1, 1] -> '00001:00001'
function! s:pos2poskey(pos) abort
return join(map(copy(a:pos), "printf('%05d', v:val)"), ':')
endfunction
" s:poskey2pos() convertes poskey to pos.
" @param {string} poskey e.g. '00001:00001'
" @return {(number,number)}
" e.g. '00001:00001' -> [1, 1]
function! s:poskey2pos(poskey) abort
return map(split(a:poskey, ':'), 'str2nr(v:val)')
endfunction
function! s:overwin.choose_prompt(hint_dict) abort
if empty(a:hint_dict)
redraw
echo 'No target'
return -1
endif
let hinter = s:Hinter.new(a:hint_dict, self.config)
try
call hinter.before()
call hinter.show_hint()
redraw
echo 'Target key: '
let c = s:getchar()
if self.config.user_upper
let c = toupper(c)
endif
finally
call hinter.after()
endtry
if has_key(a:hint_dict, c)
let target = a:hint_dict[c]
return type(target) is# type({}) ? self.choose_prompt(target) : target
else
redraw
echo 'Invalid target: ' . c
return -1
endif
endfunction
" Hinter show hints accross window.
" save_lines: {{winnr: {lnum: string}}}
" w2l2c2h: winnr to lnum to col num to hints. col2hints is tuple because we
" need sorted col to hints pair.
" save_syntax: {{winnr: &syntax}}
" {{winnr: {lnum: list<(cnum, list<char>)>}}}
let s:Hinter = {
\ 'save_lines': {},
\ 'w2l2c2h': {},
\ 'winnrs': [],
\ 'save_syntax': {},
\ 'save_conceallevel': {},
\ 'save_concealcursor': {},
\ 'save_modified': {},
\ 'save_modifiable': {},
\ 'save_readonly': {},
\ 'highlight_ids': {},
\ }
" @param {{winnr: {string: list<char>}}}
" function! s:Hinter.new(win2pos2hint) abort
function! s:Hinter.new(hint_dict, config) abort
let s = deepcopy(self)
let s.config = a:config
let win2pos2hint = s:create_win2pos2hint(a:hint_dict)
let s.winnrs = map(keys(win2pos2hint), 'str2nr(v:val)')
let s.win2pos2hint = win2pos2hint
let s.w2l2c2h = s:win2pos2hint_to_w2l2c2h(win2pos2hint)
call s._save_lines()
return s
endfunction
function! s:Hinter.before() abort
call self.modify_env()
call self.disable_conceal_in_other_win()
endfunction
function! s:Hinter.after() abort
call self.restore_env()
call self.restore_conceal_in_other_win()
call self.restore_lines()
endfunction
function! s:Hinter._save_lines() abort
let nr = winnr()
try
for [winnr, pos2hint] in items(self.win2pos2hint)
call s:move_to_win(winnr)
let lnums = map(copy(keys(pos2hint)), 's:poskey2pos(v:val)[0]')
let self.save_lines[winnr] = get(self.save_lines, winnr, {})
for lnum in lnums
let self.save_lines[winnr][lnum] = getline(lnum)
endfor
endfor
finally
call s:move_to_win(nr)
endtry
endfunction
function! s:Hinter.restore_lines() abort
let nr = winnr()
try
for [winnr, lnum2line] in items(self.save_lines)
call s:move_to_win(winnr)
for [lnum, line] in items(lnum2line)
if line isnot# getline(lnum)
call setline(lnum, line)
endif
endfor
endfor
finally
call s:move_to_win(nr)
endtry
endfunction
function! s:Hinter.modify_env() abort
let nr = winnr()
try
let self.highlight_id_cursor = matchadd('Cursor', '\%#', 1000001)
for winnr in self.winnrs
call s:move_to_win(winnr)
let self.save_syntax[winnr] = &syntax
let self.save_conceallevel[winnr] = &l:conceallevel
let self.save_concealcursor[winnr] = &l:concealcursor
let self.save_modified[winnr] = &l:modified
let self.save_modifiable[winnr] = &l:modifiable
let self.save_readonly[winnr] = &l:readonly
setlocal modifiable
setlocal noreadonly
let self.save_conceal = s:PHighlight.get('Conceal')
ownsyntax overwin
syntax clear
setlocal conceallevel=2
setlocal concealcursor=ncv
execute 'highlight! link Conceal' self.config.highlight.target
let self.highlight_ids[winnr] = get(self.highlight_ids, winnr, [])
let self.highlight_ids[winnr] += [matchadd(self.config.highlight.shade, '\_.*', 100)]
endfor
finally
call s:move_to_win(nr)
endtry
endfunction
function! s:Hinter.restore_env() abort
let nr = winnr()
try
call matchdelete(self.highlight_id_cursor)
for winnr in self.winnrs
call s:move_to_win(winnr)
let &syntax = self.save_syntax[winnr]
call s:PHighlight.set('Conceal', self.save_conceal)
let &l:conceallevel = self.save_conceallevel[winnr]
let &l:concealcursor = self.save_concealcursor[winnr]
" Turn off &l:modified before restoring thie value so that restore undo
" state. It's important to turn off this option after manipulating
" buffer text.
let &l:modified = 0
let &l:modified = self.save_modified[winnr]
let &l:modifiable = self.save_modifiable[winnr]
let &l:readonly = self.save_readonly[winnr]
for id in self.highlight_ids[winnr]
call matchdelete(id)
endfor
endfor
finally
call s:move_to_win(nr)
endtry
endfunction
function! s:Hinter.disable_conceal_in_other_win() abort
let allwinnrs = s:Set.set(range(1, winnr('$')))
let other_winnrs = allwinnrs.sub(self.winnrs).to_list()
for w in other_winnrs
if 'help' !=# getwinvar(w, '&buftype')
call setwinvar(w, 'overwin_save_conceallevel', getwinvar(w, '&conceallevel'))
call setwinvar(w, '&conceallevel', 0)
endif
endfor
endfunction
function! s:Hinter.restore_conceal_in_other_win() abort
let allwinnrs = s:Set.set(range(1, winnr('$')))
let other_winnrs = allwinnrs.sub(self.winnrs).to_list()
for w in other_winnrs
if 'help' !=# getwinvar(w, '&buftype')
call setwinvar(w, '&conceallevel', getwinvar(w, 'overwin_save_conceallevel'))
endif
endfor
endfunction
" ._pos2hint_to_line2col2hint() converts pos2hint to line2col2hint dict whose
" key is line number and whose value is list of tuple of col number to hint.
" line2col2hint is for show hint with replacing line by line.
" col should be sorted.
" @param {{string: list<char>}} pos2hint
" @return {number: [(number, list<char>)]}
function! s:Hinter._pos2hint_to_line2col2hint(pos2hint) abort
let line2col2hint = {}
let poskeys = sort(keys(a:pos2hint))
for poskey in poskeys
let [lnum, cnum] = s:poskey2pos(poskey)
let line2col2hint[lnum] = get(line2col2hint, lnum, [])
let line2col2hint[lnum] += [[cnum, a:pos2hint[poskey]]]
endfor
return line2col2hint
endfunction
function! s:Hinter.show_hint() abort
let nr = winnr()
try
for winnr in self.winnrs
call s:move_to_win(winnr)
call self._show_hint_for_win(winnr)
endfor
finally
call s:move_to_win(nr)
endtry
endfunction
function! s:Hinter._show_hint_for_win(winnr) abort
for [lnum, col2hint] in items(self.w2l2c2h[a:winnr])
call self._show_hint_for_line(a:winnr, lnum, col2hint)
endfor
endfunction
function! s:Hinter._show_hint_for_line(winnr, lnum, col2hint) abort
let line = self.save_lines[a:winnr][a:lnum]
let col_offset = 0
let prev_cnum = -1
let next_offset = 0
for [cnum, hint] in a:col2hint
let col_num = cnum + col_offset
let is_consecutive = cnum is# prev_cnum + 1
" if cnum isnot# prev_cnum + 1
if !is_consecutive
let col_num += next_offset
else
let save_next_offset = next_offset
endif
let [line, offset, next_offset] = self._replace_line_for_hint(a:lnum, col_num, line, hint)
if is_consecutive
let col_offset += save_next_offset
endif
let col_offset += offset
call s:show_hint_pos(a:lnum, col_num, hint[0])
if len(hint) > 1
call s:show_hint_pos(a:lnum, col_num + 1, hint[1])
endif
let prev_cnum = cnum
endfor
endfunction
" ._replace_line_for_hint() replaces line to show hints.
" - It appends space if the line is empty
" - It replaces <Tab> to space if the target character is <Tab>
" - It replaces next target character if it's <Tab> and len(hint) > 1
" Replacing line changes col number, so it returns offset of col number.
" As for replaceing next target character, the timing to calculate offset
" depends on the col number of next hint in the same line, so it returns
" `next_offset` instead of returning offset all at once.
" @return {(string, number, number)} (line, offset, next_offset)
function! s:Hinter._replace_line_for_hint(lnum, col_num, line, hint) abort
let line = a:line
let col_num = a:col_num
let target = matchstr(line, '\%' . col_num .'c.')
" Append one space for empty line or match at end of line
if target is# ''
let hintwidth = strdisplaywidth(join(a:hint[:1], ''))
let line .= repeat(' ', hintwidth)
call setline(a:lnum, line)
return [line, hintwidth, 0]
endif
let offset = 0
if target is# "\t"
let [line, offset] = self._replace_tab_target(a:lnum, col_num, line)
elseif strdisplaywidth(target) > 1
let line = self._replace_text_to_space(line, a:lnum, col_num, strdisplaywidth(target))
let offset = strdisplaywidth(target) - len(target)
endif
let next_offset = 0
if len(a:hint) > 1
" pass [] as hint to stop recursion.
let [line, next_offset, _] = self._replace_line_for_hint(a:lnum, col_num + offset + 1, line, [])
endif
return [line, offset, next_offset]
endfunction
" @return {(line, offset)}
function! s:Hinter._replace_tab_target(lnum, col_num, line) abort
let space_len = s:tab2spacelen(a:line, a:col_num)
let line = self._replace_text_to_space(a:line, a:lnum, a:col_num, space_len)
return [line, space_len - 1]
endfunction
function! s:Hinter._replace_text_to_space(line, lnum, col_num, len) abort
let target = printf('\%%%dc.', a:col_num)
let line = substitute(a:line, target, repeat(' ', a:len), '')
call setline(a:lnum, line)
return line
endfunction
" @param {number} col_num col_num is 1 origin like col()
function! s:tab2spacelen(line, col_num) abort
let before_line = a:col_num > 2 ? a:line[: a:col_num - 2]
\ : a:col_num is# 2 ? a:line[0]
\ : ''
let vcol_num = 1
for c in split(before_line, '\zs')
let vcol_num += c is# "\t" ? s:_virtual_tab2spacelen(vcol_num) : len(c)
endfor
return s:_virtual_tab2spacelen(vcol_num)
endfunction
function! s:_virtual_tab2spacelen(col_num) abort
return &tabstop - ((a:col_num - 1) % &tabstop)
endfunction
function! s:win2pos2hint_to_w2l2c2h(win2pos2hint) abort
let w2l2c2h = {}
for [winnr, pos2hint] in items(a:win2pos2hint)
let w2l2c2h[winnr] = s:pos2hint_to_line2col2hint(pos2hint)
endfor
return w2l2c2h
endfunction
" s:pos2hint_to_line2col2hint() converts pos2hint to line2col2hint dict whose
" key is line number and whose value is list of tuple of col number to hint.
" line2col2hint is for show hint with replacing line by line.
" col should be sorted.
" @param {{string: list<char>}} pos2hint
" @return {number: [(number, list<char>)]}
function! s:pos2hint_to_line2col2hint(pos2hint) abort
let line2col2hint = {}
let poskeys = sort(keys(a:pos2hint))
for poskey in poskeys
let [lnum, cnum] = s:poskey2pos(poskey)
let line2col2hint[lnum] = get(line2col2hint, lnum, [])
let line2col2hint[lnum] += [[cnum, a:pos2hint[poskey]]]
endfor
return line2col2hint
endfunction
" @param {number} winnr
function! s:move_to_win(winnr) abort
if a:winnr !=# winnr()
execute a:winnr . 'wincmd w'
endif
endfunction
" @param {regex} pattern
" @return {{winnr: list<list<(number,number))>}}
function! s:overwin.gather_poses_overwin(pattern) abort
return s:wincall(function('s:gather_poses'), [a:pattern])
endfunction
" s:gather_poses() aggregates patterm matched positions in visible current
" window for both direction excluding poses in fold.
" @return {{list<list<(number,number))>}}
function! s:gather_poses(pattern) abort
let f = s:gather_visible_matched_poses(a:pattern, s:DIRECTION.forward, s:TRUE)
let b = s:gather_visible_matched_poses(a:pattern, s:DIRECTION.backward, s:FALSE)
return filter(f + b, '!s:is_in_fold(v:val[0])')
endfunction
" s:gather_visible_matched_poses() aggregates pattern matched positions in visible current
" window.
" @param {regex} pattern
" @param {enum<DIRECTION>} direction see s:DIRECTION
" @param {bool} allow_cursor_pos_match
" @return {list<list<(number,number)>>} positions
function! s:gather_visible_matched_poses(pattern, direction, allow_cursor_pos_match) abort
let stop_line = line(a:direction is# s:DIRECTION.forward ? 'w$' : 'w0')
let search_flag = (a:direction is# s:DIRECTION.forward ? '' : 'b')
let c_flag = a:allow_cursor_pos_match ? 'c' : ''
let view = winsaveview()
let poses = []
keepjumps let pos = searchpos(a:pattern, c_flag . search_flag, stop_line)
while pos != [0, 0]
let poses += [pos]
keepjumps let pos = searchpos(a:pattern, search_flag, stop_line)
endwhile
call winrestview(view)
return poses
endfunction
" @param {{winnr: list<list<(number,number))>}} winnr2poses
" @param {number?} first_winnr the top winnr poses in returned list
" @return {list<{list<(winnr, (number,number))}>}
function! s:winnr2poses_to_list(winnr2poses, ...) abort
let first_winnr = get(a:, 1, winnr())
let first_winnr_poses = []
let other_poses = []
for [winnr_str, poses] in items(a:winnr2poses)
let winnr = str2nr(winnr_str)
if winnr is# first_winnr
let first_winnr_poses = map(copy(poses), '[winnr, v:val]')
else
let other_poses += map(copy(poses), '[winnr, v:val]')
endif
endfor
return first_winnr_poses + other_poses
endfunction
" @param {number} lnum line number
function! s:is_in_fold(lnum) abort
return foldclosed(a:lnum) != -1
endfunction
function! s:getchar(...)
let mode = get(a:, 1, 0)
while 1
let char = call('getchar', a:000)
" Workaround for the <expr> mappings
if string(char) !~# "\x80\xfd`"
return mode == 1 ? !!char
\ : type(char) == type(0) ? nr2char(char) : char
endif
endwhile
endfunction
" @param {funcref} func
" @param {arglist} list<S>
" @param {dict?} dict for :h call()
" @return {{winnr: <T>}}
function! s:wincall(func, arglist, ...) abort
let dict = get(a:, 1, {})
let r = {}
let start_winnr = winnr()
let r[start_winnr] = call(a:func, a:arglist, dict)
if s:Buffer.is_cmdwin()
return r
endif
noautocmd wincmd w
while winnr() isnot# start_winnr
let r[winnr()] = call(a:func, a:arglist, dict)
noautocmd wincmd w
endwhile
return r
endfunction
function! s:show_hint_pos(lnum, cnum, char) abort
let p = '\%'. a:lnum . 'l\%'. a:cnum . 'c.'
exec "syntax match HitAHintTarget '". p . "' conceal cchar=". a:char
endfunction
" deepextend (nest: 1)
function! s:deepextend(expr1, expr2) abort
let expr2 = copy(a:expr2)
for [k, V] in items(a:expr1)
if (type(V) is type({}) || type(V) is type([])) && has_key(expr2, k)
let a:expr1[k] = extend(a:expr1[k], expr2[k])
unlet expr2[k]
endif
unlet V
endfor
return extend(a:expr1, expr2)
endfunction

View File

@ -0,0 +1,385 @@
let s:save_cpo = &cpo
set cpo&vim
if v:version ># 703 ||
\ (v:version is 703 && has('patch465'))
function! s:glob(expr) abort
return glob(a:expr, 1, 1)
endfunction
else
function! s:glob(expr) abort
let R = glob(a:expr, 1)
return split(R, '\n')
endfunction
endif
function! s:globpath(path, expr) abort
let R = globpath(a:path, a:expr, 1)
return split(R, '\n')
endfunction
" Wrapper functions for type().
let [
\ s:__TYPE_NUMBER,
\ s:__TYPE_STRING,
\ s:__TYPE_FUNCREF,
\ s:__TYPE_LIST,
\ s:__TYPE_DICT,
\ s:__TYPE_FLOAT] = [
\ type(3),
\ type(''),
\ type(function('tr')),
\ type([]),
\ type({}),
\ has('float') ? type(str2float('0')) : -1]
" __TYPE_FLOAT = -1 when -float
" This doesn't match to anything.
" Number or Float
function! s:is_numeric(Value) abort
let _ = type(a:Value)
return _ ==# s:__TYPE_NUMBER
\ || _ ==# s:__TYPE_FLOAT
endfunction
" Number
function! s:is_number(Value) abort
return type(a:Value) ==# s:__TYPE_NUMBER
endfunction
" Float
function! s:is_float(Value) abort
return type(a:Value) ==# s:__TYPE_FLOAT
endfunction
" String
function! s:is_string(Value) abort
return type(a:Value) ==# s:__TYPE_STRING
endfunction
" Funcref
function! s:is_funcref(Value) abort
return type(a:Value) ==# s:__TYPE_FUNCREF
endfunction
" List
function! s:is_list(Value) abort
return type(a:Value) ==# s:__TYPE_LIST
endfunction
" Dictionary
function! s:is_dict(Value) abort
return type(a:Value) ==# s:__TYPE_DICT
endfunction
function! s:truncate_skipping(str, max, footer_width, separator) abort
call s:_warn_deprecated('truncate_skipping', 'Data.String.truncate_skipping')
let width = s:wcswidth(a:str)
if width <= a:max
let ret = a:str
else
let header_width = a:max - s:wcswidth(a:separator) - a:footer_width
let ret = s:strwidthpart(a:str, header_width) . a:separator
\ . s:strwidthpart_reverse(a:str, a:footer_width)
endif
return s:truncate(ret, a:max)
endfunction
function! s:truncate(str, width) abort
" Original function is from mattn.
" http://github.com/mattn/googlereader-vim/tree/master
call s:_warn_deprecated('truncate', 'Data.String.truncate')
if a:str =~# '^[\x00-\x7f]*$'
return len(a:str) < a:width ?
\ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width)
endif
let ret = a:str
let width = s:wcswidth(a:str)
if width > a:width
let ret = s:strwidthpart(ret, a:width)
let width = s:wcswidth(ret)
endif
if width < a:width
let ret .= repeat(' ', a:width - width)
endif
return ret
endfunction
function! s:strwidthpart(str, width) abort
call s:_warn_deprecated('strwidthpart', 'Data.String.strwidthpart')
if a:width <= 0
return ''
endif
let ret = a:str
let width = s:wcswidth(a:str)
while width > a:width
let char = matchstr(ret, '.$')
let ret = ret[: -1 - len(char)]
let width -= s:wcswidth(char)
endwhile
return ret
endfunction
function! s:strwidthpart_reverse(str, width) abort
call s:_warn_deprecated('strwidthpart_reverse', 'Data.String.strwidthpart_reverse')
if a:width <= 0
return ''
endif
let ret = a:str
let width = s:wcswidth(a:str)
while width > a:width
let char = matchstr(ret, '^.')
let ret = ret[len(char) :]
let width -= s:wcswidth(char)
endwhile
return ret
endfunction
if v:version >= 703
" Use builtin function.
function! s:wcswidth(str) abort
call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth')
return strwidth(a:str)
endfunction
else
function! s:wcswidth(str) abort
call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth')
if a:str =~# '^[\x00-\x7f]*$'
return strlen(a:str)
end
let mx_first = '^\(.\)'
let str = a:str
let width = 0
while 1
let ucs = char2nr(substitute(str, mx_first, '\1', ''))
if ucs == 0
break
endif
let width += s:_wcwidth(ucs)
let str = substitute(str, mx_first, '', '')
endwhile
return width
endfunction
" UTF-8 only.
function! s:_wcwidth(ucs) abort
let ucs = a:ucs
if (ucs >= 0x1100
\ && (ucs <= 0x115f
\ || ucs == 0x2329
\ || ucs == 0x232a
\ || (ucs >= 0x2e80 && ucs <= 0xa4cf
\ && ucs != 0x303f)
\ || (ucs >= 0xac00 && ucs <= 0xd7a3)
\ || (ucs >= 0xf900 && ucs <= 0xfaff)
\ || (ucs >= 0xfe30 && ucs <= 0xfe6f)
\ || (ucs >= 0xff00 && ucs <= 0xff60)
\ || (ucs >= 0xffe0 && ucs <= 0xffe6)
\ || (ucs >= 0x20000 && ucs <= 0x2fffd)
\ || (ucs >= 0x30000 && ucs <= 0x3fffd)
\ ))
return 2
endif
return 1
endfunction
endif
let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95')
let s:is_cygwin = has('win32unix')
let s:is_mac = !s:is_windows && !s:is_cygwin
\ && (has('mac') || has('macunix') || has('gui_macvim') ||
\ (!isdirectory('/proc') && executable('sw_vers')))
let s:is_unix = has('unix')
function! s:is_windows() abort
return s:is_windows
endfunction
function! s:is_cygwin() abort
return s:is_cygwin
endfunction
function! s:is_mac() abort
return s:is_mac
endfunction
function! s:is_unix() abort
return s:is_unix
endfunction
function! s:_warn_deprecated(name, alternative) abort
try
echohl Error
echomsg 'Prelude.' . a:name . ' is deprecated! Please use ' . a:alternative . ' instead.'
finally
echohl None
endtry
endfunction
function! s:smart_execute_command(action, word) abort
execute a:action . ' ' . (a:word ==# '' ? '' : '`=a:word`')
endfunction
function! s:escape_file_searching(buffer_name) abort
return escape(a:buffer_name, '*[]?{}, ')
endfunction
function! s:escape_pattern(str) abort
return escape(a:str, '~"\.^$[]*')
endfunction
function! s:getchar(...) abort
let c = call('getchar', a:000)
return type(c) == type(0) ? nr2char(c) : c
endfunction
function! s:getchar_safe(...) abort
let c = s:input_helper('getchar', a:000)
return type(c) == type('') ? c : nr2char(c)
endfunction
function! s:input_safe(...) abort
return s:input_helper('input', a:000)
endfunction
function! s:input_helper(funcname, args) abort
let success = 0
if inputsave() !=# success
throw 'vital: Prelude: inputsave() failed'
endif
try
return call(a:funcname, a:args)
finally
if inputrestore() !=# success
throw 'vital: Prelude: inputrestore() failed'
endif
endtry
endfunction
function! s:set_default(var, val) abort
if !exists(a:var) || type({a:var}) != type(a:val)
let {a:var} = a:val
endif
endfunction
function! s:substitute_path_separator(path) abort
return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path
endfunction
function! s:path2directory(path) abort
return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h'))
endfunction
function! s:_path2project_directory_git(path) abort
let parent = a:path
while 1
let path = parent . '/.git'
if isdirectory(path) || filereadable(path)
return parent
endif
let next = fnamemodify(parent, ':h')
if next == parent
return ''
endif
let parent = next
endwhile
endfunction
function! s:_path2project_directory_svn(path) abort
let search_directory = a:path
let directory = ''
let find_directory = s:escape_file_searching(search_directory)
let d = finddir('.svn', find_directory . ';')
if d ==# ''
return ''
endif
let directory = fnamemodify(d, ':p:h:h')
" Search parent directories.
let parent_directory = s:path2directory(
\ fnamemodify(directory, ':h'))
if parent_directory !=# ''
let d = finddir('.svn', parent_directory . ';')
if d !=# ''
let directory = s:_path2project_directory_svn(parent_directory)
endif
endif
return directory
endfunction
function! s:_path2project_directory_others(vcs, path) abort
let vcs = a:vcs
let search_directory = a:path
let find_directory = s:escape_file_searching(search_directory)
let d = finddir(vcs, find_directory . ';')
if d ==# ''
return ''
endif
return fnamemodify(d, ':p:h:h')
endfunction
function! s:path2project_directory(path, ...) abort
let is_allow_empty = get(a:000, 0, 0)
let search_directory = s:path2directory(a:path)
let directory = ''
" Search VCS directory.
for vcs in ['.git', '.bzr', '.hg', '.svn']
if vcs ==# '.git'
let directory = s:_path2project_directory_git(search_directory)
elseif vcs ==# '.svn'
let directory = s:_path2project_directory_svn(search_directory)
else
let directory = s:_path2project_directory_others(vcs, search_directory)
endif
if directory !=# ''
break
endif
endfor
" Search project file.
if directory ==# ''
for d in ['build.xml', 'prj.el', '.project', 'pom.xml', 'package.json',
\ 'Makefile', 'configure', 'Rakefile', 'NAnt.build',
\ 'P4CONFIG', 'tags', 'gtags']
let d = findfile(d, s:escape_file_searching(search_directory) . ';')
if d !=# ''
let directory = fnamemodify(d, ':p:h')
break
endif
endfor
endif
if directory ==# ''
" Search /src/ directory.
let base = s:substitute_path_separator(search_directory)
if base =~# '/src/'
let directory = base[: strridx(base, '/src/') + 3]
endif
endif
if directory ==# '' && !is_allow_empty
" Use original path.
let directory = search_directory
endif
return s:substitute_path_separator(directory)
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -0,0 +1,96 @@
let s:save_cpo = &cpo
set cpo&vim
function! s:_vital_loaded(V) abort
let s:V = a:V
let s:P = s:V.import('Prelude')
endfunction
function! s:_vital_depends() abort
return ['Prelude']
endfunction
if exists('*getcmdwintype')
function! s:is_cmdwin() abort
return getcmdwintype() !=# ''
endfunction
else
function! s:is_cmdwin() abort
return bufname('%') ==# '[Command Line]'
endfunction
endif
function! s:open(buffer, opener) abort
let save_wildignore = &wildignore
let &wildignore = ''
try
if s:P.is_funcref(a:opener)
let loaded = !bufloaded(a:buffer)
call a:opener(a:buffer)
elseif a:buffer is 0 || a:buffer is# ''
let loaded = 1
silent execute a:opener
enew
else
let loaded = !bufloaded(a:buffer)
if s:P.is_string(a:buffer)
execute a:opener '`=a:buffer`'
elseif s:P.is_number(a:buffer)
silent execute a:opener
execute a:buffer 'buffer'
else
throw 'vital: Vim.Buffer: Unknown opener type.'
endif
endif
finally
let &wildignore = save_wildignore
endtry
return loaded
endfunction
function! s:get_selected_text(...) abort
echohl WarningMsg
echom "[WARN] s:get_selected_text() is deprecated. Use 's:get_last_selected()'."
echohl None
return call('s:get_last_selected', a:000)
endfunction
" Get the last selected text in visual mode
" without using |gv| to avoid |textlock|.
" NOTE:
" * This function uses |gv| only when using |CTRL-V|
" because |gv| is the only way to get selected text
" when using <C-v>$ .
" Please see #192 for the details.
" * If you don't care about |textlock|,
" you can use simple version of this function.
" https://github.com/vim-jp/vital.vim/commit/39aae80f3839fdbeebd838ff14d87327a6b889a9
function! s:get_last_selected() abort
if visualmode() ==# "\<C-v>"
let save = getreg('"', 1)
let save_type = getregtype('"')
try
normal! gv""y
return @"
finally
call setreg('"', save, save_type)
endtry
else
let [begin, end] = [getpos("'<"), getpos("'>")]
let lastchar = matchstr(getline(end[1])[end[2]-1 :], '.')
if begin[1] ==# end[1]
let lines = [getline(begin[1])[begin[2]-1 : end[2]-2]]
else
let lines = [getline(begin[1])[begin[2]-1 :]]
\ + (end[1] - begin[1] <# 2 ? [] : getline(begin[1]+1, end[1]-1))
\ + [getline(end[1])[: end[2]-2]]
endif
return join(lines, "\n") . lastchar . (visualmode() ==# 'V' ? "\n" : '')
endif
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et ts=2 sts=2 sw=2 tw=0:

View File

@ -3,6 +3,15 @@ set cpo&vim
function! s:echo(hl, msg) abort
execute 'echohl' a:hl
try
echo a:msg
finally
echohl None
endtry
endfunction
function! s:echomsg(hl, msg) abort
execute 'echohl' a:hl
try

View File

@ -1,5 +1,5 @@
easymotion
423c2c0
65c9afb0799cb950cbaf9258aefc6c3ad700a98f
Over.Commandline.Base
Over.Commandline.Modules.Cancel
@ -17,3 +17,4 @@ Over.Commandline.Modules.Exit
Over.Commandline.Modules.DrawCommandline
Over.Commandline.Modules.ExceptionMessage
Over.Commandline.Modules.ExceptionExit
HitAHint.Motion

View File

@ -130,7 +130,11 @@ call s:motion_map_helper({
\ 'tln' : {'fnc' : 'TL' , 'cnt' : -1, 'direction' : 0},
\ 'Tln' : {'fnc' : 'TL' , 'cnt' : -1, 'direction' : 1},
\ 'bd-tln' : {'fnc' : 'TL' , 'cnt' : -1, 'direction' : 2},
\ })
\ })
nnoremap <silent> <Plug>(easymotion-overwin-f) :<C-u>call EasyMotion#OverwinF(1)<CR>
nnoremap <silent> <Plug>(easymotion-overwin-f2) :<C-u>call EasyMotion#OverwinF(2)<CR>
"}}}
" -- Word Motion {{{