├── autoload ├── vital │ ├── amake.vital │ ├── _amake.vim │ ├── _amake │ │ ├── System │ │ │ ├── Job.vim │ │ │ └── Job │ │ │ │ ├── Neovim.vim │ │ │ │ └── Vim.vim │ │ ├── Vim │ │ │ ├── Type.vim │ │ │ ├── Buffer.vim │ │ │ └── Guard.vim │ │ ├── Data │ │ │ ├── Dict.vim │ │ │ └── List.vim │ │ ├── Async │ │ │ └── Promise.vim │ │ └── Prelude.vim │ └── amake.vim ├── amake │ ├── runner │ │ ├── python.vim │ │ ├── javascript.vim │ │ └── vim.vim │ ├── buffer.vim │ ├── runner.vim │ └── process.vim └── amake.vim ├── plugin └── amake.vim ├── README.md └── LICENSE /autoload/vital/amake.vital: -------------------------------------------------------------------------------- 1 | amake 2 | f382ea5b1bb179cfa54ee0be5565eb0b97953677 3 | 4 | Vim.Buffer 5 | System.Job 6 | Async.Promise 7 | -------------------------------------------------------------------------------- /autoload/amake/runner/python.vim: -------------------------------------------------------------------------------- 1 | function! amake#runner#python#new() abort 2 | return { 'build_args': { f -> ['python', f] } } 3 | endfunction 4 | -------------------------------------------------------------------------------- /autoload/amake/runner/javascript.vim: -------------------------------------------------------------------------------- 1 | function! amake#runner#javascript#new() abort 2 | return { 'build_args': { f -> ['node', f] } } 3 | endfunction 4 | 5 | -------------------------------------------------------------------------------- /plugin/amake.vim: -------------------------------------------------------------------------------- 1 | " plugin/amake.vim 2 | 3 | if exists('g:loaded_amake') 4 | finish 5 | endif 6 | let g:loaded_amake = 1 7 | 8 | command! -nargs=? Amake call amake#run() 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amake 2 | 3 | An example Vim plugin for "Effective Modern Vim scripting" in VimConf 2018. 4 | 5 | - [VimConf 2018](https://vimconf.org/2018/) 6 | - [vital.vim](https://github.com/vim-jp/vital.vim) 7 | - [vital-Whisky](https://github.com/lambdalisue/vital-Whisky) 8 | -------------------------------------------------------------------------------- /autoload/vital/_amake.vim: -------------------------------------------------------------------------------- 1 | let s:_plugin_name = expand(':t:r') 2 | 3 | function! vital#{s:_plugin_name}#new() abort 4 | return vital#{s:_plugin_name[1:]}#new() 5 | endfunction 6 | 7 | function! vital#{s:_plugin_name}#function(funcname) abort 8 | silent! return function(a:funcname) 9 | endfunction 10 | -------------------------------------------------------------------------------- /autoload/amake/buffer.vim: -------------------------------------------------------------------------------- 1 | let s:Buffer = vital#amake#import('Vim.Buffer') 2 | 3 | function! amake#buffer#new(bufname, content, ...) abort 4 | let options = a:0 ? a:1 : {} 5 | call s:Buffer.open(a:bufname, options) 6 | setlocal modifiable 7 | silent %delete _ 8 | call setline(1, a:content) 9 | setlocal nomodified nomodifiable 10 | setlocal buftype=nofile bufhidden=wipe 11 | endfunction 12 | -------------------------------------------------------------------------------- /autoload/amake/runner/vim.vim: -------------------------------------------------------------------------------- 1 | function! amake#runner#vim#new() abort 2 | return { 'build_args': funcref('s:build_args') } 3 | endfunction 4 | 5 | function! s:build_args(filename) abort 6 | let cmd = printf( 7 | \ 'source %s', 8 | \ fnameescape(a:filename), 9 | \) 10 | return [ 11 | \ 'nvim', '-n', '--headless', 12 | \ '--cmd', cmd, '--cmd', 'quit', 13 | \] 14 | endfunction 15 | -------------------------------------------------------------------------------- /autoload/amake.vim: -------------------------------------------------------------------------------- 1 | " autoload/amake.vim 2 | 3 | function! amake#hello_world() abort 4 | echo "Hello World" 5 | endfunction 6 | 7 | function! amake#run(opener) abort 8 | let runner = amake#runner#new(&filetype) 9 | let result = amake#runner#run(runner, expand('%:p')) 10 | let bufname = printf('amake://%s', join(result.args, ' ')) 11 | let options = { 12 | \ 'opener': empty(a:opener) ? 'new' : a:opener, 13 | \} 14 | let Open = { c -> amake#buffer#new(bufname, c, options) } 15 | call result 16 | \.then({ v -> Open(v.stdout) }) 17 | \.catch({ v -> Open(v.stdout + [''] + v.stderr) }) 18 | endfunction 19 | -------------------------------------------------------------------------------- /autoload/amake/runner.vim: -------------------------------------------------------------------------------- 1 | function! amake#runner#new(filetype) abort 2 | let namespace = substitute(a:filetype, '\W', '_', 'g') 3 | let funcname = printf( 4 | \ 'amake#runner#%s#new', 5 | \ namespace, 6 | \) 7 | try 8 | return call(funcname, []) 9 | catch /:E117: [^:]\+: amake#runner#[^#]\+#new/ 10 | throw printf( 11 | \ 'amake: Runner is not found: %s', 12 | \ a:filetype, 13 | \) 14 | endtry 15 | endfunction 16 | 17 | function! amake#runner#run(runner, filename) abort 18 | let args = a:runner.build_args(a:filename) 19 | let result = amake#process#open(args) 20 | let result.args = args 21 | return result 22 | endfunction 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alisue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /autoload/amake/process.vim: -------------------------------------------------------------------------------- 1 | let s:Job = vital#amake#import('System.Job') 2 | let s:Promise = vital#amake#import('Async.Promise') 3 | 4 | function! amake#process#open(args) abort 5 | return s:Promise.new(funcref('s:executor', [a:args])) 6 | endfunction 7 | 8 | function! s:executor(args, resolve, reject) abort 9 | let ns = { 10 | \ 'resolve': a:resolve, 'reject': a:reject, 11 | \ 'stdout': [''], 'stderr': [''], 12 | \} 13 | call s:Job.start(a:args, { 14 | \ 'on_stdout': funcref('s:on_receive', [ns.stdout]), 15 | \ 'on_stderr': funcref('s:on_receive', [ns.stderr]), 16 | \ 'on_exit': funcref('s:on_exit', [ns]), 17 | \}) 18 | endfunction 19 | 20 | function! s:on_receive(bs, data) abort 21 | let a:bs[-1] .= a:data[0] 22 | call extend(a:bs, a:data[1:]) 23 | endfunction 24 | 25 | function! s:on_exit(ns, exitval) abort 26 | let data = { 27 | \ 'stdout': a:ns.stdout, 28 | \ 'stderr': a:ns.stderr, 29 | \ 'exitval': a:exitval, 30 | \} 31 | if a:exitval is# 0 32 | call a:ns.resolve(data) 33 | else 34 | call a:ns.reject(data) 35 | endif 36 | endfunction 37 | -------------------------------------------------------------------------------- /autoload/vital/_amake/System/Job.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#System#Job#import() abort', printf("return map({'_vital_depends': '', '_vital_healthcheck': '', 'is_available': '', 'start': '', '_vital_loaded': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:t_string = type('') 11 | let s:t_list = type([]) 12 | 13 | function! s:_vital_loaded(V) abort 14 | if has('nvim') 15 | let s:Job = a:V.import('System.Job.Neovim') 16 | else 17 | let s:Job = a:V.import('System.Job.Vim') 18 | endif 19 | endfunction 20 | 21 | function! s:_vital_depends() abort 22 | return [ 23 | \ 'System.Job.Vim', 24 | \ 'System.Job.Neovim', 25 | \] 26 | endfunction 27 | 28 | function! s:_vital_healthcheck() abort 29 | if has('patch-8.0.0027') || has('nvim-0.2.0') 30 | return 31 | endif 32 | return 'This module requires Vim 8.0.0027 or Neovim 0.2.0' 33 | endfunction 34 | 35 | 36 | " Note: 37 | " Vim does not raise E902 on Unix system even the prog is not found so use a 38 | " custom exception instead to make the method compatible. 39 | " Note: 40 | " Vim/Neovim treat String a bit differently so prohibit String as well 41 | function! s:_validate_args(args) abort 42 | if type(a:args) != s:t_list 43 | throw 'vital: System.Job: Argument requires to be a List instance.' 44 | endif 45 | if len(a:args) == 0 46 | throw 'vital: System.Job: Argument vector must have at least one item.' 47 | endif 48 | let prog = a:args[0] 49 | if !executable(prog) 50 | throw printf('vital: System.Job: "%s" is not an executable', prog) 51 | endif 52 | endfunction 53 | 54 | function! s:is_available() abort 55 | return s:Job.is_available() 56 | endfunction 57 | 58 | function! s:start(args, ...) abort 59 | call s:_validate_args(a:args) 60 | return s:Job.start(a:args, a:0 ? a:1 : {}) 61 | endfunction 62 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Vim/Type.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#Vim#Type#import() abort', printf("return map({'is_comparable': '', '_vital_created': '', 'is_predicate': '', 'is_numeric': '', 'is_special': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:types = { 11 | \ 'number': 0, 12 | \ 'string': 1, 13 | \ 'func': 2, 14 | \ 'list': 3, 15 | \ 'dict': 4, 16 | \ 'float': 5, 17 | \ 'bool': 6, 18 | \ 'none': 7, 19 | \ 'job': 8, 20 | \ 'channel': 9, 21 | \ } 22 | lockvar 1 s:types 23 | 24 | let s:type_names = { 25 | \ '0': 'number', 26 | \ '1': 'string', 27 | \ '2': 'func', 28 | \ '3': 'list', 29 | \ '4': 'dict', 30 | \ '5': 'float', 31 | \ '6': 'bool', 32 | \ '7': 'none', 33 | \ '8': 'job', 34 | \ '9': 'channel', 35 | \ } 36 | lockvar 1 s:type_names 37 | 38 | function! s:_vital_created(module) abort 39 | let a:module.types = s:types 40 | let a:module.type_names = s:type_names 41 | endfunction 42 | 43 | 44 | function! s:is_numeric(value) abort 45 | let t = type(a:value) 46 | return t == s:types.number || t == s:types.float 47 | endfunction 48 | 49 | function! s:is_special(value) abort 50 | let t = type(a:value) 51 | return t == s:types.bool || t == s:types.none 52 | endfunction 53 | 54 | function! s:is_predicate(value) abort 55 | let t = type(a:value) 56 | return t == s:types.number || t == s:types.string || 57 | \ t == s:types.bool || t == s:types.none 58 | endfunction 59 | 60 | function! s:is_comparable(value1, value2) abort 61 | if !exists('s:is_comparable_cache') 62 | let s:is_comparable_cache = s:_make_is_comparable_cache() 63 | endif 64 | return s:is_comparable_cache[type(a:value1)][type(a:value2)] 65 | endfunction 66 | 67 | function! s:_make_is_comparable_cache() abort 68 | let vals = [ 69 | \ 0, '', function('type'), [], {}, 0.0, 70 | \ get(v:, 'false'), 71 | \ get(v:, 'null'), 72 | \ exists('*test_null_job') ? test_null_job() : 0, 73 | \ exists('*test_null_channel') ? test_null_channel() : 0, 74 | \ ] 75 | 76 | let result = [] 77 | for l:V1 in vals 78 | let result_V1 = [] 79 | let result += [result_V1] 80 | for l:V2 in vals 81 | try 82 | let _ = V1 == V2 83 | let result_V1 += [1] 84 | catch 85 | let result_V1 += [0] 86 | endtry 87 | unlet V2 88 | endfor 89 | unlet V1 90 | endfor 91 | return result 92 | endfunction 93 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Data/Dict.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#Data#Dict#import() abort', printf("return map({'_vital_depends': '', 'clear': '', 'max_by': '', 'foldl': '', 'pick': '', 'from_list': '', 'swap': '', 'omit': '', 'min_by': '', 'foldr': '', 'make_index': '', 'make': '', '_vital_loaded': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " Utilities for dictionary. 11 | 12 | let s:save_cpo = &cpo 13 | set cpo&vim 14 | 15 | function! s:_vital_loaded(V) abort 16 | let s:t = a:V.import('Vim.Type').types 17 | endfunction 18 | 19 | function! s:_vital_depends() abort 20 | return ['Vim.Type'] 21 | endfunction 22 | 23 | 24 | function! s:_ensure_key(key) abort 25 | let t = type(a:key) 26 | if t != s:t.string && t != s:t.number 27 | throw 'vital: Data.Dict: Invalid key: ' . string(a:key) 28 | endif 29 | endfunction 30 | 31 | function! s:from_list(list) abort 32 | let dict = {} 33 | let i = 0 34 | let len = len(a:list) 35 | while i < len 36 | if type(a:list[i]) == s:t.list 37 | let key_value = a:list[i] 38 | if len(key_value) != 2 39 | throw 'vital: Data.Dict: Invalid key-value pair index at ' . i 40 | endif 41 | call s:_ensure_key(key_value[0]) 42 | let dict[key_value[0]] = key_value[1] 43 | let i += 1 44 | else 45 | if len <= i + 1 46 | throw 'vital: Data.Dict: Invalid key-value pair index at ' . i 47 | endif 48 | call s:_ensure_key(a:list[i]) 49 | let dict[a:list[i]] = a:list[i + 1] 50 | let i += 2 51 | endif 52 | endwhile 53 | return dict 54 | endfunction 55 | 56 | " Makes a dict from keys and values 57 | function! s:make(keys, values, ...) abort 58 | let dict = {} 59 | let fill = a:0 ? a:1 : 0 60 | for i in range(len(a:keys)) 61 | let key = type(a:keys[i]) == s:t.string ? a:keys[i] : string(a:keys[i]) 62 | if key ==# '' 63 | throw "vital: Data.Dict: Can't use an empty string for key." 64 | endif 65 | let dict[key] = get(a:values, i, fill) 66 | endfor 67 | return dict 68 | endfunction 69 | 70 | " Swaps keys and values 71 | function! s:swap(dict) abort 72 | return s:make(values(a:dict), keys(a:dict)) 73 | endfunction 74 | 75 | " Makes a index dict from a list 76 | function! s:make_index(list, ...) abort 77 | let value = a:0 ? a:1 : 1 78 | return s:make(a:list, [], value) 79 | endfunction 80 | 81 | function! s:pick(dict, keys) abort 82 | let new_dict = {} 83 | for key in a:keys 84 | if has_key(a:dict, key) 85 | let new_dict[key] = a:dict[key] 86 | endif 87 | endfor 88 | return new_dict 89 | endfunction 90 | 91 | function! s:omit(dict, keys) abort 92 | let new_dict = copy(a:dict) 93 | for key in a:keys 94 | if has_key(a:dict, key) 95 | call remove(new_dict, key) 96 | endif 97 | endfor 98 | return new_dict 99 | endfunction 100 | 101 | function! s:clear(dict) abort 102 | for key in keys(a:dict) 103 | call remove(a:dict, key) 104 | endfor 105 | return a:dict 106 | endfunction 107 | 108 | function! s:_max_by(dict, expr) abort 109 | let dict = s:swap(map(copy(a:dict), a:expr)) 110 | let key = dict[max(keys(dict))] 111 | return [key, a:dict[key]] 112 | endfunction 113 | 114 | function! s:max_by(dict, expr) abort 115 | if empty(a:dict) 116 | throw 'vital: Data.Dict: Empty dictionary' 117 | endif 118 | return s:_max_by(a:dict, a:expr) 119 | endfunction 120 | 121 | function! s:min_by(dict, expr) abort 122 | if empty(a:dict) 123 | throw 'vital: Data.Dict: Empty dictionary' 124 | endif 125 | return s:_max_by(a:dict, '-(' . a:expr . ')') 126 | endfunction 127 | 128 | function! s:_foldl(f, init, xs) abort 129 | let memo = a:init 130 | for [k, v] in a:xs 131 | let expr = substitute(a:f, 'v:key', string(k), 'g') 132 | let expr = substitute(expr, 'v:val', string(v), 'g') 133 | let expr = substitute(expr, 'v:memo', string(memo), 'g') 134 | unlet memo 135 | let memo = eval(expr) 136 | endfor 137 | return memo 138 | endfunction 139 | 140 | function! s:foldl(f, init, dict) abort 141 | return s:_foldl(a:f, a:init, items(a:dict)) 142 | endfunction 143 | 144 | function! s:foldr(f, init, dict) abort 145 | return s:_foldl(a:f, a:init, reverse(items(a:dict))) 146 | endfunction 147 | 148 | let &cpo = s:save_cpo 149 | unlet s:save_cpo 150 | 151 | " vim:set et ts=2 sts=2 sw=2 tw=0: 152 | -------------------------------------------------------------------------------- /autoload/vital/_amake/System/Job/Neovim.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#System#Job#Neovim#import() abort', printf("return map({'is_available': '', 'start': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " http://vim-jp.org/blog/2016/03/23/take-care-of-patch-1577.html 11 | function! s:is_available() abort 12 | return has('nvim') && has('nvim-0.2.0') 13 | endfunction 14 | 15 | function! s:start(args, options) abort 16 | let job = extend(copy(s:job), a:options) 17 | let job_options = {} 18 | if has_key(a:options, 'cwd') 19 | let job_options.cwd = a:options.cwd 20 | endif 21 | if has_key(job, 'on_stdout') 22 | let job_options.on_stdout = funcref('s:_on_stdout', [job]) 23 | endif 24 | if has_key(job, 'on_stderr') 25 | let job_options.on_stderr = funcref('s:_on_stderr', [job]) 26 | endif 27 | if has_key(job, 'on_exit') 28 | let job_options.on_exit = funcref('s:_on_exit', [job]) 29 | else 30 | let job_options.on_exit = funcref('s:_on_exit_raw', [job]) 31 | endif 32 | let job.__job = jobstart(a:args, job_options) 33 | let job.__exitval = v:null 34 | let job.args = a:args 35 | return job 36 | endfunction 37 | 38 | if has('nvim-0.3.0') 39 | " Neovim 0.3.0 and over seems to invoke on_stdout/on_stderr with an empty 40 | " string data when the stdout/stderr channel has closed. 41 | " It is different behavior from Vim and Neovim prior to 0.3.0 so remove an 42 | " empty string list to keep compatibility. 43 | function! s:_on_stdout(job, job_id, data, event) abort 44 | if a:data == [''] 45 | return 46 | endif 47 | call a:job.on_stdout(a:data) 48 | endfunction 49 | 50 | function! s:_on_stderr(job, job_id, data, event) abort 51 | if a:data == [''] 52 | return 53 | endif 54 | call a:job.on_stderr(a:data) 55 | endfunction 56 | else 57 | function! s:_on_stdout(job, job_id, data, event) abort 58 | call a:job.on_stdout(a:data) 59 | endfunction 60 | 61 | function! s:_on_stderr(job, job_id, data, event) abort 62 | call a:job.on_stderr(a:data) 63 | endfunction 64 | endif 65 | 66 | function! s:_on_exit(job, job_id, exitval, event) abort 67 | let a:job.__exitval = a:exitval 68 | call a:job.on_exit(a:exitval) 69 | endfunction 70 | 71 | function! s:_on_exit_raw(job, job_id, exitval, event) abort 72 | let a:job.__exitval = a:exitval 73 | endfunction 74 | 75 | " Instance ------------------------------------------------------------------- 76 | function! s:_job_id() abort dict 77 | if &verbose 78 | echohl WarningMsg 79 | echo 'vital: System.Job: job.id() is deprecated. Use job.pid() instead.' 80 | echohl None 81 | endif 82 | return self.pid() 83 | endfunction 84 | 85 | function! s:_job_pid() abort dict 86 | return jobpid(self.__job) 87 | endfunction 88 | 89 | function! s:_job_status() abort dict 90 | try 91 | sleep 1m 92 | call jobpid(self.__job) 93 | return 'run' 94 | catch /^Vim\%((\a\+)\)\=:E900/ 95 | return 'dead' 96 | endtry 97 | endfunction 98 | 99 | if exists('*chansend') " Neovim 0.2.3 100 | function! s:_job_send(data) abort dict 101 | return chansend(self.__job, a:data) 102 | endfunction 103 | else 104 | function! s:_job_send(data) abort dict 105 | return jobsend(self.__job, a:data) 106 | endfunction 107 | endif 108 | 109 | if exists('*chanclose') " Neovim 0.2.3 110 | function! s:_job_close() abort dict 111 | call chanclose(self.__job, 'stdin') 112 | endfunction 113 | else 114 | function! s:_job_close() abort dict 115 | call jobclose(self.__job, 'stdin') 116 | endfunction 117 | endif 118 | 119 | function! s:_job_stop() abort dict 120 | try 121 | call jobstop(self.__job) 122 | catch /^Vim\%((\a\+)\)\=:E900/ 123 | " NOTE: 124 | " Vim does not raise exception even the job has already closed so fail 125 | " silently for 'E900: Invalid job id' exception 126 | endtry 127 | endfunction 128 | 129 | function! s:_job_wait(...) abort dict 130 | let timeout = a:0 ? a:1 : v:null 131 | let exitval = timeout is# v:null 132 | \ ? jobwait([self.__job])[0] 133 | \ : jobwait([self.__job], timeout)[0] 134 | if exitval != -3 135 | return exitval 136 | endif 137 | " Wait until 'on_exit' callback is called 138 | while self.__exitval is# v:null 139 | sleep 1m 140 | endwhile 141 | return self.__exitval 142 | endfunction 143 | 144 | " To make debug easier, use funcref instead. 145 | let s:job = { 146 | \ 'id': funcref('s:_job_id'), 147 | \ 'pid': funcref('s:_job_pid'), 148 | \ 'status': funcref('s:_job_status'), 149 | \ 'send': funcref('s:_job_send'), 150 | \ 'close': funcref('s:_job_close'), 151 | \ 'stop': funcref('s:_job_stop'), 152 | \ 'wait': funcref('s:_job_wait'), 153 | \} 154 | -------------------------------------------------------------------------------- /autoload/vital/_amake/System/Job/Vim.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#System#Job#Vim#import() abort', printf("return map({'is_available': '', 'start': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " https://github.com/neovim/neovim/blob/f629f83/src/nvim/event/process.c#L24-L26 11 | let s:KILL_TIMEOUT_MS = 2000 12 | 13 | function! s:is_available() abort 14 | return !has('nvim') && has('patch-8.0.0027') 15 | endfunction 16 | 17 | function! s:start(args, options) abort 18 | let job = extend(copy(s:job), a:options) 19 | let job_options = { 20 | \ 'mode': 'raw', 21 | \ 'timeout': 0, 22 | \} 23 | if has_key(job, 'on_stdout') 24 | let job_options.out_cb = funcref('s:_out_cb', [job]) 25 | else 26 | let job_options.out_io = 'null' 27 | endif 28 | if has_key(job, 'on_stderr') 29 | let job_options.err_cb = funcref('s:_err_cb', [job]) 30 | else 31 | let job_options.err_io = 'null' 32 | endif 33 | if has_key(job, 'on_exit') 34 | let job_options.exit_cb = funcref('s:_exit_cb', [job]) 35 | endif 36 | if has_key(job, 'cwd') && has('patch-8.0.0902') 37 | let job_options.cwd = job.cwd 38 | endif 39 | let job.__job = job_start(a:args, job_options) 40 | let job.args = a:args 41 | return job 42 | endfunction 43 | 44 | function! s:_out_cb(job, channel, msg) abort 45 | call a:job.on_stdout(split(a:msg, "\n", 1)) 46 | endfunction 47 | 48 | function! s:_err_cb(job, channel, msg) abort 49 | call a:job.on_stderr(split(a:msg, "\n", 1)) 50 | endfunction 51 | 52 | function! s:_exit_cb(job, channel, exitval) abort 53 | " Make sure on_stdout/on_stderr are called prior to on_exit. 54 | if has_key(a:job, 'on_stdout') 55 | let options = {'part': 'out'} 56 | while ch_status(a:channel, options) ==# 'open' 57 | sleep 1m 58 | endwhile 59 | while ch_status(a:channel, options) ==# 'buffered' 60 | call s:_out_cb(a:job, a:channel, ch_readraw(a:channel, options)) 61 | endwhile 62 | endif 63 | if has_key(a:job, 'on_stderr') 64 | let options = {'part': 'err'} 65 | while ch_status(a:channel, options) ==# 'open' 66 | sleep 1m 67 | endwhile 68 | while ch_status(a:channel, options) ==# 'buffered' 69 | call s:_err_cb(a:job, a:channel, ch_readraw(a:channel, options)) 70 | endwhile 71 | endif 72 | call a:job.on_exit(a:exitval) 73 | endfunction 74 | 75 | 76 | " Instance ------------------------------------------------------------------- 77 | function! s:_job_id() abort dict 78 | if &verbose 79 | echohl WarningMsg 80 | echo 'vital: System.Job: job.id() is deprecated. Use job.pid() instead.' 81 | echohl None 82 | endif 83 | return self.pid() 84 | endfunction 85 | 86 | function! s:_job_pid() abort dict 87 | return job_info(self.__job).process 88 | endfunction 89 | 90 | " NOTE: 91 | " On Unix a non-existing command results in "dead" instead 92 | " So returns "dead" instead of "fail" even in non Unix. 93 | function! s:_job_status() abort dict 94 | let status = job_status(self.__job) 95 | return status ==# 'fail' ? 'dead' : status 96 | endfunction 97 | 98 | " NOTE: 99 | " A Null character (\0) is used as a terminator of a string in Vim. 100 | " Neovim can send \0 by using \n splitted list but in Vim. 101 | " So replace all \n in \n splitted list to '' 102 | function! s:_job_send(data) abort dict 103 | let data = type(a:data) == v:t_list 104 | \ ? join(map(a:data, 'substitute(v:val, "\n", '''', ''g'')'), "\n") 105 | \ : a:data 106 | return ch_sendraw(self.__job, data) 107 | endfunction 108 | 109 | function! s:_job_close() abort dict 110 | call ch_close_in(self.__job) 111 | endfunction 112 | 113 | function! s:_job_stop() abort dict 114 | call job_stop(self.__job) 115 | call timer_start(s:KILL_TIMEOUT_MS, { -> job_stop(self.__job, 'kill') }) 116 | endfunction 117 | 118 | function! s:_job_wait(...) abort dict 119 | let timeout = a:0 ? a:1 : v:null 120 | let timeout = timeout is# v:null ? v:null : timeout / 1000.0 121 | let start_time = reltime() 122 | let job = self.__job 123 | try 124 | while timeout is# v:null || timeout > reltimefloat(reltime(start_time)) 125 | let status = job_status(job) 126 | if status !=# 'run' 127 | return status ==# 'dead' ? job_info(job).exitval : -3 128 | endif 129 | sleep 1m 130 | endwhile 131 | catch /^Vim:Interrupt$/ 132 | call self.stop() 133 | return -2 134 | endtry 135 | return -1 136 | endfunction 137 | 138 | " To make debug easier, use funcref instead. 139 | let s:job = { 140 | \ 'id': funcref('s:_job_id'), 141 | \ 'pid': funcref('s:_job_pid'), 142 | \ 'status': funcref('s:_job_status'), 143 | \ 'send': funcref('s:_job_send'), 144 | \ 'close': funcref('s:_job_close'), 145 | \ 'stop': funcref('s:_job_stop'), 146 | \ 'wait': funcref('s:_job_wait'), 147 | \} 148 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Vim/Buffer.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#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': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | let s:t_funcref = type(function('tr')) 14 | let s:t_string = type('') 15 | let s:t_number = type(0) 16 | 17 | function! s:_vital_loaded(V) abort 18 | let s:V = a:V 19 | let s:Guard = s:V.import('Vim.Guard') 20 | endfunction 21 | 22 | function! s:_vital_depends() abort 23 | return ['Vim.Guard'] 24 | endfunction 25 | 26 | if exists('*getcmdwintype') 27 | function! s:is_cmdwin() abort 28 | return getcmdwintype() !=# '' 29 | endfunction 30 | else 31 | function! s:is_cmdwin() abort 32 | return bufname('%') ==# '[Command Line]' 33 | endfunction 34 | endif 35 | 36 | function! s:open(buffer, ...) abort 37 | if a:0 == 1 && (type(a:1) == s:t_string || type(a:1) == s:t_funcref) 38 | " For backward compatibility 39 | let options = {'opener': a:1} 40 | else 41 | let options = get(a:000, 0, {}) 42 | endif 43 | let options = extend({ 44 | \ 'mods': '', 45 | \ 'cmdarg': '', 46 | \ 'opener': empty(a:buffer) ? 'enew' : 'edit', 47 | \}, options 48 | \) 49 | 50 | let guard = s:Guard.store(['&wildignore']) 51 | try 52 | let &wildignore = '' 53 | if type(options.opener) == s:t_funcref 54 | let loaded = !bufloaded(a:buffer) 55 | call options.opener(a:buffer) 56 | elseif a:buffer is 0 || a:buffer is# '' 57 | let loaded = 1 58 | silent execute options.mods options.opener 59 | enew 60 | else 61 | let loaded = !bufloaded(a:buffer) 62 | if type(a:buffer) == s:t_string 63 | execute options.mods options.opener options.cmdarg '`=a:buffer`' 64 | elseif type(a:buffer) == s:t_number 65 | silent execute options.mods options.opener 66 | execute a:buffer 'buffer' 67 | else 68 | throw 'vital: Vim.Buffer: Unknown {buffer} type.' 69 | endif 70 | endif 71 | finally 72 | call guard.restore() 73 | endtry 74 | return loaded 75 | endfunction 76 | 77 | function! s:get_selected_text(...) abort 78 | echohl WarningMsg 79 | echom "vital: Vim.Buffer: Warning: s:get_selected_text() is deprecated. Use 's:get_last_selected()'." 80 | echohl None 81 | return call('s:get_last_selected', a:000) 82 | endfunction 83 | 84 | " Get the last selected text in visual mode 85 | " without using |gv| to avoid |textlock|. 86 | " NOTE: 87 | " * This function uses |gv| only when using |CTRL-V| 88 | " because |gv| is the only way to get selected text 89 | " when using $ . 90 | " Please see #192 for the details. 91 | " * If you don't care about |textlock|, 92 | " you can use simple version of this function. 93 | " https://github.com/vim-jp/vital.vim/commit/39aae80f3839fdbeebd838ff14d87327a6b889a9 94 | function! s:get_last_selected() abort 95 | if visualmode() ==# "\" 96 | let save = getreg('"', 1) 97 | let save_type = getregtype('"') 98 | try 99 | normal! gv""y 100 | return @" 101 | finally 102 | call setreg('"', save, save_type) 103 | endtry 104 | else 105 | let [begin, end] = [getpos("'<"), getpos("'>")] 106 | let lastchar = matchstr(getline(end[1])[end[2]-1 :], '.') 107 | if begin[1] ==# end[1] 108 | let lines = [getline(begin[1])[begin[2]-1 : end[2]-2]] 109 | else 110 | let lines = [getline(begin[1])[begin[2]-1 :]] 111 | \ + (end[1] - begin[1] <# 2 ? [] : getline(begin[1]+1, end[1]-1)) 112 | \ + [getline(end[1])[: end[2]-2]] 113 | endif 114 | return join(lines, "\n") . lastchar . (visualmode() ==# 'V' ? "\n" : '') 115 | endif 116 | endfunction 117 | 118 | function! s:read_content(content, ...) abort 119 | let options = extend({ 120 | \ 'tempfile': '', 121 | \ 'fileformat': '', 122 | \ 'encoding': '', 123 | \ 'binary': 0, 124 | \ 'nobinary': 0, 125 | \ 'bad': '', 126 | \ 'edit': 0, 127 | \ 'line': '', 128 | \ 'lockmarks': 0, 129 | \}, get(a:000, 0, {})) 130 | let tempfile = empty(options.tempfile) ? tempname() : options.tempfile 131 | let optnames = [ 132 | \ empty(options.fileformat) ? '' : '++ff=' . options.fileformat, 133 | \ empty(options.encoding) ? '' : '++enc=' . options.encoding, 134 | \ empty(options.binary) ? '' : '++bin', 135 | \ empty(options.nobinary) ? '' : '++nobin', 136 | \ empty(options.bad) ? '' : '++bad=' . options.bad, 137 | \ empty(options.edit) ? '' : '++edit', 138 | \] 139 | let optname = join(filter(optnames, '!empty(v:val)')) 140 | try 141 | call writefile(a:content, tempfile) 142 | execute printf('keepalt keepjumps %s%sread %s%s', 143 | \ options.lockmarks ? 'lockmarks ' : '', 144 | \ options.line, 145 | \ empty(optname) ? '' : optname . ' ', 146 | \ fnameescape(tempfile), 147 | \) 148 | finally 149 | call delete(tempfile) 150 | " To remove 'tempfile' from unlisted-buffer #439 151 | silent execute 'bwipeout!' fnameescape(tempfile) 152 | endtry 153 | endfunction 154 | 155 | function! s:edit_content(content, ...) abort 156 | let options = extend({ 157 | \ 'edit': 1, 158 | \ 'lockmarks': 0, 159 | \}, get(a:000, 0, {})) 160 | let guard = s:Guard.store(['&l:modifiable']) 161 | let saved_view = winsaveview() 162 | try 163 | let &l:modifiable=1 164 | silent execute printf( 165 | \ 'keepjumps %s%%delete _', 166 | \ options.lockmarks ? 'lockmarks ' : '', 167 | \) 168 | silent call s:read_content(a:content, options) 169 | silent execute printf( 170 | \ 'keepjumps %s1delete _', 171 | \ options.lockmarks ? 'lockmarks ' : '', 172 | \) 173 | finally 174 | keepjumps call winrestview(saved_view) 175 | call guard.restore() 176 | endtry 177 | setlocal nomodified 178 | endfunction 179 | 180 | function! s:parse_cmdarg(...) abort 181 | let cmdarg = get(a:000, 0, v:cmdarg) 182 | let options = {} 183 | if cmdarg =~# '++enc=' 184 | let options.encoding = matchstr(cmdarg, '++enc=\zs[^ ]\+\ze') 185 | endif 186 | if cmdarg =~# '++ff=' 187 | let options.fileformat = matchstr(cmdarg, '++ff=\zs[^ ]\+\ze') 188 | endif 189 | if cmdarg =~# '++bad=' 190 | let options.bad = matchstr(cmdarg, '++bad=\zs[^ ]\+\ze') 191 | endif 192 | if cmdarg =~# '++bin' 193 | let options.binary = 1 194 | endif 195 | if cmdarg =~# '++nobin' 196 | let options.nobinary = 1 197 | endif 198 | if cmdarg =~# '++edit' 199 | let options.edit = 1 200 | endif 201 | return options 202 | endfunction 203 | 204 | let &cpo = s:save_cpo 205 | unlet s:save_cpo 206 | 207 | " vim:set et ts=2 sts=2 sw=2 tw=0: 208 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Vim/Guard.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#Vim#Guard#import() abort', printf("return map({'_vital_depends': '', '_vital_created': '', 'store': '', '_vital_loaded': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | " Use a Funcref as a special term _UNDEFINED 14 | function! s:_undefined() abort 15 | return 'undefined' 16 | endfunction 17 | let s:_UNDEFINED = function('s:_undefined') 18 | 19 | function! s:_vital_loaded(V) abort 20 | let s:V = a:V 21 | let s:Prelude = s:V.import('Prelude') 22 | let s:List = s:V.import('Data.List') 23 | let s:Dict = s:V.import('Data.Dict') 24 | endfunction 25 | function! s:_vital_depends() abort 26 | return ['Prelude', 'Data.List', 'Data.Dict'] 27 | endfunction 28 | function! s:_vital_created(module) abort 29 | " define constant variables 30 | if !exists('s:const') 31 | let s:const = {} 32 | let s:const.is_local_variable_supported = 33 | \ v:version > 703 || (v:version == 703 && has('patch560')) 34 | " NOTE: 35 | " The third argument is available from 7.4.242 but it had bug and that 36 | " bug was fixed from 7.4.513 37 | let s:const.is_third_argument_of_getreg_supported = has('patch-7.4.513') 38 | lockvar s:const 39 | endif 40 | call extend(a:module, s:const) 41 | endfunction 42 | function! s:_throw(msg) abort 43 | throw printf('vital: Vim.Guard: %s', a:msg) 44 | endfunction 45 | 46 | let s:option = {} 47 | function! s:_new_option(name) abort 48 | if a:name !~# '^&' 49 | call s:_throw(printf( 50 | \'An option name "%s" requires to be started from "&"', a:name 51 | \)) 52 | elseif !exists(a:name) 53 | call s:_throw(printf( 54 | \'An option name "%s" does not exist', a:name 55 | \)) 56 | endif 57 | let option = copy(s:option) 58 | let option.name = a:name 59 | let option.value = eval(a:name) 60 | return option 61 | endfunction 62 | function! s:option.restore() abort 63 | execute printf('let %s = %s', self.name, string(self.value)) 64 | endfunction 65 | 66 | let s:register = {} 67 | function! s:_new_register(name) abort 68 | if len(a:name) != 2 69 | call s:_throw(printf( 70 | \'A register name "%s" requires to be "@" + a single character', a:name 71 | \)) 72 | elseif a:name !~# '^@' 73 | call s:_throw(printf( 74 | \'A register name "%s" requires to be started from "@"', a:name 75 | \)) 76 | elseif a:name =~# '^@[:.%]$' 77 | call s:_throw(printf( 78 | \'A register name "%s" is read only', a:name 79 | \)) 80 | elseif a:name !~# '^@[@0-9a-zA-Z#=*+~_/-]$' 81 | call s:_throw(printf( 82 | \'A register name "%s" does not exist. See ":help let-register"', a:name 83 | \)) 84 | endif 85 | let name = a:name ==# '@@' ? '' : a:name[1] 86 | let register = copy(s:register) 87 | let register.name = name 88 | if s:const.is_third_argument_of_getreg_supported 89 | let register.value = getreg(name, 1, 1) 90 | else 91 | let register.value = getreg(name, 1) 92 | endif 93 | let register.type = getregtype(name) 94 | return register 95 | endfunction 96 | function! s:register.restore() abort 97 | " https://github.com/vim/vim/commit/5a50c2255c447838d08d3b4895a3be3a41cd8eda 98 | if has('patch-7.4.243') || self.name !=# '=' 99 | call setreg(self.name, self.value, self.type) 100 | else 101 | let @= = self.value 102 | endif 103 | endfunction 104 | 105 | let s:environment = {} 106 | function! s:_new_environment(name) abort 107 | if a:name !~# '^\$' 108 | call s:_throw(printf( 109 | \'An environment variable name "%s" requires to be started from "$"', a:name 110 | \)) 111 | elseif !exists(a:name) 112 | call s:_throw(printf( 113 | \'An environment variable name "%s" does not exist. While Vim cannot unlet environment variable, it requires to exist', a:name 114 | \)) 115 | endif 116 | let environment = copy(s:environment) 117 | let environment.name = a:name 118 | let environment.value = eval(a:name) 119 | return environment 120 | endfunction 121 | function! s:environment.restore() abort 122 | execute printf('let %s = %s', self.name, string(self.value)) 123 | endfunction 124 | 125 | let s:variable = {} 126 | function! s:_new_variable(name, ...) abort 127 | if a:0 == 0 128 | let m = matchlist(a:name, '^\([bwtg]:\)\(.*\)$') 129 | if empty(m) 130 | call s:_throw(printf( 131 | \ join([ 132 | \ 'An variable name "%s" requires to start from b:, w:, t:, or g:', 133 | \ 'while no {namespace} is specified', 134 | \ ]), 135 | \ a:name, 136 | \)) 137 | endif 138 | let [prefix, name] = m[1 : 2] 139 | let namespace = eval(prefix) 140 | else 141 | let name = a:name 142 | let namespace = a:1 143 | endif 144 | let variable = copy(s:variable) 145 | let variable.name = name 146 | let variable.value = get(namespace, name, s:_UNDEFINED) 147 | let variable.value = 148 | \ type(variable.value) == type({}) || type(variable.value) == type([]) 149 | \ ? deepcopy(variable.value) 150 | \ : variable.value 151 | let variable._namespace = namespace 152 | return variable 153 | endfunction 154 | function! s:variable.restore() abort 155 | " unlet the variable to prevent variable type mis-match in case 156 | silent! unlet! self._namespace[self.name] 157 | if type(self.value) == type(s:_UNDEFINED) && self.value == s:_UNDEFINED 158 | " do nothing, leave the variable as undefined 159 | else 160 | let self._namespace[self.name] = self.value 161 | endif 162 | endfunction 163 | 164 | let s:instance = {} 165 | function! s:_new_instance(instance, ...) abort 166 | let shallow = get(a:000, 0, 0) 167 | if !s:Prelude.is_list(a:instance) && !s:Prelude.is_dict(a:instance) 168 | call s:_throw(printf( 169 | \'An instance "%s" requires to be List or Dictionary', string(a:instance) 170 | \)) 171 | endif 172 | let instance = copy(s:instance) 173 | let instance.instance = a:instance 174 | let instance.values = shallow ? copy(a:instance) : deepcopy(a:instance) 175 | return instance 176 | endfunction 177 | function! s:instance.restore() abort 178 | if s:Prelude.is_list(self.instance) 179 | call s:List.clear(self.instance) 180 | else 181 | call s:Dict.clear(self.instance) 182 | endif 183 | call extend(self.instance, self.values) 184 | endfunction 185 | 186 | let s:guard = {} 187 | function! s:store(targets) abort 188 | let resources = [] 189 | for meta in a:targets 190 | if s:Prelude.is_list(meta) 191 | if len(meta) == 1 192 | call add(resources, s:_new_instance(meta[0])) 193 | elseif len(meta) == 2 194 | if s:Prelude.is_string(meta[0]) 195 | call add(resources, call('s:_new_variable', meta)) 196 | else 197 | call add(resources, call('s:_new_instance', meta)) 198 | endif 199 | else 200 | call s:_throw('List assignment requires one or two elements') 201 | endif 202 | elseif type(meta) == type('') 203 | if meta =~# '^[bwtgls]:' 204 | " Note: 205 | " To improve an error message, handle l:XXX or s:XXX as well 206 | call add(resources, s:_new_variable(meta)) 207 | elseif meta =~# '^&' 208 | call add(resources, s:_new_option(meta)) 209 | elseif meta =~# '^@' 210 | call add(resources, s:_new_register(meta)) 211 | elseif meta =~# '^\$' 212 | call add(resources, s:_new_environment(meta)) 213 | else 214 | call s:_throw(printf( 215 | \ 'Unknown value "%s" was specified', 216 | \ meta 217 | \)) 218 | endif 219 | endif 220 | unlet meta 221 | endfor 222 | let guard = copy(s:guard) 223 | let guard._resources = resources 224 | return guard 225 | endfunction 226 | function! s:guard.restore() abort 227 | for resource in self._resources 228 | call resource.restore() 229 | endfor 230 | endfunction 231 | 232 | let &cpo = s:save_cpo 233 | unlet! s:save_cpo 234 | " vim:set et ts=2 sts=2 sw=2 tw=0 fdm=marker: 235 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Async/Promise.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#Async#Promise#import() abort', printf("return map({'resolve': '', 'all': '', 'wait': '', '_vital_created': '', 'race': '', 'noop': '', 'is_promise': '', 'is_available': '', 'reject': '', 'new': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " ECMAScript like Promise library for asynchronous operations. 11 | " Spec: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 12 | " This implementation is based upon es6-promise npm package. 13 | " Repo: https://github.com/stefanpenner/es6-promise 14 | 15 | " States of promise 16 | let s:PENDING = 0 17 | let s:FULFILLED = 1 18 | let s:REJECTED = 2 19 | 20 | let s:DICT_T = type({}) 21 | 22 | let s:TIMEOUT_ERROR = 'vital: Async.Promise: Timeout' 23 | let s:DEFAULT_WAIT_INTERVAL = 30 24 | 25 | function! s:_vital_created(module) abort 26 | let a:module.TimeoutError = s:TIMEOUT_ERROR 27 | lockvar a:module.TimeoutError 28 | endfunction 29 | 30 | " @vimlint(EVL103, 1, a:resolve) 31 | " @vimlint(EVL103, 1, a:reject) 32 | function! s:noop(resolve, reject) abort 33 | endfunction 34 | " @vimlint(EVL103, 0, a:resolve) 35 | " @vimlint(EVL103, 0, a:reject) 36 | let s:NOOP = function('s:noop') 37 | 38 | " Internal APIs 39 | 40 | let s:PROMISE = { 41 | \ '_state': s:PENDING, 42 | \ '_children': [], 43 | \ '_fulfillments': [], 44 | \ '_rejections': [], 45 | \ '_result': v:null, 46 | \ } 47 | 48 | let s:id = -1 49 | function! s:_next_id() abort 50 | let s:id += 1 51 | return s:id 52 | endfunction 53 | 54 | " ... is added to use this function as a callback of timer_start() 55 | function! s:_invoke_callback(settled, promise, callback, result, ...) abort 56 | let has_callback = a:callback isnot v:null 57 | let success = 1 58 | let err = v:null 59 | if has_callback 60 | try 61 | let l:Result = a:callback(a:result) 62 | catch 63 | let err = { 64 | \ 'exception' : v:exception, 65 | \ 'throwpoint' : v:throwpoint, 66 | \ } 67 | let success = 0 68 | endtry 69 | else 70 | let l:Result = a:result 71 | endif 72 | 73 | if a:promise._state != s:PENDING 74 | " Do nothing 75 | elseif has_callback && success 76 | call s:_resolve(a:promise, Result) 77 | elseif !success 78 | call s:_reject(a:promise, err) 79 | elseif a:settled == s:FULFILLED 80 | call s:_fulfill(a:promise, Result) 81 | elseif a:settled == s:REJECTED 82 | call s:_reject(a:promise, Result) 83 | endif 84 | endfunction 85 | 86 | " ... is added to use this function as a callback of timer_start() 87 | function! s:_publish(promise, ...) abort 88 | let settled = a:promise._state 89 | if settled == s:PENDING 90 | throw 'vital: Async.Promise: Cannot publish a pending promise' 91 | endif 92 | 93 | if empty(a:promise._children) 94 | return 95 | endif 96 | 97 | for i in range(len(a:promise._children)) 98 | if settled == s:FULFILLED 99 | let l:CB = a:promise._fulfillments[i] 100 | else 101 | " When rejected 102 | let l:CB = a:promise._rejections[i] 103 | endif 104 | let child = a:promise._children[i] 105 | if child isnot v:null 106 | call s:_invoke_callback(settled, child, l:CB, a:promise._result) 107 | else 108 | call l:CB(a:promise._result) 109 | endif 110 | endfor 111 | 112 | let a:promise._children = [] 113 | let a:promise._fulfillments = [] 114 | let a:promise._rejections = [] 115 | endfunction 116 | 117 | function! s:_subscribe(parent, child, on_fulfilled, on_rejected) abort 118 | let a:parent._children += [ a:child ] 119 | let a:parent._fulfillments += [ a:on_fulfilled ] 120 | let a:parent._rejections += [ a:on_rejected ] 121 | endfunction 122 | 123 | function! s:_handle_thenable(promise, thenable) abort 124 | if a:thenable._state == s:FULFILLED 125 | call s:_fulfill(a:promise, a:thenable._result) 126 | elseif a:thenable._state == s:REJECTED 127 | call s:_reject(a:promise, a:thenable._result) 128 | else 129 | call s:_subscribe( 130 | \ a:thenable, 131 | \ v:null, 132 | \ function('s:_resolve', [a:promise]), 133 | \ function('s:_reject', [a:promise]), 134 | \ ) 135 | endif 136 | endfunction 137 | 138 | function! s:_resolve(promise, ...) abort 139 | let l:Result = a:0 > 0 ? a:1 : v:null 140 | if s:is_promise(Result) 141 | call s:_handle_thenable(a:promise, Result) 142 | else 143 | call s:_fulfill(a:promise, Result) 144 | endif 145 | endfunction 146 | 147 | function! s:_fulfill(promise, value) abort 148 | if a:promise._state != s:PENDING 149 | return 150 | endif 151 | let a:promise._result = a:value 152 | let a:promise._state = s:FULFILLED 153 | if !empty(a:promise._children) 154 | call timer_start(0, function('s:_publish', [a:promise])) 155 | endif 156 | endfunction 157 | 158 | function! s:_reject(promise, ...) abort 159 | if a:promise._state != s:PENDING 160 | return 161 | endif 162 | let a:promise._result = a:0 > 0 ? a:1 : v:null 163 | let a:promise._state = s:REJECTED 164 | call timer_start(0, function('s:_publish', [a:promise])) 165 | endfunction 166 | 167 | function! s:_notify_done(wg, index, value) abort 168 | let a:wg.results[a:index] = a:value 169 | let a:wg.remaining -= 1 170 | if a:wg.remaining == 0 171 | call a:wg.resolve(a:wg.results) 172 | endif 173 | endfunction 174 | 175 | function! s:_all(promises, resolve, reject) abort 176 | let total = len(a:promises) 177 | if total == 0 178 | call a:resolve([]) 179 | return 180 | endif 181 | 182 | let wait_group = { 183 | \ 'results': repeat([v:null], total), 184 | \ 'resolve': a:resolve, 185 | \ 'remaining': total, 186 | \ } 187 | 188 | " 'for' statement is not available here because iteration variable is captured into lambda 189 | " expression by **reference**. 190 | call map( 191 | \ copy(a:promises), 192 | \ {i, p -> p.then({v -> s:_notify_done(wait_group, i, v)}, a:reject)}, 193 | \ ) 194 | endfunction 195 | 196 | function! s:_race(promises, resolve, reject) abort 197 | for p in a:promises 198 | call p.then(a:resolve, a:reject) 199 | endfor 200 | endfunction 201 | 202 | " Public APIs 203 | 204 | function! s:new(resolver) abort 205 | let promise = deepcopy(s:PROMISE) 206 | let promise._vital_promise = s:_next_id() 207 | try 208 | if a:resolver != s:NOOP 209 | call a:resolver( 210 | \ function('s:_resolve', [promise]), 211 | \ function('s:_reject', [promise]), 212 | \ ) 213 | endif 214 | catch 215 | call s:_reject(promise, { 216 | \ 'exception' : v:exception, 217 | \ 'throwpoint' : v:throwpoint, 218 | \ }) 219 | endtry 220 | return promise 221 | endfunction 222 | 223 | function! s:all(promises) abort 224 | return s:new(function('s:_all', [a:promises])) 225 | endfunction 226 | 227 | function! s:race(promises) abort 228 | return s:new(function('s:_race', [a:promises])) 229 | endfunction 230 | 231 | function! s:resolve(...) abort 232 | let promise = s:new(s:NOOP) 233 | call s:_resolve(promise, a:0 > 0 ? a:1 : v:null) 234 | return promise 235 | endfunction 236 | 237 | function! s:reject(...) abort 238 | let promise = s:new(s:NOOP) 239 | call s:_reject(promise, a:0 > 0 ? a:1 : v:null) 240 | return promise 241 | endfunction 242 | 243 | function! s:is_available() abort 244 | return has('lambda') && has('timers') 245 | endfunction 246 | 247 | function! s:is_promise(maybe_promise) abort 248 | return type(a:maybe_promise) == s:DICT_T && has_key(a:maybe_promise, '_vital_promise') 249 | endfunction 250 | 251 | function! s:wait(promise, ...) abort 252 | if a:0 && type(a:1) is# v:t_number 253 | let t = a:1 254 | let i = s:DEFAULT_WAIT_INTERVAL . 'm' 255 | else 256 | let o = a:0 ? a:1 : {} 257 | let t = get(o, 'timeout', v:null) 258 | let i = get(o, 'interval', s:DEFAULT_WAIT_INTERVAL) . 'm' 259 | endif 260 | let s = reltime() 261 | while a:promise._state is# s:PENDING 262 | if (t isnot# v:null && reltimefloat(reltime(s)) * 1000 > t) 263 | return [v:null, s:TIMEOUT_ERROR] 264 | endif 265 | execute 'sleep' i 266 | endwhile 267 | if a:promise._state is# s:FULFILLED 268 | return [a:promise._result, v:null] 269 | else 270 | return [v:null, a:promise._result] 271 | endif 272 | endfunction 273 | 274 | function! s:_promise_then(...) dict abort 275 | let parent = self 276 | let state = parent._state 277 | let child = s:new(s:NOOP) 278 | let l:Res = a:0 > 0 ? a:1 : v:null 279 | let l:Rej = a:0 > 1 ? a:2 : v:null 280 | if state == s:FULFILLED 281 | call timer_start(0, function('s:_invoke_callback', [state, child, Res, parent._result])) 282 | elseif state == s:REJECTED 283 | call timer_start(0, function('s:_invoke_callback', [state, child, Rej, parent._result])) 284 | else 285 | call s:_subscribe(parent, child, Res, Rej) 286 | endif 287 | return child 288 | endfunction 289 | let s:PROMISE.then = function('s:_promise_then') 290 | 291 | " .catch() is just a syntax sugar of .then() 292 | function! s:_promise_catch(...) dict abort 293 | return self.then(v:null, a:0 > 0 ? a:1 : v:null) 294 | endfunction 295 | let s:PROMISE.catch = function('s:_promise_catch') 296 | 297 | function! s:_on_finally(CB, parent, Result) abort 298 | call a:CB() 299 | if a:parent._state == s:FULFILLED 300 | return a:Result 301 | else " REJECTED 302 | return s:reject(a:Result) 303 | endif 304 | endfunction 305 | function! s:_promise_finally(...) dict abort 306 | let parent = self 307 | let state = parent._state 308 | let child = s:new(s:NOOP) 309 | if a:0 == 0 310 | let l:CB = v:null 311 | else 312 | let l:CB = function('s:_on_finally', [a:1, parent]) 313 | endif 314 | if state != s:PENDING 315 | call timer_start(0, function('s:_invoke_callback', [state, child, CB, parent._result])) 316 | else 317 | call s:_subscribe(parent, child, CB, CB) 318 | endif 319 | return child 320 | endfunction 321 | let s:PROMISE.finally = function('s:_promise_finally') 322 | 323 | " vim:set et ts=2 sts=2 sw=2 tw=0: 324 | -------------------------------------------------------------------------------- /autoload/vital/amake.vim: -------------------------------------------------------------------------------- 1 | let s:plugin_name = expand(':t:r') 2 | let s:vital_base_dir = expand(':h') 3 | let s:project_root = expand(':h:h:h') 4 | let s:is_vital_vim = s:plugin_name is# 'vital' 5 | 6 | let s:loaded = {} 7 | let s:cache_sid = {} 8 | 9 | " function() wrapper 10 | if v:version > 703 || v:version == 703 && has('patch1170') 11 | function! s:_function(fstr) abort 12 | return function(a:fstr) 13 | endfunction 14 | else 15 | function! s:_SID() abort 16 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 17 | endfunction 18 | let s:_s = '' . s:_SID() . '_' 19 | function! s:_function(fstr) abort 20 | return function(substitute(a:fstr, 's:', s:_s, 'g')) 21 | endfunction 22 | endif 23 | 24 | function! vital#{s:plugin_name}#new() abort 25 | return s:new(s:plugin_name) 26 | endfunction 27 | 28 | function! vital#{s:plugin_name}#import(...) abort 29 | if !exists('s:V') 30 | let s:V = s:new(s:plugin_name) 31 | endif 32 | return call(s:V.import, a:000, s:V) 33 | endfunction 34 | 35 | let s:Vital = {} 36 | 37 | function! s:new(plugin_name) abort 38 | let base = deepcopy(s:Vital) 39 | let base._plugin_name = a:plugin_name 40 | return base 41 | endfunction 42 | 43 | function! s:vital_files() abort 44 | if !exists('s:vital_files') 45 | let s:vital_files = map( 46 | \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), 47 | \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') 48 | endif 49 | return copy(s:vital_files) 50 | endfunction 51 | let s:Vital.vital_files = s:_function('s:vital_files') 52 | 53 | function! s:import(name, ...) abort dict 54 | let target = {} 55 | let functions = [] 56 | for a in a:000 57 | if type(a) == type({}) 58 | let target = a 59 | elseif type(a) == type([]) 60 | let functions = a 61 | endif 62 | unlet a 63 | endfor 64 | let module = self._import(a:name) 65 | if empty(functions) 66 | call extend(target, module, 'keep') 67 | else 68 | for f in functions 69 | if has_key(module, f) && !has_key(target, f) 70 | let target[f] = module[f] 71 | endif 72 | endfor 73 | endif 74 | return target 75 | endfunction 76 | let s:Vital.import = s:_function('s:import') 77 | 78 | function! s:load(...) abort dict 79 | for arg in a:000 80 | let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] 81 | let target = split(join(as, ''), '\W\+') 82 | let dict = self 83 | let dict_type = type({}) 84 | while !empty(target) 85 | let ns = remove(target, 0) 86 | if !has_key(dict, ns) 87 | let dict[ns] = {} 88 | endif 89 | if type(dict[ns]) == dict_type 90 | let dict = dict[ns] 91 | else 92 | unlet dict 93 | break 94 | endif 95 | endwhile 96 | if exists('dict') 97 | call extend(dict, self._import(name)) 98 | endif 99 | unlet arg 100 | endfor 101 | return self 102 | endfunction 103 | let s:Vital.load = s:_function('s:load') 104 | 105 | function! s:unload() abort dict 106 | let s:loaded = {} 107 | let s:cache_sid = {} 108 | unlet! s:vital_files 109 | endfunction 110 | let s:Vital.unload = s:_function('s:unload') 111 | 112 | function! s:exists(name) abort dict 113 | if a:name !~# '\v^\u\w*%(\.\u\w*)*$' 114 | throw 'vital: Invalid module name: ' . a:name 115 | endif 116 | return s:_module_path(a:name) isnot# '' 117 | endfunction 118 | let s:Vital.exists = s:_function('s:exists') 119 | 120 | function! s:search(pattern) abort dict 121 | let paths = s:_extract_files(a:pattern, self.vital_files()) 122 | let modules = sort(map(paths, 's:_file2module(v:val)')) 123 | return s:_uniq(modules) 124 | endfunction 125 | let s:Vital.search = s:_function('s:search') 126 | 127 | function! s:plugin_name() abort dict 128 | return self._plugin_name 129 | endfunction 130 | let s:Vital.plugin_name = s:_function('s:plugin_name') 131 | 132 | function! s:_self_vital_files() abort 133 | let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) 134 | let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) 135 | let base = builtin . ',' . installed 136 | return split(globpath(base, '**/*.vim', 1), "\n") 137 | endfunction 138 | 139 | function! s:_global_vital_files() abort 140 | let pattern = 'autoload/vital/__*__/**/*.vim' 141 | return split(globpath(&runtimepath, pattern, 1), "\n") 142 | endfunction 143 | 144 | function! s:_extract_files(pattern, files) abort 145 | let tr = {'.': '/', '*': '[^/]*', '**': '.*'} 146 | let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') 147 | let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) 148 | return filter(a:files, 'v:val =~# regexp') 149 | endfunction 150 | 151 | function! s:_file2module(file) abort 152 | let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') 153 | let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') 154 | return join(split(tail, '[\\/]\+'), '.') 155 | endfunction 156 | 157 | " @param {string} name e.g. Data.List 158 | function! s:_import(name) abort dict 159 | if has_key(s:loaded, a:name) 160 | return copy(s:loaded[a:name]) 161 | endif 162 | let module = self._get_module(a:name) 163 | if has_key(module, '_vital_created') 164 | call module._vital_created(module) 165 | endif 166 | let export_module = filter(copy(module), 'v:key =~# "^\\a"') 167 | " Cache module before calling module.vital_loaded() to avoid cyclic 168 | " dependences but remove the cache if module._vital_loaded() fails. 169 | " let s:loaded[a:name] = export_module 170 | let s:loaded[a:name] = export_module 171 | if has_key(module, '_vital_loaded') 172 | try 173 | call module._vital_loaded(vital#{s:plugin_name}#new()) 174 | catch 175 | unlet s:loaded[a:name] 176 | throw 'vital: fail to call ._vital_loaded(): ' . v:exception 177 | endtry 178 | endif 179 | return copy(s:loaded[a:name]) 180 | endfunction 181 | let s:Vital._import = s:_function('s:_import') 182 | 183 | " s:_get_module() returns module object wihch has all script local functions. 184 | function! s:_get_module(name) abort dict 185 | let funcname = s:_import_func_name(self.plugin_name(), a:name) 186 | try 187 | return call(funcname, []) 188 | catch /^Vim\%((\a\+)\)\?:E117/ 189 | return s:_get_builtin_module(a:name) 190 | endtry 191 | endfunction 192 | 193 | function! s:_get_builtin_module(name) abort 194 | return s:sid2sfuncs(s:_module_sid(a:name)) 195 | endfunction 196 | 197 | if s:is_vital_vim 198 | " For vital.vim, we can use s:_get_builtin_module directly 199 | let s:Vital._get_module = s:_function('s:_get_builtin_module') 200 | else 201 | let s:Vital._get_module = s:_function('s:_get_module') 202 | endif 203 | 204 | function! s:_import_func_name(plugin_name, module_name) abort 205 | return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) 206 | endfunction 207 | 208 | function! s:_module_sid(name) abort 209 | let path = s:_module_path(a:name) 210 | if !filereadable(path) 211 | throw 'vital: module not found: ' . a:name 212 | endif 213 | let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) 214 | let base = join([vital_dir, ''], '[/\\]\+') 215 | let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') 216 | let sid = s:_sid(path, p) 217 | if !sid 218 | call s:_source(path) 219 | let sid = s:_sid(path, p) 220 | if !sid 221 | throw printf('vital: cannot get from path: %s', path) 222 | endif 223 | endif 224 | return sid 225 | endfunction 226 | 227 | function! s:_module_path(name) abort 228 | return get(s:_extract_files(a:name, s:vital_files()), 0, '') 229 | endfunction 230 | 231 | function! s:_module_sid_base_dir() abort 232 | return s:is_vital_vim ? &rtp : s:project_root 233 | endfunction 234 | 235 | function! s:_dot_to_sharp(name) abort 236 | return substitute(a:name, '\.', '#', 'g') 237 | endfunction 238 | 239 | function! s:_source(path) abort 240 | execute 'source' fnameescape(a:path) 241 | endfunction 242 | 243 | " @vimlint(EVL102, 1, l:_) 244 | " @vimlint(EVL102, 1, l:__) 245 | function! s:_sid(path, filter_pattern) abort 246 | let unified_path = s:_unify_path(a:path) 247 | if has_key(s:cache_sid, unified_path) 248 | return s:cache_sid[unified_path] 249 | endif 250 | for line in filter(split(s:_execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') 251 | let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') 252 | if s:_unify_path(path) is# unified_path 253 | let s:cache_sid[unified_path] = sid 254 | return s:cache_sid[unified_path] 255 | endif 256 | endfor 257 | return 0 258 | endfunction 259 | 260 | " We want to use a execute() builtin function instead of s:_execute(), 261 | " however there is a bug in execute(). 262 | " execute() returns empty string when it is called in 263 | " completion function of user defined ex command. 264 | " https://github.com/vim-jp/issues/issues/1129 265 | function! s:_execute(cmd) abort 266 | let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] 267 | set verbose=0 verbosefile= 268 | redir => res 269 | silent! execute a:cmd 270 | redir END 271 | let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] 272 | return res 273 | endfunction 274 | 275 | if filereadable(expand(':r') . '.VIM') " is case-insensitive or not 276 | let s:_unify_path_cache = {} 277 | " resolve() is slow, so we cache results. 278 | " Note: On windows, vim can't expand path names from 8.3 formats. 279 | " So if getting full path via and $HOME was set as 8.3 format, 280 | " vital load duplicated scripts. Below's :~ avoid this issue. 281 | function! s:_unify_path(path) abort 282 | if has_key(s:_unify_path_cache, a:path) 283 | return s:_unify_path_cache[a:path] 284 | endif 285 | let value = tolower(fnamemodify(resolve(fnamemodify( 286 | \ a:path, ':p')), ':~:gs?[\\/]?/?')) 287 | let s:_unify_path_cache[a:path] = value 288 | return value 289 | endfunction 290 | else 291 | function! s:_unify_path(path) abort 292 | return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) 293 | endfunction 294 | endif 295 | 296 | " copied and modified from Vim.ScriptLocal 297 | let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') 298 | function! s:sid2sfuncs(sid) abort 299 | let fs = split(s:_execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") 300 | let r = {} 301 | let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) 302 | for fname in map(fs, 'matchstr(v:val, pattern)') 303 | let r[fname] = function(s:_sfuncname(a:sid, fname)) 304 | endfor 305 | return r 306 | endfunction 307 | 308 | "" Return funcname of script local functions with SID 309 | function! s:_sfuncname(sid, funcname) abort 310 | return printf('%s_%s', a:sid, a:funcname) 311 | endfunction 312 | 313 | if exists('*uniq') 314 | function! s:_uniq(list) abort 315 | return uniq(a:list) 316 | endfunction 317 | else 318 | function! s:_uniq(list) abort 319 | let i = len(a:list) - 1 320 | while 0 < i 321 | if a:list[i] ==# a:list[i - 1] 322 | call remove(a:list, i) 323 | endif 324 | let i -= 1 325 | endwhile 326 | return a:list 327 | endfunction 328 | endif 329 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Prelude.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#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': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | if v:version > 703 || 14 | \ (v:version == 703 && has('patch465')) 15 | function! s:glob(expr) abort 16 | return glob(a:expr, 1, 1) 17 | endfunction 18 | else 19 | function! s:glob(expr) abort 20 | return split(glob(a:expr, 1), '\n') 21 | endfunction 22 | endif 23 | 24 | if v:version > 704 || 25 | \ (v:version == 704 && has('patch279')) 26 | function! s:globpath(path, expr) abort 27 | return globpath(a:path, a:expr, 1, 1) 28 | endfunction 29 | else 30 | function! s:globpath(path, expr) abort 31 | return split(globpath(a:path, a:expr, 1), '\n') 32 | endfunction 33 | endif 34 | 35 | " Wrapper functions for type(). 36 | " NOTE: __TYPE_FLOAT = -1 when -float. 37 | " this doesn't match to anything. 38 | if has('patch-7.4.2071') 39 | let [ 40 | \ s:__TYPE_NUMBER, 41 | \ s:__TYPE_STRING, 42 | \ s:__TYPE_FUNCREF, 43 | \ s:__TYPE_LIST, 44 | \ s:__TYPE_DICT, 45 | \ s:__TYPE_FLOAT] = [ 46 | \ v:t_number, 47 | \ v:t_string, 48 | \ v:t_func, 49 | \ v:t_list, 50 | \ v:t_dict, 51 | \ v:t_float] 52 | else 53 | let [ 54 | \ s:__TYPE_NUMBER, 55 | \ s:__TYPE_STRING, 56 | \ s:__TYPE_FUNCREF, 57 | \ s:__TYPE_LIST, 58 | \ s:__TYPE_DICT, 59 | \ s:__TYPE_FLOAT] = [ 60 | \ type(3), 61 | \ type(''), 62 | \ type(function('tr')), 63 | \ type([]), 64 | \ type({}), 65 | \ has('float') ? type(str2float('0')) : -1] 66 | endif 67 | 68 | " Number or Float 69 | function! s:is_numeric(Value) abort 70 | let _ = type(a:Value) 71 | return _ ==# s:__TYPE_NUMBER 72 | \ || _ ==# s:__TYPE_FLOAT 73 | endfunction 74 | 75 | " Number 76 | function! s:is_number(Value) abort 77 | return type(a:Value) ==# s:__TYPE_NUMBER 78 | endfunction 79 | 80 | " String 81 | function! s:is_string(Value) abort 82 | return type(a:Value) ==# s:__TYPE_STRING 83 | endfunction 84 | 85 | " Funcref 86 | function! s:is_funcref(Value) abort 87 | return type(a:Value) ==# s:__TYPE_FUNCREF 88 | endfunction 89 | 90 | " List 91 | function! s:is_list(Value) abort 92 | return type(a:Value) ==# s:__TYPE_LIST 93 | endfunction 94 | 95 | " Dictionary 96 | function! s:is_dict(Value) abort 97 | return type(a:Value) ==# s:__TYPE_DICT 98 | endfunction 99 | 100 | " Float 101 | function! s:is_float(Value) abort 102 | return type(a:Value) ==# s:__TYPE_FLOAT 103 | endfunction 104 | 105 | 106 | function! s:truncate_skipping(str, max, footer_width, separator) abort 107 | call s:_warn_deprecated('truncate_skipping', 'Data.String.truncate_skipping') 108 | 109 | let width = s:wcswidth(a:str) 110 | if width <= a:max 111 | let ret = a:str 112 | else 113 | let header_width = a:max - s:wcswidth(a:separator) - a:footer_width 114 | let ret = s:strwidthpart(a:str, header_width) . a:separator 115 | \ . s:strwidthpart_reverse(a:str, a:footer_width) 116 | endif 117 | 118 | return s:truncate(ret, a:max) 119 | endfunction 120 | 121 | function! s:truncate(str, width) abort 122 | " Original function is from mattn. 123 | " http://github.com/mattn/googlereader-vim/tree/master 124 | 125 | call s:_warn_deprecated('truncate', 'Data.String.truncate') 126 | 127 | if a:str =~# '^[\x00-\x7f]*$' 128 | return len(a:str) < a:width ? 129 | \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) 130 | endif 131 | 132 | let ret = a:str 133 | let width = s:wcswidth(a:str) 134 | if width > a:width 135 | let ret = s:strwidthpart(ret, a:width) 136 | let width = s:wcswidth(ret) 137 | endif 138 | 139 | if width < a:width 140 | let ret .= repeat(' ', a:width - width) 141 | endif 142 | 143 | return ret 144 | endfunction 145 | 146 | function! s:strwidthpart(str, width) abort 147 | call s:_warn_deprecated('strwidthpart', 'Data.String.strwidthpart') 148 | 149 | if a:width <= 0 150 | return '' 151 | endif 152 | let ret = a:str 153 | let width = s:wcswidth(a:str) 154 | while width > a:width 155 | let char = matchstr(ret, '.$') 156 | let ret = ret[: -1 - len(char)] 157 | let width -= s:wcswidth(char) 158 | endwhile 159 | 160 | return ret 161 | endfunction 162 | function! s:strwidthpart_reverse(str, width) abort 163 | call s:_warn_deprecated('strwidthpart_reverse', 'Data.String.strwidthpart_reverse') 164 | 165 | if a:width <= 0 166 | return '' 167 | endif 168 | let ret = a:str 169 | let width = s:wcswidth(a:str) 170 | while width > a:width 171 | let char = matchstr(ret, '^.') 172 | let ret = ret[len(char) :] 173 | let width -= s:wcswidth(char) 174 | endwhile 175 | 176 | return ret 177 | endfunction 178 | 179 | if v:version >= 703 180 | " Use builtin function. 181 | function! s:wcswidth(str) abort 182 | call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth') 183 | return strwidth(a:str) 184 | endfunction 185 | else 186 | function! s:wcswidth(str) abort 187 | call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth') 188 | 189 | if a:str =~# '^[\x00-\x7f]*$' 190 | return strlen(a:str) 191 | end 192 | 193 | let mx_first = '^\(.\)' 194 | let str = a:str 195 | let width = 0 196 | while 1 197 | let ucs = char2nr(substitute(str, mx_first, '\1', '')) 198 | if ucs == 0 199 | break 200 | endif 201 | let width += s:_wcwidth(ucs) 202 | let str = substitute(str, mx_first, '', '') 203 | endwhile 204 | return width 205 | endfunction 206 | 207 | " UTF-8 only. 208 | function! s:_wcwidth(ucs) abort 209 | let ucs = a:ucs 210 | if (ucs >= 0x1100 211 | \ && (ucs <= 0x115f 212 | \ || ucs == 0x2329 213 | \ || ucs == 0x232a 214 | \ || (ucs >= 0x2e80 && ucs <= 0xa4cf 215 | \ && ucs != 0x303f) 216 | \ || (ucs >= 0xac00 && ucs <= 0xd7a3) 217 | \ || (ucs >= 0xf900 && ucs <= 0xfaff) 218 | \ || (ucs >= 0xfe30 && ucs <= 0xfe6f) 219 | \ || (ucs >= 0xff00 && ucs <= 0xff60) 220 | \ || (ucs >= 0xffe0 && ucs <= 0xffe6) 221 | \ || (ucs >= 0x20000 && ucs <= 0x2fffd) 222 | \ || (ucs >= 0x30000 && ucs <= 0x3fffd) 223 | \ )) 224 | return 2 225 | endif 226 | return 1 227 | endfunction 228 | endif 229 | 230 | let s:is_windows = has('win32') " This means any versions of windows https://github.com/vim-jp/vital.vim/wiki/Coding-Rule#how-to-check-if-the-runtime-os-is-windows 231 | let s:is_cygwin = has('win32unix') 232 | let s:is_mac = !s:is_windows && !s:is_cygwin 233 | \ && (has('mac') || has('macunix') || has('gui_macvim') || 234 | \ (!isdirectory('/proc') && executable('sw_vers'))) 235 | let s:is_unix = has('unix') 236 | 237 | function! s:is_windows() abort 238 | return s:is_windows 239 | endfunction 240 | 241 | function! s:is_cygwin() abort 242 | return s:is_cygwin 243 | endfunction 244 | 245 | function! s:is_mac() abort 246 | return s:is_mac 247 | endfunction 248 | 249 | function! s:is_unix() abort 250 | return s:is_unix 251 | endfunction 252 | 253 | function! s:_warn_deprecated(name, alternative) abort 254 | try 255 | echohl Error 256 | echomsg 'Prelude.' . a:name . ' is deprecated! Please use ' . a:alternative . ' instead.' 257 | finally 258 | echohl None 259 | endtry 260 | endfunction 261 | 262 | function! s:smart_execute_command(action, word) abort 263 | execute a:action . ' ' . (a:word ==# '' ? '' : '`=a:word`') 264 | endfunction 265 | 266 | function! s:escape_file_searching(buffer_name) abort 267 | return escape(a:buffer_name, '*[]?{}, ') 268 | endfunction 269 | 270 | function! s:escape_pattern(str) abort 271 | call s:_warn_deprecated( 272 | \ 'escape_pattern', 273 | \ 'Data.String.escape_pattern', 274 | \) 275 | return escape(a:str, '~"\.^$[]*') 276 | endfunction 277 | 278 | function! s:getchar(...) abort 279 | let c = call('getchar', a:000) 280 | return type(c) == type(0) ? nr2char(c) : c 281 | endfunction 282 | 283 | function! s:getchar_safe(...) abort 284 | let c = s:input_helper('getchar', a:000) 285 | return type(c) == type('') ? c : nr2char(c) 286 | endfunction 287 | 288 | function! s:input_safe(...) abort 289 | return s:input_helper('input', a:000) 290 | endfunction 291 | 292 | function! s:input_helper(funcname, args) abort 293 | let success = 0 294 | if inputsave() !=# success 295 | throw 'vital: Prelude: inputsave() failed' 296 | endif 297 | try 298 | return call(a:funcname, a:args) 299 | finally 300 | if inputrestore() !=# success 301 | throw 'vital: Prelude: inputrestore() failed' 302 | endif 303 | endtry 304 | endfunction 305 | 306 | function! s:set_default(var, val) abort 307 | if !exists(a:var) || type({a:var}) != type(a:val) 308 | let {a:var} = a:val 309 | endif 310 | endfunction 311 | 312 | function! s:substitute_path_separator(path) abort 313 | return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path 314 | endfunction 315 | 316 | function! s:path2directory(path) abort 317 | return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h')) 318 | endfunction 319 | 320 | function! s:_path2project_directory_git(path) abort 321 | let parent = a:path 322 | 323 | while 1 324 | let path = parent . '/.git' 325 | if isdirectory(path) || filereadable(path) 326 | return parent 327 | endif 328 | let next = fnamemodify(parent, ':h') 329 | if next == parent 330 | return '' 331 | endif 332 | let parent = next 333 | endwhile 334 | endfunction 335 | 336 | function! s:_path2project_directory_svn(path) abort 337 | let search_directory = a:path 338 | let directory = '' 339 | 340 | let find_directory = s:escape_file_searching(search_directory) 341 | let d = finddir('.svn', find_directory . ';') 342 | if d ==# '' 343 | return '' 344 | endif 345 | 346 | let directory = fnamemodify(d, ':p:h:h') 347 | 348 | " Search parent directories. 349 | let parent_directory = s:path2directory( 350 | \ fnamemodify(directory, ':h')) 351 | 352 | if parent_directory !=# '' 353 | let d = finddir('.svn', parent_directory . ';') 354 | if d !=# '' 355 | let directory = s:_path2project_directory_svn(parent_directory) 356 | endif 357 | endif 358 | return directory 359 | endfunction 360 | 361 | function! s:_path2project_directory_others(vcs, path) abort 362 | let vcs = a:vcs 363 | let search_directory = a:path 364 | 365 | let find_directory = s:escape_file_searching(search_directory) 366 | let d = finddir(vcs, find_directory . ';') 367 | if d ==# '' 368 | return '' 369 | endif 370 | return fnamemodify(d, ':p:h:h') 371 | endfunction 372 | 373 | function! s:path2project_directory(path, ...) abort 374 | let is_allow_empty = get(a:000, 0, 0) 375 | let search_directory = s:path2directory(a:path) 376 | let directory = '' 377 | 378 | " Search VCS directory. 379 | for vcs in ['.git', '.bzr', '.hg', '.svn'] 380 | if vcs ==# '.git' 381 | let directory = s:_path2project_directory_git(search_directory) 382 | elseif vcs ==# '.svn' 383 | let directory = s:_path2project_directory_svn(search_directory) 384 | else 385 | let directory = s:_path2project_directory_others(vcs, search_directory) 386 | endif 387 | if directory !=# '' 388 | break 389 | endif 390 | endfor 391 | 392 | " Search project file. 393 | if directory ==# '' 394 | for d in ['build.xml', 'prj.el', '.project', 'pom.xml', 'package.json', 395 | \ 'Makefile', 'configure', 'Rakefile', 'NAnt.build', 396 | \ 'P4CONFIG', 'tags', 'gtags'] 397 | let d = findfile(d, s:escape_file_searching(search_directory) . ';') 398 | if d !=# '' 399 | let directory = fnamemodify(d, ':p:h') 400 | break 401 | endif 402 | endfor 403 | endif 404 | 405 | if directory ==# '' 406 | " Search /src/ directory. 407 | let base = s:substitute_path_separator(search_directory) 408 | if base =~# '/src/' 409 | let directory = base[: strridx(base, '/src/') + 3] 410 | endif 411 | endif 412 | 413 | if directory ==# '' && !is_allow_empty 414 | " Use original path. 415 | let directory = search_directory 416 | endif 417 | 418 | return s:substitute_path_separator(directory) 419 | endfunction 420 | 421 | let &cpo = s:save_cpo 422 | unlet s:save_cpo 423 | 424 | " vim:set et ts=2 sts=2 sw=2 tw=0: 425 | -------------------------------------------------------------------------------- /autoload/vital/_amake/Data/List.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_amake#Data#List#import() abort', printf("return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'filter': '', 'find_indices': '', 'any': '', 'map': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'map_accum': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'drop_while': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'shift': '', 'clear': '', 'has_common_items': '', 'product': '', 'uncons': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, \"vital#_amake#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " Utilities for list. 11 | 12 | let s:save_cpo = &cpo 13 | set cpo&vim 14 | 15 | function! s:pop(list) abort 16 | return remove(a:list, -1) 17 | endfunction 18 | 19 | function! s:push(list, val) abort 20 | call add(a:list, a:val) 21 | return a:list 22 | endfunction 23 | 24 | function! s:shift(list) abort 25 | return remove(a:list, 0) 26 | endfunction 27 | 28 | function! s:unshift(list, val) abort 29 | return insert(a:list, a:val) 30 | endfunction 31 | 32 | function! s:cons(x, xs) abort 33 | return [a:x] + a:xs 34 | endfunction 35 | 36 | function! s:uncons(xs) abort 37 | if len(a:xs) < 1 38 | throw 'vital: Data.List: uncons() requires non empty list' 39 | endif 40 | " This is pair (tuple) 41 | return [a:xs[0], a:xs[1:]] 42 | endfunction 43 | 44 | function! s:conj(xs, x) abort 45 | return a:xs + [a:x] 46 | endfunction 47 | 48 | function! s:map(xs, f) abort 49 | let l:Call = s:_get_unary_caller(a:f) 50 | let result = [] 51 | for x in a:xs 52 | call add(result, l:Call(a:f, [x])) 53 | endfor 54 | return result 55 | endfunction 56 | 57 | function! s:filter(xs, f) abort 58 | let l:Call = s:_get_unary_caller(a:f) 59 | let result = [] 60 | for x in a:xs 61 | if l:Call(a:f, [x]) 62 | call add(result, x) 63 | endif 64 | endfor 65 | return result 66 | endfunction 67 | 68 | " Removes duplicates from a list. 69 | function! s:uniq(list) abort 70 | return s:uniq_by(a:list, 'v:val') 71 | endfunction 72 | 73 | " Removes duplicates from a list. 74 | function! s:uniq_by(list, f) abort 75 | let l:Call = s:_get_unary_caller(a:f) 76 | let applied = [] 77 | let result = [] 78 | for x in a:list 79 | let y = l:Call(a:f, [x]) 80 | if !s:has(applied, y) 81 | call add(result, x) 82 | call add(applied, y) 83 | endif 84 | unlet x y 85 | endfor 86 | return result 87 | endfunction 88 | 89 | function! s:clear(list) abort 90 | if !empty(a:list) 91 | unlet! a:list[0 : len(a:list) - 1] 92 | endif 93 | return a:list 94 | endfunction 95 | 96 | " Concatenates a list of lists. 97 | " XXX: Should we verify the input? 98 | function! s:concat(list) abort 99 | let memo = [] 100 | for Value in a:list 101 | let memo += Value 102 | endfor 103 | return memo 104 | endfunction 105 | 106 | " Take each elements from lists to a new list. 107 | function! s:flatten(list, ...) abort 108 | let limit = a:0 > 0 ? a:1 : -1 109 | let memo = [] 110 | if limit == 0 111 | return a:list 112 | endif 113 | let limit -= 1 114 | for Value in a:list 115 | let memo += 116 | \ type(Value) == type([]) ? 117 | \ s:flatten(Value, limit) : 118 | \ [Value] 119 | unlet! Value 120 | endfor 121 | return memo 122 | endfunction 123 | 124 | " Sorts a list with expression to compare each two values. 125 | " a:a and a:b can be used in string expression ({f}). 126 | function! s:sort(list, f) abort 127 | if type(a:f) is type(function('function')) 128 | return sort(a:list, a:f) 129 | else 130 | " Give up job safety (atomically) 131 | let s:sort_expr = a:f 132 | return sort(a:list, 's:_compare_by_string_expr') 133 | endif 134 | endfunction 135 | 136 | " Lifts the string expression to the function. 137 | " s:sort_expr must be defined as the string expression of the binary function 138 | " before this is called. 139 | " a:a and a:b are used in s:sort_expr . 140 | " @vimlint(EVL103, 1) 141 | function! s:_compare_by_string_expr(a, b) abort 142 | return eval(s:sort_expr) 143 | endfunction 144 | " @vimlint(EVL103, 0) 145 | 146 | " Sorts a list using a set of keys generated by mapping the values in the list 147 | " through the given {unary_f}. 148 | function! s:sort_by(list, unary_f) abort 149 | let list = s:zip(a:list, s:map(copy(a:list), a:unary_f)) 150 | return map(sort(list, 's:_compare_with'), 'v:val[0]') 151 | endfunction 152 | 153 | let s:__number_pair = [type(10), type(10)] 154 | let s:__string_pair = [type(''), type('')] 155 | let s:__list_pair = [type([]), type([])] 156 | let s:__dict_pair = [type({}), type({})] 157 | let s:__function_pair = [type(function('function')), type(function('function'))] 158 | function! s:_compare_with(x, y) abort 159 | let x = a:x[1] 160 | let y = a:y[1] 161 | 162 | let type_pair = [type(x), type(y)] 163 | return type_pair ==# s:__number_pair ? s:_basic_comparator(x, y) 164 | \ : type_pair ==# s:__string_pair ? s:_basic_comparator(x, y) 165 | \ : type_pair ==# s:__list_pair ? s:_list_comparator(x, y) 166 | \ : type_pair ==# s:__dict_pair ? 1 167 | \ : type_pair ==# s:__function_pair 168 | \ ? execute('throw "vital: Data.List: sort_by() cannot compare a function and a function"') 169 | \ : execute(printf("throw 'vital: Data.List: sort_by() cannot compare %s and %s'", string(x), string(y))) 170 | endfunction 171 | 172 | " The basic comparator for Number and String 173 | function! s:_basic_comparator(x, y) abort 174 | return a:x ># a:y ? 1 175 | \ : a:x <# a:y ? -1 176 | \ : 0 177 | endfunction 178 | 179 | " The comparator of the dictionary order 180 | function! s:_list_comparator(xs, ys) abort 181 | let [xlen, ylen] = [len(a:xs), len(a:ys)] 182 | if xlen isnot ylen 183 | return s:_basic_comparator(xlen, ylen) 184 | endif 185 | 186 | for z in s:zip(a:xs, a:ys) 187 | let [x, y] = z 188 | let order = s:_basic_comparator(x, y) 189 | if order isnot 0 190 | return order 191 | endif 192 | endfor 193 | 194 | " if a:xs equals a:ys 195 | return 0 196 | endfunction 197 | 198 | " Returns a maximum value in {list} through given {function}. 199 | " Returns 0 if {list} is empty. 200 | " v:val is used in {function} if {function} is string expression 201 | function! s:max_by(list, f) abort 202 | if empty(a:list) 203 | return 0 204 | endif 205 | let list = s:map(copy(a:list), a:f) 206 | return a:list[index(list, max(list))] 207 | endfunction 208 | 209 | " Returns a minimum value in {list} through given {expr}. 210 | " Returns 0 if {list} is empty. 211 | " v:val is used in {expr}. 212 | " FIXME: -0x80000000 == 0x80000000 213 | function! s:min_by(list, f) abort 214 | if empty(a:list) 215 | return 0 216 | endif 217 | let list = s:map(copy(a:list), a:f) 218 | return a:list[index(list, min(list))] 219 | endfunction 220 | 221 | " Returns List of character sequence between [a:from, a:to] . 222 | " e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] 223 | function! s:char_range(from, to) abort 224 | return map( 225 | \ range(char2nr(a:from), char2nr(a:to)), 226 | \ 'nr2char(v:val)' 227 | \) 228 | endfunction 229 | 230 | " Returns true if a:list has a:value. 231 | " Returns false otherwise. 232 | function! s:has(list, value) abort 233 | return index(a:list, a:value) isnot -1 234 | endfunction 235 | 236 | " Returns true if a:list[a:index] exists. 237 | " Returns false otherwise. 238 | " NOTE: Returns false when a:index is negative number. 239 | function! s:has_index(list, index) abort 240 | " Return true when negative index? 241 | " let index = a:index >= 0 ? a:index : len(a:list) + a:index 242 | return 0 <= a:index && a:index < len(a:list) 243 | endfunction 244 | 245 | " Similar to Haskell's Data.List.span . 246 | function! s:span(f, xs) abort 247 | let body = s:take_while(a:f, a:xs) 248 | let tail = a:xs[len(body) :] 249 | return [body, tail] 250 | endfunction 251 | 252 | " Similar to Haskell's Data.List.break . 253 | function! s:break(f, xs) abort 254 | let l:Call = s:_get_unary_caller(a:f) 255 | let first = [] 256 | for x in a:xs 257 | if l:Call(a:f, [x]) 258 | break 259 | endif 260 | call add(first, x) 261 | endfor 262 | return [first, a:xs[len(first) :]] 263 | endfunction 264 | 265 | " Similar to Haskell's Data.List.takeWhile . 266 | function! s:take_while(f, xs) abort 267 | let l:Call = s:_get_unary_caller(a:f) 268 | let result = [] 269 | for x in a:xs 270 | if l:Call(a:f, [x]) 271 | call add(result, x) 272 | else 273 | return result 274 | endif 275 | endfor 276 | endfunction 277 | 278 | " Similar to Haskell's Data.List.dropWhile . 279 | function! s:drop_while(f, xs) abort 280 | let l:Call = s:_get_unary_caller(a:f) 281 | let i = -1 282 | for x in a:xs 283 | if !l:Call(a:f, [x]) 284 | break 285 | endif 286 | let i += 1 287 | endfor 288 | return a:xs[i + 1 :] 289 | endfunction 290 | 291 | " Similar to Haskell's Data.List.partition . 292 | function! s:partition(f, xs) abort 293 | let l:Call = s:_get_unary_caller(a:f) 294 | let satisfied = s:filter(a:xs, a:f) 295 | let dissatisfied = [] 296 | for x in a:xs 297 | if !l:Call(a:f, [x]) 298 | call add(dissatisfied, x) 299 | endif 300 | endfor 301 | return [satisfied, dissatisfied] 302 | endfunction 303 | 304 | " Similar to Haskell's Prelude.all . 305 | function! s:all(f, xs) abort 306 | return empty(filter(s:map(a:xs, a:f), '!v:val')) 307 | endfunction 308 | 309 | " Similar to Haskell's Prelude.any . 310 | function! s:any(f, xs) abort 311 | return !empty(filter(s:map(a:xs, a:f), 'v:val')) 312 | endfunction 313 | 314 | " Similar to Haskell's Prelude.and . 315 | function! s:and(xs) abort 316 | return s:all('v:val', a:xs) 317 | endfunction 318 | 319 | " Similar to Haskell's Prelude.or . 320 | function! s:or(xs) abort 321 | return s:any('v:val', a:xs) 322 | endfunction 323 | 324 | function! s:map_accum(binary_f, xs, init) abort 325 | let l:Call = s:_get_binary_caller_(a:binary_f) 326 | let results = [] 327 | let acc = a:init 328 | for x in a:xs 329 | let [result, acc] = l:Call(a:binary_f, [x, acc]) 330 | call add(results, result) 331 | endfor 332 | return results 333 | endfunction 334 | 335 | " Similar to Haskell's Prelude.foldl . 336 | function! s:foldl(f, init, xs) abort 337 | "NOTE: The 'Call' should be named with l: for the conflict problem 338 | let l:Call = s:_get_binary_caller(a:f) 339 | let memo = a:init 340 | for x in a:xs 341 | let memo_new = l:Call(a:f, [memo, x]) 342 | unlet memo 343 | let memo = memo_new 344 | endfor 345 | return memo 346 | endfunction 347 | 348 | " Similar to Haskell's Prelude.foldl1 . 349 | function! s:foldl1(f, xs) abort 350 | if len(a:xs) == 0 351 | throw 'vital: Data.List: foldl1' 352 | endif 353 | return s:foldl(a:f, a:xs[0], a:xs[1:]) 354 | endfunction 355 | 356 | " Similar to Haskell's Prelude.foldr . 357 | function! s:foldr(f, init, xs) abort 358 | "NOTE: The 'Call' should be named with l: for the conflict problem 359 | let l:Call = s:_get_binary_caller(a:f) 360 | return s:_foldr_internal(l:Call, a:f, a:init, a:xs) 361 | endfunction 362 | 363 | " Avoids caller's overhead 364 | function! s:_foldr_internal(call, f, state, xs) abort 365 | if empty(a:xs) 366 | return a:state 367 | endif 368 | 369 | let [y, ys] = s:uncons(a:xs) 370 | return a:call(a:f, [y, s:_foldr_internal(a:call, a:f, a:state, ys)]) 371 | endfunction 372 | 373 | " Similar to Haskell's Prelude.fold11 . 374 | function! s:foldr1(f, xs) abort 375 | if len(a:xs) == 0 376 | throw 'vital: Data.List: foldr1' 377 | endif 378 | return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) 379 | endfunction 380 | 381 | " Similar to python's zip() . 382 | function! s:zip(...) abort 383 | return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") 384 | endfunction 385 | 386 | " Similar to zip(), but goes until the longer one. 387 | function! s:zip_fill(xs, ys, filler) abort 388 | if empty(a:xs) && empty(a:ys) 389 | return [] 390 | elseif empty(a:ys) 391 | return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) 392 | elseif empty(a:xs) 393 | return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) 394 | else 395 | return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) 396 | endif 397 | endfunction 398 | 399 | " Inspired by Ruby's with_index method. 400 | function! s:with_index(list, ...) abort 401 | let base = a:0 > 0 ? a:1 : 0 402 | return map(copy(a:list), '[v:val, v:key + base]') 403 | endfunction 404 | 405 | " Similar to Ruby's detect or Haskell's find. 406 | function! s:find(list, default, f) abort 407 | let l:Call = s:_get_unary_caller(a:f) 408 | for x in a:list 409 | if l:Call(a:f, [x]) 410 | return x 411 | endif 412 | endfor 413 | return a:default 414 | endfunction 415 | 416 | " Returns the index of the first element which satisfies the given expr. 417 | function! s:find_index(xs, f, ...) abort 418 | let len_xs = len(a:xs) 419 | let default = get(a:000, 1, -1) 420 | 421 | let start = get(a:000, 0, 0) 422 | " Emulate list[-n] 423 | if start < 0 424 | let start += len_xs 425 | endif 426 | 427 | if len_xs <= start 428 | return default 429 | endif 430 | 431 | let l:Call = s:_get_unary_caller(a:f) 432 | for i in range(start, len_xs - 1) 433 | let x = a:xs[i] 434 | if l:Call(a:f, [x]) 435 | return i 436 | endif 437 | endfor 438 | return default 439 | endfunction 440 | 441 | " Returns the index of the last element which satisfies the given expr. 442 | function! s:find_last_index(xs, f, ...) abort 443 | let len_xs = len(a:xs) 444 | let default = get(a:000, 1, -1) 445 | 446 | let start = get(a:000, 0, len_xs - 1) 447 | if start < 0 448 | let start += len_xs 449 | endif 450 | 451 | if len_xs <= start 452 | return default 453 | endif 454 | 455 | let l:Call = s:_get_unary_caller(a:f) 456 | for i in range(start, 0, -1) 457 | let x = a:xs[i] 458 | if l:Call(a:f, [x]) 459 | return i 460 | endif 461 | endfor 462 | return default 463 | endfunction 464 | 465 | " Similar to find_index but returns the list of indices satisfying the given expr. 466 | function! s:find_indices(xs, f, ...) abort 467 | let len_xs = len(a:xs) 468 | 469 | let start = get(a:000, 0, 0) 470 | " Emulate list[-n] 471 | if start < 0 472 | let start += len_xs 473 | endif 474 | 475 | if len_xs <= start 476 | return [] 477 | endif 478 | 479 | let l:Call = s:_get_unary_caller(a:f) 480 | let result = [] 481 | for i in range(start, len_xs - 1) 482 | let x = a:xs[i] 483 | if l:Call(a:f, [x]) 484 | call add(result, i) 485 | endif 486 | endfor 487 | return result 488 | endfunction 489 | 490 | " Return non-zero if a:list1 and a:list2 have any common item(s). 491 | " Return zero otherwise. 492 | function! s:has_common_items(list1, list2) abort 493 | return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) 494 | endfunction 495 | 496 | function! s:intersect(list1, list2) abort 497 | let items = [] 498 | " for funcref 499 | for X in a:list1 500 | if index(a:list2, X) != -1 && index(items, X) == -1 501 | let items += [X] 502 | endif 503 | endfor 504 | return items 505 | endfunction 506 | 507 | " Similar to Ruby's group_by. 508 | function! s:group_by(xs, f) abort 509 | let result = {} 510 | let l:Call = s:_get_unary_caller(a:f) 511 | 512 | for l:X in a:xs 513 | let a_key = l:Call(a:f, [l:X]) 514 | let key = type(a_key) isnot type('') ? string(a_key) : a_key 515 | unlet a_key 516 | 517 | if has_key(result, key) 518 | call add(result[key], l:X) 519 | else 520 | let result[key] = [l:X] 521 | endif 522 | endfor 523 | 524 | return result 525 | endfunction 526 | 527 | function! s:binary_search(list, value, ...) abort 528 | let Predicate = a:0 >= 1 ? a:1 : 's:_basic_comparator' 529 | let dic = a:0 >= 2 ? a:2 : {} 530 | let start = 0 531 | let end = len(a:list) - 1 532 | 533 | while 1 534 | if start > end 535 | return -1 536 | endif 537 | 538 | let middle = (start + end) / 2 539 | 540 | let compared = call(Predicate, [a:value, a:list[middle]], dic) 541 | 542 | if compared < 0 543 | let end = middle - 1 544 | elseif compared > 0 545 | let start = middle + 1 546 | else 547 | return middle 548 | endif 549 | endwhile 550 | endfunction 551 | 552 | function! s:product(lists) abort 553 | let result = [[]] 554 | for pool in a:lists 555 | let tmp = [] 556 | for x in result 557 | let tmp += map(copy(pool), 'x + [v:val]') 558 | endfor 559 | let result = tmp 560 | endfor 561 | return result 562 | endfunction 563 | 564 | function! s:permutations(list, ...) abort 565 | if a:0 > 1 566 | throw 'vital: Data.List: too many arguments' 567 | endif 568 | let r = a:0 == 1 ? a:1 : len(a:list) 569 | if r > len(a:list) 570 | return [] 571 | elseif r < 0 572 | throw 'vital: Data.List: {r} must be non-negative integer' 573 | endif 574 | let n = len(a:list) 575 | let result = [] 576 | for indices in s:product(map(range(r), 'range(n)')) 577 | if len(s:uniq(indices)) == r 578 | call add(result, map(indices, 'a:list[v:val]')) 579 | endif 580 | endfor 581 | return result 582 | endfunction 583 | 584 | function! s:combinations(list, r) abort 585 | if a:r > len(a:list) 586 | return [] 587 | elseif a:r < 0 588 | throw 'vital: Data.List: {r} must be non-negative integer' 589 | endif 590 | let n = len(a:list) 591 | let result = [] 592 | for indices in s:permutations(range(n), a:r) 593 | if s:sort(copy(indices), 'a:a - a:b') == indices 594 | call add(result, map(indices, 'a:list[v:val]')) 595 | endif 596 | endfor 597 | return result 598 | endfunction 599 | 600 | 601 | " Takes the unary function of the funcref or the string expression. 602 | " Returns the caller function that is like call() . 603 | function! s:_get_unary_caller(f) abort 604 | return type(a:f) is type(function('function')) 605 | \ ? function('call') 606 | \ : function('s:_call_string_expr') 607 | endfunction 608 | 609 | " Takes the binary function of the funcref or the string expression. 610 | " if the binary function is the string expression, v:val and v:memo can be used in it. 611 | " Returns the caller function that is like call(), but it takes a tuple as an argument. 612 | function! s:_get_binary_caller(binary_f) abort 613 | return type(a:binary_f) is type(function('function')) 614 | \ ? function('call') 615 | \ : function('s:_call_binary_string_expr') 616 | endfunction 617 | 618 | " Returns the result of that apply the two element list to the binary string expression. 619 | " The binary expression has 'v:memo' and 'v:val'. 620 | function! s:_call_binary_string_expr(expr, pair) abort 621 | let expr = substitute(a:expr, 'v:memo', string(a:pair[0]), 'g') 622 | let expr = substitute(expr, 'v:val', string(a:pair[1]), 'g') 623 | return eval(expr) 624 | endfunction 625 | 626 | " This is similar to s:_get_binary_caller(), 627 | " but the behavior is different if a:binary_f is the string expression. 628 | " This uses s:_call_binary_string_expr_val_memo() . 629 | function! s:_get_binary_caller_(binary_f) abort 630 | return type(a:binary_f) is type(function('function')) 631 | \ ? function('call') 632 | \ : function('s:_call_binary_string_expr_val_memo') 633 | endfunction 634 | 635 | " This is similar to s:_call_binary_string_expr(), 636 | " but a:pair[0] is regarted as v:val, and a:pair[1] is regarted as v:memo. 637 | function! s:_call_binary_string_expr_val_memo(expr, pair) abort 638 | let x = substitute(a:expr, 'v:memo', string(a:pair[1]), 'g') 639 | let y = substitute(x, 'v:val', string(a:pair[0]), 'g') 640 | return eval(y) 641 | endfunction 642 | 643 | " Applies the string expression to the head element of a:args. 644 | " Returns the result. 645 | function! s:_call_string_expr(expr, args) abort 646 | return map([a:args[0]], a:expr)[0] 647 | endfunction 648 | 649 | let &cpo = s:save_cpo 650 | unlet s:save_cpo 651 | 652 | " vim:set et ts=2 sts=2 sw=2 tw=0: 653 | --------------------------------------------------------------------------------