All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 let s:plugin_name = expand('<sfile>:t:r')
2 let s:vital_base_dir = expand('<sfile>:h')
3 let s:project_root = expand('<sfile>:h:h:h')
4 let s:is_vital_vim = s:plugin_name is# 'vital'
9 function! vital#{s:plugin_name}#new() abort
10 return s:new(s:plugin_name)
13 function! vital#{s:plugin_name}#import(...) abort
15 let s:V = s:new(s:plugin_name)
17 return call(s:V.import, a:000, s:V)
22 function! s:new(plugin_name) abort
23 let base = deepcopy(s:Vital)
24 let base._plugin_name = a:plugin_name
28 function! s:vital_files() abort
29 if !exists('s:vital_files')
30 let s:vital_files = map(
31 \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(),
32 \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")')
34 return copy(s:vital_files)
36 let s:Vital.vital_files = function('s:vital_files')
38 function! s:import(name, ...) abort dict
42 if type(a) == type({})
44 elseif type(a) == type([])
49 let module = self._import(a:name)
51 call extend(target, module, 'keep')
54 if has_key(module, f) && !has_key(target, f)
55 let target[f] = module[f]
61 let s:Vital.import = function('s:import')
63 function! s:load(...) abort dict
65 let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg]
66 let target = split(join(as, ''), '\W\+')
68 let dict_type = type({})
70 let ns = remove(target, 0)
74 if type(dict[ns]) == dict_type
82 call extend(dict, self._import(name))
88 let s:Vital.load = function('s:load')
90 function! s:unload() abort dict
95 let s:Vital.unload = function('s:unload')
97 function! s:exists(name) abort dict
98 if a:name !~# '\v^\u\w*%(\.\u\w*)*$'
99 throw 'vital: Invalid module name: ' . a:name
101 return s:_module_path(a:name) isnot# ''
103 let s:Vital.exists = function('s:exists')
105 function! s:search(pattern) abort dict
106 let paths = s:_extract_files(a:pattern, self.vital_files())
107 let modules = sort(map(paths, 's:_file2module(v:val)'))
110 let s:Vital.search = function('s:search')
112 function! s:plugin_name() abort dict
113 return self._plugin_name
115 let s:Vital.plugin_name = function('s:plugin_name')
117 function! s:_self_vital_files() abort
118 let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name)
119 let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name)
120 let base = builtin . ',' . installed
121 return split(globpath(base, '**/*.vim', 1), "\n")
124 function! s:_global_vital_files() abort
125 let pattern = 'autoload/vital/__*__/**/*.vim'
126 return split(globpath(&runtimepath, pattern, 1), "\n")
129 function! s:_extract_files(pattern, files) abort
130 let tr = {'.': '/', '*': '[^/]*', '**': '.*'}
131 let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g')
132 let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target)
133 return filter(a:files, 'v:val =~# regexp')
136 function! s:_file2module(file) abort
137 let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?')
138 let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
139 return join(split(tail, '[\\/]\+'), '.')
142 " @param {string} name e.g. Data.List
143 function! s:_import(name) abort dict
144 if has_key(s:loaded, a:name)
145 return copy(s:loaded[a:name])
147 let module = self._get_module(a:name)
148 if has_key(module, '_vital_created')
149 call module._vital_created(module)
151 let export_module = filter(copy(module), 'v:key =~# "^\\a"')
152 " Cache module before calling module._vital_loaded() to avoid cyclic
153 " dependences but remove the cache if module._vital_loaded() fails.
154 " let s:loaded[a:name] = export_module
155 let s:loaded[a:name] = export_module
156 if has_key(module, '_vital_loaded')
158 call module._vital_loaded(vital#{s:plugin_name}#new())
160 unlet s:loaded[a:name]
161 throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint)
164 return copy(s:loaded[a:name])
166 let s:Vital._import = function('s:_import')
168 function! s:_format_throwpoint(throwpoint) abort
170 let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$')
171 for line in split(stack, '\.\.')
172 let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$')
174 let [name, lnum, lnum2] = m[1:3]
178 let info = s:_get_func_info(name)
180 let attrs = empty(info.attrs) ? '' : join([''] + info.attrs)
181 let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum)
182 call add(funcs, printf('function %s(...)%s Line:%d (%s%s)',
183 \ info.funcname, attrs, lnum, info.filename, flnum))
187 " fallback when function information cannot be detected
188 call add(funcs, line)
190 return join(funcs, "\n")
193 " @vimlint(EVL102, 1, l:_)
194 " @vimlint(EVL102, 1, l:__)
195 function! s:_get_func_info(name) abort
197 if a:name =~# '^\d\+$' " is anonymous-function
198 let name = printf('{%s}', a:name)
199 elseif a:name =~# '^<lambda>\d\+$' " is lambda-function
200 let name = printf("{'%s'}", a:name)
202 if !exists('*' . name)
205 let body = execute(printf('verbose function %s', name))
206 let lines = split(body, "\n")
207 let signature = matchstr(lines[0], '^\s*\zs.*')
208 let [_, file, lnum; __] = matchlist(lines[1],
209 \ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$')
211 \ 'filename': substitute(file, '[/\\]\+', '/', 'g'),
213 \ 'funcname': a:name,
214 \ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'),
215 \ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'),
218 " @vimlint(EVL102, 0, l:__)
219 " @vimlint(EVL102, 0, l:_)
221 " s:_get_module() returns module object which has all script local functions.
222 function! s:_get_module(name) abort dict
223 let funcname = s:_import_func_name(self.plugin_name(), a:name)
225 return call(funcname, [])
226 catch /^Vim\%((\a\+)\)\?:E117:/
227 return s:_get_builtin_module(a:name)
231 function! s:_get_builtin_module(name) abort
232 return s:sid2sfuncs(s:_module_sid(a:name))
236 " For vital.vim, we can use s:_get_builtin_module directly
237 let s:Vital._get_module = function('s:_get_builtin_module')
239 let s:Vital._get_module = function('s:_get_module')
242 function! s:_import_func_name(plugin_name, module_name) abort
243 return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name))
246 function! s:_module_sid(name) abort
247 let path = s:_module_path(a:name)
248 if !filereadable(path)
249 throw 'vital: module not found: ' . a:name
251 let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name)
252 let base = join([vital_dir, ''], '[/\\]\+')
253 let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g')
254 let sid = s:_sid(path, p)
257 let sid = s:_sid(path, p)
259 throw printf('vital: cannot get <SID> from path: %s', path)
265 function! s:_module_path(name) abort
266 return get(s:_extract_files(a:name, s:vital_files()), 0, '')
269 function! s:_module_sid_base_dir() abort
270 return s:is_vital_vim ? &rtp : s:project_root
273 function! s:_dot_to_sharp(name) abort
274 return substitute(a:name, '\.', '#', 'g')
277 function! s:_source(path) abort
278 execute 'source' fnameescape(a:path)
281 " @vimlint(EVL102, 1, l:_)
282 " @vimlint(EVL102, 1, l:__)
283 function! s:_sid(path, filter_pattern) abort
284 let unified_path = s:_unify_path(a:path)
285 if has_key(s:cache_sid, unified_path)
286 return s:cache_sid[unified_path]
288 for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern')
289 let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$')
290 if s:_unify_path(path) is# unified_path
291 let s:cache_sid[unified_path] = sid
292 return s:cache_sid[unified_path]
298 if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not
299 let s:_unify_path_cache = {}
300 " resolve() is slow, so we cache results.
301 " Note: On windows, vim can't expand path names from 8.3 formats.
302 " So if getting full path via <sfile> and $HOME was set as 8.3 format,
303 " vital load duplicated scripts. Below's :~ avoid this issue.
304 function! s:_unify_path(path) abort
305 if has_key(s:_unify_path_cache, a:path)
306 return s:_unify_path_cache[a:path]
308 let value = tolower(fnamemodify(resolve(fnamemodify(
309 \ a:path, ':p')), ':~:gs?[\\/]?/?'))
310 let s:_unify_path_cache[a:path] = value
314 function! s:_unify_path(path) abort
315 return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?'))
319 " copied and modified from Vim.ScriptLocal
320 let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '')
321 function! s:sid2sfuncs(sid) abort
322 let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n")
324 let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid)
325 for fname in map(fs, 'matchstr(v:val, pattern)')
326 let r[fname] = function(s:_sfuncname(a:sid, fname))
331 "" Return funcname of script local functions with SID
332 function! s:_sfuncname(sid, funcname) abort
333 return printf('<SNR>%s_%s', a:sid, a:funcname)