├── .travis.yml ├── LICENSE ├── README.md ├── autoload ├── reading_vimrc.vim ├── reading_vimrc │ ├── buffer.vim │ └── url.vim └── vital │ ├── _reading_vimrc.vim │ ├── _reading_vimrc │ ├── Data │ │ ├── List.vim │ │ └── String.vim │ ├── Prelude.vim │ ├── Process.vim │ └── Web │ │ ├── HTTP.vim │ │ ├── HTTP_python2.py │ │ ├── HTTP_python3.py │ │ └── JSON.vim │ ├── reading_vimrc.vim │ └── reading_vimrc.vital ├── doc └── reading_vimrc.txt ├── plugin └── reading_vimrc.vim └── test └── reading_vimrc ├── buffer.vim └── url.vim /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | sudo: false 4 | 5 | install: 6 | - git clone --depth=1 https://github.com/thinca/vim-themis /tmp/vim-themis 7 | - (if ! test -d $HOME/vim-$VIM_VERSION/bin; then 8 | git clone https://github.com/vim/vim $HOME/vim && 9 | cd $HOME/vim && 10 | git checkout v$VIM_VERSION && 11 | ./configure --prefix=$HOME/vim-$VIM_VERSION && 12 | make && 13 | make install; 14 | fi) 15 | 16 | cache: 17 | directories: 18 | - $HOME/vim-$VIM_VERSION 19 | 20 | env: 21 | - VIM_VERSION=8.0.0000 22 | - VIM_VERSION=8.0.1427 23 | 24 | script: 25 | - export PATH=$HOME/vim-$VIM_VERSION/bin:$PATH 26 | - vim --version 27 | - /tmp/vim-themis/bin/themis --reporter spec test/** 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 y0za 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, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-reading-vimrc 2 | 3 | [![Build Status](https://travis-ci.org/y0za/vim-reading-vimrc.svg?branch=master)](https://travis-ci.org/y0za/vim-reading-vimrc) 4 | 5 | file loading tool for [vimrc読書会](http://vim-jp.org/reading-vimrc/) 6 | 7 | ## Usage 8 | 9 | ### Commands 10 | ```vim 11 | " show next reading-vimrc info 12 | :ReadingVimrcNext 13 | 14 | " create next reading-vimrc buffers and show those names 15 | :ReadingVimrcList 16 | 17 | " load next reading-vimrc as buffers 18 | :ReadingVimrcLoad 19 | ``` 20 | 21 | ### Config 22 | ``` 23 | " register label to clipboard 24 | vmap (reading_vimrc-update_clipboard) 25 | ``` 26 | 27 | ## License 28 | MIT License 29 | -------------------------------------------------------------------------------- /autoload/reading_vimrc.vim: -------------------------------------------------------------------------------- 1 | " file loading tool for reading-vimrc 2 | " Version: 0.1.0 3 | " Author: y0za 4 | " License: MIT License 5 | 6 | let s:save_cpo = &cpo 7 | set cpo&vim 8 | 9 | let s:V = vital#reading_vimrc#new() 10 | let s:HTTP = s:V.import('Web.HTTP') 11 | let s:JSON = s:V.import('Web.JSON') 12 | let s:NEXT_JSON_URL = 'http://vim-jp.org/reading-vimrc/json/next.json' 13 | 14 | " fetch next.json from reading-vimrc website 15 | function! reading_vimrc#fetch_next_json() 16 | if exists('s:next_json') 17 | return s:next_json 18 | endif 19 | 20 | let response = s:HTTP.get(s:NEXT_JSON_URL) 21 | let s:next_json = s:JSON.decode(response.content) 22 | 23 | return s:next_json 24 | endfunction 25 | 26 | " create buffers of vimrcs 27 | function! s:load_vimrcs(vimrcs, nth) 28 | for vimrc in a:vimrcs 29 | let parsed_url = reading_vimrc#url#parse_github_url(vimrc['url']) 30 | let parsed_url.nth = a:nth 31 | let buffer_name = reading_vimrc#buffer#name(parsed_url) 32 | execute 'badd ' . buffer_name 33 | endfor 34 | endfunction 35 | 36 | " load reading-vimrc files 37 | function! reading_vimrc#load() 38 | let vimrcs = reading_vimrc#fetch_next_json() 39 | call s:load_vimrcs(vimrcs[0]['vimrcs'], 'next') 40 | endfunction 41 | 42 | " show reading-vimrc buffer list 43 | function! reading_vimrc#list() 44 | call reading_vimrc#load() 45 | for info in reading_vimrc#buffer#info_list() 46 | echo printf('%3d %s', info.bufnr, info.name) 47 | endfor 48 | endfunction 49 | 50 | " open readingvimrc://next 51 | function! reading_vimrc#next() 52 | let bufname = reading_vimrc#buffer#name({'nth': 'next'}) 53 | execute 'new' fnameescape(bufname) 54 | endfunction 55 | 56 | " update clipboard for reading-vimrc bot in gitter. 57 | function! reading_vimrc#update_clipboard() range 58 | let splited_name = split(expand('%'), '/') 59 | let filename = splited_name[len(splited_name) - 1] 60 | if a:firstline == a:lastline 61 | let label = printf('%s#L%s', filename, a:firstline) 62 | else 63 | let label = printf('%s#L%s-%s', filename, a:firstline, a:lastline) 64 | endif 65 | let @+ = label 66 | echo 'Updated clipboard : ' . label 67 | endfunction 68 | 69 | let &cpo = s:save_cpo 70 | unlet s:save_cpo 71 | -------------------------------------------------------------------------------- /autoload/reading_vimrc/buffer.vim: -------------------------------------------------------------------------------- 1 | let s:V = vital#reading_vimrc#new() 2 | let s:HTTP = s:V.import('Web.HTTP') 3 | 4 | " parse buffer name to file info dictionary 5 | function! reading_vimrc#buffer#parse_name(name) abort 6 | let paths = split(matchstr(a:name, 'readingvimrc://\zs.*'), '/') 7 | let full_keys = ['nth', 'user', 'repo', 'branch', 'path'] 8 | let keys_num = len(full_keys) 9 | let keys = full_keys[0 : min([keys_num, len(paths)]) - 1] 10 | if len(keys) == keys_num 11 | let path_part = join(paths[keys_num - 1 : -1], '/') 12 | let paths = paths[0 : keys_num - 2] + [path_part] 13 | endif 14 | let info = {} 15 | for i in range(len(keys)) 16 | let info[keys[i]] = paths[i] 17 | endfor 18 | return info 19 | endfunction 20 | 21 | " build buffer name from file info dictionary 22 | function! reading_vimrc#buffer#name(info) abort 23 | let full_keys = ['nth', 'user', 'repo', 'branch', 'path'] 24 | let paths = map(full_keys, 'get(a:info, v:val, "")') 25 | return 'readingvimrc://' . join(filter(paths, 'v:val !=# ""'), '/') 26 | endfunction 27 | 28 | " return vimrc buffer info list 29 | function! reading_vimrc#buffer#info_list() abort 30 | let info_list = [] 31 | for info in getbufinfo() 32 | if info.name =~# '^readingvimrc://' 33 | call add(info_list, info) 34 | endif 35 | endfor 36 | return info_list 37 | endfunction 38 | 39 | " load vimrc buffer content from github 40 | function! reading_vimrc#buffer#load_content(path) abort 41 | if line('$') > 1 || getline(1) != '' 42 | return 43 | endif 44 | 45 | let parsed_name = reading_vimrc#buffer#parse_name(a:path) 46 | if has_key(parsed_name, 'path') 47 | let raw_url = reading_vimrc#url#raw_github_url(parsed_name) 48 | let response = s:HTTP.get(raw_url) 49 | setlocal buftype=nofile bufhidden=hide noswapfile 50 | setlocal nomodeline number norelativenumber 51 | call s:set_content(response.content) 52 | filetype detect 53 | else 54 | call s:show_info_page(parsed_name.nth) 55 | endif 56 | endfunction 57 | 58 | function! s:show_info_page(nth) abort 59 | " TODO see nth 60 | let info = deepcopy(reading_vimrc#fetch_next_json()[0]) 61 | let info.nth = a:nth 62 | let b:reading_vimrc_info = info 63 | 64 | let id = repeat(' ', 3 - len(info.id)) . info.id 65 | let part = info.part ==# '' ? '' : ' (' . info.part . ')' 66 | let header = [ 67 | \ '第 ' . id . '回 ' . info.date, 68 | \ info.author.name . ' さん' . part, 69 | \ '-------------------------', 70 | \ ] 71 | let content = header + map(copy(info.vimrcs), 'v:val.name') 72 | 73 | setlocal buftype=nofile bufhidden=hide noswapfile 74 | setlocal nomodeline nonumber norelativenumber 75 | call s:set_content(content) 76 | 77 | nnoremap (reading-vimrc-open-file) 78 | \ :call open_vimrc(line('.') - 4) 79 | nmap (reading-vimrc-open-file) 80 | endfunction 81 | 82 | function! s:open_vimrc(n) abort 83 | let vimrcs = b:reading_vimrc_info.vimrcs 84 | if a:n < 0 || len(vimrcs) <= a:n 85 | return 86 | endif 87 | let vimrc = vimrcs[a:n] 88 | let info = reading_vimrc#url#parse_github_url(vimrc.url) 89 | let info.nth = b:reading_vimrc_info.nth 90 | let bufname = reading_vimrc#buffer#name(info) 91 | new `=bufname` 92 | endfunction 93 | 94 | function! s:set_content(content) abort 95 | setlocal noreadonly modifiable 96 | silent put =a:content 97 | silent 1 delete _ 98 | setlocal readonly nomodifiable 99 | endfunction 100 | -------------------------------------------------------------------------------- /autoload/reading_vimrc/url.vim: -------------------------------------------------------------------------------- 1 | 2 | " parse github blob url to file info dictionary 3 | function! reading_vimrc#url#parse_github_url(url) abort 4 | let pattern = 'https://github\.com/\([^/]\+\)/\([^/]\+\)/\%(blob\|tree\)/\([^/]\+\)/\(.\+\)$' 5 | let results = matchlist(a:url, pattern) 6 | 7 | return { 8 | \ 'user': results[1], 9 | \ 'repo': results[2], 10 | \ 'branch': results[3], 11 | \ 'path': results[4] 12 | \ } 13 | endfunction 14 | 15 | " build raw github url from file info dictionary 16 | function! reading_vimrc#url#raw_github_url(info) abort 17 | return printf('https://raw.githubusercontent.com/%s/%s/%s/%s', 18 | \ a:info.user, 19 | \ a:info.repo, 20 | \ a:info.branch, 21 | \ a:info.path) 22 | endfunction 23 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc.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/vital/_reading_vimrc/Data/List.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#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': '', 'count': '', 'find_last_index': '', 'find': '', 'partition': '', 'map_accum': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'new': '', '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#_reading_vimrc#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:new(size, f) abort 16 | return map(range(a:size), a:f) 17 | endfunction 18 | 19 | function! s:pop(list) abort 20 | return remove(a:list, -1) 21 | endfunction 22 | 23 | function! s:push(list, val) abort 24 | call add(a:list, a:val) 25 | return a:list 26 | endfunction 27 | 28 | function! s:shift(list) abort 29 | return remove(a:list, 0) 30 | endfunction 31 | 32 | function! s:unshift(list, val) abort 33 | return insert(a:list, a:val) 34 | endfunction 35 | 36 | function! s:cons(x, xs) abort 37 | return [a:x] + a:xs 38 | endfunction 39 | 40 | function! s:uncons(xs) abort 41 | if len(a:xs) < 1 42 | throw 'vital: Data.List: uncons() requires non empty list' 43 | endif 44 | " This is pair (tuple) 45 | return [a:xs[0], a:xs[1:]] 46 | endfunction 47 | 48 | function! s:conj(xs, x) abort 49 | return a:xs + [a:x] 50 | endfunction 51 | 52 | function! s:map(xs, f) abort 53 | let l:Call = s:_get_unary_caller(a:f) 54 | let result = [] 55 | for x in a:xs 56 | call add(result, l:Call(a:f, [x])) 57 | endfor 58 | return result 59 | endfunction 60 | 61 | function! s:filter(xs, f) abort 62 | let l:Call = s:_get_unary_caller(a:f) 63 | let result = [] 64 | for x in a:xs 65 | if l:Call(a:f, [x]) 66 | call add(result, x) 67 | endif 68 | endfor 69 | return result 70 | endfunction 71 | 72 | " Removes duplicates from a list. 73 | function! s:uniq(list) abort 74 | return s:uniq_by(a:list, 'v:val') 75 | endfunction 76 | 77 | " Removes duplicates from a list. 78 | function! s:uniq_by(list, f) abort 79 | let l:Call = s:_get_unary_caller(a:f) 80 | let applied = [] 81 | let result = [] 82 | for x in a:list 83 | let y = l:Call(a:f, [x]) 84 | if !s:has(applied, y) 85 | call add(result, x) 86 | call add(applied, y) 87 | endif 88 | unlet x y 89 | endfor 90 | return result 91 | endfunction 92 | 93 | function! s:clear(list) abort 94 | if !empty(a:list) 95 | unlet! a:list[0 : len(a:list) - 1] 96 | endif 97 | return a:list 98 | endfunction 99 | 100 | " Concatenates a list of lists. 101 | " XXX: Should we verify the input? 102 | function! s:concat(list) abort 103 | let memo = [] 104 | for Value in a:list 105 | let memo += Value 106 | endfor 107 | return memo 108 | endfunction 109 | 110 | " Take all elements from lists to a new list. 111 | function! s:flatten(list, ...) abort 112 | let limit = a:0 > 0 ? a:1 : -1 113 | let memo = [] 114 | if limit == 0 115 | return a:list 116 | endif 117 | let limit -= 1 118 | for Value in a:list 119 | let memo += 120 | \ type(Value) == type([]) ? 121 | \ s:flatten(Value, limit) : 122 | \ [Value] 123 | unlet! Value 124 | endfor 125 | return memo 126 | endfunction 127 | 128 | " Sorts a list with expression to compare each two values. 129 | " a:a and a:b can be used in string expression ({f}). 130 | function! s:sort(list, f) abort 131 | if type(a:f) is type(function('function')) 132 | return sort(a:list, a:f) 133 | else 134 | " Give up job safety (atomically) 135 | let s:sort_expr = a:f 136 | return sort(a:list, 's:_compare_by_string_expr') 137 | endif 138 | endfunction 139 | 140 | " Lifts the string expression to the function. 141 | " s:sort_expr must be defined as the string expression of the binary function 142 | " before this is called. 143 | " a:a and a:b are used in s:sort_expr . 144 | " @vimlint(EVL103, 1) 145 | function! s:_compare_by_string_expr(a, b) abort 146 | return eval(s:sort_expr) 147 | endfunction 148 | " @vimlint(EVL103, 0) 149 | 150 | " Sorts a list using a set of keys generated by mapping the values in the list 151 | " through the given {unary_f}. 152 | function! s:sort_by(list, unary_f) abort 153 | let list = s:zip(a:list, s:map(copy(a:list), a:unary_f)) 154 | return map(sort(list, 's:_compare_with'), 'v:val[0]') 155 | endfunction 156 | 157 | let s:__number_pair = [type(10), type(10)] 158 | let s:__string_pair = [type(''), type('')] 159 | let s:__list_pair = [type([]), type([])] 160 | let s:__dict_pair = [type({}), type({})] 161 | let s:__function_pair = [type(function('function')), type(function('function'))] 162 | function! s:_compare_with(x, y) abort 163 | let x = a:x[1] 164 | let y = a:y[1] 165 | 166 | let type_pair = [type(x), type(y)] 167 | return type_pair ==# s:__number_pair ? s:_basic_comparator(x, y) 168 | \ : type_pair ==# s:__string_pair ? s:_basic_comparator(x, y) 169 | \ : type_pair ==# s:__list_pair ? s:_list_comparator(x, y) 170 | \ : type_pair ==# s:__dict_pair ? 1 171 | \ : type_pair ==# s:__function_pair 172 | \ ? execute('throw "vital: Data.List: sort_by() cannot compare a function and a function"') 173 | \ : execute(printf("throw 'vital: Data.List: sort_by() cannot compare %s and %s'", string(x), string(y))) 174 | endfunction 175 | 176 | " The basic comparator for Number and String 177 | function! s:_basic_comparator(x, y) abort 178 | return a:x ># a:y ? 1 179 | \ : a:x <# a:y ? -1 180 | \ : 0 181 | endfunction 182 | 183 | " The comparator of the dictionary order 184 | function! s:_list_comparator(xs, ys) abort 185 | let [xlen, ylen] = [len(a:xs), len(a:ys)] 186 | if xlen isnot ylen 187 | return s:_basic_comparator(xlen, ylen) 188 | endif 189 | 190 | for z in s:zip(a:xs, a:ys) 191 | let [x, y] = z 192 | let order = s:_basic_comparator(x, y) 193 | if order isnot 0 194 | return order 195 | endif 196 | endfor 197 | 198 | " if a:xs equals a:ys 199 | return 0 200 | endfunction 201 | 202 | " Returns a maximum value in {list} through given {function}. 203 | " Returns 0 if {list} is empty. 204 | " v:val is used in {function} if {function} is string expression 205 | function! s:max_by(list, f) abort 206 | if empty(a:list) 207 | return 0 208 | endif 209 | let list = s:map(copy(a:list), a:f) 210 | return a:list[index(list, max(list))] 211 | endfunction 212 | 213 | " Returns a minimum value in {list} through given {expr}. 214 | " Returns 0 if {list} is empty. 215 | " v:val is used in {expr}. 216 | " FIXME: -0x80000000 == 0x80000000 217 | function! s:min_by(list, f) abort 218 | if empty(a:list) 219 | return 0 220 | endif 221 | let list = s:map(copy(a:list), a:f) 222 | return a:list[index(list, min(list))] 223 | endfunction 224 | 225 | " Returns List of character sequence between [a:from, a:to] . 226 | " e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] 227 | function! s:char_range(from, to) abort 228 | return map( 229 | \ range(char2nr(a:from), char2nr(a:to)), 230 | \ 'nr2char(v:val)' 231 | \) 232 | endfunction 233 | 234 | " Returns true if a:list has a:value. 235 | " Returns false otherwise. 236 | function! s:has(list, value) abort 237 | return index(a:list, a:value) isnot -1 238 | endfunction 239 | 240 | " Returns true if a:list[a:index] exists. 241 | " Returns false otherwise. 242 | " NOTE: Returns false when a:index is negative number. 243 | function! s:has_index(list, index) abort 244 | " Return true when negative index? 245 | " let index = a:index >= 0 ? a:index : len(a:list) + a:index 246 | return 0 <= a:index && a:index < len(a:list) 247 | endfunction 248 | 249 | " Similar to Haskell's Data.List.span . 250 | function! s:span(f, xs) abort 251 | let body = s:take_while(a:f, a:xs) 252 | let tail = a:xs[len(body) :] 253 | return [body, tail] 254 | endfunction 255 | 256 | " Similar to Haskell's Data.List.break . 257 | function! s:break(f, xs) abort 258 | let l:Call = s:_get_unary_caller(a:f) 259 | let first = [] 260 | for x in a:xs 261 | if l:Call(a:f, [x]) 262 | break 263 | endif 264 | call add(first, x) 265 | endfor 266 | return [first, a:xs[len(first) :]] 267 | endfunction 268 | 269 | " Similar to Haskell's Data.List.takeWhile . 270 | function! s:take_while(f, xs) abort 271 | let l:Call = s:_get_unary_caller(a:f) 272 | let result = [] 273 | for x in a:xs 274 | if l:Call(a:f, [x]) 275 | call add(result, x) 276 | else 277 | return result 278 | endif 279 | endfor 280 | endfunction 281 | 282 | " Similar to Haskell's Data.List.dropWhile . 283 | function! s:drop_while(f, xs) abort 284 | let l:Call = s:_get_unary_caller(a:f) 285 | let i = -1 286 | for x in a:xs 287 | if !l:Call(a:f, [x]) 288 | break 289 | endif 290 | let i += 1 291 | endfor 292 | return a:xs[i + 1 :] 293 | endfunction 294 | 295 | " Similar to Haskell's Data.List.partition . 296 | function! s:partition(f, xs) abort 297 | let l:Call = s:_get_unary_caller(a:f) 298 | let satisfied = s:filter(a:xs, a:f) 299 | let dissatisfied = [] 300 | for x in a:xs 301 | if !l:Call(a:f, [x]) 302 | call add(dissatisfied, x) 303 | endif 304 | endfor 305 | return [satisfied, dissatisfied] 306 | endfunction 307 | 308 | " Similar to Haskell's Prelude.all . 309 | function! s:all(f, xs) abort 310 | return empty(filter(s:map(a:xs, a:f), '!v:val')) 311 | endfunction 312 | 313 | " Similar to Haskell's Prelude.any . 314 | function! s:any(f, xs) abort 315 | return !empty(filter(s:map(a:xs, a:f), 'v:val')) 316 | endfunction 317 | 318 | " Similar to Haskell's Prelude.and . 319 | function! s:and(xs) abort 320 | return s:all('v:val', a:xs) 321 | endfunction 322 | 323 | " Similar to Haskell's Prelude.or . 324 | function! s:or(xs) abort 325 | return s:any('v:val', a:xs) 326 | endfunction 327 | 328 | function! s:map_accum(binary_f, xs, init) abort 329 | let l:Call = s:_get_binary_caller_(a:binary_f) 330 | let results = [] 331 | let acc = a:init 332 | for x in a:xs 333 | let [result, acc] = l:Call(a:binary_f, [x, acc]) 334 | call add(results, result) 335 | endfor 336 | return results 337 | endfunction 338 | 339 | " Similar to Haskell's Prelude.foldl . 340 | function! s:foldl(f, init, xs) abort 341 | "NOTE: The 'Call' should be named with l: for the conflict problem 342 | let l:Call = s:_get_binary_caller(a:f) 343 | let memo = a:init 344 | for x in a:xs 345 | let memo_new = l:Call(a:f, [memo, x]) 346 | unlet memo 347 | let memo = memo_new 348 | endfor 349 | return memo 350 | endfunction 351 | 352 | " Similar to Haskell's Prelude.foldl1 . 353 | function! s:foldl1(f, xs) abort 354 | if len(a:xs) == 0 355 | throw 'vital: Data.List: foldl1' 356 | endif 357 | return s:foldl(a:f, a:xs[0], a:xs[1:]) 358 | endfunction 359 | 360 | " Similar to Haskell's Prelude.foldr . 361 | function! s:foldr(f, init, xs) abort 362 | "NOTE: The 'Call' should be named with l: for the conflict problem 363 | let l:Call = s:_get_binary_caller(a:f) 364 | return s:_foldr_internal(l:Call, a:f, a:init, a:xs) 365 | endfunction 366 | 367 | " Avoids caller's overhead 368 | function! s:_foldr_internal(call, f, state, xs) abort 369 | if empty(a:xs) 370 | return a:state 371 | endif 372 | 373 | let [y, ys] = s:uncons(a:xs) 374 | return a:call(a:f, [y, s:_foldr_internal(a:call, a:f, a:state, ys)]) 375 | endfunction 376 | 377 | " Similar to Haskell's Prelude.fold11 . 378 | function! s:foldr1(f, xs) abort 379 | if len(a:xs) == 0 380 | throw 'vital: Data.List: foldr1' 381 | endif 382 | return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) 383 | endfunction 384 | 385 | function! s:count(f, xs) abort 386 | let num = 0 387 | for x in a:xs 388 | if a:f(x) 389 | let num += 1 390 | endif 391 | endfor 392 | return num 393 | endfunction 394 | 395 | 396 | " Similar to python's zip() . 397 | function! s:zip(...) abort 398 | return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") 399 | endfunction 400 | 401 | " Similar to zip(), but goes until the longer one. 402 | function! s:zip_fill(xs, ys, filler) abort 403 | if empty(a:xs) && empty(a:ys) 404 | return [] 405 | elseif empty(a:ys) 406 | return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) 407 | elseif empty(a:xs) 408 | return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) 409 | else 410 | return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) 411 | endif 412 | endfunction 413 | 414 | " Inspired by Ruby's with_index method. 415 | function! s:with_index(list, ...) abort 416 | let base = a:0 > 0 ? a:1 : 0 417 | return map(copy(a:list), '[v:val, v:key + base]') 418 | endfunction 419 | 420 | " Similar to Ruby's detect or Haskell's find. 421 | function! s:find(list, default, f) abort 422 | let l:Call = s:_get_unary_caller(a:f) 423 | for x in a:list 424 | if l:Call(a:f, [x]) 425 | return x 426 | endif 427 | endfor 428 | return a:default 429 | endfunction 430 | 431 | " Returns the index of the first element which satisfies the given expr. 432 | function! s:find_index(xs, f, ...) abort 433 | let len_xs = len(a:xs) 434 | let default = get(a:000, 1, -1) 435 | 436 | let start = get(a:000, 0, 0) 437 | " Emulate list[-n] 438 | if start < 0 439 | let start += len_xs 440 | endif 441 | 442 | if len_xs <= start 443 | return default 444 | endif 445 | 446 | let l:Call = s:_get_unary_caller(a:f) 447 | for i in range(start, len_xs - 1) 448 | let x = a:xs[i] 449 | if l:Call(a:f, [x]) 450 | return i 451 | endif 452 | endfor 453 | return default 454 | endfunction 455 | 456 | " Returns the index of the last element which satisfies the given expr. 457 | function! s:find_last_index(xs, f, ...) abort 458 | let len_xs = len(a:xs) 459 | let default = get(a:000, 1, -1) 460 | 461 | let start = get(a:000, 0, len_xs - 1) 462 | if start < 0 463 | let start += len_xs 464 | endif 465 | 466 | if len_xs <= start 467 | return default 468 | endif 469 | 470 | let l:Call = s:_get_unary_caller(a:f) 471 | for i in range(start, 0, -1) 472 | let x = a:xs[i] 473 | if l:Call(a:f, [x]) 474 | return i 475 | endif 476 | endfor 477 | return default 478 | endfunction 479 | 480 | " Similar to find_index but returns the list of indices satisfying the given expr. 481 | function! s:find_indices(xs, f, ...) abort 482 | let len_xs = len(a:xs) 483 | 484 | let start = get(a:000, 0, 0) 485 | " Emulate list[-n] 486 | if start < 0 487 | let start += len_xs 488 | endif 489 | 490 | if len_xs <= start 491 | return [] 492 | endif 493 | 494 | let l:Call = s:_get_unary_caller(a:f) 495 | let result = [] 496 | for i in range(start, len_xs - 1) 497 | let x = a:xs[i] 498 | if l:Call(a:f, [x]) 499 | call add(result, i) 500 | endif 501 | endfor 502 | return result 503 | endfunction 504 | 505 | " Return non-zero if a:list1 and a:list2 have any common item(s). 506 | " Return zero otherwise. 507 | function! s:has_common_items(list1, list2) abort 508 | return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) 509 | endfunction 510 | 511 | function! s:intersect(list1, list2) abort 512 | let items = [] 513 | " for funcref 514 | for X in a:list1 515 | if index(a:list2, X) != -1 && index(items, X) == -1 516 | let items += [X] 517 | endif 518 | endfor 519 | return items 520 | endfunction 521 | 522 | " Similar to Ruby's group_by. 523 | function! s:group_by(xs, f) abort 524 | let result = {} 525 | let l:Call = s:_get_unary_caller(a:f) 526 | 527 | for l:X in a:xs 528 | let a_key = l:Call(a:f, [l:X]) 529 | let key = type(a_key) isnot type('') ? string(a_key) : a_key 530 | unlet a_key 531 | 532 | if has_key(result, key) 533 | call add(result[key], l:X) 534 | else 535 | let result[key] = [l:X] 536 | endif 537 | endfor 538 | 539 | return result 540 | endfunction 541 | 542 | function! s:binary_search(list, value, ...) abort 543 | let Predicate = a:0 >= 1 ? a:1 : 's:_basic_comparator' 544 | let dic = a:0 >= 2 ? a:2 : {} 545 | let start = 0 546 | let end = len(a:list) - 1 547 | 548 | while 1 549 | if start > end 550 | return -1 551 | endif 552 | 553 | let middle = (start + end) / 2 554 | 555 | let compared = call(Predicate, [a:value, a:list[middle]], dic) 556 | 557 | if compared < 0 558 | let end = middle - 1 559 | elseif compared > 0 560 | let start = middle + 1 561 | else 562 | return middle 563 | endif 564 | endwhile 565 | endfunction 566 | 567 | function! s:product(lists) abort 568 | let result = [[]] 569 | for pool in a:lists 570 | let tmp = [] 571 | for x in result 572 | let tmp += map(copy(pool), 'x + [v:val]') 573 | endfor 574 | let result = tmp 575 | endfor 576 | return result 577 | endfunction 578 | 579 | function! s:permutations(list, ...) abort 580 | if a:0 > 1 581 | throw 'vital: Data.List: too many arguments' 582 | endif 583 | let r = a:0 == 1 ? a:1 : len(a:list) 584 | if r > len(a:list) 585 | return [] 586 | elseif r < 0 587 | throw 'vital: Data.List: {r} must be non-negative integer' 588 | endif 589 | let n = len(a:list) 590 | let result = [] 591 | for indices in s:product(map(range(r), 'range(n)')) 592 | if len(s:uniq(indices)) == r 593 | call add(result, map(indices, 'a:list[v:val]')) 594 | endif 595 | endfor 596 | return result 597 | endfunction 598 | 599 | function! s:combinations(list, r) abort 600 | if a:r > len(a:list) 601 | return [] 602 | elseif a:r < 0 603 | throw 'vital: Data.List: {r} must be non-negative integer' 604 | endif 605 | let n = len(a:list) 606 | let result = [] 607 | for indices in s:permutations(range(n), a:r) 608 | if s:sort(copy(indices), 'a:a - a:b') == indices 609 | call add(result, map(indices, 'a:list[v:val]')) 610 | endif 611 | endfor 612 | return result 613 | endfunction 614 | 615 | 616 | " Takes the unary function of the funcref or the string expression. 617 | " Returns the caller function that is like call() . 618 | function! s:_get_unary_caller(f) abort 619 | return type(a:f) is type(function('function')) 620 | \ ? function('call') 621 | \ : function('s:_call_string_expr') 622 | endfunction 623 | 624 | " Takes the binary function of the funcref or the string expression. 625 | " if the binary function is the string expression, v:val and v:memo can be used in it. 626 | " Returns the caller function that is like call(), but it takes a tuple as an argument. 627 | function! s:_get_binary_caller(binary_f) abort 628 | return type(a:binary_f) is type(function('function')) 629 | \ ? function('call') 630 | \ : function('s:_call_binary_string_expr') 631 | endfunction 632 | 633 | " Returns the result of that apply the two element list to the binary string expression. 634 | " The binary expression has 'v:memo' and 'v:val'. 635 | function! s:_call_binary_string_expr(expr, pair) abort 636 | let expr = substitute(a:expr, 'v:memo', string(a:pair[0]), 'g') 637 | let expr = substitute(expr, 'v:val', string(a:pair[1]), 'g') 638 | return eval(expr) 639 | endfunction 640 | 641 | " This is similar to s:_get_binary_caller(), 642 | " but the behavior is different if a:binary_f is the string expression. 643 | " This uses s:_call_binary_string_expr_val_memo() . 644 | function! s:_get_binary_caller_(binary_f) abort 645 | return type(a:binary_f) is type(function('function')) 646 | \ ? function('call') 647 | \ : function('s:_call_binary_string_expr_val_memo') 648 | endfunction 649 | 650 | " This is similar to s:_call_binary_string_expr(), 651 | " but a:pair[0] is regarded as v:val, and a:pair[1] is regarded as v:memo. 652 | function! s:_call_binary_string_expr_val_memo(expr, pair) abort 653 | let x = substitute(a:expr, 'v:memo', string(a:pair[1]), 'g') 654 | let y = substitute(x, 'v:val', string(a:pair[0]), 'g') 655 | return eval(y) 656 | endfunction 657 | 658 | " Applies the string expression to the head element of a:args. 659 | " Returns the result. 660 | function! s:_call_string_expr(expr, args) abort 661 | return map([a:args[0]], a:expr)[0] 662 | endfunction 663 | 664 | let &cpo = s:save_cpo 665 | unlet s:save_cpo 666 | 667 | " vim:set et ts=2 sts=2 sw=2 tw=0: 668 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Data/String.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#Data#String#import() abort', printf("return map({'starts_with': '', 'split3': '', 'replace_first': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'justify_equal_spacing': '', 'nr2hex': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', '_vital_created': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'dstring': '', 'pad_both_sides': '', 'substitute_last': '', 'pad_right': '', 'remove_ansi_sequences': '', '_vital_loaded': ''}, \"vital#_reading_vimrc#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " Utilities for string. 11 | 12 | let s:save_cpo = &cpo 13 | set cpo&vim 14 | 15 | function! s:_vital_loaded(V) abort 16 | let s:V = a:V 17 | let s:L = s:V.import('Data.List') 18 | endfunction 19 | 20 | function! s:_vital_depends() abort 21 | return ['Data.List'] 22 | endfunction 23 | 24 | function! s:_vital_created(module) abort 25 | " Expose script-local funcref 26 | let a:module.strchars = function('strchars') 27 | let a:module.wcswidth = function('strwidth') 28 | endfunction 29 | 30 | " Substitute a:from => a:to by string. 31 | " To substitute by pattern, use substitute() instead. 32 | function! s:replace(str, from, to) abort 33 | return s:_replace(a:str, a:from, a:to, 'g') 34 | endfunction 35 | 36 | " Substitute a:from => a:to only once. 37 | " cf. s:replace() 38 | function! s:replace_first(str, from, to) abort 39 | return s:_replace(a:str, a:from, a:to, '') 40 | endfunction 41 | 42 | " implement of replace() and replace_first() 43 | function! s:_replace(str, from, to, flags) abort 44 | return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags) 45 | endfunction 46 | 47 | function! s:scan(str, pattern) abort 48 | let list = [] 49 | call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g') 50 | return list 51 | endfunction 52 | 53 | function! s:reverse(str) abort 54 | return join(reverse(split(a:str, '.\zs')), '') 55 | endfunction 56 | 57 | function! s:starts_with(str, prefix) abort 58 | return stridx(a:str, a:prefix) == 0 59 | endfunction 60 | 61 | function! s:ends_with(str, suffix) abort 62 | let idx = strridx(a:str, a:suffix) 63 | return 0 <= idx && idx + len(a:suffix) == len(a:str) 64 | endfunction 65 | 66 | function! s:common_head(strs) abort 67 | if empty(a:strs) 68 | return '' 69 | endif 70 | let len = len(a:strs) 71 | if len == 1 72 | return a:strs[0] 73 | endif 74 | let strs = len == 2 ? a:strs : sort(copy(a:strs)) 75 | let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g') 76 | return pat ==# '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']') 77 | endfunction 78 | 79 | " Split to two elements of List. ([left, right]) 80 | " e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache'] 81 | function! s:split_leftright(expr, pattern) abort 82 | let [left, _, right] = s:split3(a:expr, a:pattern) 83 | return [left, right] 84 | endfunction 85 | 86 | function! s:split3(expr, pattern) abort 87 | let ERROR = ['', '', ''] 88 | if a:expr ==# '' || a:pattern ==# '' 89 | return ERROR 90 | endif 91 | let begin = match(a:expr, a:pattern) 92 | if begin is -1 93 | return ERROR 94 | endif 95 | let end = matchend(a:expr, a:pattern) 96 | let left = begin <=# 0 ? '' : a:expr[: begin - 1] 97 | let right = a:expr[end :] 98 | return [left, a:expr[begin : end-1], right] 99 | endfunction 100 | 101 | " Slices into strings determines the number of substrings. 102 | " e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache'] 103 | function! s:nsplit(expr, n, ...) abort 104 | let pattern = get(a:000, 0, '\s') 105 | let keepempty = get(a:000, 1, 1) 106 | let ret = [] 107 | let expr = a:expr 108 | if a:n <= 1 109 | return [expr] 110 | endif 111 | 112 | while len(ret) < a:n - 1 113 | let pos = match(expr, pattern) 114 | if pos >= 0 " matched 115 | let left = pos > 0 ? expr[:pos-1] : '' 116 | let ml = len(matchstr(expr, pattern)) 117 | let right = expr[pos+ml :] 118 | if pos > 0 || keepempty 119 | call add(ret, left) 120 | endif 121 | if pos == 0 && ml == 0 122 | let pos = 1 123 | endif 124 | let expr = right 125 | else " pos == -1 means no more matches 126 | if expr !~ pattern || keepempty 127 | call add(ret, expr) 128 | endif 129 | let expr = v:none 130 | break 131 | endif 132 | endwhile 133 | 134 | " node count last check 135 | if len(ret) == a:n - 1 && type(expr) == type('') 136 | if len(expr) > 0 || keepempty 137 | call add(ret, expr) 138 | endif 139 | endif 140 | return ret 141 | endfunction 142 | 143 | " Returns the bool of contains any multibyte character in s:str 144 | function! s:contains_multibyte(str) abort "{{{ 145 | return strlen(a:str) != strchars(a:str) 146 | endfunction "}}} 147 | 148 | " Remove last character from a:str. 149 | " NOTE: This returns proper value 150 | " even if a:str contains multibyte character(s). 151 | function! s:chop(str) abort "{{{ 152 | return substitute(a:str, '.$', '', '') 153 | endfunction "}}} 154 | 155 | " Remove last \r,\n,\r\n from a:str. 156 | function! s:chomp(str) abort "{{{ 157 | return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '') 158 | endfunction "}}} 159 | 160 | " wrap() and its internal functions 161 | " * _split_by_wcswidth_once() 162 | " * _split_by_wcswidth() 163 | " * _concat() 164 | " * wrap() 165 | " 166 | " NOTE _concat() is just a copy of Data.List.concat(). 167 | " FIXME don't repeat yourself 168 | function! s:_split_by_wcswidth_once(body, x) abort 169 | let fst = s:strwidthpart(a:body, a:x) 170 | let snd = s:strwidthpart_reverse(a:body, strwidth(a:body) - strwidth(fst)) 171 | return [fst, snd] 172 | endfunction 173 | 174 | function! s:_split_by_wcswidth(body, x) abort 175 | let memo = [] 176 | let body = a:body 177 | while strwidth(body) > a:x 178 | let [tmp, body] = s:_split_by_wcswidth_once(body, a:x) 179 | call add(memo, tmp) 180 | endwhile 181 | call add(memo, body) 182 | return memo 183 | endfunction 184 | 185 | function! s:trim(str) abort 186 | return substitute(a:str,'\%#=1^[[:space:]]\+\|[[:space:]]\+$', '', 'g') 187 | endfunction 188 | 189 | function! s:trim_start(str) abort 190 | return substitute(a:str,'\%#=1^[[:space:]]\+', '', '') 191 | endfunction 192 | 193 | function! s:trim_end(str) abort 194 | let i = match(a:str, '\%#=2[[:space:]]*$') 195 | return i is# 0 ? '' : a:str[: i-1] 196 | endfunction 197 | 198 | function! s:wrap(str,...) abort 199 | let _columns = a:0 > 0 ? a:1 : &columns 200 | return s:L.concat( 201 | \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)')) 202 | endfunction 203 | 204 | function! s:nr2byte(nr) abort 205 | if a:nr < 0x80 206 | return nr2char(a:nr) 207 | elseif a:nr < 0x800 208 | return nr2char(a:nr/64+192).nr2char(a:nr%64+128) 209 | else 210 | return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128) 211 | endif 212 | endfunction 213 | 214 | function! s:nr2enc_char(charcode) abort 215 | if &encoding ==# 'utf-8' 216 | return nr2char(a:charcode) 217 | endif 218 | let char = s:nr2byte(a:charcode) 219 | if strlen(char) > 1 220 | let char = strtrans(iconv(char, 'utf-8', &encoding)) 221 | endif 222 | return char 223 | endfunction 224 | 225 | function! s:nr2hex(nr) abort 226 | let n = a:nr 227 | let r = '' 228 | while n 229 | let r = '0123456789ABCDEF'[n % 16] . r 230 | let n = n / 16 231 | endwhile 232 | return r 233 | endfunction 234 | 235 | " If a ==# b, returns -1. 236 | " If a !=# b, returns first index of different character. 237 | function! s:diffidx(a, b) abort 238 | return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b])) 239 | endfunction 240 | 241 | function! s:substitute_last(expr, pat, sub) abort 242 | return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '') 243 | endfunction 244 | 245 | function! s:dstring(expr) abort 246 | let x = substitute(string(a:expr), "^'\\|'$", '', 'g') 247 | let x = substitute(x, "''", "'", 'g') 248 | return printf('"%s"', escape(x, '"')) 249 | endfunction 250 | 251 | function! s:lines(str) abort 252 | return split(a:str, '\r\?\n') 253 | endfunction 254 | 255 | function! s:_pad_with_char(str, left, right, char) abort 256 | return repeat(a:char, a:left). a:str. repeat(a:char, a:right) 257 | endfunction 258 | 259 | function! s:pad_left(str, width, ...) abort 260 | let char = get(a:, 1, ' ') 261 | if strdisplaywidth(char) != 1 262 | throw "vital: Data.String: Can't use non-half-width characters for padding." 263 | endif 264 | let left = max([0, a:width - strdisplaywidth(a:str)]) 265 | return s:_pad_with_char(a:str, left, 0, char) 266 | endfunction 267 | 268 | function! s:pad_right(str, width, ...) abort 269 | let char = get(a:, 1, ' ') 270 | if strdisplaywidth(char) != 1 271 | throw "vital: Data.String: Can't use non-half-width characters for padding." 272 | endif 273 | let right = max([0, a:width - strdisplaywidth(a:str)]) 274 | return s:_pad_with_char(a:str, 0, right, char) 275 | endfunction 276 | 277 | function! s:pad_both_sides(str, width, ...) abort 278 | let char = get(a:, 1, ' ') 279 | if strdisplaywidth(char) != 1 280 | throw "vital: Data.String: Can't use non-half-width characters for padding." 281 | endif 282 | let space = max([0, a:width - strdisplaywidth(a:str)]) 283 | let left = space / 2 284 | let right = space - left 285 | return s:_pad_with_char(a:str, left, right, char) 286 | endfunction 287 | 288 | function! s:pad_between_letters(str, width, ...) abort 289 | let char = get(a:, 1, ' ') 290 | if strdisplaywidth(char) != 1 291 | throw "vital: Data.String: Can't use non-half-width characters for padding." 292 | endif 293 | let letters = split(a:str, '\zs') 294 | let each_width = a:width / len(letters) 295 | let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '') 296 | if a:width - strdisplaywidth(str) > 0 297 | return char. s:pad_both_sides(str, a:width - 1, char) 298 | endif 299 | return str 300 | endfunction 301 | 302 | function! s:justify_equal_spacing(str, width, ...) abort 303 | let char = get(a:, 1, ' ') 304 | if strdisplaywidth(char) != 1 305 | throw "vital: Data.String: Can't use non-half-width characters for padding." 306 | endif 307 | let letters = split(a:str, '\zs') 308 | let first_letter = letters[0] 309 | " {width w/o the first letter} / {length w/o the first letter} 310 | let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1) 311 | let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1) 312 | return first_letter. join(s:L.concat([ 313 | \ map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'), 314 | \ map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)') 315 | \ ]), '') 316 | endfunction 317 | 318 | function! s:levenshtein_distance(str1, str2) abort 319 | let letters1 = split(a:str1, '\zs') 320 | let letters2 = split(a:str2, '\zs') 321 | let length1 = len(letters1) 322 | let length2 = len(letters2) 323 | let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), ''0'')') 324 | 325 | for i1 in range(0, length1) 326 | let distances[i1][0] = i1 327 | endfor 328 | for i2 in range(0, length2) 329 | let distances[0][i2] = i2 330 | endfor 331 | 332 | for i1 in range(1, length1) 333 | for i2 in range(1, length2) 334 | let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1 335 | 336 | let distances[i1][i2] = min([ 337 | \ distances[i1 - 1][i2 ] + 1, 338 | \ distances[i1 ][i2 - 1] + 1, 339 | \ distances[i1 - 1][i2 - 1] + cost, 340 | \]) 341 | endfor 342 | endfor 343 | 344 | return distances[length1][length2] 345 | endfunction 346 | 347 | function! s:padding_by_displaywidth(expr, width, float) abort 348 | let padding_char = ' ' 349 | let n = a:width - strdisplaywidth(a:expr) 350 | if n <= 0 351 | let n = 0 352 | endif 353 | if a:float < 0 354 | return a:expr . repeat(padding_char, n) 355 | elseif 0 < a:float 356 | return repeat(padding_char, n) . a:expr 357 | else 358 | if n % 2 is 0 359 | return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2) 360 | else 361 | return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char 362 | endif 363 | endif 364 | endfunction 365 | 366 | function! s:split_by_displaywidth(expr, width, float, is_wrap) abort 367 | if a:width is 0 368 | return [''] 369 | endif 370 | 371 | let lines = [] 372 | 373 | let cs = split(a:expr, '\zs') 374 | let cs_index = 0 375 | 376 | let text = '' 377 | while cs_index < len(cs) 378 | if cs[cs_index] is# "\n" 379 | let text = s:padding_by_displaywidth(text, a:width, a:float) 380 | let lines += [text] 381 | let text = '' 382 | else 383 | let w = strdisplaywidth(text . cs[cs_index]) 384 | 385 | if w < a:width 386 | let text .= cs[cs_index] 387 | elseif a:width < w 388 | let text = s:padding_by_displaywidth(text, a:width, a:float) 389 | else 390 | let text .= cs[cs_index] 391 | endif 392 | 393 | if a:width <= w 394 | let lines += [text] 395 | let text = '' 396 | if a:is_wrap 397 | if a:width < w 398 | if a:width < strdisplaywidth(cs[cs_index]) 399 | while get(cs, cs_index, "\n") isnot# "\n" 400 | let cs_index += 1 401 | endwhile 402 | continue 403 | else 404 | let text = cs[cs_index] 405 | endif 406 | endif 407 | else 408 | while get(cs, cs_index, "\n") isnot# "\n" 409 | let cs_index += 1 410 | endwhile 411 | continue 412 | endif 413 | endif 414 | 415 | endif 416 | let cs_index += 1 417 | endwhile 418 | 419 | if !empty(text) 420 | let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ] 421 | endif 422 | 423 | return lines 424 | endfunction 425 | 426 | function! s:hash(str) abort 427 | if exists('*sha256') 428 | return sha256(a:str) 429 | else 430 | " This gives up sha256ing but just adds up char with index. 431 | let sum = 0 432 | for i in range(len(a:str)) 433 | let sum += char2nr(a:str[i]) * (i + 1) 434 | endfor 435 | 436 | return printf('%x', sum) 437 | endif 438 | endfunction 439 | 440 | function! s:truncate(str, width) abort 441 | " Original function is from mattn. 442 | " http://github.com/mattn/googlereader-vim/tree/master 443 | 444 | if a:str =~# '^[\x00-\x7f]*$' 445 | return len(a:str) < a:width 446 | \ ? printf('%-' . a:width . 's', a:str) 447 | \ : strpart(a:str, 0, a:width) 448 | endif 449 | 450 | let ret = a:str 451 | let width = strwidth(a:str) 452 | if width > a:width 453 | let ret = s:strwidthpart(ret, a:width) 454 | let width = strwidth(ret) 455 | endif 456 | 457 | if width < a:width 458 | let ret .= repeat(' ', a:width - width) 459 | endif 460 | 461 | return ret 462 | endfunction 463 | 464 | function! s:truncate_skipping(str, max, footer_width, separator) abort 465 | let width = strwidth(a:str) 466 | if width <= a:max 467 | let ret = a:str 468 | else 469 | let header_width = a:max - strwidth(a:separator) - a:footer_width 470 | let ret = s:strwidthpart(a:str, header_width) . a:separator 471 | \ . s:strwidthpart_reverse(a:str, a:footer_width) 472 | endif 473 | return s:truncate(ret, a:max) 474 | endfunction 475 | 476 | function! s:strwidthpart(str, width) abort 477 | let str = tr(a:str, "\t", ' ') 478 | let vcol = a:width + 2 479 | return matchstr(str, '.*\%<' . (vcol < 0 ? 0 : vcol) . 'v') 480 | endfunction 481 | 482 | function! s:strwidthpart_reverse(str, width) abort 483 | let str = tr(a:str, "\t", ' ') 484 | let vcol = strwidth(str) - a:width 485 | return matchstr(str, '\%>' . (vcol < 0 ? 0 : vcol) . 'v.*') 486 | endfunction 487 | 488 | function! s:remove_ansi_sequences(text) abort 489 | return substitute(a:text, '\e\[\%(\%(\d\+;\)*\d\+\)\?[mK]', '', 'g') 490 | endfunction 491 | 492 | function! s:escape_pattern(str) abort 493 | " escape characters for no-magic 494 | return escape(a:str, '^$~.*[]\') 495 | endfunction 496 | 497 | function! s:unescape_pattern(str) abort 498 | " unescape characters for no-magic 499 | return s:unescape(a:str, '^$~.*[]\') 500 | endfunction 501 | 502 | function! s:unescape(str, chars) abort 503 | let chars = map(split(a:chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')') 504 | return substitute(a:str, '\\\(' . join(chars, '\|') . '\)', '\1', 'g') 505 | endfunction 506 | 507 | function! s:iconv(expr, from, to) abort 508 | if a:from ==# '' || a:to ==# '' || a:from ==? a:to 509 | return a:expr 510 | endif 511 | let result = iconv(a:expr, a:from, a:to) 512 | return empty(result) ? a:expr : result 513 | endfunction 514 | 515 | " NOTE: 516 | " A definition of a TEXT file is "A file that contains characters organized 517 | " into one or more lines." 518 | " A definition of a LINE is "A sequence of zero or more non- s 519 | " plus a terminating " 520 | " That's why {stdin} always ends with ideally. However, there are 521 | " some programs which does not follow the POSIX rule and a Vim's way to join 522 | " List into TEXT; join({text}, "\n"); does not add to the end of 523 | " the last line. 524 | " That's why add a trailing if it does not exist. 525 | " REF: 526 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 527 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 528 | " :help split() 529 | " NOTE: 530 | " it does nothing if the text is a correct POSIX text 531 | function! s:repair_posix_text(text, ...) abort 532 | let newline = get(a:000, 0, "\n") 533 | return a:text =~# '\n$' ? a:text : a:text . newline 534 | endfunction 535 | 536 | " NOTE: 537 | " A definition of a TEXT file is "A file that contains characters organized 538 | " into one or more lines." 539 | " A definition of a LINE is "A sequence of zero or more non- s 540 | " plus a terminating " 541 | " REF: 542 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 543 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 544 | function! s:join_posix_lines(lines, ...) abort 545 | let newline = get(a:000, 0, "\n") 546 | return join(a:lines, newline) . newline 547 | endfunction 548 | 549 | " NOTE: 550 | " A definition of a TEXT file is "A file that contains characters organized 551 | " into one or more lines." 552 | " A definition of a LINE is "A sequence of zero or more non- s 553 | " plus a terminating " 554 | " TEXT into List; split({text}, '\r\?\n', 1); add an extra empty line at the 555 | " end of List because the end of TEXT ends with and keepempty=1 is 556 | " specified. (btw. keepempty=0 cannot be used because it will remove 557 | " emptylines in the head and the tail). 558 | " That's why removing a trailing before proceeding to 'split' is required 559 | " REF: 560 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 561 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 562 | function! s:split_posix_text(text, ...) abort 563 | let newline = get(a:000, 0, '\r\?\n') 564 | let text = substitute(a:text, newline . '$', '', '') 565 | return split(text, newline, 1) 566 | endfunction 567 | 568 | let &cpo = s:save_cpo 569 | unlet s:save_cpo 570 | " vim:set et ts=2 sts=2 sw=2 tw=0: 571 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Prelude.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#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_infinity': '', 'is_mac': '', 'strwidthpart': '', 'getchar': '', 'is_unix': '', 'is_windows': '', 'globpath': '', 'is_float': '', 'smart_execute_command': ''}, \"vital#_reading_vimrc#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 | function! s:glob(expr) abort 14 | return glob(a:expr, 1, 1) 15 | endfunction 16 | 17 | function! s:globpath(path, expr) abort 18 | return globpath(a:path, a:expr, 1, 1) 19 | endfunction 20 | 21 | " Wrapper functions for type(). 22 | let [ 23 | \ s:__TYPE_NUMBER, 24 | \ s:__TYPE_STRING, 25 | \ s:__TYPE_FUNCREF, 26 | \ s:__TYPE_LIST, 27 | \ s:__TYPE_DICT, 28 | \ s:__TYPE_FLOAT] = [ 29 | \ v:t_number, 30 | \ v:t_string, 31 | \ v:t_func, 32 | \ v:t_list, 33 | \ v:t_dict, 34 | \ v:t_float] 35 | 36 | " Number or Float 37 | function! s:is_numeric(Value) abort 38 | let _ = type(a:Value) 39 | return _ ==# s:__TYPE_NUMBER 40 | \ || _ ==# s:__TYPE_FLOAT 41 | endfunction 42 | 43 | " Number 44 | function! s:is_number(Value) abort 45 | return type(a:Value) ==# s:__TYPE_NUMBER 46 | endfunction 47 | 48 | " String 49 | function! s:is_string(Value) abort 50 | return type(a:Value) ==# s:__TYPE_STRING 51 | endfunction 52 | 53 | " Funcref 54 | function! s:is_funcref(Value) abort 55 | return type(a:Value) ==# s:__TYPE_FUNCREF 56 | endfunction 57 | 58 | " List 59 | function! s:is_list(Value) abort 60 | return type(a:Value) ==# s:__TYPE_LIST 61 | endfunction 62 | 63 | " Dictionary 64 | function! s:is_dict(Value) abort 65 | return type(a:Value) ==# s:__TYPE_DICT 66 | endfunction 67 | 68 | " Float 69 | function! s:is_float(Value) abort 70 | return type(a:Value) ==# s:__TYPE_FLOAT 71 | endfunction 72 | 73 | " Infinity 74 | if exists('*isinf') 75 | function! s:is_infinity(Value) abort 76 | return isinf(a:Value) 77 | endfunction 78 | else 79 | function! s:is_infinity(Value) abort 80 | if type(a:Value) ==# s:__TYPE_FLOAT 81 | let s = string(a:Value) 82 | if s ==# 'inf' 83 | return 1 84 | elseif s ==# '-inf' 85 | return -1 86 | endif 87 | endif 88 | return 0 89 | endfunction 90 | endif 91 | 92 | 93 | function! s:truncate_skipping(str, max, footer_width, separator) abort 94 | call s:_warn_deprecated('truncate_skipping', 'Data.String.truncate_skipping') 95 | 96 | let width = s:wcswidth(a:str) 97 | if width <= a:max 98 | let ret = a:str 99 | else 100 | let header_width = a:max - s:wcswidth(a:separator) - a:footer_width 101 | let ret = s:strwidthpart(a:str, header_width) . a:separator 102 | \ . s:strwidthpart_reverse(a:str, a:footer_width) 103 | endif 104 | 105 | return s:truncate(ret, a:max) 106 | endfunction 107 | 108 | function! s:truncate(str, width) abort 109 | " Original function is from mattn. 110 | " http://github.com/mattn/googlereader-vim/tree/master 111 | 112 | call s:_warn_deprecated('truncate', 'Data.String.truncate') 113 | 114 | if a:str =~# '^[\x00-\x7f]*$' 115 | return len(a:str) < a:width ? 116 | \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) 117 | endif 118 | 119 | let ret = a:str 120 | let width = s:wcswidth(a:str) 121 | if width > a:width 122 | let ret = s:strwidthpart(ret, a:width) 123 | let width = s:wcswidth(ret) 124 | endif 125 | 126 | if width < a:width 127 | let ret .= repeat(' ', a:width - width) 128 | endif 129 | 130 | return ret 131 | endfunction 132 | 133 | function! s:strwidthpart(str, width) abort 134 | call s:_warn_deprecated('strwidthpart', 'Data.String.strwidthpart') 135 | 136 | if a:width <= 0 137 | return '' 138 | endif 139 | let ret = a:str 140 | let width = s:wcswidth(a:str) 141 | while width > a:width 142 | let char = matchstr(ret, '.$') 143 | let ret = ret[: -1 - len(char)] 144 | let width -= s:wcswidth(char) 145 | endwhile 146 | 147 | return ret 148 | endfunction 149 | function! s:strwidthpart_reverse(str, width) abort 150 | call s:_warn_deprecated('strwidthpart_reverse', 'Data.String.strwidthpart_reverse') 151 | 152 | if a:width <= 0 153 | return '' 154 | endif 155 | let ret = a:str 156 | let width = s:wcswidth(a:str) 157 | while width > a:width 158 | let char = matchstr(ret, '^.') 159 | let ret = ret[len(char) :] 160 | let width -= s:wcswidth(char) 161 | endwhile 162 | 163 | return ret 164 | endfunction 165 | 166 | function! s:wcswidth(str) abort 167 | call s:_warn_deprecated('wcswidth', 'Data.String.wcswidth') 168 | return strwidth(a:str) 169 | endfunction 170 | 171 | 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 172 | let s:is_cygwin = has('win32unix') 173 | let s:is_mac = !s:is_windows && !s:is_cygwin 174 | \ && (has('mac') || has('macunix') || has('gui_macvim') || 175 | \ (!isdirectory('/proc') && executable('sw_vers'))) 176 | let s:is_unix = has('unix') 177 | 178 | function! s:is_windows() abort 179 | return s:is_windows 180 | endfunction 181 | 182 | function! s:is_cygwin() abort 183 | return s:is_cygwin 184 | endfunction 185 | 186 | function! s:is_mac() abort 187 | return s:is_mac 188 | endfunction 189 | 190 | function! s:is_unix() abort 191 | return s:is_unix 192 | endfunction 193 | 194 | function! s:_warn_deprecated(name, alternative) abort 195 | try 196 | echohl Error 197 | echomsg 'Prelude.' . a:name . ' is deprecated! Please use ' . a:alternative . ' instead.' 198 | finally 199 | echohl None 200 | endtry 201 | endfunction 202 | 203 | function! s:smart_execute_command(action, word) abort 204 | execute a:action . ' ' . (a:word ==# '' ? '' : '`=a:word`') 205 | endfunction 206 | 207 | function! s:_escape_file_searching(buffer_name) abort 208 | return escape(a:buffer_name, '*[]?{}, ') 209 | endfunction 210 | 211 | function! s:escape_pattern(str) abort 212 | call s:_warn_deprecated( 213 | \ 'escape_pattern', 214 | \ 'Data.String.escape_pattern', 215 | \) 216 | return escape(a:str, '~"\.^$[]*') 217 | endfunction 218 | 219 | function! s:getchar(...) abort 220 | let c = call('getchar', a:000) 221 | return type(c) == type(0) ? nr2char(c) : c 222 | endfunction 223 | 224 | function! s:getchar_safe(...) abort 225 | let c = s:input_helper('getchar', a:000) 226 | return type(c) == type('') ? c : nr2char(c) 227 | endfunction 228 | 229 | function! s:input_safe(...) abort 230 | return s:input_helper('input', a:000) 231 | endfunction 232 | 233 | function! s:input_helper(funcname, args) abort 234 | let success = 0 235 | if inputsave() !=# success 236 | throw 'vital: Prelude: inputsave() failed' 237 | endif 238 | try 239 | return call(a:funcname, a:args) 240 | finally 241 | if inputrestore() !=# success 242 | throw 'vital: Prelude: inputrestore() failed' 243 | endif 244 | endtry 245 | endfunction 246 | 247 | function! s:set_default(var, val) abort 248 | if !exists(a:var) || type({a:var}) != type(a:val) 249 | let {a:var} = a:val 250 | endif 251 | endfunction 252 | 253 | function! s:substitute_path_separator(path) abort 254 | return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path 255 | endfunction 256 | 257 | function! s:path2directory(path) abort 258 | return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h')) 259 | endfunction 260 | 261 | function! s:_path2project_directory_git(path) abort 262 | let parent = a:path 263 | 264 | while 1 265 | let path = parent . '/.git' 266 | if isdirectory(path) || filereadable(path) 267 | return parent 268 | endif 269 | let next = fnamemodify(parent, ':h') 270 | if next == parent 271 | return '' 272 | endif 273 | let parent = next 274 | endwhile 275 | endfunction 276 | 277 | function! s:_path2project_directory_svn(path) abort 278 | let search_directory = a:path 279 | let directory = '' 280 | 281 | let find_directory = s:_escape_file_searching(search_directory) 282 | let d = finddir('.svn', find_directory . ';') 283 | if d ==# '' 284 | return '' 285 | endif 286 | 287 | let directory = fnamemodify(d, ':p:h:h') 288 | 289 | " Search parent directories. 290 | let parent_directory = s:path2directory( 291 | \ fnamemodify(directory, ':h')) 292 | 293 | if parent_directory !=# '' 294 | let d = finddir('.svn', parent_directory . ';') 295 | if d !=# '' 296 | let directory = s:_path2project_directory_svn(parent_directory) 297 | endif 298 | endif 299 | return directory 300 | endfunction 301 | 302 | function! s:_path2project_directory_others(vcs, path) abort 303 | let vcs = a:vcs 304 | let search_directory = a:path 305 | 306 | let find_directory = s:_escape_file_searching(search_directory) 307 | let d = finddir(vcs, find_directory . ';') 308 | if d ==# '' 309 | return '' 310 | endif 311 | return fnamemodify(d, ':p:h:h') 312 | endfunction 313 | 314 | function! s:path2project_directory(path, ...) abort 315 | let is_allow_empty = get(a:000, 0, 0) 316 | let search_directory = s:path2directory(a:path) 317 | let directory = '' 318 | 319 | " Search VCS directory. 320 | for vcs in ['.git', '.bzr', '.hg', '.svn'] 321 | if vcs ==# '.git' 322 | let directory = s:_path2project_directory_git(search_directory) 323 | elseif vcs ==# '.svn' 324 | let directory = s:_path2project_directory_svn(search_directory) 325 | else 326 | let directory = s:_path2project_directory_others(vcs, search_directory) 327 | endif 328 | if directory !=# '' 329 | break 330 | endif 331 | endfor 332 | 333 | " Search project file. 334 | if directory ==# '' 335 | for d in ['build.xml', 'prj.el', '.project', 'pom.xml', 'package.json', 336 | \ 'Makefile', 'configure', 'Rakefile', 'NAnt.build', 337 | \ 'P4CONFIG', 'tags', 'gtags'] 338 | let d = findfile(d, s:_escape_file_searching(search_directory) . ';') 339 | if d !=# '' 340 | let directory = fnamemodify(d, ':p:h') 341 | break 342 | endif 343 | endfor 344 | endif 345 | 346 | if directory ==# '' 347 | " Search /src/ directory. 348 | let base = s:substitute_path_separator(search_directory) 349 | if base =~# '/src/' 350 | let directory = base[: strridx(base, '/src/') + 3] 351 | endif 352 | endif 353 | 354 | if directory ==# '' && !is_allow_empty 355 | " Use original path. 356 | let directory = search_directory 357 | endif 358 | 359 | return s:substitute_path_separator(directory) 360 | endfunction 361 | 362 | let &cpo = s:save_cpo 363 | unlet s:save_cpo 364 | 365 | " vim:set et ts=2 sts=2 sw=2 tw=0: 366 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Process.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#Process#import() abort', printf("return map({'shellescape': '', 'has_vimproc': '', 'system': '', 'iconv': '', 'spawn': '', 'get_last_status': ''}, \"vital#_reading_vimrc#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | " TODO: move all comments to doc file. 11 | " 12 | " 13 | " FIXME: This module name should be Vital.System ? 14 | " But the name has been already taken. 15 | 16 | let s:save_cpo = &cpo 17 | set cpo&vim 18 | 19 | 20 | " FIXME: Unfortunately, can't use s:_vital_loaded() for this purpose. 21 | " Because these variables are used when this script file is loaded. 22 | 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 23 | let s:is_unix = has('unix') 24 | 25 | let s:TYPE_DICT = type({}) 26 | let s:TYPE_LIST = type([]) 27 | let s:TYPE_STRING = type('') 28 | 29 | function! s:spawn(expr, ...) abort 30 | if type(a:expr) is s:TYPE_LIST 31 | let special = 1 32 | let cmdline = join(map(a:expr, 's:shellescape(v:val, special)'), ' ') 33 | elseif type(a:expr) is s:TYPE_STRING 34 | let cmdline = a:expr 35 | if a:0 && a:1 36 | " for :! command 37 | let cmdline = substitute(cmdline, '\([!%#]\|<[^<>]\+>\)', '\\\1', 'g') 38 | endif 39 | else 40 | throw 'vital: Process: invalid argument (value type:' . type(a:expr) . ')' 41 | endif 42 | if s:is_windows 43 | silent execute '!start' cmdline 44 | else 45 | silent execute '!' cmdline '&' 46 | endif 47 | return '' 48 | endfunction 49 | 50 | " iconv() wrapper for safety. 51 | function! s:iconv(expr, from, to) abort 52 | if a:from ==# '' || a:to ==# '' || a:from ==? a:to 53 | return a:expr 54 | endif 55 | let result = iconv(a:expr, a:from, a:to) 56 | return result !=# '' ? result : a:expr 57 | endfunction 58 | 59 | " Check vimproc. 60 | function! s:has_vimproc() abort 61 | if !exists('s:exists_vimproc') 62 | try 63 | call vimproc#version() 64 | let s:exists_vimproc = 1 65 | catch 66 | let s:exists_vimproc = 0 67 | endtry 68 | endif 69 | return s:exists_vimproc 70 | endfunction 71 | 72 | " * {command} [, {input} [, {timeout}]] 73 | " * {command} [, {dict}] 74 | " {dict} = { 75 | " use_vimproc: bool, 76 | " input: string, 77 | " timeout: bool, 78 | " background: bool, 79 | " } 80 | function! s:system(str, ...) abort 81 | " Process optional arguments at first 82 | " because use_vimproc is required later 83 | " for a:str argument. 84 | let input = '' 85 | let use_vimproc = s:has_vimproc() 86 | let background = 0 87 | let args = [] 88 | if a:0 ==# 1 89 | " {command} [, {dict}] 90 | " a:1 = {dict} 91 | if type(a:1) is s:TYPE_DICT 92 | if has_key(a:1, 'use_vimproc') 93 | let use_vimproc = a:1.use_vimproc 94 | endif 95 | if has_key(a:1, 'input') 96 | let args += [s:iconv(a:1.input, &encoding, 'char')] 97 | endif 98 | if use_vimproc && has_key(a:1, 'timeout') 99 | " ignores timeout unless you have vimproc. 100 | let args += [a:1.timeout] 101 | endif 102 | if has_key(a:1, 'background') 103 | let background = a:1.background 104 | endif 105 | elseif type(a:1) is s:TYPE_STRING 106 | let args += [s:iconv(a:1, &encoding, 'char')] 107 | else 108 | throw 'vital: Process: invalid argument (value type:' . type(a:1) . ')' 109 | endif 110 | elseif a:0 >= 2 111 | " {command} [, {input} [, {timeout}]] 112 | " a:000 = [{input} [, {timeout}]] 113 | let [input; rest] = a:000 114 | let input = s:iconv(input, &encoding, 'char') 115 | let args += [input] + rest 116 | endif 117 | 118 | " Process a:str argument. 119 | if type(a:str) is s:TYPE_LIST 120 | let expr = use_vimproc ? '"''" . v:val . "''"' : 's:shellescape(v:val)' 121 | let command = join(map(copy(a:str), expr), ' ') 122 | elseif type(a:str) is s:TYPE_STRING 123 | let command = a:str 124 | else 125 | throw 'vital: Process: invalid argument (value type:' . type(a:str) . ')' 126 | endif 127 | let args = [command] + args 128 | if background && (use_vimproc || !s:is_windows) 129 | if has('nvim') 130 | throw "vital: Process: neovim's system() doesn't support background(&) process (cmdline:" . string(a:str) . ')' 131 | endif 132 | let args[0] = args[0] . ' &' 133 | endif 134 | 135 | let funcname = use_vimproc ? 'vimproc#system' : 'system' 136 | let output = call(funcname, args) 137 | let output = s:iconv(output, 'char', &encoding) 138 | return output 139 | endfunction 140 | 141 | function! s:get_last_status() abort 142 | return s:has_vimproc() ? 143 | \ vimproc#get_last_status() : v:shell_error 144 | endfunction 145 | 146 | if s:is_windows 147 | function! s:shellescape(...) abort 148 | try 149 | let shellslash = &shellslash 150 | set noshellslash 151 | return call('shellescape', a:000) 152 | finally 153 | let &shellslash = shellslash 154 | endtry 155 | endfunction 156 | else 157 | function! s:shellescape(...) abort 158 | return call('shellescape', a:000) 159 | endfunction 160 | endif 161 | 162 | 163 | let &cpo = s:save_cpo 164 | unlet s:save_cpo 165 | 166 | " vim:set et ts=2 sts=2 sw=2 tw=0: 167 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Web/HTTP.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#Web#HTTP#import() abort', printf("return map({'decodeURI': '', '_vital_depends': '', 'parseHeader': '', 'encodeURIComponent': '', 'encodeURI': '', 'escape': '', 'post': '', 'get': '', 'request': '', '_vital_loaded': ''}, \"vital#_reading_vimrc#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:py2source = expand(':h') . '/HTTP_python2.py' 14 | let s:py3source = expand(':h') . '/HTTP_python3.py' 15 | 16 | function! s:_vital_loaded(V) abort 17 | let s:V = a:V 18 | let s:Prelude = s:V.import('Prelude') 19 | let s:Process = s:V.import('Process') 20 | let s:String = s:V.import('Data.String') 21 | endfunction 22 | 23 | function! s:_vital_depends() abort 24 | return { 25 | \ 'modules':['Prelude', 'Data.String', 'Process'] , 26 | \ 'files': ['HTTP_python2.py', 'HTTP_python3.py'], 27 | \} 28 | endfunction 29 | 30 | function! s:__urlencode_char(c) abort 31 | return printf('%%%02X', char2nr(a:c)) 32 | endfunction 33 | 34 | function! s:decodeURI(str) abort 35 | let ret = a:str 36 | let ret = substitute(ret, '+', ' ', 'g') 37 | let ret = substitute(ret, '%\(\x\x\)', '\=printf("%c", str2nr(submatch(1), 16))', 'g') 38 | return ret 39 | endfunction 40 | 41 | function! s:escape(str) abort 42 | let result = '' 43 | for i in range(len(a:str)) 44 | if a:str[i] =~# '^[a-zA-Z0-9_.~-]$' 45 | let result .= a:str[i] 46 | else 47 | let result .= s:__urlencode_char(a:str[i]) 48 | endif 49 | endfor 50 | return result 51 | endfunction 52 | 53 | function! s:encodeURI(items) abort 54 | let ret = '' 55 | if s:Prelude.is_dict(a:items) 56 | for key in sort(keys(a:items)) 57 | if strlen(ret) 58 | let ret .= '&' 59 | endif 60 | let ret .= key . '=' . s:encodeURI(a:items[key]) 61 | endfor 62 | elseif s:Prelude.is_list(a:items) 63 | for item in sort(a:items) 64 | if strlen(ret) 65 | let ret .= '&' 66 | endif 67 | let ret .= item 68 | endfor 69 | else 70 | let ret = s:escape(a:items) 71 | endif 72 | return ret 73 | endfunction 74 | 75 | function! s:encodeURIComponent(items) abort 76 | let ret = '' 77 | if s:Prelude.is_dict(a:items) 78 | for key in sort(keys(a:items)) 79 | if strlen(ret) | let ret .= '&' | endif 80 | let ret .= key . '=' . s:encodeURIComponent(a:items[key]) 81 | endfor 82 | elseif s:Prelude.is_list(a:items) 83 | for item in sort(a:items) 84 | if strlen(ret) | let ret .= '&' | endif 85 | let ret .= item 86 | endfor 87 | else 88 | let items = iconv(a:items, &enc, 'utf-8') 89 | let len = strlen(items) 90 | let i = 0 91 | while i < len 92 | let ch = items[i] 93 | if ch =~# '[0-9A-Za-z-._~!''()*]' 94 | let ret .= ch 95 | elseif ch ==# ' ' 96 | let ret .= '+' 97 | else 98 | let ret .= '%' . substitute('0' . s:String.nr2hex(char2nr(ch)), '^.*\(..\)$', '\1', '') 99 | endif 100 | let i = i + 1 101 | endwhile 102 | endif 103 | return ret 104 | endfunction 105 | 106 | function! s:request(...) abort 107 | let settings = s:_build_settings(a:000) 108 | let settings.method = toupper(settings.method) 109 | if !has_key(settings, 'url') 110 | throw 'vital: Web.HTTP: "url" parameter is required.' 111 | endif 112 | if !s:Prelude.is_list(settings.client) 113 | let settings.client = [settings.client] 114 | endif 115 | let client = s:_get_client(settings) 116 | if empty(client) 117 | throw 'vital: Web.HTTP: Available client not found: ' 118 | \ . string(settings.client) 119 | endif 120 | if has_key(settings, 'contentType') 121 | let settings.headers['Content-Type'] = settings.contentType 122 | endif 123 | if has_key(settings, 'param') 124 | if s:Prelude.is_dict(settings.param) 125 | let getdatastr = s:encodeURI(settings.param) 126 | else 127 | let getdatastr = settings.param 128 | endif 129 | if strlen(getdatastr) 130 | let settings.url .= '?' . getdatastr 131 | endif 132 | endif 133 | if has_key(settings, 'data') 134 | let settings.data = s:_postdata(settings.data) 135 | let settings.headers['Content-Length'] = len(join(settings.data, "\n")) 136 | endif 137 | let settings._file = {} 138 | 139 | let responses = client.request(settings) 140 | 141 | for file in values(settings._file) 142 | if filereadable(file) 143 | call delete(file) 144 | endif 145 | endfor 146 | 147 | call map(responses, 's:_build_response(v:val[0], v:val[1])') 148 | return s:_build_last_response(responses) 149 | endfunction 150 | 151 | function! s:get(url, ...) abort 152 | let settings = { 153 | \ 'url': a:url, 154 | \ 'param': a:0 > 0 ? a:1 : {}, 155 | \ 'headers': a:0 > 1 ? a:2 : {}, 156 | \ } 157 | return s:request(settings) 158 | endfunction 159 | 160 | function! s:post(url, ...) abort 161 | let settings = { 162 | \ 'url': a:url, 163 | \ 'data': a:0 > 0 ? a:1 : {}, 164 | \ 'headers': a:0 > 1 ? a:2 : {}, 165 | \ 'method': a:0 > 2 ? a:3 : 'POST', 166 | \ } 167 | return s:request(settings) 168 | endfunction 169 | 170 | function! s:_readfile(file) abort 171 | if filereadable(a:file) 172 | return join(readfile(a:file, 'b'), "\n") 173 | endif 174 | return '' 175 | endfunction 176 | 177 | function! s:_make_postfile(data) abort 178 | let fname = s:_tempname() 179 | call writefile(a:data, fname, 'b') 180 | return fname 181 | endfunction 182 | 183 | function! s:_tempname() abort 184 | return tr(tempname(), '\', '/') 185 | endfunction 186 | 187 | function! s:_postdata(data) abort 188 | if s:Prelude.is_dict(a:data) 189 | return [s:encodeURI(a:data)] 190 | elseif s:Prelude.is_list(a:data) 191 | return a:data 192 | else 193 | return split(a:data, "\n") 194 | endif 195 | endfunction 196 | 197 | function! s:_build_response(header, content) abort 198 | let response = { 199 | \ 'header' : a:header, 200 | \ 'content': a:content, 201 | \ 'status': 0, 202 | \ 'statusText': '', 203 | \ 'success': 0, 204 | \ } 205 | 206 | if !empty(a:header) 207 | let status_line = get(a:header, 0) 208 | let matched = matchlist(status_line, '^HTTP/\%(1\.\d\|2\)\s\+\(\d\+\)\s\+\(.*\)') 209 | if !empty(matched) 210 | let [status, status_text] = matched[1 : 2] 211 | let response.status = status - 0 212 | let response.statusText = status_text 213 | let response.success = status =~# '^2' 214 | call remove(a:header, 0) 215 | endif 216 | endif 217 | return response 218 | endfunction 219 | 220 | function! s:_build_last_response(responses) abort 221 | let all_headers = [] 222 | for response in a:responses 223 | call extend(all_headers, response.header) 224 | endfor 225 | let last_response = remove(a:responses, -1) 226 | let last_response.redirectInfo = a:responses 227 | let last_response.allHeaders = all_headers 228 | return last_response 229 | endfunction 230 | 231 | function! s:_build_settings(args) abort 232 | let settings = { 233 | \ 'method': 'GET', 234 | \ 'headers': {}, 235 | \ 'client': ['python', 'curl', 'wget', 'python3', 'python2'], 236 | \ 'maxRedirect': 20, 237 | \ 'retry': 1, 238 | \ } 239 | let args = copy(a:args) 240 | if len(args) == 0 241 | throw 'vital: Web.HTTP: request() needs one or more arguments.' 242 | endif 243 | if s:Prelude.is_dict(args[-1]) 244 | call extend(settings, remove(args, -1)) 245 | endif 246 | if len(args) == 2 247 | let settings.method = remove(args, 0) 248 | endif 249 | if !empty(args) 250 | let settings.url = args[0] 251 | endif 252 | 253 | return settings 254 | endfunction 255 | 256 | function! s:_make_header_args(headdata, option, quote) abort 257 | let args = '' 258 | for [key, value] in items(a:headdata) 259 | if s:Prelude.is_windows() 260 | let value = substitute(value, '"', '"""', 'g') 261 | endif 262 | let args .= ' ' . a:option . a:quote . key . ': ' . value . a:quote 263 | endfor 264 | return args 265 | endfunction 266 | 267 | function! s:parseHeader(headers) abort 268 | " FIXME: User should be able to specify the treatment method of the duplicate item. 269 | let header = {} 270 | for h in a:headers 271 | let matched = matchlist(h, '^\([^:]\+\):\s*\(.*\)$') 272 | if !empty(matched) 273 | let [name, value] = matched[1 : 2] 274 | let header[name] = value 275 | endif 276 | endfor 277 | return header 278 | endfunction 279 | 280 | " Clients 281 | function! s:_get_client(settings) abort 282 | for name in a:settings.client 283 | if name ==? 'python' 284 | let name = 'python3' 285 | if !has('python3') && has('python') 286 | " python2 fallback 287 | let name = 'python2' 288 | endif 289 | endif 290 | if has_key(s:clients, name) && s:clients[name].available(a:settings) 291 | return s:clients[name] 292 | endif 293 | endfor 294 | return {} 295 | endfunction 296 | 297 | " implements clients 298 | let s:clients = {} 299 | 300 | let s:clients.curl = {} 301 | 302 | let s:clients.curl.errcode = {} 303 | let s:clients.curl.errcode[1] = 'Unsupported protocol. This build of curl has no support for this protocol.' 304 | let s:clients.curl.errcode[2] = 'Failed to initialize.' 305 | let s:clients.curl.errcode[3] = 'URL malformed. The syntax was not correct.' 306 | let s:clients.curl.errcode[4] = 'A feature or option that was needed to perform the desired request was not enabled or was explicitly disabled at buildtime. To make curl able to do this, you probably need another build of libcurl!' 307 | let s:clients.curl.errcode[5] = 'Couldn''t resolve proxy. The given proxy host could not be resolved.' 308 | let s:clients.curl.errcode[6] = 'Couldn''t resolve host. The given remote host was not resolved.' 309 | let s:clients.curl.errcode[7] = 'Failed to connect to host.' 310 | let s:clients.curl.errcode[8] = 'FTP weird server reply. The server sent data curl couldn''t parse.' 311 | let s:clients.curl.errcode[9] = 'FTP access denied. The server denied login or denied access to the particular resource or directory you wanted to reach. Most often you tried to change to a directory that doesn''t exist on the server.' 312 | let s:clients.curl.errcode[11] = 'FTP weird PASS reply. Curl couldn''t parse the reply sent to the PASS request.' 313 | let s:clients.curl.errcode[13] = 'FTP weird PASV reply, Curl couldn''t parse the reply sent to the PASV request.' 314 | let s:clients.curl.errcode[14] = 'FTP weird 227 format. Curl couldn''t parse the 227-line the server sent.' 315 | let s:clients.curl.errcode[15] = 'FTP can''t get host. Couldn''t resolve the host IP we got in the 227-line.' 316 | let s:clients.curl.errcode[17] = 'FTP couldn''t set binary. Couldn''t change transfer method to binary.' 317 | let s:clients.curl.errcode[18] = 'Partial file. Only a part of the file was transferred.' 318 | let s:clients.curl.errcode[19] = 'FTP couldn''t download/access the given file, the RETR (or similar) command failed.' 319 | let s:clients.curl.errcode[21] = 'FTP quote error. A quote command returned error from the server.' 320 | let s:clients.curl.errcode[22] = 'HTTP page not retrieved. The requested url was not found or returned another error with the HTTP error code being 400 or above. This return code only appears if -f, --fail is used.' 321 | let s:clients.curl.errcode[23] = 'Write error. Curl couldn''t write data to a local filesystem or similar.' 322 | let s:clients.curl.errcode[25] = 'FTP couldn''t STOR file. The server denied the STOR operation, used for FTP uploading.' 323 | let s:clients.curl.errcode[26] = 'Read error. Various reading problems.' 324 | let s:clients.curl.errcode[27] = 'Out of memory. A memory allocation request failed.' 325 | let s:clients.curl.errcode[28] = 'Operation timeout. The specified time-out period was reached according to the conditions.' 326 | let s:clients.curl.errcode[30] = 'FTP PORT failed. The PORT command failed. Not all FTP servers support the PORT command, try doing a transfer using PASV instead!' 327 | let s:clients.curl.errcode[31] = 'FTP couldn''t use REST. The REST command failed. This command is used for resumed FTP transfers.' 328 | let s:clients.curl.errcode[33] = 'HTTP range error. The range "command" didn''t work.' 329 | let s:clients.curl.errcode[34] = 'HTTP post error. Internal post-request generation error.' 330 | let s:clients.curl.errcode[35] = 'SSL connect error. The SSL handshaking failed.' 331 | let s:clients.curl.errcode[36] = 'FTP bad download resume. Couldn''t continue an earlier aborted download.' 332 | let s:clients.curl.errcode[37] = 'FILE couldn''t read file. Failed to open the file. Permissions?' 333 | let s:clients.curl.errcode[38] = 'LDAP cannot bind. LDAP bind operation failed.' 334 | let s:clients.curl.errcode[39] = 'LDAP search failed.' 335 | let s:clients.curl.errcode[41] = 'Function not found. A required LDAP function was not found.' 336 | let s:clients.curl.errcode[42] = 'Aborted by callback. An application told curl to abort the operation.' 337 | let s:clients.curl.errcode[43] = 'Internal error. A function was called with a bad parameter.' 338 | let s:clients.curl.errcode[45] = 'Interface error. A specified outgoing interface could not be used.' 339 | let s:clients.curl.errcode[47] = 'Too many redirects. When following redirects, curl hit the maximum amount.' 340 | let s:clients.curl.errcode[48] = 'Unknown option specified to libcurl. This indicates that you passed a weird option to curl that was passed on to libcurl and rejected. Read up in the manual!' 341 | let s:clients.curl.errcode[49] = 'Malformed telnet option.' 342 | let s:clients.curl.errcode[51] = 'The peer''s SSL certificate or SSH MD5 fingerprint was not OK.' 343 | let s:clients.curl.errcode[52] = 'The server didn''t reply anything, which here is considered an error.' 344 | let s:clients.curl.errcode[53] = 'SSL crypto engine not found.' 345 | let s:clients.curl.errcode[54] = 'Cannot set SSL crypto engine as default.' 346 | let s:clients.curl.errcode[55] = 'Failed sending network data.' 347 | let s:clients.curl.errcode[56] = 'Failure in receiving network data.' 348 | let s:clients.curl.errcode[58] = 'Problem with the local certificate.' 349 | let s:clients.curl.errcode[59] = 'Couldn''t use specified SSL cipher.' 350 | let s:clients.curl.errcode[60] = 'Peer certificate cannot be authenticated with known CA certificates.' 351 | let s:clients.curl.errcode[61] = 'Unrecognized transfer encoding.' 352 | let s:clients.curl.errcode[62] = 'Invalid LDAP URL.' 353 | let s:clients.curl.errcode[63] = 'Maximum file size exceeded.' 354 | let s:clients.curl.errcode[64] = 'Requested FTP SSL level failed.' 355 | let s:clients.curl.errcode[65] = 'Sending the data requires a rewind that failed.' 356 | let s:clients.curl.errcode[66] = 'Failed to initialise SSL Engine.' 357 | let s:clients.curl.errcode[67] = 'The user name, password, or similar was not accepted and curl failed to log in.' 358 | let s:clients.curl.errcode[68] = 'File not found on TFTP server.' 359 | let s:clients.curl.errcode[69] = 'Permission problem on TFTP server.' 360 | let s:clients.curl.errcode[70] = 'Out of disk space on TFTP server.' 361 | let s:clients.curl.errcode[71] = 'Illegal TFTP operation.' 362 | let s:clients.curl.errcode[72] = 'Unknown TFTP transfer ID.' 363 | let s:clients.curl.errcode[73] = 'File already exists (TFTP).' 364 | let s:clients.curl.errcode[74] = 'No such user (TFTP).' 365 | let s:clients.curl.errcode[75] = 'Character conversion failed.' 366 | let s:clients.curl.errcode[76] = 'Character conversion functions required.' 367 | let s:clients.curl.errcode[77] = 'Problem with reading the SSL CA cert (path? access rights?).' 368 | let s:clients.curl.errcode[78] = 'The resource referenced in the URL does not exist.' 369 | let s:clients.curl.errcode[79] = 'An unspecified error occurred during the SSH session.' 370 | let s:clients.curl.errcode[80] = 'Failed to shut down the SSL connection.' 371 | let s:clients.curl.errcode[82] = 'Could not load CRL file, missing or wrong format (added in 7.19.0).' 372 | let s:clients.curl.errcode[83] = 'Issuer check failed (added in 7.19.0).' 373 | let s:clients.curl.errcode[84] = 'The FTP PRET command failed' 374 | let s:clients.curl.errcode[85] = 'RTSP: mismatch of CSeq numbers' 375 | let s:clients.curl.errcode[86] = 'RTSP: mismatch of Session Identifiers' 376 | let s:clients.curl.errcode[87] = 'unable to parse FTP file list' 377 | let s:clients.curl.errcode[88] = 'FTP chunk callback reported error' 378 | let s:clients.curl.errcode[89] = 'No connection available, the session will be queued' 379 | let s:clients.curl.errcode[90] = 'SSL public key does not matched pinned public key' 380 | 381 | 382 | function! s:clients.curl.available(settings) abort 383 | return executable(self._command(a:settings)) 384 | endfunction 385 | 386 | function! s:clients.curl._command(settings) abort 387 | return get(get(a:settings, 'command', {}), 'curl', 'curl') 388 | endfunction 389 | 390 | function! s:clients.curl.request(settings) abort 391 | let quote = s:_quote() 392 | let command = self._command(a:settings) 393 | if has_key(a:settings, 'unixSocket') 394 | let command .= ' --unix-socket ' . quote . a:settings.unixSocket . quote 395 | endif 396 | let a:settings._file.header = s:_tempname() 397 | let command .= ' --dump-header ' . quote . a:settings._file.header . quote 398 | let has_output_file = has_key(a:settings, 'outputFile') 399 | if has_output_file 400 | let output_file = a:settings.outputFile 401 | else 402 | let output_file = s:_tempname() 403 | let a:settings._file.content = output_file 404 | endif 405 | let command .= ' --output ' . quote . output_file . quote 406 | if has_key(a:settings, 'gzipDecompress') && a:settings.gzipDecompress 407 | let command .= ' --compressed ' 408 | endif 409 | let command .= ' -L -s -k ' 410 | if a:settings.method ==? 'HEAD' 411 | let command .= '--head' 412 | else 413 | let command .= '-X ' . a:settings.method 414 | endif 415 | let command .= ' --max-redirs ' . a:settings.maxRedirect 416 | let command .= s:_make_header_args(a:settings.headers, '-H ', quote) 417 | let timeout = get(a:settings, 'timeout', '') 418 | let command .= ' --retry ' . a:settings.retry 419 | if timeout =~# '^\d\+$' 420 | let command .= ' --max-time ' . timeout 421 | endif 422 | if has_key(a:settings, 'username') 423 | let auth = a:settings.username . ':' . get(a:settings, 'password', '') 424 | let auth = escape(auth, quote) 425 | if has_key(a:settings, 'authMethod') 426 | if index(['basic', 'digest', 'ntlm', 'negotiate'], a:settings.authMethod) == -1 427 | throw 'vital: Web.HTTP: Invalid authorization method: ' . a:settings.authMethod 428 | endif 429 | let method = a:settings.authMethod 430 | else 431 | let method = 'anyauth' 432 | endif 433 | let command .= ' --' . method . ' --user ' . quote . auth . quote 434 | endif 435 | if has_key(a:settings, 'bearerToken') 436 | \ && has_key(a:settings, 'authMethod') && (a:settings.authMethod ==? 'oauth2') 437 | let command .= ' --oauth2-bearer ' . quote . a:settings.bearerToken . quote 438 | endif 439 | if has_key(a:settings, 'data') 440 | let a:settings._file.post = s:_make_postfile(a:settings.data) 441 | let command .= ' --data-binary @' . quote . a:settings._file.post . quote 442 | endif 443 | let command .= ' ' . quote . a:settings.url . quote 444 | 445 | call s:Process.system(command) 446 | let retcode = s:Process.get_last_status() 447 | 448 | let headerstr = s:_readfile(a:settings._file.header) 449 | let header_chunks = split(headerstr, "\r\n\r\n") 450 | let headers = map(header_chunks, 'split(v:val, "\r\n")') 451 | if retcode != 0 && empty(headers) 452 | if has_key(s:clients.curl.errcode, retcode) 453 | throw 'vital: Web.HTTP: ' . s:clients.curl.errcode[retcode] 454 | else 455 | throw 'vital: Web.HTTP: Unknown error code has occurred in curl: code=' . retcode 456 | endif 457 | endif 458 | if !empty(headers) 459 | let responses = map(headers, '[v:val, ""]') 460 | else 461 | let responses = [[[], '']] 462 | endif 463 | if has_output_file || a:settings.method ==? 'HEAD' 464 | let content = '' 465 | else 466 | let content = s:_readfile(output_file) 467 | endif 468 | let responses[-1][1] = content 469 | return responses 470 | endfunction 471 | 472 | let s:clients.wget = {} 473 | let s:clients.wget.errcode = {} 474 | let s:clients.wget.errcode[1] = 'Generic error code.' 475 | let s:clients.wget.errcode[2] = 'Parse error---for instance, when parsing command-line options, the .wgetrc or .netrc...' 476 | let s:clients.wget.errcode[3] = 'File I/O error.' 477 | let s:clients.wget.errcode[4] = 'Network failure.' 478 | let s:clients.wget.errcode[5] = 'SSL verification failure.' 479 | let s:clients.wget.errcode[6] = 'Username/password authentication failure.' 480 | let s:clients.wget.errcode[7] = 'Protocol errors.' 481 | let s:clients.wget.errcode[8] = 'Server issued an error response.' 482 | 483 | 484 | function! s:clients.wget.available(settings) abort 485 | if has_key(a:settings, 'authMethod') 486 | return 0 487 | endif 488 | return executable(self._command(a:settings)) 489 | endfunction 490 | 491 | function! s:clients.wget._command(settings) abort 492 | return get(get(a:settings, 'command', {}), 'wget', 'wget') 493 | endfunction 494 | 495 | function! s:clients.wget.request(settings) abort 496 | if has_key(a:settings, 'unixSocket') 497 | throw 'vital: Web.HTTP: unixSocket only can be used with the curl.' 498 | endif 499 | let quote = s:_quote() 500 | let command = self._command(a:settings) 501 | let method = a:settings.method 502 | if method ==# 'HEAD' 503 | let command .= ' --spider' 504 | elseif method !=# 'GET' && method !=# 'POST' 505 | let a:settings.headers['X-HTTP-Method-Override'] = a:settings.method 506 | endif 507 | let a:settings._file.header = s:_tempname() 508 | let command .= ' -o ' . quote . a:settings._file.header . quote 509 | let has_output_file = has_key(a:settings, 'outputFile') 510 | if has_output_file 511 | let output_file = a:settings.outputFile 512 | else 513 | let output_file = s:_tempname() 514 | let a:settings._file.content = output_file 515 | endif 516 | let command .= ' -O ' . quote . output_file . quote 517 | let command .= ' --server-response -q -L ' 518 | let command .= ' --max-redirect=' . a:settings.maxRedirect 519 | let command .= s:_make_header_args(a:settings.headers, '--header=', quote) 520 | let timeout = get(a:settings, 'timeout', '') 521 | let command .= ' --tries=' . a:settings.retry 522 | if timeout =~# '^\d\+$' 523 | let command .= ' --timeout=' . timeout 524 | endif 525 | if has_key(a:settings, 'username') 526 | let command .= ' --http-user=' . quote . escape(a:settings.username, quote) . quote 527 | endif 528 | if has_key(a:settings, 'password') 529 | let command .= ' --http-password=' . quote . escape(a:settings.password, quote) . quote 530 | endif 531 | if has_key(a:settings, 'bearerToken') 532 | let command .= ' --header=' . quote . 'Authorization: Bearer ' . a:settings.bearerToken . quote 533 | endif 534 | let command .= ' ' . quote . a:settings.url . quote 535 | if has_key(a:settings, 'data') 536 | let a:settings._file.post = s:_make_postfile(a:settings.data) 537 | let command .= ' --post-file=' . quote . a:settings._file.post . quote 538 | endif 539 | 540 | call s:Process.system(command) 541 | let retcode = s:Process.get_last_status() 542 | 543 | if filereadable(a:settings._file.header) 544 | let header_lines = readfile(a:settings._file.header, 'b') 545 | call map(header_lines, 'matchstr(v:val, "^\\s*\\zs.*")') 546 | let headerstr = join(header_lines, "\r\n") 547 | let header_chunks = split(headerstr, '\r\n\zeHTTP/\%(1\.\d\|2\)') 548 | let headers = map(header_chunks, 'split(v:val, "\r\n")') 549 | let responses = map(headers, '[v:val, ""]') 550 | else 551 | let headers = [] 552 | let responses = [[[], '']] 553 | endif 554 | if has_key(s:clients.wget.errcode, retcode) && empty(headers) 555 | throw 'vital: Web.HTTP: ' . s:clients.wget.errcode[retcode] 556 | endif 557 | if has_output_file 558 | let content = '' 559 | else 560 | let content = s:_readfile(output_file) 561 | endif 562 | let responses[-1][1] = content 563 | return responses 564 | endfunction 565 | 566 | let s:clients.python3 = {} 567 | 568 | function! s:clients.python3.available(settings) abort 569 | if !has('python3') 570 | return 0 571 | endif 572 | if has_key(a:settings, 'outputFile') 573 | " 'outputFile' is not supported yet 574 | return 0 575 | endif 576 | if get(a:settings, 'retry', 0) != 1 577 | " 'retry' is not supported yet 578 | return 0 579 | endif 580 | if has_key(a:settings, 'authMethod') 581 | return 0 582 | endif 583 | return 1 584 | endfunction 585 | 586 | function! s:clients.python3.request(settings) abort 587 | if has_key(a:settings, 'unixSocket') 588 | throw 'vital: Web.HTTP: unixSocket only can be used with the curl.' 589 | endif 590 | 591 | " TODO: retry, outputFile 592 | let responses = [] 593 | execute 'py3file' s:py3source 594 | return responses 595 | endfunction 596 | 597 | let s:clients.python2 = {} 598 | 599 | function! s:clients.python2.available(settings) abort 600 | if !has('python') 601 | return 0 602 | endif 603 | if has_key(a:settings, 'outputFile') 604 | " 'outputFile' is not supported yet 605 | return 0 606 | endif 607 | if get(a:settings, 'retry', 0) != 1 608 | " 'retry' is not supported yet 609 | return 0 610 | endif 611 | if has_key(a:settings, 'authMethod') 612 | return 0 613 | endif 614 | return 1 615 | endfunction 616 | 617 | function! s:clients.python2.request(settings) abort 618 | if has_key(a:settings, 'unixSocket') 619 | throw 'vital: Web.HTTP: unixSocket only can be used with the curl.' 620 | endif 621 | 622 | " TODO: retry, outputFile 623 | let responses = [] 624 | execute 'pyfile' s:py2source 625 | return responses 626 | endfunction 627 | 628 | 629 | function! s:_quote() abort 630 | return &shell =~# 'sh$' ? "'" : '"' 631 | endfunction 632 | 633 | let &cpo = s:save_cpo 634 | unlet s:save_cpo 635 | 636 | " vim:set et ts=2 sts=2 sw=2 tw=0: 637 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Web/HTTP_python2.py: -------------------------------------------------------------------------------- 1 | try: 2 | class DummyClassForLocalScope: 3 | def main(): 4 | try: 5 | from StringIO import StringIO 6 | except ImportError: 7 | from io import StringIO 8 | import vim, urllib2, socket, gzip 9 | 10 | responses = vim.bindeval('responses') 11 | 12 | class CustomHTTPRedirectHandler(urllib2.HTTPRedirectHandler): 13 | def __init__(self, max_redirect): 14 | self.max_redirect = max_redirect 15 | 16 | def redirect_request(self, req, fp, code, msg, headers, newurl): 17 | if self.max_redirect == 0: 18 | return None 19 | if 0 < self.max_redirect: 20 | self.max_redirect -= 1 21 | header_list = filter(None, str(headers).split("\r\n")) 22 | responses.extend([[[status(code, msg)] + header_list, fp.read()]]) 23 | return urllib2.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) 24 | 25 | def vimlist2str(list): 26 | if not list: 27 | return None 28 | return "\n".join([s.replace("\n", "\0") for s in list]) 29 | 30 | def status(code, msg): 31 | return "HTTP/1.0 %d %s\r\n" % (code, msg) 32 | 33 | def access(): 34 | settings = vim.eval('a:settings') 35 | data = vimlist2str(settings.get('data')) 36 | timeout = settings.get('timeout') 37 | if timeout: 38 | timeout = float(timeout) 39 | request_headers = settings.get('headers') 40 | max_redirect = int(settings.get('maxRedirect')) 41 | director = urllib2.build_opener(CustomHTTPRedirectHandler(max_redirect)) 42 | if settings.has_key('username'): 43 | passman = urllib2.HTTPPasswordMgrWithDefaultRealm() 44 | passman.add_password( 45 | None, 46 | settings['url'], 47 | settings['username'], 48 | settings.get('password', '')) 49 | basicauth = urllib2.HTTPBasicAuthHandler(passman) 50 | digestauth = urllib2.HTTPDigestAuthHandler(passman) 51 | director.add_handler(basicauth) 52 | director.add_handler(digestauth) 53 | if settings.has_key('bearerToken'): 54 | request_headers.setdefault('Authorization', 'Bearer ' + settings['bearerToken']) 55 | req = urllib2.Request(settings['url'], data, request_headers) 56 | req.get_method = lambda: settings['method'] 57 | default_timeout = socket.getdefaulttimeout() 58 | res = None 59 | try: 60 | # for Python 2.5 or before 61 | socket.setdefaulttimeout(timeout) 62 | res = director.open(req, timeout=timeout) 63 | except urllib2.HTTPError as e: 64 | res = e 65 | except urllib2.URLError: 66 | return ('', '') 67 | except socket.timeout: 68 | return ('', '') 69 | finally: 70 | socket.setdefaulttimeout(default_timeout) 71 | 72 | if res is None: 73 | return ('', '') 74 | 75 | st = status(res.code, res.msg) 76 | response_headers = st + ''.join(res.info().headers) 77 | response_body = res.read() 78 | 79 | gzip_decompress = settings.get('gzipDecompress', False) 80 | if gzip_decompress: 81 | buf = StringIO(response_body) 82 | f = gzip.GzipFile(fileobj=buf) 83 | response_body = f.read()[:-1] 84 | 85 | return (response_headers, response_body) 86 | 87 | (header, body) = access() 88 | responses.extend([[header.split("\r\n"), body]]) 89 | 90 | main() 91 | raise RuntimeError("Exit from local scope") 92 | 93 | except RuntimeError as exception: 94 | if exception.args != ("Exit from local scope",): 95 | raise exception 96 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Web/HTTP_python3.py: -------------------------------------------------------------------------------- 1 | try: 2 | class DummyClassForLocalScope: 3 | def main(): 4 | try: 5 | from StringIO import StringIO 6 | except ImportError: 7 | from io import StringIO 8 | import vim, urllib.request, urllib.error, socket, gzip 9 | 10 | responses = vim.bindeval('responses') 11 | 12 | class CustomHTTPRedirectHandler(urllib.request.HTTPRedirectHandler): 13 | def __init__(self, max_redirect): 14 | self.max_redirect = max_redirect 15 | 16 | def redirect_request(self, req, fp, code, msg, headers, newurl): 17 | if self.max_redirect == 0: 18 | return None 19 | if 0 < self.max_redirect: 20 | self.max_redirect -= 1 21 | header_list = list(filter(None, str(headers).split("\r\n"))) 22 | responses.extend([[[status(code, msg)] + header_list, fp.read()]]) 23 | return urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) 24 | 25 | def vimlist2str(list): 26 | if not list: 27 | return None 28 | return "\n".join([s.replace("\n", "\0") for s in list]) 29 | 30 | def status(code, msg): 31 | return "HTTP/1.0 %d %s\r\n" % (code, msg) 32 | 33 | def access(): 34 | settings = vim.eval('a:settings') 35 | data = vimlist2str(settings.get('data')) 36 | timeout = settings.get('timeout') 37 | if timeout: 38 | timeout = float(timeout) 39 | request_headers = settings.get('headers') 40 | max_redirect = int(settings.get('maxRedirect')) 41 | director = urllib.request.build_opener(CustomHTTPRedirectHandler(max_redirect)) 42 | if 'username' in settings: 43 | passman = urllib.request.HTTPPasswordMgrWithDefaultRealm() 44 | passman.add_password( 45 | None, 46 | settings['url'], 47 | settings['username'], 48 | settings.get('password', '')) 49 | basicauth = urllib.request.HTTPBasicAuthHandler(passman) 50 | digestauth = urllib.request.HTTPDigestAuthHandler(passman) 51 | director.add_handler(basicauth) 52 | director.add_handler(digestauth) 53 | if 'bearerToken' in settings: 54 | request_headers.setdefault('Authorization', 'Bearer ' + settings['bearerToken']) 55 | req = urllib.request.Request(settings['url'], data, request_headers) 56 | req.get_method = lambda: settings['method'] 57 | default_timeout = socket.getdefaulttimeout() 58 | res = None 59 | try: 60 | # for Python 2.5 or before <- Is this needed? 61 | socket.setdefaulttimeout(timeout) 62 | res = director.open(req, timeout=timeout) 63 | except urllib.error.HTTPError as e: 64 | res = e 65 | except (urllib.error.URLError, urllib.error.ContentTooShortError): 66 | return ('', '') 67 | except socket.timeout: 68 | return ('', '') 69 | finally: 70 | socket.setdefaulttimeout(default_timeout) 71 | 72 | if res is None: 73 | return ('', '') 74 | 75 | st = status(res.code, res.msg) 76 | response_headers = st + ''.join(res.headers) 77 | response_body = res.read() 78 | 79 | gzip_decompress = settings.get('gzipDecompress', False) 80 | if gzip_decompress: 81 | buf = StringIO(response_body) 82 | f = gzip.GzipFile(fileobj=buf) 83 | response_body = f.read()[:-1] 84 | 85 | return (response_headers, response_body) 86 | 87 | (header, body) = access() 88 | responses.extend([[header.split("\r\n"), body]]) 89 | 90 | main() 91 | raise RuntimeError("Exit from local scope") 92 | 93 | except RuntimeError as exception: 94 | if exception.args != ("Exit from local scope",): 95 | raise exception 96 | -------------------------------------------------------------------------------- /autoload/vital/_reading_vimrc/Web/JSON.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify 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#_reading_vimrc#Web#JSON#import() abort', printf("return map({'decode': '', '_vital_depends': '', '_vital_created': '', 'encode': '', '_vital_loaded': ''}, \"vital#_reading_vimrc#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:control_chars = { 14 | \ '\': '\\', 15 | \ '"': '\"', 16 | \ "\x01": '\u0001', 17 | \ "\x02": '\u0002', 18 | \ "\x03": '\u0003', 19 | \ "\x04": '\u0004', 20 | \ "\x05": '\u0005', 21 | \ "\x06": '\u0006', 22 | \ "\x07": '\u0007', 23 | \ "\x08": '\b', 24 | \ "\x09": '\t', 25 | \ "\x0a": '\n', 26 | \ "\x0b": '\u000b', 27 | \ "\x0c": '\f', 28 | \ "\x0d": '\r', 29 | \ "\x0e": '\u000e', 30 | \ "\x0f": '\u000f', 31 | \ "\x10": '\u0010', 32 | \ "\x11": '\u0011', 33 | \ "\x12": '\u0012', 34 | \ "\x13": '\u0013', 35 | \ "\x14": '\u0014', 36 | \ "\x15": '\u0015', 37 | \ "\x16": '\u0016', 38 | \ "\x17": '\u0017', 39 | \ "\x18": '\u0018', 40 | \ "\x19": '\u0019', 41 | \ "\x1a": '\u001a', 42 | \ "\x1b": '\u001b', 43 | \ "\x1c": '\u001c', 44 | \ "\x1d": '\u001d', 45 | \ "\x1e": '\u001e', 46 | \ "\x1f": '\u001f', 47 | \ } 48 | lockvar s:control_chars 49 | 50 | function! s:_true() abort 51 | return 1 52 | endfunction 53 | 54 | function! s:_false() abort 55 | return 0 56 | endfunction 57 | 58 | function! s:_null() abort 59 | return 0 60 | endfunction 61 | 62 | function! s:_resolve(val, prefix) abort 63 | let t = type(a:val) 64 | if t == type('') 65 | let m = matchlist(a:val, '^' . a:prefix . '\(null\|true\|false\)$') 66 | if !empty(m) 67 | return s:const[m[1]] 68 | endif 69 | elseif t == type([]) || t == type({}) 70 | return map(a:val, 's:_resolve(v:val, a:prefix)') 71 | endif 72 | return a:val 73 | endfunction 74 | 75 | function! s:_vital_created(module) abort 76 | " define constant variables 77 | if !exists('s:const') 78 | let s:const = {} 79 | let s:const.true = function('s:_true') 80 | let s:const.false = function('s:_false') 81 | let s:const.null = function('s:_null') 82 | lockvar s:const 83 | endif 84 | call extend(a:module, s:const) 85 | endfunction 86 | 87 | function! s:_vital_loaded(V) abort 88 | let s:V = a:V 89 | let s:string = s:V.import('Data.String') 90 | endfunction 91 | 92 | function! s:_vital_depends() abort 93 | return ['Data.String'] 94 | endfunction 95 | 96 | " @vimlint(EVL102, 1, l:null) 97 | " @vimlint(EVL102, 1, l:true) 98 | " @vimlint(EVL102, 1, l:false) 99 | function! s:decode(json, ...) abort 100 | let settings = extend({ 101 | \ 'use_token': 0, 102 | \}, get(a:000, 0, {})) 103 | let json = iconv(a:json, 'utf-8', &encoding) 104 | let json = join(split(json, "\n"), '') 105 | let json = substitute(json, '\\u34;', '\\"', 'g') 106 | let json = substitute(json, '\\u\(\x\x\x\x\)', '\=s:string.nr2enc_char("0x".submatch(1))', 'g') 107 | " convert surrogate pair 108 | let json = substitute(json, '\([\uD800-\uDBFF]\)\([\uDC00-\uDFFF]\)', 109 | \ '\=nr2char(0x10000+and(0x7ff,char2nr(submatch(1)))*0x400+and(0x3ff,char2nr(submatch(2))))', 110 | \ 'g') 111 | if settings.use_token 112 | let prefix = '__Web.JSON__' 113 | while stridx(json, prefix) != -1 114 | let prefix .= '_' 115 | endwhile 116 | let [null,true,false] = map(['null','true','false'], 'prefix . v:val') 117 | sandbox return s:_resolve(eval(json), prefix) 118 | else 119 | let [null,true,false] = [s:const.null(),s:const.true(),s:const.false()] 120 | sandbox return eval(json) 121 | endif 122 | endfunction 123 | " @vimlint(EVL102, 0, l:null) 124 | " @vimlint(EVL102, 0, l:true) 125 | " @vimlint(EVL102, 0, l:false) 126 | 127 | function! s:encode(val, ...) abort 128 | let settings = extend({ 129 | \ 'indent': 0, 130 | \}, get(a:000, 0, {}) 131 | \) 132 | if type(a:val) == 0 133 | return a:val 134 | elseif type(a:val) == 1 135 | let s = substitute(a:val, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') 136 | let s = iconv(s, &encoding, 'utf-8') 137 | return '"' . s . '"' 138 | elseif type(a:val) == 2 139 | if s:const.true == a:val 140 | return 'true' 141 | elseif s:const.false == a:val 142 | return 'false' 143 | elseif s:const.null == a:val 144 | return 'null' 145 | else 146 | " backward compatibility 147 | return string(a:val) 148 | endif 149 | elseif type(a:val) == 3 150 | return s:_encode_list(a:val, settings) 151 | elseif type(a:val) == 4 152 | return s:_encode_dict(a:val, settings) 153 | else 154 | return string(a:val) 155 | endif 156 | endfunction 157 | 158 | " @vimlint(EVL102, 1, l:ns) 159 | function! s:_encode_list(val, settings) abort 160 | if empty(a:val) 161 | return '[]' 162 | elseif !a:settings.indent 163 | let encoded_candidates = map(copy(a:val), 's:encode(v:val, a:settings)') 164 | return printf('[%s]', join(encoded_candidates, ',')) 165 | else 166 | let previous_indent = get(a:settings, '_previous_indent') 167 | let indent = previous_indent + a:settings.indent 168 | let ns = extend(copy(a:settings), { 169 | \ '_previous_indent': indent, 170 | \}) 171 | let encoded_candidates = map( 172 | \ copy(a:val), 173 | \ printf('''%s'' . s:encode(v:val, ns)', repeat(' ', indent)), 174 | \) 175 | return printf( 176 | \ "[\n%s\n%s]", 177 | \ join(encoded_candidates, ",\n"), 178 | \ repeat(' ', previous_indent) 179 | \) 180 | endif 181 | endfunction 182 | " @vimlint(EVL102, 0, l:ns) 183 | 184 | " @vimlint(EVL102, 1, l:ns) 185 | function! s:_encode_dict(val, settings) abort 186 | if empty(a:val) 187 | return '{}' 188 | elseif !a:settings.indent 189 | let encoded_candidates = map(keys(a:val), 190 | \ 's:encode(v:val, a:settings) . '':'' . s:encode(a:val[v:val], a:settings)' 191 | \) 192 | return printf('{%s}', join(encoded_candidates, ',')) 193 | else 194 | let previous_indent = get(a:settings, '_previous_indent') 195 | let indent = previous_indent + a:settings.indent 196 | let ns = extend(copy(a:settings), { 197 | \ '_previous_indent': indent, 198 | \}) 199 | let encoded_candidates = map(keys(a:val), 200 | \ printf( 201 | \ '''%s'' . s:encode(v:val, ns) . '': '' . s:encode(a:val[v:val], ns)', 202 | \ repeat(' ', indent), 203 | \ ), 204 | \) 205 | return printf("{\n%s\n%s}", 206 | \ join(encoded_candidates, ",\n"), 207 | \ repeat(' ', previous_indent), 208 | \) 209 | endif 210 | endfunction 211 | " @vimlint(EVL102, 0, l:ns) 212 | 213 | let &cpo = s:save_cpo 214 | unlet s:save_cpo 215 | 216 | " vim:set et ts=2 sts=2 sw=2 tw=0: 217 | -------------------------------------------------------------------------------- /autoload/vital/reading_vimrc.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! vital#{s:plugin_name}#new() abort 10 | return s:new(s:plugin_name) 11 | endfunction 12 | 13 | function! vital#{s:plugin_name}#import(...) abort 14 | if !exists('s:V') 15 | let s:V = s:new(s:plugin_name) 16 | endif 17 | return call(s:V.import, a:000, s:V) 18 | endfunction 19 | 20 | let s:Vital = {} 21 | 22 | function! s:new(plugin_name) abort 23 | let base = deepcopy(s:Vital) 24 | let base._plugin_name = a:plugin_name 25 | return base 26 | endfunction 27 | 28 | function! s:vital_files() abort 29 | if !exists('s:vital_files') 30 | let s:vital_files = map( 31 | \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), 32 | \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') 33 | endif 34 | return copy(s:vital_files) 35 | endfunction 36 | let s:Vital.vital_files = function('s:vital_files') 37 | 38 | function! s:import(name, ...) abort dict 39 | let target = {} 40 | let functions = [] 41 | for a in a:000 42 | if type(a) == type({}) 43 | let target = a 44 | elseif type(a) == type([]) 45 | let functions = a 46 | endif 47 | unlet a 48 | endfor 49 | let module = self._import(a:name) 50 | if empty(functions) 51 | call extend(target, module, 'keep') 52 | else 53 | for f in functions 54 | if has_key(module, f) && !has_key(target, f) 55 | let target[f] = module[f] 56 | endif 57 | endfor 58 | endif 59 | return target 60 | endfunction 61 | let s:Vital.import = function('s:import') 62 | 63 | function! s:load(...) abort dict 64 | for arg in a:000 65 | let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] 66 | let target = split(join(as, ''), '\W\+') 67 | let dict = self 68 | let dict_type = type({}) 69 | while !empty(target) 70 | let ns = remove(target, 0) 71 | if !has_key(dict, ns) 72 | let dict[ns] = {} 73 | endif 74 | if type(dict[ns]) == dict_type 75 | let dict = dict[ns] 76 | else 77 | unlet dict 78 | break 79 | endif 80 | endwhile 81 | if exists('dict') 82 | call extend(dict, self._import(name)) 83 | endif 84 | unlet arg 85 | endfor 86 | return self 87 | endfunction 88 | let s:Vital.load = function('s:load') 89 | 90 | function! s:unload() abort dict 91 | let s:loaded = {} 92 | let s:cache_sid = {} 93 | unlet! s:vital_files 94 | endfunction 95 | let s:Vital.unload = function('s:unload') 96 | 97 | function! s:exists(name) abort dict 98 | if a:name !~# '\v^\u\w*%(\.\u\w*)*$' 99 | throw 'vital: Invalid module name: ' . a:name 100 | endif 101 | return s:_module_path(a:name) isnot# '' 102 | endfunction 103 | let s:Vital.exists = function('s:exists') 104 | 105 | function! s:search(pattern) abort dict 106 | let paths = s:_extract_files(a:pattern, self.vital_files()) 107 | let modules = sort(map(paths, 's:_file2module(v:val)')) 108 | return uniq(modules) 109 | endfunction 110 | let s:Vital.search = function('s:search') 111 | 112 | function! s:plugin_name() abort dict 113 | return self._plugin_name 114 | endfunction 115 | let s:Vital.plugin_name = function('s:plugin_name') 116 | 117 | function! s:_self_vital_files() abort 118 | let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) 119 | let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) 120 | let base = builtin . ',' . installed 121 | return split(globpath(base, '**/*.vim', 1), "\n") 122 | endfunction 123 | 124 | function! s:_global_vital_files() abort 125 | let pattern = 'autoload/vital/__*__/**/*.vim' 126 | return split(globpath(&runtimepath, pattern, 1), "\n") 127 | endfunction 128 | 129 | function! s:_extract_files(pattern, files) abort 130 | let tr = {'.': '/', '*': '[^/]*', '**': '.*'} 131 | let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') 132 | let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) 133 | return filter(a:files, 'v:val =~# regexp') 134 | endfunction 135 | 136 | function! s:_file2module(file) abort 137 | let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') 138 | let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') 139 | return join(split(tail, '[\\/]\+'), '.') 140 | endfunction 141 | 142 | " @param {string} name e.g. Data.List 143 | function! s:_import(name) abort dict 144 | if has_key(s:loaded, a:name) 145 | return copy(s:loaded[a:name]) 146 | endif 147 | let module = self._get_module(a:name) 148 | if has_key(module, '_vital_created') 149 | call module._vital_created(module) 150 | endif 151 | let export_module = filter(copy(module), 'v:key =~# "^\\a"') 152 | " Cache module before calling module._vital_loaded() to avoid cyclic 153 | " dependences but remove the cache if module._vital_loaded() fails. 154 | " let s:loaded[a:name] = export_module 155 | let s:loaded[a:name] = export_module 156 | if has_key(module, '_vital_loaded') 157 | try 158 | call module._vital_loaded(vital#{s:plugin_name}#new()) 159 | catch 160 | unlet s:loaded[a:name] 161 | throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint) 162 | endtry 163 | endif 164 | return copy(s:loaded[a:name]) 165 | endfunction 166 | let s:Vital._import = function('s:_import') 167 | 168 | function! s:_format_throwpoint(throwpoint) abort 169 | let funcs = [] 170 | let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$') 171 | for line in split(stack, '\.\.') 172 | let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$') 173 | if !empty(m) 174 | let [name, lnum, lnum2] = m[1:3] 175 | if empty(lnum) 176 | let lnum = lnum2 177 | endif 178 | let info = s:_get_func_info(name) 179 | if !empty(info) 180 | let attrs = empty(info.attrs) ? '' : join([''] + info.attrs) 181 | let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum) 182 | call add(funcs, printf('function %s(...)%s Line:%d (%s%s)', 183 | \ info.funcname, attrs, lnum, info.filename, flnum)) 184 | continue 185 | endif 186 | endif 187 | " fallback when function information cannot be detected 188 | call add(funcs, line) 189 | endfor 190 | return join(funcs, "\n") 191 | endfunction 192 | 193 | function! s:_get_func_info(name) abort 194 | let name = a:name 195 | if a:name =~# '^\d\+$' " is anonymous-function 196 | let name = printf('{%s}', a:name) 197 | elseif a:name =~# '^\d\+$' " is lambda-function 198 | let name = printf("{'%s'}", a:name) 199 | endif 200 | if !exists('*' . name) 201 | return {} 202 | endif 203 | let body = execute(printf('verbose function %s', name)) 204 | let lines = split(body, "\n") 205 | let signature = matchstr(lines[0], '^\s*\zs.*') 206 | let [_, file, lnum; __] = matchlist(lines[1], 207 | \ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$') 208 | return { 209 | \ 'filename': substitute(file, '[/\\]\+', '/', 'g'), 210 | \ 'lnum': 0 + lnum, 211 | \ 'funcname': a:name, 212 | \ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'), 213 | \ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'), 214 | \ } 215 | endfunction 216 | 217 | " s:_get_module() returns module object wihch has all script local functions. 218 | function! s:_get_module(name) abort dict 219 | let funcname = s:_import_func_name(self.plugin_name(), a:name) 220 | try 221 | return call(funcname, []) 222 | catch /^Vim\%((\a\+)\)\?:E117:/ 223 | return s:_get_builtin_module(a:name) 224 | endtry 225 | endfunction 226 | 227 | function! s:_get_builtin_module(name) abort 228 | return s:sid2sfuncs(s:_module_sid(a:name)) 229 | endfunction 230 | 231 | if s:is_vital_vim 232 | " For vital.vim, we can use s:_get_builtin_module directly 233 | let s:Vital._get_module = function('s:_get_builtin_module') 234 | else 235 | let s:Vital._get_module = function('s:_get_module') 236 | endif 237 | 238 | function! s:_import_func_name(plugin_name, module_name) abort 239 | return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) 240 | endfunction 241 | 242 | function! s:_module_sid(name) abort 243 | let path = s:_module_path(a:name) 244 | if !filereadable(path) 245 | throw 'vital: module not found: ' . a:name 246 | endif 247 | let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) 248 | let base = join([vital_dir, ''], '[/\\]\+') 249 | let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') 250 | let sid = s:_sid(path, p) 251 | if !sid 252 | call s:_source(path) 253 | let sid = s:_sid(path, p) 254 | if !sid 255 | throw printf('vital: cannot get from path: %s', path) 256 | endif 257 | endif 258 | return sid 259 | endfunction 260 | 261 | function! s:_module_path(name) abort 262 | return get(s:_extract_files(a:name, s:vital_files()), 0, '') 263 | endfunction 264 | 265 | function! s:_module_sid_base_dir() abort 266 | return s:is_vital_vim ? &rtp : s:project_root 267 | endfunction 268 | 269 | function! s:_dot_to_sharp(name) abort 270 | return substitute(a:name, '\.', '#', 'g') 271 | endfunction 272 | 273 | function! s:_source(path) abort 274 | execute 'source' fnameescape(a:path) 275 | endfunction 276 | 277 | " @vimlint(EVL102, 1, l:_) 278 | " @vimlint(EVL102, 1, l:__) 279 | function! s:_sid(path, filter_pattern) abort 280 | let unified_path = s:_unify_path(a:path) 281 | if has_key(s:cache_sid, unified_path) 282 | return s:cache_sid[unified_path] 283 | endif 284 | for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') 285 | let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') 286 | if s:_unify_path(path) is# unified_path 287 | let s:cache_sid[unified_path] = sid 288 | return s:cache_sid[unified_path] 289 | endif 290 | endfor 291 | return 0 292 | endfunction 293 | 294 | if filereadable(expand(':r') . '.VIM') " is case-insensitive or not 295 | let s:_unify_path_cache = {} 296 | " resolve() is slow, so we cache results. 297 | " Note: On windows, vim can't expand path names from 8.3 formats. 298 | " So if getting full path via and $HOME was set as 8.3 format, 299 | " vital load duplicated scripts. Below's :~ avoid this issue. 300 | function! s:_unify_path(path) abort 301 | if has_key(s:_unify_path_cache, a:path) 302 | return s:_unify_path_cache[a:path] 303 | endif 304 | let value = tolower(fnamemodify(resolve(fnamemodify( 305 | \ a:path, ':p')), ':~:gs?[\\/]?/?')) 306 | let s:_unify_path_cache[a:path] = value 307 | return value 308 | endfunction 309 | else 310 | function! s:_unify_path(path) abort 311 | return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) 312 | endfunction 313 | endif 314 | 315 | " copied and modified from Vim.ScriptLocal 316 | let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') 317 | function! s:sid2sfuncs(sid) abort 318 | let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") 319 | let r = {} 320 | let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) 321 | for fname in map(fs, 'matchstr(v:val, pattern)') 322 | let r[fname] = function(s:_sfuncname(a:sid, fname)) 323 | endfor 324 | return r 325 | endfunction 326 | 327 | "" Return funcname of script local functions with SID 328 | function! s:_sfuncname(sid, funcname) abort 329 | return printf('%s_%s', a:sid, a:funcname) 330 | endfunction 331 | -------------------------------------------------------------------------------- /autoload/vital/reading_vimrc.vital: -------------------------------------------------------------------------------- 1 | reading_vimrc 2 | 5828301d6bae0858e9ea21012913544f5ef8e375 3 | 4 | Web.HTTP 5 | Web.JSON 6 | -------------------------------------------------------------------------------- /doc/reading_vimrc.txt: -------------------------------------------------------------------------------- 1 | *reading_vimrc.txt* file loading tool for reading-vimrc 2 | 3 | Version : 0.1.0 4 | Author : y0za 5 | License : MIT license 6 | 7 | ============================================================================== 8 | CONTENTS *reading_vimrc-contents* 9 | 10 | INTRODUCTION |reading_vimrc-introduction| 11 | INTERFACE |reading_vimrc-interface| 12 | COMMANDS |reading_vimrc-commands| 13 | KEY-MAPPINGS |reading_vimrc-key-mappings| 14 | CHANGELOG |reading_vimrc-changelog| 15 | 16 | 17 | ============================================================================== 18 | INTRODUCTION *reading_vimrc-introduction* 19 | 20 | *reading_vimrc* is file loading tool for reading-vimrc 21 | 22 | Requirements: 23 | - curl or wget 24 | 25 | Latest version: 26 | https://github.com/y0za/vim-reading-vimrc 27 | 28 | 29 | ============================================================================== 30 | INTERFACE *reading_vimrc-interface* 31 | 32 | ------------------------------------------------------------------------------ 33 | COMMANDS *reading_vimrc-commands* 34 | 35 | :ReadingVimrcLoad *:ReadingVimrcLoad* 36 | Load vimrcs of next reading-vimrc as new buffer. For example, after 37 | execution of this command, you could open vimrc file |:ls| and :e 38 | #[buffer number]. 39 | 40 | :ReadingVimrcList *:ReadingVimrcList* 41 | Do |:ReadingVimrcLoad| and show created buffer names. 42 | 43 | :ReadingVimrcNext *:ReadingVimrcNext* 44 | Open "readingvimrc://next". It shows next reading-vimrc info. 45 | And you can open files with when cursor on a file. 46 | 47 | 48 | ------------------------------------------------------------------------------ 49 | KEY-MAPPINGS *reading_vimrc-key-mappings* 50 | 51 | (reading_vimrc-update_clipboard) *(reading_vimrc-update_clipboard)* 52 | available mode: visual select 53 | 54 | 55 | ============================================================================== 56 | CHANGELOG *reading_vimrc-changelog* 57 | 58 | 0.1.0 2016-12-31 59 | - Add ReadingVimrcList command 60 | - Implements to register label for reading_vimrc bot in gitter. 61 | (contributed by MaxMEllon) 62 | 63 | 0.0.1 2016-10-11 64 | - Init. 65 | 66 | ============================================================================== 67 | vim:tw=78:ts=8:ft=help:norl: 68 | -------------------------------------------------------------------------------- /plugin/reading_vimrc.vim: -------------------------------------------------------------------------------- 1 | " file loading tool for reading-vimrc 2 | " Version: 0.1.0 3 | " Author: y0za 4 | " License: MIT License 5 | 6 | if exists('g:loaded_reading_vimrc') 7 | finish 8 | endif 9 | let g:loaded_reading_vimrc = 1 10 | 11 | let s:save_cpo = &cpo 12 | set cpo&vim 13 | 14 | command! -nargs=0 15 | \ ReadingVimrcLoad 16 | \ call reading_vimrc#load() 17 | 18 | command! -nargs=0 19 | \ ReadingVimrcList 20 | \ call reading_vimrc#list() 21 | 22 | command! -nargs=0 23 | \ ReadingVimrcNext 24 | \ call reading_vimrc#next() 25 | 26 | vnoremap (reading_vimrc-update_clipboard) :call reading_vimrc#update_clipboard() 27 | 28 | augroup reading_vimrc 29 | autocmd! 30 | autocmd BufReadCmd readingvimrc://* 31 | \ call reading_vimrc#buffer#load_content(expand('')) 32 | augroup END 33 | 34 | let &cpo = s:save_cpo 35 | unlet s:save_cpo 36 | -------------------------------------------------------------------------------- /test/reading_vimrc/buffer.vim: -------------------------------------------------------------------------------- 1 | let s:suite = themis#suite('url') 2 | let s:assert = themis#helper('assert') 3 | 4 | function! s:suite.parse_name() abort 5 | let test_cases = [ 6 | \ { 7 | \ 'name': 'readingvimrc://next/someone/dotfiles/master/.vimrc', 8 | \ 'expected': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': '.vimrc'} 9 | \ }, 10 | \ { 11 | \ 'name': 'readingvimrc://next/someone/dotfiles/master/vim/.vimrc', 12 | \ 'expected': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': 'vim/.vimrc'} 13 | \ }, 14 | \ { 15 | \ 'name': 'readingvimrc://next', 16 | \ 'expected': {'nth': 'next'} 17 | \ }, 18 | \ { 19 | \ 'name': 'readingvimrc://next/someone/dotfiles', 20 | \ 'expected': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles'} 21 | \ }, 22 | \ ] 23 | for tc in test_cases 24 | call s:assert.equals(reading_vimrc#buffer#parse_name(tc.name), tc.expected) 25 | endfor 26 | endfunction 27 | 28 | function! s:suite.name() abort 29 | let test_cases = [ 30 | \ { 31 | \ 'info': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': '.vimrc'}, 32 | \ 'expected': 'readingvimrc://next/someone/dotfiles/master/.vimrc' 33 | \ }, 34 | \ { 35 | \ 'info': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': 'vim/.vimrc'}, 36 | \ 'expected': 'readingvimrc://next/someone/dotfiles/master/vim/.vimrc' 37 | \ }, 38 | \ { 39 | \ 'info': {'nth': 'next'}, 40 | \ 'expected': 'readingvimrc://next' 41 | \ }, 42 | \ { 43 | \ 'info': {'nth': 'next', 'user': 'someone', 'repo': 'dotfiles'}, 44 | \ 'expected': 'readingvimrc://next/someone/dotfiles' 45 | \ }, 46 | \ ] 47 | for tc in test_cases 48 | call s:assert.equals(reading_vimrc#buffer#name(tc.info), tc.expected) 49 | endfor 50 | endfunction 51 | 52 | -------------------------------------------------------------------------------- /test/reading_vimrc/url.vim: -------------------------------------------------------------------------------- 1 | let s:suite = themis#suite('url') 2 | let s:assert = themis#helper('assert') 3 | 4 | function! s:suite.parse_github_url() abort 5 | let test_cases = [ 6 | \ { 7 | \ 'url': 'https://github.com/someone/dotfiles/blob/master/.vimrc', 8 | \ 'expected': {'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': '.vimrc'} 9 | \ }, 10 | \ { 11 | \ 'url': 'https://github.com/someone/dotfiles/blob/master/vim/.vimrc', 12 | \ 'expected': {'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': 'vim/.vimrc'} 13 | \ }, 14 | \ ] 15 | for tc in test_cases 16 | call s:assert.equals(reading_vimrc#url#parse_github_url(tc.url), tc.expected) 17 | endfor 18 | endfunction 19 | 20 | function! s:suite.raw_github_url() abort 21 | let test_cases = [ 22 | \ { 23 | \ 'info': {'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': '.vimrc'}, 24 | \ 'expected': 'https://raw.githubusercontent.com/someone/dotfiles/master/.vimrc' 25 | \ }, 26 | \ { 27 | \ 'info': {'user': 'someone', 'repo': 'dotfiles', 'branch': 'master', 'path': 'vim/.vimrc'}, 28 | \ 'expected': 'https://raw.githubusercontent.com/someone/dotfiles/master/vim/.vimrc' 29 | \ }, 30 | \ ] 31 | for tc in test_cases 32 | call s:assert.equals(reading_vimrc#url#raw_github_url(tc.info), tc.expected) 33 | endfor 34 | endfunction 35 | --------------------------------------------------------------------------------