55 changed files with 3977 additions and 613 deletions
@ -1,4 +0,0 @@
@@ -1,4 +0,0 @@
|
||||
doc/tags |
||||
tmp* |
||||
*.lock |
||||
.vim-flavor |
@ -1,10 +0,0 @@
@@ -1,10 +0,0 @@
|
||||
language: ruby |
||||
rvm: |
||||
- 2.0.0 |
||||
before_script: |
||||
- bundle install |
||||
- bundle show |
||||
script: |
||||
- rake ci |
||||
install: |
||||
- git clone https://github.com/kana/vim-vspec.git |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
let s:V = vital#easymotion#new() |
||||
let s:HitAHintMotion = s:V.import('HitAHint.Motion') |
||||
|
||||
call EasyMotion#init() |
||||
|
||||
function! EasyMotion#overwin#move(pattern) abort |
||||
return s:HitAHintMotion.move(a:pattern, { |
||||
\ 'keys': g:EasyMotion_keys, |
||||
\ 'use_upper': g:EasyMotion_use_upper, |
||||
\ 'highlight': { |
||||
\ 'shade': g:EasyMotion_hl_group_shade, |
||||
\ 'target': g:EasyMotion_hl_group_target, |
||||
\ }, |
||||
\ 'jump_first_target_keys': |
||||
\ (g:EasyMotion_enter_jump_first ? ["\<CR>"] : []) + |
||||
\ (g:EasyMotion_space_jump_first ? ["\<Space>"] : []), |
||||
\ 'do_shade': g:EasyMotion_do_shade, |
||||
\ }) |
||||
endfunction |
||||
|
||||
function! EasyMotion#overwin#line() abort |
||||
return EasyMotion#overwin#move('^') |
||||
endfunction |
||||
|
||||
function! EasyMotion#overwin#w() abort |
||||
return EasyMotion#overwin#move('\(\<.\|^$\)') |
||||
endfunction |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
let s:Buffer = vital#easymotion#import('Vim.Buffer') |
||||
|
||||
function! EasyMotion#undo#save() abort |
||||
return s:undo_lock.save() |
||||
endfunction |
||||
|
||||
let s:undo_lock = {} |
||||
|
||||
function! s:undo_lock.save() abort |
||||
let undo = deepcopy(self) |
||||
call undo._save() |
||||
return undo |
||||
endfunction |
||||
|
||||
function! s:undo_lock._save() abort |
||||
if undotree().seq_last == 0 |
||||
" if there are no undo history, disable undo feature by setting |
||||
" 'undolevels' to -1 and restore it. |
||||
let self.save_undolevels = &l:undolevels |
||||
let &l:undolevels = -1 |
||||
elseif !s:Buffer.is_cmdwin() |
||||
" command line window doesn't support :wundo. |
||||
let self.undofile = tempname() |
||||
execute 'wundo!' self.undofile |
||||
else |
||||
let self.is_cmdwin = s:TRUE |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:undo_lock.restore() abort |
||||
if has_key(self, 'save_undolevels') |
||||
let &l:undolevels = self.save_undolevels |
||||
endif |
||||
if has_key(self, 'undofile') && filereadable(self.undofile) |
||||
silent execute 'rundo' self.undofile |
||||
call delete(self.undofile) |
||||
endif |
||||
if has_key(self, 'is_cmdwin') |
||||
" XXX: it breaks undo history. AFAIK, there are no way to save and restore |
||||
" undo history in commandline window. |
||||
call self.undobreak() |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:undo_lock.undobreak() abort |
||||
let old_undolevels = &l:undolevels |
||||
setlocal undolevels=-1 |
||||
keepjumps call setline('.', getline('.')) |
||||
let &l:undolevels = old_undolevels |
||||
endfunction |
@ -1,319 +1,5 @@
@@ -1,319 +1,5 @@
|
||||
let s:self_version = expand('<sfile>:t:r') |
||||
let s:self_file = expand('<sfile>') |
||||
let s:_plugin_name = expand('<sfile>:t:r') |
||||
|
||||
" Note: The extra argument to globpath() was added in Patch 7.2.051. |
||||
let s:globpath_third_arg = v:version > 702 || v:version == 702 && has('patch51') |
||||
|
||||
let s:loaded = {} |
||||
let s:cache_module_path = {} |
||||
let s:cache_sid = {} |
||||
|
||||
let s:_vital_files_cache_runtimepath = '' |
||||
let s:_vital_files_cache = [] |
||||
let s:_unify_path_cache = {} |
||||
|
||||
function! s:import(name, ...) abort |
||||
let target = {} |
||||
let functions = [] |
||||
for a in a:000 |
||||
if type(a) == type({}) |
||||
let target = a |
||||
elseif type(a) == type([]) |
||||
let functions = a |
||||
endif |
||||
unlet a |
||||
endfor |
||||
let module = s:_import(a:name) |
||||
if empty(functions) |
||||
call extend(target, module, 'keep') |
||||
else |
||||
for f in functions |
||||
if has_key(module, f) && !has_key(target, f) |
||||
let target[f] = module[f] |
||||
endif |
||||
endfor |
||||
endif |
||||
return target |
||||
endfunction |
||||
|
||||
function! s:load(...) dict abort |
||||
for arg in a:000 |
||||
let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] |
||||
let target = split(join(as, ''), '\W\+') |
||||
let dict = self |
||||
let dict_type = type({}) |
||||
while !empty(target) |
||||
let ns = remove(target, 0) |
||||
if !has_key(dict, ns) |
||||
let dict[ns] = {} |
||||
endif |
||||
if type(dict[ns]) == dict_type |
||||
let dict = dict[ns] |
||||
else |
||||
unlet dict |
||||
break |
||||
endif |
||||
endwhile |
||||
|
||||
if exists('dict') |
||||
call extend(dict, s:_import(name)) |
||||
endif |
||||
unlet arg |
||||
endfor |
||||
return self |
||||
endfunction |
||||
|
||||
function! s:unload() abort |
||||
let s:loaded = {} |
||||
let s:cache_sid = {} |
||||
let s:cache_module_path = {} |
||||
endfunction |
||||
|
||||
function! s:exists(name) abort |
||||
return s:_get_module_path(a:name) !=# '' |
||||
endfunction |
||||
|
||||
function! s:search(pattern) abort |
||||
let paths = s:_vital_files(a:pattern) |
||||
let modules = sort(map(paths, 's:_file2module(v:val)')) |
||||
return s:_uniq(modules) |
||||
endfunction |
||||
|
||||
function! s:expand_modules(entry, all) abort |
||||
if type(a:entry) == type([]) |
||||
let candidates = s:_concat(map(copy(a:entry), 's:search(v:val)')) |
||||
if empty(candidates) |
||||
throw printf('vital: Any of module %s is not found', string(a:entry)) |
||||
endif |
||||
if eval(join(map(copy(candidates), 'has_key(a:all, v:val)'), '+')) |
||||
let modules = [] |
||||
else |
||||
let modules = [candidates[0]] |
||||
endif |
||||
else |
||||
let modules = s:search(a:entry) |
||||
if empty(modules) |
||||
throw printf('vital: Module %s is not found', a:entry) |
||||
endif |
||||
endif |
||||
call filter(modules, '!has_key(a:all, v:val)') |
||||
for module in modules |
||||
let a:all[module] = 1 |
||||
endfor |
||||
return modules |
||||
endfunction |
||||
|
||||
function! s:_import(name) abort |
||||
if type(a:name) == type(0) |
||||
return s:_build_module(a:name) |
||||
endif |
||||
let path = s:_get_module_path(a:name) |
||||
if path ==# '' |
||||
throw 'vital: module not found: ' . a:name |
||||
endif |
||||
let sid = s:_get_sid_by_script(path) |
||||
if !sid |
||||
try |
||||
execute 'source' fnameescape(path) |
||||
catch /^Vim\%((\a\+)\)\?:E484/ |
||||
throw 'vital: module not found: ' . a:name |
||||
catch /^Vim\%((\a\+)\)\?:E127/ |
||||
" Ignore. |
||||
endtry |
||||
|
||||
let sid = s:_get_sid_by_script(path) |
||||
endif |
||||
return s:_build_module(sid) |
||||
endfunction |
||||
|
||||
function! s:_get_module_path(name) abort |
||||
let key = a:name . '_' |
||||
if has_key(s:cache_module_path, key) |
||||
return s:cache_module_path[key] |
||||
endif |
||||
if s:_is_absolute_path(a:name) && filereadable(a:name) |
||||
return a:name |
||||
endif |
||||
if a:name ==# '' |
||||
let paths = [s:self_file] |
||||
elseif a:name =~# '\v^\u\w*%(\.\u\w*)*$' |
||||
let paths = s:_vital_files(a:name) |
||||
else |
||||
throw 'vital: Invalid module name: ' . a:name |
||||
endif |
||||
|
||||
call filter(paths, 'filereadable(expand(v:val, 1))') |
||||
let path = get(paths, 0, '') |
||||
let s:cache_module_path[key] = path |
||||
return path |
||||
endfunction |
||||
|
||||
function! s:_get_sid_by_script(path) abort |
||||
if has_key(s:cache_sid, a:path) |
||||
return s:cache_sid[a:path] |
||||
endif |
||||
|
||||
let path = s:_unify_path(a:path) |
||||
for line in filter(split(s:_redir('scriptnames'), "\n"), |
||||
\ 'stridx(v:val, s:self_version) > 0') |
||||
let list = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') |
||||
if !empty(list) && s:_unify_path(list[2]) ==# path |
||||
let s:cache_sid[a:path] = list[1] - 0 |
||||
return s:cache_sid[a:path] |
||||
endif |
||||
endfor |
||||
return 0 |
||||
endfunction |
||||
|
||||
function! s:_file2module(file) abort |
||||
let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') |
||||
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') |
||||
return join(split(tail, '[\\/]\+'), '.') |
||||
endfunction |
||||
|
||||
if filereadable(expand('<sfile>:r') . '.VIM') |
||||
" resolve() is slow, so we cache results. |
||||
" Note: On windows, vim can't expand path names from 8.3 formats. |
||||
" So if getting full path via <sfile> and $HOME was set as 8.3 format, |
||||
" vital load duplicated scripts. Below's :~ avoid this issue. |
||||
function! s:_unify_path(path) abort |
||||
if has_key(s:_unify_path_cache, a:path) |
||||
return s:_unify_path_cache[a:path] |
||||
endif |
||||
let value = tolower(fnamemodify(resolve(fnamemodify( |
||||
\ a:path, ':p')), ':~:gs?[\\/]?/?')) |
||||
let s:_unify_path_cache[a:path] = value |
||||
return value |
||||
endfunction |
||||
else |
||||
function! s:_unify_path(path) abort |
||||
return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) |
||||
endfunction |
||||
endif |
||||
|
||||
if s:globpath_third_arg |
||||
function! s:_runtime_files(path) abort |
||||
return split(globpath(&runtimepath, a:path, 1), "\n") |
||||
endfunction |
||||
else |
||||
function! s:_runtime_files(path) abort |
||||
return split(globpath(&runtimepath, a:path), "\n") |
||||
endfunction |
||||
endif |
||||
|
||||
function! s:_vital_files(pattern) abort |
||||
if s:_vital_files_cache_runtimepath !=# &runtimepath |
||||
let path = printf('autoload/vital/%s/**/*.vim', s:self_version) |
||||
let s:_vital_files_cache = s:_runtime_files(path) |
||||
let mod = ':p:gs?[\\/]\+?/?' |
||||
call map(s:_vital_files_cache, 'fnamemodify(v:val, mod)') |
||||
let s:_vital_files_cache_runtimepath = &runtimepath |
||||
endif |
||||
let target = substitute(a:pattern, '\.', '/', 'g') |
||||
let target = substitute(target, '\*', '[^/]*', 'g') |
||||
let regexp = printf('autoload/vital/%s/%s.vim', s:self_version, target) |
||||
return filter(copy(s:_vital_files_cache), 'v:val =~# regexp') |
||||
endfunction |
||||
|
||||
" Copy from System.Filepath |
||||
if has('win16') || has('win32') || has('win64') |
||||
function! s:_is_absolute_path(path) abort |
||||
return a:path =~? '^[a-z]:[/\\]' |
||||
endfunction |
||||
else |
||||
function! s:_is_absolute_path(path) abort |
||||
return a:path[0] ==# '/' |
||||
endfunction |
||||
endif |
||||
|
||||
function! s:_build_module(sid) abort |
||||
if has_key(s:loaded, a:sid) |
||||
return copy(s:loaded[a:sid]) |
||||
endif |
||||
let functions = s:_get_functions(a:sid) |
||||
|
||||
let prefix = '<SNR>' . a:sid . '_' |
||||
let module = {} |
||||
for func in functions |
||||
let module[func] = function(prefix . func) |
||||
endfor |
||||
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 |
||||
endif |
||||
if !get(g:, 'vital_debug', 0) |
||||
call filter(module, 'v:key =~# "^\\a"') |
||||
endif |
||||
let s:loaded[a:sid] = module |
||||
return copy(module) |
||||
endfunction |
||||
|
||||
if exists('+regexpengine') |
||||
function! s:_get_functions(sid) abort |
||||
let funcs = s:_redir(printf("function /\\%%#=2^\<SNR>%d_", a:sid)) |
||||
let map_pat = '<SNR>' . a:sid . '_\zs\w\+' |
||||
return map(split(funcs, "\n"), 'matchstr(v:val, map_pat)') |
||||
endfunction |
||||
else |
||||
function! s:_get_functions(sid) abort |
||||
let prefix = '<SNR>' . a:sid . '_' |
||||
let funcs = s:_redir('function') |
||||
let filter_pat = '^\s*function ' . prefix |
||||
let map_pat = prefix . '\zs\w\+' |
||||
return map(filter(split(funcs, "\n"), |
||||
\ 'stridx(v:val, prefix) > 0 && v:val =~# filter_pat'), |
||||
\ 'matchstr(v:val, map_pat)') |
||||
endfunction |
||||
endif |
||||
|
||||
if exists('*uniq') |
||||
function! s:_uniq(list) abort |
||||
return uniq(a:list) |
||||
endfunction |
||||
else |
||||
function! s:_uniq(list) abort |
||||
let i = len(a:list) - 1 |
||||
while 0 < i |
||||
if a:list[i] ==# a:list[i - 1] |
||||
call remove(a:list, i) |
||||
let i -= 2 |
||||
else |
||||
let i -= 1 |
||||
endif |
||||
endwhile |
||||
return a:list |
||||
endfunction |
||||
endif |
||||
|
||||
function! s:_concat(lists) abort |
||||
let result_list = [] |
||||
for list in a:lists |
||||
let result_list += list |
||||
endfor |
||||
return result_list |
||||
endfunction |
||||
|
||||
function! s:_redir(cmd) abort |
||||
let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] |
||||
set verbose=0 verbosefile= |
||||
redir => res |
||||
silent! execute a:cmd |
||||
redir END |
||||
let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] |
||||
return res |
||||
endfunction |
||||
|
||||
function! vital#{s:self_version}#new() abort |
||||
return s:_import('') |
||||
function! vital#{s:_plugin_name}#new() abort |
||||
return vital#{s:_plugin_name[1:]}#new() |
||||
endfunction |
||||
|
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#Data#Dict#import() abort |
||||
return map({'pick': '', 'clear': '', 'max_by': '', 'foldl': '', 'swap': '', 'omit': '', 'min_by': '', 'foldr': '', 'make_index': '', 'make': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#Data#Dict#import() abort', printf("return map({'pick': '', 'clear': '', 'max_by': '', 'foldl': '', 'swap': '', 'omit': '', 'min_by': '', 'foldr': '', 'make_index': '', 'make': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
" Utilities for dictionary. |
||||
|
||||
let s:save_cpo = &cpo |
||||
set cpo&vim |
||||
|
||||
" Makes a dict from keys and values |
||||
function! s:make(keys, values, ...) abort |
||||
let dict = {} |
||||
let fill = a:0 ? a:1 : 0 |
||||
for i in range(len(a:keys)) |
||||
let key = type(a:keys[i]) == type('') ? a:keys[i] : string(a:keys[i]) |
||||
if key ==# '' |
||||
throw "vital: Data.Dict: Can't use an empty string for key." |
||||
endif |
||||
let dict[key] = get(a:values, i, fill) |
||||
endfor |
||||
return dict |
||||
endfunction |
||||
|
||||
" Swaps keys and values |
||||
function! s:swap(dict) abort |
||||
return s:make(values(a:dict), keys(a:dict)) |
||||
endfunction |
||||
|
||||
" Makes a index dict from a list |
||||
function! s:make_index(list, ...) abort |
||||
let value = a:0 ? a:1 : 1 |
||||
return s:make(a:list, [], value) |
||||
endfunction |
||||
|
||||
function! s:pick(dict, keys) abort |
||||
let new_dict = {} |
||||
for key in a:keys |
||||
if has_key(a:dict, key) |
||||
let new_dict[key] = a:dict[key] |
||||
endif |
||||
endfor |
||||
return new_dict |
||||
endfunction |
||||
|
||||
function! s:omit(dict, keys) abort |
||||
let new_dict = copy(a:dict) |
||||
for key in a:keys |
||||
if has_key(a:dict, key) |
||||
call remove(new_dict, key) |
||||
endif |
||||
endfor |
||||
return new_dict |
||||
endfunction |
||||
|
||||
function! s:clear(dict) abort |
||||
for key in keys(a:dict) |
||||
call remove(a:dict, key) |
||||
endfor |
||||
return a:dict |
||||
endfunction |
||||
|
||||
function! s:_max_by(dict, expr) abort |
||||
let dict = s:swap(map(copy(a:dict), a:expr)) |
||||
let key = dict[max(keys(dict))] |
||||
return [key, a:dict[key]] |
||||
endfunction |
||||
|
||||
function! s:max_by(dict, expr) abort |
||||
if empty(a:dict) |
||||
throw 'vital: Data.Dict: Empty dictionary' |
||||
endif |
||||
return s:_max_by(a:dict, a:expr) |
||||
endfunction |
||||
|
||||
function! s:min_by(dict, expr) abort |
||||
if empty(a:dict) |
||||
throw 'vital: Data.Dict: Empty dictionary' |
||||
endif |
||||
return s:_max_by(a:dict, '-(' . a:expr . ')') |
||||
endfunction |
||||
|
||||
function! s:_foldl(f, init, xs) abort |
||||
let memo = a:init |
||||
for [k, v] in a:xs |
||||
let expr = substitute(a:f, 'v:key', string(k), 'g') |
||||
let expr = substitute(expr, 'v:val', string(v), 'g') |
||||
let expr = substitute(expr, 'v:memo', string(memo), 'g') |
||||
unlet memo |
||||
let memo = eval(expr) |
||||
endfor |
||||
return memo |
||||
endfunction |
||||
|
||||
function! s:foldl(f, init, dict) abort |
||||
return s:_foldl(a:f, a:init, items(a:dict)) |
||||
endfunction |
||||
|
||||
function! s:foldr(f, init, dict) abort |
||||
return s:_foldl(a:f, a:init, reverse(items(a:dict))) |
||||
endfunction |
||||
|
||||
let &cpo = s:save_cpo |
||||
unlet s:save_cpo |
||||
|
||||
" vim:set et ts=2 sts=2 sw=2 tw=0: |
@ -0,0 +1,284 @@
@@ -0,0 +1,284 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#Data#Set#import() abort |
||||
return map({'set': '', 'frozenset': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#Data#Set#import() abort', printf("return map({'set': '', 'frozenset': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
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 |
@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#HitAHint#Hint#import() abort |
||||
return map({'create': '', '_vital_loaded': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#HitAHint#Hint#import() abort', printf("return map({'create': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
" 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 |
@ -0,0 +1,806 @@
@@ -0,0 +1,806 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#HitAHint#Motion#import() abort |
||||
return map({'deepextend': '', 'gather_poses': '', 'tab2spacelen': '', 'move_f': '', 'setline': '', '_vital_depends': '', 'wincall': '', 'move': '', 'move_to_winpos': '', 'pos2hint_to_line2col2hint': '', 'gather_visible_matched_poses': '', 'move_to_win': '', 'throw': '', 'has_patch': '', 'win2pos2hint_to_w2l2c2h': '', 'move_f2': '', 'new_overwin': '', 'create_win2pos2hint': '', 'pos2poskey': '', 'winnr2poses_to_list': '', 'poskey2pos': '', 'is_in_fold': '', '_vital_loaded': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#HitAHint#Motion#import() abort', printf("return map({'deepextend': '', 'gather_poses': '', 'tab2spacelen': '', 'move_f': '', 'setline': '', '_vital_depends': '', 'wincall': '', 'move': '', 'move_to_winpos': '', 'pos2hint_to_line2col2hint': '', 'gather_visible_matched_poses': '', 'move_to_win': '', 'throw': '', 'has_patch': '', 'win2pos2hint_to_w2l2c2h': '', 'move_f2': '', 'new_overwin': '', 'create_win2pos2hint': '', 'pos2poskey': '', 'winnr2poses_to_list': '', 'poskey2pos': '', 'is_in_fold': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
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') |
||||
let s:Input = a:V.import('Over.Input') |
||||
endfunction |
||||
|
||||
function! s:_vital_depends() abort |
||||
return [ |
||||
\ 'HitAHint.Hint', |
||||
\ 'Palette.Highlight', |
||||
\ 'Vim.Buffer', |
||||
\ 'Prelude', |
||||
\ 'Data.Set', |
||||
\ 'Over.Input', |
||||
\ ] |
||||
endfunction |
||||
|
||||
let s:TRUE = !0 |
||||
let s:FALSE = 0 |
||||
let s:DIRECTION = {'forward': 0, 'backward': 1} |
||||
|
||||
" Check Vim version |
||||
function! s:has_patch(major, minor, patch) abort |
||||
let l:version = (a:major * 100 + a:minor) |
||||
return has('patch-' . a:major . '.' . a:minor . '.' . a:patch) || |
||||
\ (v:version > l:version) || |
||||
\ (v:version == l:version && 'patch' . a:patch) |
||||
endfunction |
||||
|
||||
" matchadd('Conceal', {pattern}, {priority}, -1, {'conceal': {char}}}) can |
||||
" highlight pattern and conceal target correctly even if the target is keyword |
||||
" characters. |
||||
" - http://ftp.vim.org/vim/patches/7.4/7.4.792 |
||||
" - https://groups.google.com/forum/#!searchin/vim_dev/matchadd$20conceal/vim_dev/8bKa98GhHdk/VOzIBhd1m8YJ |
||||
let s:can_preserve_syntax = s:has_patch(7, 4, 792) |
||||
|
||||
" s:move() moves cursor over/across 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:Input.getchar() |
||||
return s:move(c, get(a:, 1, {})) |
||||
endfunction |
||||
|
||||
function! s:move_f2() abort |
||||
echo 'Target: ' |
||||
let c = s:Input.getchar() |
||||
redraw |
||||
echo 'Target: ' . c |
||||
let c2 = s:Input.getchar() |
||||
return s:move(s:Prelude.escape_pattern(c . c2), get(a:, 1, {})) |
||||
endfunction |
||||
|
||||
|
||||
let s:overwin = { |
||||
\ 'config': { |
||||
\ 'keys': 'asdghklqwertyuiopzxcvbnmfj;', |
||||
\ 'use_upper': s:FALSE, |
||||
\ 'auto_land': s:TRUE, |
||||
\ 'highlight': { |
||||
\ 'shade': 'HitAHintShade', |
||||
\ 'target': 'HitAHintTarget', |
||||
\ 'cursor': 'HitAHintCursor', |
||||
\ }, |
||||
\ 'jump_first_target_keys': [], |
||||
\ 'do_shade': s:TRUE, |
||||
\ } |
||||
\ } |
||||
|
||||
function! s:_init_hl() abort |
||||
highlight default HitAHintShade ctermfg=242 guifg=#777777 |
||||
highlight default HitAHintTarget ctermfg=81 guifg=#66D9EF |
||||
" Cursor highlight doesn't exist for some environment with some |
||||
" colorscheme ref:#275 |
||||
" e.g. |
||||
" - :colorscheme default |
||||
" - :colorscheme hybrid |
||||
if hlexists('Cursor') |
||||
highlight default link HitAHintCursor Cursor |
||||
else |
||||
highlight default HitAHintCursor term=reverse cterm=reverse gui=reverse |
||||
endif |
||||
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 |
||||
call s:move_to_winpos(winpos) |
||||
endif |
||||
endfunction |
||||
|
||||
" @param {{winnr: [lnum, cnum]}} |
||||
function! s:move_to_winpos(winpos) abort |
||||
let [winnr_str, pos] = a:winpos |
||||
let winnr = str2nr(winnr_str) |
||||
let is_win_moved = !(winnr is# winnr()) |
||||
if is_win_moved |
||||
if exists('#WinLeave') |
||||
silent doautocmd WinLeave |
||||
endif |
||||
call s:move_to_win(winnr) |
||||
else |
||||
normal! m` |
||||
endif |
||||
call cursor(pos) |
||||
if is_win_moved && exists('#WinEnter') |
||||
silent doautocmd WinEnter |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:overwin.select_winpos(winnr2poses, keys) abort |
||||
let wposes = s:winnr2poses_to_list(a:winnr2poses) |
||||
if self.config.auto_land && len(wposes) is# 1 |
||||
return wposes[0] |
||||
endif |
||||
call self.set_options() |
||||
try |
||||
return self.choose_prompt(s:Hint.create(wposes, a:keys)) |
||||
finally |
||||
call self.restore_options() |
||||
endtry |
||||
endfunction |
||||
|
||||
function! s:overwin.set_options() abort |
||||
" s:move_to_win() takes long time if 'foldmethod' == 'syntax' or 'expr' |
||||
let self.save_foldmethod = {} |
||||
for winnr in range(1, winnr('$')) |
||||
let self.save_foldmethod[winnr] = getwinvar(winnr, '&foldmethod') |
||||
call setwinvar(winnr, '&foldmethod', 'manual') |
||||
endfor |
||||
endfunction |
||||
|
||||
function! s:overwin.restore_options() abort |
||||
for winnr in range(1, winnr('$')) |
||||
call setwinvar(winnr, '&foldmethod', self.save_foldmethod[winnr]) |
||||
endfor |
||||
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:Input.getchar() |
||||
if self.config.use_upper |
||||
let c = toupper(c) |
||||
endif |
||||
catch |
||||
echo v:throwpoint . ':' . v:exception |
||||
return -1 |
||||
finally |
||||
call hinter.after() |
||||
endtry |
||||
|
||||
" Jump to first target if target key is in config.jump_first_target_keys. |
||||
if index(self.config.jump_first_target_keys, c) isnot# -1 |
||||
let c = split(self.config.keys, '\zs')[0] |
||||
endif |
||||
|
||||
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': {}, |
||||
\ 'save_undo': {}, |
||||
\ 'highlight_ids': {}, |
||||
\ } |
||||
|
||||
function! s:Hinter.new(hint_dict, config) abort |
||||
let s = deepcopy(self) |
||||
let s.config = a:config |
||||
call s.init(a:hint_dict) |
||||
return s |
||||
endfunction |
||||
|
||||
function! s:Hinter.init(hint_dict) abort |
||||
let win2pos2hint = s:create_win2pos2hint(a:hint_dict) |
||||
let self.winnrs = sort(map(keys(win2pos2hint), 'str2nr(v:val)')) |
||||
let self.win2pos2hint = win2pos2hint |
||||
let self.w2l2c2h = s:win2pos2hint_to_w2l2c2h(win2pos2hint) |
||||
let self.hl_target_ids = {} |
||||
for winnr in self.winnrs |
||||
let self.hl_target_ids[winnr] = [] |
||||
endfor |
||||
call self._save_lines() |
||||
endfunction |
||||
|
||||
function! s:Hinter.before() abort |
||||
let self.highlight_id_cursor = matchadd(self.config.highlight.cursor, '\%#', 101) |
||||
call self.save_options() |
||||
call self.disable_conceal_in_other_win() |
||||
endfunction |
||||
|
||||
function! s:Hinter.after() abort |
||||
call matchdelete(self.highlight_id_cursor) |
||||
call self.restore_env() |
||||
call self.restore_conceal_in_other_win() |
||||
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_for_win(winnr) abort |
||||
let lnum2line = self.save_lines[a:winnr] |
||||
for [lnum, line] in items(lnum2line) |
||||
call s:setline(lnum, line) |
||||
endfor |
||||
endfunction |
||||
|
||||
function! s:Hinter.save_options() abort |
||||
for winnr in self.winnrs |
||||
let self.save_syntax[winnr] = getwinvar(winnr, '&syntax') |
||||
let self.save_conceallevel[winnr] = getwinvar(winnr, '&conceallevel') |
||||
let self.save_concealcursor[winnr] = getwinvar(winnr, '&concealcursor') |
||||
let self.save_modified[winnr] = getwinvar(winnr, '&modified') |
||||
let self.save_modifiable[winnr] = getwinvar(winnr, '&modifiable') |
||||
let self.save_readonly[winnr] = getwinvar(winnr, '&readonly') |
||||
endfor |
||||
endfunction |
||||
|
||||
function! s:Hinter.restore_options() abort |
||||
for winnr in self.winnrs |
||||
call setwinvar(winnr, '&conceallevel', self.save_conceallevel[winnr]) |
||||
call setwinvar(winnr, '&concealcursor', self.save_concealcursor[winnr]) |
||||
call setwinvar(winnr, '&modified', self.save_modified[winnr]) |
||||
call setwinvar(winnr, '&modifiable', self.save_modifiable[winnr]) |
||||
call setwinvar(winnr, '&readonly', self.save_readonly[winnr]) |
||||
endfor |
||||
endfunction |
||||
|
||||
function! s:Hinter.modify_env_for_win(winnr) abort |
||||
let self.save_conceal = s:PHighlight.get('Conceal') |
||||
let self.save_undo[a:winnr] = s:undo_lock.save() |
||||
|
||||
setlocal modifiable |
||||
setlocal noreadonly |
||||
|
||||
if !s:can_preserve_syntax |
||||
ownsyntax overwin |
||||
endif |
||||
|
||||
setlocal conceallevel=2 |
||||
setlocal concealcursor=ncv |
||||
|
||||
let self.highlight_ids[a:winnr] = get(self.highlight_ids, a:winnr, []) |
||||
if self.config.do_shade |
||||
if !s:can_preserve_syntax |
||||
syntax clear |
||||
endif |
||||
let self.highlight_ids[a:winnr] += [matchadd(self.config.highlight.shade, '\_.*', 100)] |
||||
endif |
||||
|
||||
" XXX: other plugins specific handling |
||||
if getbufvar('%', 'indentLine_enabled', 0) |
||||
silent! syntax clear IndentLine |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:Hinter.restore_env() abort |
||||
call s:PHighlight.set('Conceal', self.save_conceal) |
||||
let nr = winnr() |
||||
try |
||||
for winnr in self.winnrs |
||||
call s:move_to_win(winnr) |
||||
call self.restore_lines_for_win(winnr) |
||||
call self.remove_hints(winnr) |
||||
|
||||
if !s:can_preserve_syntax && self.config.do_shade |
||||
let &syntax = self.save_syntax[winnr] |
||||
endif |
||||
|
||||
call self.save_undo[winnr].restore() |
||||
|
||||
for id in self.highlight_ids[winnr] |
||||
call matchdelete(id) |
||||
endfor |
||||
|
||||
" XXX: other plugins specific handling |
||||
if getbufvar('%', 'indentLine_enabled', 0) && exists(':IndentLinesEnable') is# 2 |
||||
call setbufvar('%', 'indentLine_enabled', 0) |
||||
:IndentLinesEnable |
||||
endif |
||||
endfor |
||||
catch |
||||
call s:throw(v:throwpoint . ' ' . v:exception) |
||||
finally |
||||
call s:move_to_win(nr) |
||||
endtry |
||||
|
||||
call self.restore_options() |
||||
endfunction |
||||
|
||||
let s:undo_lock = {} |
||||
|
||||
function! s:undo_lock.save() abort |
||||
let undo = deepcopy(self) |
||||
call undo._save() |
||||
return undo |
||||
endfunction |
||||
|
||||
function! s:undo_lock._save() abort |
||||
if undotree().seq_last == 0 |
||||
" if there are no undo history, disable undo feature by setting |
||||
" 'undolevels' to -1 and restore it. |
||||
let self.save_undolevels = &l:undolevels |
||||
let &l:undolevels = -1 |
||||
elseif !s:Buffer.is_cmdwin() |
||||
" command line window doesn't support :wundo. |
||||
let self.undofile = tempname() |
||||
execute 'wundo!' self.undofile |
||||
else |
||||
let self.is_cmdwin = s:TRUE |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:undo_lock.restore() abort |
||||
if has_key(self, 'save_undolevels') |
||||
let &l:undolevels = self.save_undolevels |
||||
endif |
||||
if has_key(self, 'undofile') && filereadable(self.undofile) |
||||
silent execute 'rundo' self.undofile |
||||
call delete(self.undofile) |
||||
endif |
||||
if has_key(self, 'is_cmdwin') |
||||
" XXX: it breaks undo history. AFAIK, there are no way to save and restore |
||||
" undo history in commandline window. |
||||
call self.undobreak() |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:undo_lock.undobreak() abort |
||||
let old_undolevels = &l:undolevels |
||||
setlocal undolevels=-1 |
||||
keepjumps call setline('.', getline('.')) |
||||
let &l:undolevels = old_undolevels |
||||
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 |
||||
call self.modify_env_for_win(a:winnr) |
||||
|
||||
let hints = [] |
||||
for [lnum, col2hint] in items(self.w2l2c2h[a:winnr]) |
||||
let hints += self._show_hint_for_line(a:winnr, lnum, col2hint) |
||||
endfor |
||||
" Restore syntax and show hints after replacing all lines for performance. |
||||
if !s:can_preserve_syntax && !self.config.do_shade |
||||
let &l:syntax = self.save_syntax[a:winnr] |
||||
endif |
||||
execute 'highlight! link Conceal' self.config.highlight.target |
||||
for [lnum, cnum, char] in hints |
||||
call self.show_hint_pos(lnum, cnum, char, a:winnr) |
||||
endfor |
||||
endfunction |
||||
|
||||
function! s:Hinter._show_hint_for_line(winnr, lnum, col2hint) abort |
||||
let hints = [] " [lnum, cnum, char] |
||||
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 !is_consecutive |
||||
let col_num += next_offset |
||||
endif |
||||
let save_next_offset = next_offset |
||||
|
||||
let [line, offset, next_offset] = self._replace_line_for_hint(col_num, line, hint) |
||||
|
||||
if is_consecutive |
||||
let col_offset += save_next_offset |
||||
endif |
||||
let col_offset += offset |
||||
|
||||
let hints = [[a:lnum, col_num, hint[0]]] + hints |
||||
if len(hint) > 1 |
||||
let hints = [[a:lnum, col_num + 1, hint[1]]] + hints |
||||
endif |
||||
|
||||
let prev_cnum = cnum |
||||
endfor |
||||
call s:setline(a:lnum, line) |
||||
return hints |
||||
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(col_num, line, hint) abort |
||||
let line = a:line |
||||
let col_num = a:col_num |
||||
let do_replace_target = !(self.config.do_shade || s:can_preserve_syntax) |
||||
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 char = do_replace_target ? ' ' : '.' |
||||
let line .= repeat(char, hintwidth) |
||||
return [line, hintwidth, 0] |
||||
endif |
||||
|
||||
let offset = 0 |
||||
if target is# "\t" |
||||
let [line, offset] = self._replace_tab_target(col_num, line) |
||||
elseif strdisplaywidth(target) > 1 |
||||
let line = self._replace_text_to_space(line, col_num, strdisplaywidth(target)) |
||||
let offset = strdisplaywidth(target) - len(target) |
||||
else |
||||
if do_replace_target |
||||
" The priority of :syn-cchar is always under the priority of keywords. |
||||
" So, Hit-A-Hint replaces targets character with '.'. |
||||
let space = '.' |
||||
let line = substitute(line, '\%' . col_num . 'c.', space, '') |
||||
let offset = len(space) - len(target) |
||||
endif |
||||
endif |
||||
|
||||
let next_offset = 0 |
||||
if len(a:hint) > 1 && target isnot# "\t" |
||||
" pass [' '] as hint to stop recursion. |
||||
let [line, next_offset, _] = self._replace_line_for_hint(col_num + offset + 1, line, [' ']) |
||||
endif |
||||
return [line, offset, next_offset] |
||||
endfunction |
||||
|
||||
" @return {(line, offset)} |
||||
function! s:Hinter._replace_tab_target(col_num, line) abort |
||||
let space_len = s:tab2spacelen(a:line, a:col_num) |
||||
let line = self._replace_text_to_space(a:line, a:col_num, space_len) |
||||
return [line, space_len - 1] |
||||
endfunction |
||||
|
||||
function! s:Hinter._replace_text_to_space(line, col_num, len) abort |
||||
let target = printf('\%%%dc.', a:col_num) |
||||
let line = substitute(a:line, target, repeat(' ', a:len), '') |
||||
return line |
||||
endfunction |
||||
|
||||
function! s:Hinter.show_hint_pos(lnum, cnum, char, winnr) abort |
||||
let p = '\%'. a:lnum . 'l\%'. a:cnum . 'c.' |
||||
if s:can_preserve_syntax |
||||
let self.hl_target_ids[a:winnr] += [matchadd('Conceal', p, 101, -1, {'conceal': a:char})] |
||||
else |
||||
exec "syntax match HitAHintTarget '". p . "' contains=NONE containedin=.* conceal cchar=". a:char |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:Hinter.remove_hints(winnr) abort |
||||
if s:can_preserve_syntax |
||||
for id in self.hl_target_ids[a:winnr] |
||||
call matchdelete(id) |
||||
endfor |
||||
else |
||||
" Clear syntax defined by Hit-A-Hint motion before restoring syntax. |
||||
syntax clear HitAHintTarget |
||||
endif |
||||
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 'noautocmd' 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 |
||||
|
||||
" @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 |
||||
|
||||
" 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 |
||||
|
||||
function! s:setline(lnum, text) abort |
||||
if getline(a:lnum) isnot# a:text |
||||
call setline(a:lnum, a:text) |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:throw(message) abort |
||||
throw 'vital: HitAHint.Motion: ' . a:message |
||||
endfunction |
||||
|
@ -0,0 +1,430 @@
@@ -0,0 +1,430 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#Prelude#import() abort |
||||
return map({'escape_pattern': '', 'is_funcref': '', 'path2directory': '', 'wcswidth': '', 'is_string': '', 'input_helper': '', 'is_number': '', 'is_cygwin': '', 'path2project_directory': '', 'strwidthpart_reverse': '', 'input_safe': '', 'is_list': '', 'truncate_skipping': '', 'glob': '', 'truncate': '', 'is_dict': '', 'set_default': '', 'is_numeric': '', 'getchar_safe': '', 'substitute_path_separator': '', 'is_mac': '', 'strwidthpart': '', 'getchar': '', 'is_unix': '', 'is_windows': '', 'globpath': '', 'escape_file_searching': '', 'is_float': '', 'smart_execute_command': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#Prelude#import() abort', printf("return map({'escape_pattern': '', 'is_funcref': '', 'path2directory': '', 'wcswidth': '', 'is_string': '', 'input_helper': '', 'is_number': '', 'is_cygwin': '', 'path2project_directory': '', 'strwidthpart_reverse': '', 'input_safe': '', 'is_list': '', 'truncate_skipping': '', 'glob': '', 'truncate': '', 'is_dict': '', 'set_default': '', 'is_numeric': '', 'getchar_safe': '', 'substitute_path_separator': '', 'is_mac': '', 'strwidthpart': '', 'getchar': '', 'is_unix': '', 'is_windows': '', 'globpath': '', 'escape_file_searching': '', 'is_float': '', 'smart_execute_command': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
let s:save_cpo = &cpo |
||||
set cpo&vim |
||||
|
||||
if v:version > 703 || |
||||
\ (v:version == 703 && has('patch465')) |
||||
function! s:glob(expr) abort |
||||
return glob(a:expr, 1, 1) |
||||
endfunction |
||||
else |
||||
function! s:glob(expr) abort |
||||
return split(glob(a:expr, 1), '\n') |
||||
endfunction |
||||
endif |
||||
|
||||
if v:version > 704 || |
||||
\ (v:version == 704 && has('patch279')) |
||||
function! s:globpath(path, expr) abort |
||||
return globpath(a:path, a:expr, 1, 1) |
||||
endfunction |
||||
else |
||||
function! s:globpath(path, expr) abort |
||||
return split(globpath(a:path, a:expr, 1), '\n') |
||||
endfunction |
||||
endif |
||||
|
||||
" Wrapper functions for type(). |
||||
" NOTE: __TYPE_FLOAT = -1 when -float. |
||||
" this doesn't match to anything. |
||||
if has('patch-7.4.2071') |
||||
let [ |
||||
\ s:__TYPE_NUMBER, |
||||
\ s:__TYPE_STRING, |
||||
\ s:__TYPE_FUNCREF, |
||||
\ s:__TYPE_LIST, |
||||
\ s:__TYPE_DICT, |
||||
\ s:__TYPE_FLOAT] = [ |
||||
\ v:t_number, |
||||
\ v:t_string, |
||||
\ v:t_func, |
||||
\ v:t_list, |
||||
\ v:t_dict, |
||||
\ v:t_float] |
||||
else |
||||
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] |
||||
endif |
||||
|
||||
" 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 |
||||
|
||||
" 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 |
||||
|
||||
" Float |
||||
function! s:is_float(Value) abort |
||||
return type(a:Value) ==# s:__TYPE_FLOAT |
||||
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 |
||||
call s:_warn_deprecated( |
||||
\ 'escape_pattern', |
||||
\ 'Data.String.escape_pattern', |
||||
\) |
||||
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: |
@ -0,0 +1,187 @@
@@ -0,0 +1,187 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#Vim#Buffer#import() abort |
||||
return map({'parse_cmdarg': '', '_vital_depends': '', 'read_content': '', 'get_selected_text': '', 'is_cmdwin': '', 'edit_content': '', 'open': '', 'get_last_selected': '', '_vital_loaded': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#Vim#Buffer#import() abort', printf("return map({'parse_cmdarg': '', '_vital_depends': '', 'read_content': '', 'get_selected_text': '', 'is_cmdwin': '', 'edit_content': '', 'open': '', 'get_last_selected': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
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') |
||||
let s:G = s:V.import('Vim.Guard') |
||||
endfunction |
||||
|
||||
function! s:_vital_depends() abort |
||||
return ['Prelude', 'Vim.Guard'] |
||||
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 |
||||
|
||||
function! s:read_content(content, ...) abort |
||||
let options = extend({ |
||||
\ 'tempfile': '', |
||||
\ 'fileformat': '', |
||||
\ 'encoding': '', |
||||
\ 'binary': 0, |
||||
\ 'nobinary': 0, |
||||
\ 'bad': '', |
||||
\ 'edit': 0, |
||||
\ 'line': '', |
||||
\}, get(a:000, 0, {})) |
||||
let tempfile = empty(options.tempfile) ? tempname() : options.tempfile |
||||
let optnames = [ |
||||
\ empty(options.fileformat) ? '' : '++ff=' . options.fileformat, |
||||
\ empty(options.encoding) ? '' : '++enc=' . options.encoding, |
||||
\ empty(options.binary) ? '' : '++bin', |
||||
\ empty(options.nobinary) ? '' : '++nobin', |
||||
\ empty(options.bad) ? '' : '++bad=' . options.bad, |
||||
\ empty(options.edit) ? '' : '++edit', |
||||
\] |
||||
let optname = join(filter(optnames, '!empty(v:val)')) |
||||
try |
||||
call writefile(a:content, tempfile) |
||||
execute printf('keepalt keepjumps %sread %s%s', |
||||
\ options.line, |
||||
\ empty(optname) ? '' : optname . ' ', |
||||
\ fnameescape(tempfile), |
||||
\) |
||||
finally |
||||
call delete(tempfile) |
||||
execute 'bwipeout!' tempfile |
||||
endtry |
||||
endfunction |
||||
|
||||
function! s:edit_content(content, ...) abort |
||||
let options = extend({ |
||||
\ 'edit': 1, |
||||
\}, get(a:000, 0, {})) |
||||
let guard = s:G.store(['&l:modifiable']) |
||||
let saved_view = winsaveview() |
||||
try |
||||
let &l:modifiable=1 |
||||
silent keepjumps %delete _ |
||||
silent call s:read_content(a:content, options) |
||||
silent keepjumps 1delete _ |
||||
finally |
||||
keepjump call winrestview(saved_view) |
||||
call guard.restore() |
||||
endtry |
||||
setlocal nomodified |
||||
endfunction |
||||
|
||||
function! s:parse_cmdarg(...) abort |
||||
let cmdarg = get(a:000, 0, v:cmdarg) |
||||
let options = {} |
||||
if cmdarg =~# '++enc=' |
||||
let options.encoding = matchstr(cmdarg, '++enc=\zs[^ ]\+\ze') |
||||
endif |
||||
if cmdarg =~# '++ff=' |
||||
let options.fileformat = matchstr(cmdarg, '++ff=\zs[^ ]\+\ze') |
||||
endif |
||||
if cmdarg =~# '++bad=' |
||||
let options.bad = matchstr(cmdarg, '++bad=\zs[^ ]\+\ze') |
||||
endif |
||||
if cmdarg =~# '++bin' |
||||
let options.binary = 1 |
||||
endif |
||||
if cmdarg =~# '++nobin' |
||||
let options.nobinary = 1 |
||||
endif |
||||
if cmdarg =~# '++edit' |
||||
let options.edit = 1 |
||||
endif |
||||
return options |
||||
endfunction |
||||
|
||||
let &cpo = s:save_cpo |
||||
unlet s:save_cpo |
||||
|
||||
" vim:set et ts=2 sts=2 sw=2 tw=0: |
@ -0,0 +1,240 @@
@@ -0,0 +1,240 @@
|
||||
" ___vital___ |
||||
" NOTE: lines between '" ___vital___' is generated by :Vitalize. |
||||
" Do not mofidify the code nor insert new lines before '" ___vital___' |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! vital#_easymotion#Vim#Guard#import() abort |
||||
return map({'_vital_depends': '', '_vital_created': '', 'store': '', '_vital_loaded': ''}, 'function("s:" . v:key)') |
||||
endfunction |
||||
else |
||||
function! s:_SID() abort |
||||
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') |
||||
endfunction |
||||
execute join(['function! vital#_easymotion#Vim#Guard#import() abort', printf("return map({'_vital_depends': '', '_vital_created': '', 'store': '', '_vital_loaded': ''}, \"function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") |
||||
delfunction s:_SID |
||||
endif |
||||
" ___vital___ |
||||
let s:save_cpo = &cpo |
||||
set cpo&vim |
||||
|
||||
" Use a Funcref as a special term _UNDEFINED |
||||
function! s:_undefined() abort |
||||
return 'undefined' |
||||
endfunction |
||||
let s:_UNDEFINED = function('s:_undefined') |
||||
|
||||
function! s:_vital_loaded(V) abort |
||||
let s:V = a:V |
||||
let s:Prelude = s:V.import('Prelude') |
||||
let s:List = s:V.import('Data.List') |
||||
let s:Dict = s:V.import('Data.Dict') |
||||
endfunction |
||||
function! s:_vital_depends() abort |
||||
return ['Prelude', 'Data.List', 'Data.Dict'] |
||||
endfunction |
||||
function! s:_vital_created(module) abort |
||||
" define constant variables |
||||
if !exists('s:const') |
||||
let s:const = {} |
||||
let s:const.is_local_variable_supported = |
||||
\ v:version > 703 || (v:version == 703 && has('patch560')) |
||||
" NOTE: |
||||
" The third argument is available from 7.4.242 but it had bug and that |
||||
" bug was fixed from 7.4.513 |
||||
let s:const.is_third_argument_of_getreg_supported = has('patch-7.4.513') |
||||
lockvar s:const |
||||
endif |
||||
call extend(a:module, s:const) |
||||
endfunction |
||||
function! s:_throw(msg) abort |
||||
throw printf('vital: Vim.Guard: %s', a:msg) |
||||
endfunction |
||||
|
||||
let s:option = {} |
||||
function! s:_new_option(name) abort |
||||
if a:name !~# '^&' |
||||
call s:_throw(printf( |
||||
\'An option name "%s" requires to be started from "&"', a:name |
||||
\)) |
||||
elseif !exists(a:name) |
||||
call s:_throw(printf( |
||||
\'An option name "%s" does not exist', a:name |
||||
\)) |
||||
endif |
||||
let option = copy(s:option) |
||||
let option.name = a:name |
||||
let option.value = eval(a:name) |
||||
return option |
||||
endfunction |
||||
function! s:option.restore() abort |
||||
execute printf('let %s = %s', self.name, string(self.value)) |
||||
endfunction |
||||
|
||||
let s:register = {} |
||||
function! s:_new_register(name) abort |
||||
if len(a:name) != 2 |
||||
call s:_throw(printf( |
||||
\'A register name "%s" requires to be "@" + a single character', a:name |
||||
\)) |
||||
elseif a:name !~# '^@' |
||||
call s:_throw(printf( |
||||
\'A register name "%s" requires to be started from "@"', a:name |
||||
\)) |
||||
elseif a:name =~# '^@[:.%]$' |
||||
call s:_throw(printf( |
||||
\'A register name "%s" is read only', a:name |
||||
\)) |
||||
elseif a:name !~# '^@[@0-9a-zA-Z#=*+~_/-]$' |
||||
call s:_throw(printf( |
||||
\'A register name "%s" does not exist. See ":help let-register"', a:name |
||||
\)) |
||||
endif |
||||
let name = a:name ==# '@@' ? '' : a:name[1] |
||||
let register = copy(s:register) |
||||
let register.name = name |
||||
if s:const.is_third_argument_of_getreg_supported |
||||
let register.value = getreg(name, 1, 1) |
||||
else |
||||
let register.value = getreg(name, 1) |
||||
endif |
||||
let register.type = getregtype(name) |
||||
return register |
||||
endfunction |
||||
function! s:register.restore() abort |
||||
" https://github.com/vim/vim/commit/5a50c2255c447838d08d3b4895a3be3a41cd8eda |
||||
if has('patch-7.4.243') || self.name !=# '=' |
||||
call setreg(self.name, self.value, self.type) |
||||
else |
||||
let @= = self.value |
||||
endif |
||||
endfunction |
||||
|
||||
let s:environment = {} |
||||
function! s:_new_environment(name) abort |
||||
if a:name !~# '^\$' |
||||
call s:_throw(printf( |
||||
\'An environment variable name "%s" requires to be started from "$"', a:name |
||||
\)) |
||||
elseif !exists(a:name) |
||||
call s:_throw(printf( |
||||
\'An environment variable name "%s" does not exist. While Vim cannot unlet environment variable, it requires to exist', a:name |
||||
\)) |
||||
endif |
||||
let environment = copy(s:environment) |
||||
let environment.name = a:name |
||||
let environment.value = eval(a:name) |
||||
return environment |
||||
endfunction |
||||
function! s:environment.restore() abort |
||||
execute printf('let %s = %s', self.name, string(self.value)) |
||||
endfunction |
||||
|
||||
let s:variable = {} |
||||
function! s:_new_variable(name, ...) abort |
||||
if a:0 == 0 |
||||
let m = matchlist(a:name, '^\([bwtg]:\)\(.*\)$') |
||||
if empty(m) |
||||
call s:_throw(printf( |
||||
\ join([ |
||||
\ 'An variable name "%s" requires to start from b:, w:, t:, or g:', |
||||
\ 'while no {namespace} is specified', |
||||
\ ]), |
||||
\ a:name, |
||||
\)) |
||||
endif |
||||
let [prefix, name] = m[1 : 2] |
||||
let namespace = eval(prefix) |
||||
else |
||||
let name = a:name |
||||
let namespace = a:1 |
||||
endif |
||||
let variable = copy(s:variable) |
||||
let variable.name = name |
||||
let variable.value = get(namespace, name, s:_UNDEFINED) |
||||
let variable.value = |
||||
\ type(variable.value) == type({}) || type(variable.value) == type([]) |
||||
\ ? deepcopy(variable.value) |
||||
\ : variable.value |
||||
let variable._namespace = namespace |
||||
return variable |
||||
endfunction |
||||
function! s:variable.restore() abort |
||||
" unlet the variable to prevent variable type mis-match in case |
||||
silent! unlet! self._namespace[self.name] |
||||
if type(self.value) == type(s:_UNDEFINED) && self.value == s:_UNDEFINED |
||||
" do nothing, leave the variable as undefined |
||||
else |
||||
let self._namespace[self.name] = self.value |
||||
endif |
||||
endfunction |
||||
|
||||
let s:instance = {} |
||||
function! s:_new_instance(instance, ...) abort |
||||
let shallow = get(a:000, 0, 0) |
||||
if !s:Prelude.is_list(a:instance) && !s:Prelude.is_dict(a:instance) |
||||
call s:_throw(printf( |
||||
\'An instance "%s" requires to be List or Dictionary', string(a:instance) |
||||
\)) |
||||
endif |
||||
let instance = copy(s:instance) |
||||
let instance.instance = a:instance |
||||
let instance.values = shallow ? copy(a:instance) : deepcopy(a:instance) |
||||
return instance |
||||
endfunction |
||||
function! s:instance.restore() abort |
||||
if s:Prelude.is_list(self.instance) |
||||
call s:List.clear(self.instance) |
||||
else |
||||
call s:Dict.clear(self.instance) |
||||
endif |
||||
call extend(self.instance, self.values) |
||||
endfunction |
||||
|
||||
let s:guard = {} |
||||
function! s:store(targets) abort |
||||
let resources = [] |
||||
for meta in a:targets |
||||
if s:Prelude.is_list(meta) |
||||
if len(meta) == 1 |
||||
call add(resources, s:_new_instance(meta[0])) |
||||
elseif len(meta) == 2 |
||||
if s:Prelude.is_string(meta[0]) |
||||
call add(resources, call('s:_new_variable', meta)) |
||||
else |
||||
call add(resources, call('s:_new_instance', meta)) |
||||
endif |
||||
else |
||||
call s:_throw('List assignment requires one or two elements') |
||||
endif |
||||
elseif type(meta) == type('') |
||||
if meta =~# '^[bwtgls]:' |
||||
" Note: |
||||
" To improve an error message, handle l:XXX or s:XXX as well |
||||
call add(resources, s:_new_variable(meta)) |
||||
elseif meta =~# '^&' |
||||
call add(resources, s:_new_option(meta)) |
||||
elseif meta =~# '^@' |
||||
call add(resources, s:_new_register(meta)) |
||||
elseif meta =~# '^\$' |
||||
call add(resources, s:_new_environment(meta)) |
||||
else |
||||
call s:_throw(printf( |
||||
\ 'Unknown value "%s" was specified', |
||||
\ meta |
||||
\)) |
||||
endif |
||||
endif |
||||
unlet meta |
||||
endfor |
||||
let guard = copy(s:guard) |
||||
let guard._resources = resources |
||||
return guard |
||||
endfunction |
||||
function! s:guard.restore() abort |
||||
for resource in self._resources |
||||
call resource.restore() |
||||
endfor |
||||
endfunction |
||||
|
||||
let &cpo = s:save_cpo |
||||
unlet! s:save_cpo |
||||
" vim:set et ts=2 sts=2 sw=2 tw=0 fdm=marker: |
@ -0,0 +1,339 @@
@@ -0,0 +1,339 @@
|
||||
let s:plugin_name = expand('<sfile>:t:r') |
||||
let s:vital_base_dir = expand('<sfile>:h') |
||||
let s:project_root = expand('<sfile>:h:h:h') |
||||
let s:is_vital_vim = s:plugin_name is# 'vital' |
||||
|
||||
let s:loaded = {} |
||||
let s:cache_sid = {} |
||||
|
||||
" function() wrapper |
||||
if v:version > 703 || v:version == 703 && has('patch1170') |
||||
function! s:_function(fstr) abort |
||||
return function(a:fstr) |
||||
endfunction |
||||
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! vital#{s:plugin_name}#new() abort |
||||
return s:new(s:plugin_name) |
||||
endfunction |
||||
|
||||
function! vital#{s:plugin_name}#import(...) abort |
||||
if !exists('s:V') |
||||
let s:V = s:new(s:plugin_name) |
||||
endif |
||||
return call(s:V.import, a:000, s:V) |
||||
endfunction |
||||
|
||||
let s:Vital = {} |
||||
|
||||
function! s:new(plugin_name) abort |
||||
let base = deepcopy(s:Vital) |
||||
let base._plugin_name = a:plugin_name |
||||
return base |
||||
endfunction |
||||
|
||||
function! s:vital_files() abort |
||||
if !exists('s:vital_files') |
||||
let s:vital_files = map( |
||||
\ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), |
||||
\ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') |
||||
endif |
||||
return copy(s:vital_files) |
||||
endfunction |
||||
let s:Vital.vital_files = s:_function('s:vital_files') |
||||
|
||||
function! s:import(name, ...) abort dict |
||||
let target = {} |
||||
let functions = [] |
||||
for a in a:000 |
||||
if type(a) == type({}) |
||||
let target = a |
||||
elseif type(a) == type([]) |
||||
let functions = a |
||||
endif |
||||
unlet a |
||||
endfor |
||||
let module = self._import(a:name) |
||||
if empty(functions) |
||||
call extend(target, module, 'keep') |
||||
else |
||||
for f in functions |
||||
if has_key(module, f) && !has_key(target, f) |
||||
let target[f] = module[f] |
||||
endif |
||||
endfor |
||||
endif |
||||
return target |
||||
endfunction |
||||
let s:Vital.import = s:_function('s:import') |
||||
|
||||
function! s:load(...) abort dict |
||||
for arg in a:000 |
||||
let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] |
||||
let target = split(join(as, ''), '\W\+') |
||||
let dict = self |
||||
let dict_type = type({}) |
||||
while !empty(target) |
||||
let ns = remove(target, 0) |
||||
if !has_key(dict, ns) |
||||
let dict[ns] = {} |
||||
endif |
||||
if type(dict[ns]) == dict_type |
||||
let dict = dict[ns] |
||||
else |
||||
unlet dict |
||||
break |
||||
endif |
||||
endwhile |
||||
if exists('dict') |
||||
call extend(dict, self._import(name)) |
||||
endif |
||||
unlet arg |
||||
endfor |
||||
return self |
||||
endfunction |
||||
let s:Vital.load = s:_function('s:load') |
||||
|
||||
function! s:unload() abort dict |
||||
let s:loaded = {} |
||||
let s:cache_sid = {} |
||||
unlet! s:vital_files |
||||
endfunction |
||||
let s:Vital.unload = s:_function('s:unload') |
||||
|
||||
function! s:exists(name) abort dict |
||||
if a:name !~# '\v^\u\w*%(\.\u\w*)*$' |
||||
throw 'vital: Invalid module name: ' . a:name |
||||
endif |
||||
return s:_module_path(a:name) isnot# '' |
||||
endfunction |
||||
let s:Vital.exists = s:_function('s:exists') |
||||
|
||||
function! s:search(pattern) abort dict |
||||
let paths = s:_extract_files(a:pattern, self.vital_files()) |
||||
let modules = sort(map(paths, 's:_file2module(v:val)')) |
||||
return s:_uniq(modules) |
||||
endfunction |
||||
let s:Vital.search = s:_function('s:search') |
||||
|
||||
function! s:plugin_name() abort dict |
||||
return self._plugin_name |
||||
endfunction |
||||
let s:Vital.plugin_name = s:_function('s:plugin_name') |
||||
|
||||
function! s:_self_vital_files() abort |
||||
let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) |
||||
let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) |
||||
let base = builtin . ',' . installed |
||||
return split(globpath(base, '**/*.vim', 1), "\n") |
||||
endfunction |
||||
|
||||
function! s:_global_vital_files() abort |
||||
let pattern = 'autoload/vital/__*__/**/*.vim' |
||||
return split(globpath(&runtimepath, pattern, 1), "\n") |
||||
endfunction |
||||
|
||||
function! s:_extract_files(pattern, files) abort |
||||
let tr = {'.': '/', '*': '[^/]*', '**': '.*'} |
||||
let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') |
||||
let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) |
||||
return filter(a:files, 'v:val =~# regexp') |
||||
endfunction |
||||
|
||||
function! s:_file2module(file) abort |
||||
let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') |
||||
let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') |
||||
return join(split(tail, '[\\/]\+'), '.') |
||||
endfunction |
||||
|
||||
" @param {string} name e.g. Data.List |
||||
function! s:_import(name) abort dict |
||||
if has_key(s:loaded, a:name) |
||||
return copy(s:loaded[a:name]) |
||||
endif |
||||
let module = self._get_module(a:name) |
||||
if has_key(module, '_vital_created') |
||||
call module._vital_created(module) |
||||
endif |
||||
let export_module = filter(copy(module), 'v:key =~# "^\\a"') |
||||
" Cache module before calling module.vital_loaded() to avoid cyclic |
||||
" dependences but remove the cache if module._vital_loaded() fails. |
||||
" let s:loaded[a:name] = export_module |
||||
let s:loaded[a:name] = export_module |
||||
if has_key(module, '_vital_loaded') |
||||
try |
||||
call module._vital_loaded(vital#{s:plugin_name}#new()) |
||||
catch |
||||
unlet s:loaded[a:name] |
||||
throw 'vital: fail to call ._vital_loaded(): ' . v:exception |
||||
endtry |
||||
endif |
||||
return copy(s:loaded[a:name]) |
||||
endfunction |
||||
let s:Vital._import = s:_function('s:_import') |
||||
|
||||
" s:_get_module() returns module object wihch has all script local functions. |
||||
function! s:_get_module(name) abort dict |
||||
let funcname = s:_import_func_name(self.plugin_name(), a:name) |
||||
if s:_exists_autoload_func_with_source(funcname) |
||||
return call(funcname, []) |
||||
else |
||||
return s:_get_builtin_module(a:name) |
||||
endif |
||||
endfunction |
||||
|
||||
function! s:_get_builtin_module(name) abort |
||||
return s:sid2sfuncs(s:_module_sid(a:name)) |
||||
endfunction |
||||
|
||||
if s:is_vital_vim |
||||
" For vital.vim, we can use s:_get_builtin_module directly |
||||
let s:Vital._get_module = s:_function('s:_get_builtin_module') |
||||
else |
||||
let s:Vital._get_module = s:_function('s:_get_module') |
||||
endif |
||||
|
||||
function! s:_import_func_name(plugin_name, module_name) abort |
||||
return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) |
||||
endfunction |
||||
|
||||
function! s:_module_sid(name) abort |
||||
let path = s:_module_path(a:name) |
||||
if !filereadable(path) |
||||
throw 'vital: module not found: ' . a:name |
||||
endif |
||||
let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) |
||||
let base = join([vital_dir, ''], '[/\\]\+') |
||||
let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') |
||||
let sid = s:_sid(path, p) |
||||
if !sid |
||||
call s:_source(path) |
||||
let sid = s:_sid(path, p) |
||||
if !sid |
||||
throw printf('vital: cannot get <SID> from path: %s', path) |
||||
endif |
||||
endif |
||||
return sid |
||||
endfunction |
||||
|
||||
function! s:_module_path(name) abort |
||||
return get(s:_extract_files(a:name, s:vital_files()), 0, '') |
||||
endfunction |
||||
|
||||
function! s:_module_sid_base_dir() abort |
||||
return s:is_vital_vim ? &rtp : s:project_root |
||||
endfunction |
||||
|
||||
function! s:_dot_to_sharp(name) abort |
||||
return substitute(a:name, '\.', '#', 'g') |
||||
endfunction |
||||
|
||||
" It will sources autoload file if a given func is not already defined. |
||||
function! s:_exists_autoload_func_with_source(funcname) abort |
||||
if exists('*' . a:funcname) |
||||
" Return true if a given func is already defined |
||||
return 1 |
||||
endif |
||||
" source a file which may include a given func definition and try again. |
||||
let path = 'autoload/' . substitute(substitute(a:funcname, '#[^#]*$', '.vim', ''), '#', '/', 'g') |
||||
call s:_runtime(path) |
||||
return exists('*' . a:funcname) |
||||
endfunction |
||||
|
||||
function! s:_runtime(path) abort |
||||
execute 'runtime' fnameescape(a:path) |
||||
endfunction |
||||
|
||||
function! s:_source(path) abort |
||||
execute 'source' fnameescape(a:path) |
||||
endfunction |
||||
|
||||
" @vimlint(EVL102, 1, l:_) |
||||
" @vimlint(EVL102, 1, l:__) |
||||
function! s:_sid(path, filter_pattern) abort |
||||
let unified_path = s:_unify_path(a:path) |
||||
if has_key(s:cache_sid, unified_path) |
||||
return s:cache_sid[unified_path] |
||||
endif |
||||
for line in filter(split(s:_redir(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') |
||||
let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') |
||||
if s:_unify_path(path) is# unified_path |
||||
let s:cache_sid[unified_path] = sid |
||||
return s:cache_sid[unified_path] |
||||
endif |
||||
endfor |
||||
return 0 |
||||
endfunction |
||||
|
||||
function! s:_redir(cmd) abort |
||||
let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] |
||||
set verbose=0 verbosefile= |
||||
redir => res |
||||
silent! execute a:cmd |
||||
redir END |
||||
let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] |
||||
return res |
||||
endfunction |
||||
|
||||
if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not |
||||
let s:_unify_path_cache = {} |
||||
" resolve() is slow, so we cache results. |
||||
" Note: On windows, vim can't expand path names from 8.3 formats. |
||||
" So if getting full path via <sfile> and $HOME was set as 8.3 format, |
||||
" vital load duplicated scripts. Below's :~ avoid this issue. |
||||
function! s:_unify_path(path) abort |
||||
if has_key(s:_unify_path_cache, a:path) |
||||
return s:_unify_path_cache[a:path] |
||||
endif |
||||
let value = tolower(fnamemodify(resolve(fnamemodify( |
||||
\ a:path, ':p')), ':~:gs?[\\/]?/?')) |
||||
let s:_unify_path_cache[a:path] = value |
||||
return value |
||||
endfunction |
||||
else |
||||
function! s:_unify_path(path) abort |
||||
return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) |
||||
endfunction |
||||
endif |
||||
|
||||
" copied and modified from Vim.ScriptLocal |
||||
let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '') |
||||
function! s:sid2sfuncs(sid) abort |
||||
let fs = split(s:_redir(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") |
||||
let r = {} |
||||
let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid) |
||||
for fname in map(fs, 'matchstr(v:val, pattern)') |
||||
let r[fname] = function(s:_sfuncname(a:sid, fname)) |
||||
endfor |
||||
return r |
||||
endfunction |
||||
|
||||
"" Return funcname of script local functions with SID |
||||
function! s:_sfuncname(sid, funcname) abort |
||||
return printf('<SNR>%s_%s', a:sid, a:funcname) |
||||
endfunction |
||||
|
||||
if exists('*uniq') |
||||
function! s:_uniq(list) abort |
||||
return uniq(a:list) |
||||
endfunction |
||||
else |
||||
function! s:_uniq(list) abort |
||||
let i = len(a:list) - 1 |
||||
while 0 < i |
||||
if a:list[i] ==# a:list[i - 1] |
||||
call remove(a:list, i) |
||||
endif |
||||
let i -= 1 |
||||
endwhile |
||||
return a:list |
||||
endfunction |
||||
endif |
@ -0,0 +1,274 @@
@@ -0,0 +1,274 @@
|
||||
"============================================================================= |
||||
" FILE: t/compare_movements_spec.vim |
||||
" AUTHOR: YggdrasiI |
||||
" Test: https://github.com/kana/vim-vspec |
||||
" Description: EasyMotion keyword movement test with vim-vspec |
||||
" License: MIT license {{{ |
||||
" Permission is hereby granted, free of charge, to any person obtaining |
||||
" a copy of this software and associated documentation files (the |
||||
" "Software"), to deal in the Software without restriction, including |
||||
" without limitation the rights to use, copy, modify, merge, publish, |
||||
" distribute, sublicense, and/or sell copies of the Software, and to |
||||
" permit persons to whom the Software is furnished to do so, subject to |
||||
" the following conditions: |
||||
" |
||||
" The above copyright notice and this permission notice shall be included |
||||
" in all copies or substantial portions of the Software. |
||||
" |
||||
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||||
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
" }}} |
||||
"============================================================================= |
||||
|
||||
" Setup {{{ |
||||
let s:root_dir = matchstr(system('git rev-parse --show-cdup'), '[^\n]\+') |
||||
|
||||
" The consumed time depends from the length of the text and could be really high |
||||
" on vimdoc pages. (See it 'Loop through Vim help buffer and compare movements') |
||||
" Reduce this value to stop CompareMovements(...) before it reached the end of the |
||||
" buffer. |
||||
let s:maximal_number_of_compared_movments = 10000 |
||||
execute 'set' 'rtp +=./'.s:root_dir |
||||
runtime! plugin/EasyMotion.vim |
||||
" }}} |
||||
|
||||
" Functions for Test {{{ |
||||
function! AddLine(str) |
||||
put =a:str |
||||
endfunction |
||||
|
||||
function! CursorPos() |
||||
return [line('.'), col('.'), getline('.')[col('.')-1]] |
||||
endfunction |
||||
|
||||
" Nested normal to avoid throwing readonly errors. They abort the testing. |
||||
function TryNormal(str) |
||||
try |
||||
exec 'normal ' . a:str |
||||
catch /^Vim\%((\a\+)\)\=:E21/ |
||||
endtry |
||||
return 0 |
||||
endfunction |
||||
|
||||
let s:to_cursor = {} |
||||
function! s:to_cursor.match(actual, expected) |
||||
return a:actual == a:expected |
||||
endfunction |
||||
|
||||
" Add metadata about failure. |
||||
function! s:to_cursor.failure_message_for_should(actual, expected) |
||||
Expect a:actual[0] > 0 |
||||
Expect a:expected[0] > 0 |
||||
Expect a:actual[0] <= getpos('$')[1] |
||||
Expect a:expected[0] <= getpos('$')[1] |
||||
Expect a:actual[1] > 0 |
||||
Expect a:expected[1] > 0 |
||||
|
||||
let line1 = getline(a:actual[0]) |
||||
let line2 = getline(a:expected[0]) |
||||
" Change char on cursor to '█'. |
||||
let line1 = strpart(l:line1, 0, a:actual[1]-1) |
||||
\ . '█' |
||||
\ . strpart(l:line1, a:actual[1]) |
||||
let line2 = strpart(l:line2, 0, a:expected[1]-1) |
||||
\ . '█' |
||||
\ . strpart(l:line2, a:expected[1]) |
||||
" Separation of both cases with \n would be nice, but |
||||
" vim-vspec allow oneliners as return string, only. |
||||
let msg = 'Line ' . string(a:actual[0]) . ": '" . l:line1 |
||||
\ . "',\x09\x09 Line " . string(a:expected[0]) . ": '" . l:line2 . "'\x0a" |
||||
return l:msg |
||||
endfunction |
||||
|
||||
function! CompareMovements(movement1, movement2, backward) |
||||
let jumpmarks = [ |
||||
\ [a:movement1, []], |
||||
\ [a:movement2, []], |
||||
\ ] |
||||
|
||||
" Loop through current buffer in both variants {{ |
||||
for [l:handler, l:list] in l:jumpmarks |
||||
if a:backward == 1 |
||||
let last_line = line('$') |
||||
let last_char = len(getline(l:last_line)) |
||||
call cursor(l:last_line, l:last_char) |
||||
else |
||||
call cursor([1,1]) |
||||
endif |
||||
|
||||
let lastpos = [0,0] |
||||
|
||||
" Centralize line. Otherwise, Easymotion functions aborts |
||||
" at the end of the (virtual) window. |
||||
call TryNormal('zz') |
||||
call TryNormal(l:handler) |
||||
let curpos = getpos(".")[1:2] |
||||
|
||||
while l:lastpos != l:curpos |
||||
let list += [l:curpos] |
||||
let lastpos = l:curpos |
||||
call TryNormal('zz') |
||||
call TryNormal(l:handler) |
||||
let curpos = getpos(".")[1:2] |
||||
" Abort after a fixed number of steps. |
||||
if len(l:list) > s:maximal_number_of_compared_movments |
||||
break |
||||
endif |
||||
endwhile |
||||
endfor |
||||
" }} |
||||
|
||||
" The resulting lists are stored in l:jumpmarks[*][1], now. |
||||
let [l:cursor_positions1, l:cursor_positions2] = [ l:jumpmarks[0][1], l:jumpmarks[1][1] ] |
||||
|
||||
if l:cursor_positions1 == l:cursor_positions2 |
||||
return 0 |
||||
endif |
||||
|
||||
" Search for first unmatching position. {{ |
||||
let index = 0 |
||||
let len = min([len(l:cursor_positions2), len(l:cursor_positions1)]) |
||||
while l:index < l:len |
||||
Expect l:cursor_positions2[l:index] to_cursor l:cursor_positions1[l:index] |
||||
let index += 1 |
||||
endwhile |
||||
|
||||
" Collision with begin or end of file or while loop aborts to early. |
||||
if a:backward == 1 |
||||
Expect join([a:movement2, ': File begin reached after ', len(l:cursor_positions2), ' steps.']) |
||||
\ == join([a:movement1, ': File begin reached after ', len(l:cursor_positions1), ' steps.']) |
||||
else |
||||
Expect l:cursor_positions2[l:index-1] to_cursor l:cursor_positions1[l:index] |
||||
Expect join([a:movement2, ': File end reached after ', len(l:cursor_positions2), ' steps.']) |
||||
\ == join([a:movement1, ': File end reached after ', len(l:cursor_positions1), ' steps.']) |
||||
endif |
||||
" }} |
||||
|
||||
return -1 |
||||
endfunction |
||||
|
||||
" Hand crafted text with rare cases |
||||
function! InsertTestText1() |
||||
|
||||
" Blanks at document begin |
||||
call AddLine('') |
||||
call AddLine(' ') |
||||
call AddLine('') |
||||
|
||||
call AddLine('scriptencoding utf-8') |
||||
|
||||
" '^\s*[not-\k]'-case |
||||
call AddLine('!foo') |
||||
call AddLine(' !bar') |
||||
|
||||
call AddLine('<!{}>s! ') |
||||
|
||||
" Blanks at document end |
||||
call AddLine('') |
||||
call AddLine(' ') |
||||
call AddLine('') |
||||
endfunction |
||||
|
||||
"}}} |
||||
|
||||
"Keyword word motion {{{ |
||||
describe 'Keyword word motion' |
||||
before |
||||
new |
||||
resize 10 |
||||
nmap a <Nop> |
||||
let g:EasyMotion_keys = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
||||
let g:EasyMotion_maximal_jumpmarks = 2 " Error for value 1 unanalyzed. |
||||
nmap <Leader>w <Plug>(easymotion-iskeyword-w) |
||||
nmap <Leader>b <Plug>(easymotion-iskeyword-b) |
||||
nmap <Leader>e <Plug>(easymotion-iskeyword-e) |
||||
nmap <Leader>ge <Plug>(easymotion-iskeyword-ge) |
||||
nmap <Leader>W <Plug>(easymotion-W) |
||||
nmap <Leader>B <Plug>(easymotion-B) |
||||
nmap <Leader>E <Plug>(easymotion-E) |
||||
nmap <Leader>gE <Plug>(easymotion-gE) |
||||
call EasyMotion#init() |
||||
call vspec#customize_matcher('to_cursor', s:to_cursor) |
||||
end |
||||
|
||||
after |
||||
close! |
||||
end |
||||
|
||||
it 'Simple test to check setup of this test' |
||||
" Check if a is remapped to <Nop> to avoid start of insert mode. |
||||
normal aa\<Esc> |
||||
Expect getline(1) == '' |
||||
|
||||
call AddLine('word') |
||||
Expect CompareMovements('w', 'w', 0) == 0 |
||||
Expect CompareMovements('w', '\wa', 0) == 0 |
||||
Expect CompareMovements('b', '\ba', 1) == 0 |
||||
Expect CompareMovements('e', '\ea', 0) == 0 |
||||
Expect CompareMovements('ge', '\gea', 1) == 0 |
||||
Expect CompareMovements('W', '\Wa', 0) == 0 |
||||
Expect CompareMovements('B', '\Ba', 1) == 0 |
||||
Expect CompareMovements('E', '\Ea', 0) == 0 |
||||
Expect CompareMovements('gE', '\gEa', 1) == 0 |
||||
end |
||||
|
||||
it 'w' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('w', '\wa', 0) == 0 |
||||
end |
||||
|
||||
it 'b' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('b', '\ba', 1) == 0 |
||||
end |
||||
|
||||
it 'e' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('e', '\ea', 0) == 0 |
||||
end |
||||
|
||||
it 'ge' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('ge', '\gea', 1) == 0 |
||||
end |
||||
|
||||
it 'W' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('W', 'W', 0) == 0 |
||||
end |
||||
|
||||
it 'B' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('B', 'B', 1) == 0 |
||||
end |
||||
|
||||
it 'E' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('E', 'E', 0) == 0 |
||||
end |
||||
|
||||
it 'gE' |
||||
call InsertTestText1() |
||||
Expect CompareMovements('gE', 'gE', 1) == 0 |
||||
end |
||||
|
||||
" Really time consuming test... |
||||
"it 'Loop through Vim help buffer and compare movements' |
||||
" help motion.txt |
||||
" Expect expand('%:t') ==# 'motion.txt' |
||||
" "Optional: Copy text into editable buffer |
||||
" exec "normal! Gygg\<C-W>cP" |
||||
" Expect CompareMovements('w', '\wa', 0) == 0 |
||||
"end |
||||
|
||||
end |
||||
"}}} |
||||
|
||||
" __END__ {{{ |
||||
" vim: fdm=marker:et:ts=4:sw=4:sts=4 |
||||
" }}} |
Loading…
Reference in new issue