├── test ├── run_themis.sh ├── .themisrc ├── sample.c ├── sample.c.orig └── unified_diff.vimspec ├── autoload ├── vital │ ├── unified_diff.vital │ ├── _unified_diff.vim │ ├── _unified_diff │ │ ├── System │ │ │ └── Job.vim │ │ └── Data │ │ │ ├── List.vim │ │ │ └── String.vim │ └── unified_diff.vim ├── vital.vim └── unified_diff.vim ├── plugin └── unified_diff.vim ├── .travis.yml ├── LICENSE ├── doc └── unified_diff.txt └── README.md /test/run_themis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | themis --reporter dot \ 3 | --runtimepath $HOME/.vim/bundle/vital.vim 4 | -------------------------------------------------------------------------------- /autoload/vital/unified_diff.vital: -------------------------------------------------------------------------------- 1 | unified_diff 2 | 0efc8fae63448387eed8da65ccb0fef0beeddd0f 3 | 4 | System.Job 5 | Data.String 6 | -------------------------------------------------------------------------------- /autoload/vital/_unified_diff.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 | -------------------------------------------------------------------------------- /plugin/unified_diff.vim: -------------------------------------------------------------------------------- 1 | if exists('g:unified_diff_loaded') 2 | finish 3 | endif 4 | let g:unified_diff_loaded = 1 5 | 6 | if get(g:, 'unified_diff_enabled', 1) 7 | set diffexpr=unified_diff#diffexpr() 8 | endif 9 | -------------------------------------------------------------------------------- /test/.themisrc: -------------------------------------------------------------------------------- 1 | let s:assert = themis#helper('assert') 2 | " currently in vimspec does not work thus I have to define the 3 | " variable as a global variable. 4 | let g:vital_vcs_git_repository_root = expand(':p:h:h') 5 | 6 | call themis#option('recursive', 1) 7 | call themis#helper('command').with(s:assert) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | install: 3 | - git clone https://github.com/thinca/vim-themis 4 | - git clone https://github.com/vim-jp/vital.vim 5 | script: 6 | - vim --version 7 | - vim --cmd "try | helptags doc/ | catch | cquit | endtry" --cmd quit 8 | - vim-themis/bin/themis --runtimepath vital.vim --reporter spec 9 | -------------------------------------------------------------------------------- /test/sample.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int fib(int n) 4 | { 5 | if(n > 2) 6 | { 7 | return fib(n-1) + fib(n-2) 8 | } 9 | return 1; 10 | } 11 | 12 | // Frobs foo heartily 13 | int frobnitz(int foo) 14 | { 15 | int i; 16 | for(i = 0; i < 10; i++) 17 | { 18 | printf("%d\n", foo); 19 | } 20 | } 21 | 22 | int main(int argc, char **argv) 23 | { 24 | frobnitz(fib(10)); 25 | } 26 | -------------------------------------------------------------------------------- /autoload/vital.vim: -------------------------------------------------------------------------------- 1 | function! vital#of(name) abort 2 | let files = globpath(&runtimepath, 'autoload/vital/' . a:name . '.vital', 1) 3 | let file = split(files, "\n") 4 | if empty(file) 5 | throw 'vital: version file not found: ' . a:name 6 | endif 7 | let ver = readfile(file[0], 'b') 8 | if empty(ver) 9 | throw 'vital: invalid version file: ' . a:name 10 | endif 11 | return vital#_{substitute(ver[0], '\W', '', 'g')}#new() 12 | endfunction 13 | -------------------------------------------------------------------------------- /test/sample.c.orig: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Frobs foo heartily 4 | int frobnitz(int foo) 5 | { 6 | int i; 7 | for(i = 0; i < 10; i++) 8 | { 9 | printf("Your answer is: "); 10 | printf("%d\n", foo); 11 | } 12 | } 13 | 14 | int fact(int n) 15 | { 16 | if(n > 1) 17 | { 18 | return fact(n-1) * n; 19 | } 20 | return 1; 21 | } 22 | 23 | int main(int argc, char **argv) 24 | { 25 | frobnitz(fact(10)); 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Alisue, hashnote.net 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /doc/unified_diff.txt: -------------------------------------------------------------------------------- 1 | *unified_diff.txt* Allow unified-diff in vimdiff 2 | 3 | Version: 0.2.1 4 | Author: Alisue 5 | Support: Vim 8.0.0027 and above 6 | Support: Neovim 0.1.7 and above 7 | License: MIT License (See LICENSE) 8 | 9 | 10 | ============================================================================= 11 | CONTENTS *unified_diff-contents* 12 | 13 | INTRODUCTION |unified_diff-introduction| 14 | USAGE |unified_diff-usage| 15 | 16 | 17 | ============================================================================= 18 | INTRODUCTION *unified_diff-introduction* 19 | 20 | *unified_diff* is a plugin to use an external diff program which only support 21 | unified-diff in |vimdiff|. 22 | 23 | In default, it uses "git diff --histogram" so that installing this plugin 24 | automatically improve your |vimdiff| quality. 25 | 26 | Latest version:~ 27 | https://github.com/lambdalisue/vim-unified-diff 28 | 29 | It was inspired by http://qiita.com/takaakikasai/items/3d4f8a4867364a46dfa3 30 | and written in pure vimscript. 31 | 32 | ============================================================================= 33 | USAGE *unified_diff-usage* 34 | 35 | Once users install this plugin, it automatically configures 'diffexpr'. 36 | To disable it, assign 0 to *g:unified_diff_enabled* variable. 37 | 38 | ============================================================================= 39 | vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deprecated 2 | 3 | [Vim 8.1.0360](https://github.com/vim/vim/commit/e828b7621cf9065a3582be0c4dd1e0e846e335bf) introduce `algorithm:{text}` to `diffopt` and natively supports the following algorithms 4 | 5 | - myers (default) 6 | - minimal 7 | - patience 8 | - histogram 9 | 10 | So that if you use this plugin to use one of the above algorithm, the plugin is no longer required and use setting like below: 11 | 12 | ```vim 13 | set diffopt& diffopt+=algorithm:histogram,indent-heuristic 14 | ``` 15 | 16 | See `:help diffopt` for detail. 17 | 18 | # vim-unified-diff 19 | [![Travis CI](https://img.shields.io/travis/lambdalisue/vim-unified-diff/master.svg?style=flat-square&label=Travis%20CI)](https://travis-ci.org/lambdalisue/vim-unified-diff) 20 | ![Version 0.2.1](https://img.shields.io/badge/version-0.2.1-yellow.svg?style=flat-square) 21 | ![Support Vim 8.0.0027 or above](https://img.shields.io/badge/support-Vim%208.0.0027%20or%20above-yellowgreen.svg?style=flat-square) 22 | ![Support Neovim 0.1.7 or above](https://img.shields.io/badge/support-Neovim%200.1.7%20or%20above-yellowgreen.svg?style=flat-square) 23 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 24 | [![Powered by vital.vim](https://img.shields.io/badge/powered%20by-vital.vim-80273f.svg?style=flat-square)](https://github.com/vim-jp/vital.vim) 25 | 26 | This plugin is a plugin to use an external diff program which only support 27 | unified-diff in `vimdiff`. 28 | 29 | In default, it uses `git diff --histogram` so that installing this plugin 30 | automatically improve your `vimdiff` quality. 31 | 32 | For example 33 | 34 | ![Builtin diff](https://raw.githubusercontent.com/lambdalisue/vim-unified-diff/misc/img/builtin_diff.png) 35 | 36 | It will be turn into 37 | 38 | ![Histogram diff](https://raw.githubusercontent.com/lambdalisue/vim-unified-diff/misc/img/histogram_diff.png) 39 | 40 | by this plugin. 41 | 42 | Inspired by http://qiita.com/takaakikasai/items/3d4f8a4867364a46dfa3 and written in pure vimscript. 43 | 44 | ## INSTALL 45 | 46 | ```vim 47 | NeoBundle 'lambdalisue/vim-unified-diff' 48 | Plug 'lambdalisue/vim-unified-diff' 49 | ``` 50 | 51 | ## USAGE 52 | 53 | Once users install this plugin, it automatically configures `diffexpr`. 54 | To disable it, assign 0 to `g:unified_diff_enabled` variable. 55 | -------------------------------------------------------------------------------- /test/unified_diff.vimspec: -------------------------------------------------------------------------------- 1 | Describe vim-unified-diff 2 | Context unified_diff#parse({unified}) 3 | It should return a normal diff 4 | let unified = [ 5 | \ '--- sample.c.orig 2015-03-26 02:22:29.343996331 +0900', 6 | \ '+++ sample.c 2015-03-26 02:23:24.735243780 +0900', 7 | \ '@@ -3,2 +3 @@', 8 | \ '-// Frobs foo heartily', 9 | \ '-int frobnitz(int foo)', 10 | \ '+int fib(int n)', 11 | \ '@@ -6,2 +5 @@', 12 | \ '- int i;', 13 | \ '- for(i = 0; i < 10; i++)', 14 | \ '+ if(n > 2)', 15 | \ '@@ -9,2 +7 @@', 16 | \ '- printf("Your answer is: ");', 17 | \ '- printf("%d\n", foo);', 18 | \ '+ return fib(n-1) + fib(n-2)', 19 | \ '@@ -11,0 +9 @@', 20 | \ '+ return 1;', 21 | \ '@@ -14 +12,2 @@', 22 | \ '-int fact(int n)', 23 | \ '+// Frobs foo heartily', 24 | \ '+int frobnitz(int foo)', 25 | \ '@@ -16 +15,2 @@', 26 | \ '- if(n > 1)', 27 | \ '+ int i;', 28 | \ '+ for(i = 0; i < 10; i++)', 29 | \ '@@ -18 +18 @@', 30 | \ '- return fact(n-1) * n;', 31 | \ '+ printf("%d\n", foo);', 32 | \ '@@ -20 +19,0 @@', 33 | \ '- return 1;', 34 | \ '@@ -25 +24 @@', 35 | \ '- frobnitz(fact(10));', 36 | \ '+ frobnitz(fib(10));', 37 | \] 38 | let result = unified_diff#parse(unified) 39 | let expect = [ 40 | \ '3,4c3', 41 | \ '< // Frobs foo heartily', 42 | \ '< int frobnitz(int foo)', 43 | \ '---', 44 | \ '> int fib(int n)', 45 | \ '6,7c5', 46 | \ '< int i;', 47 | \ '< for(i = 0; i < 10; i++)', 48 | \ '---', 49 | \ '> if(n > 2)', 50 | \ '9,10c7', 51 | \ '< printf("Your answer is: ");', 52 | \ '< printf("%d\n", foo);', 53 | \ '---', 54 | \ '> return fib(n-1) + fib(n-2)', 55 | \ '11a9', 56 | \ '> return 1;', 57 | \ '14c12,13', 58 | \ '< int fact(int n)', 59 | \ '---', 60 | \ '> // Frobs foo heartily', 61 | \ '> int frobnitz(int foo)', 62 | \ '16c15,16', 63 | \ '< if(n > 1)', 64 | \ '---', 65 | \ '> int i;', 66 | \ '> for(i = 0; i < 10; i++)', 67 | \ '18c18', 68 | \ '< return fact(n-1) * n;', 69 | \ '---', 70 | \ '> printf("%d\n", foo);', 71 | \ '20d19', 72 | \ '< return 1;', 73 | \ '25c24', 74 | \ '< frobnitz(fact(10));', 75 | \ '---', 76 | \ '> frobnitz(fib(10));', 77 | \] 78 | Assert Equals(result, expect) 79 | End 80 | End 81 | End 82 | -------------------------------------------------------------------------------- /autoload/unified_diff.vim: -------------------------------------------------------------------------------- 1 | let s:Job = vital#unified_diff#import('System.Job') 2 | let s:String = vital#unified_diff#import('Data.String') 3 | let s:t_string = type('') 4 | 5 | 6 | function! s:parse_unified_region(line) abort 7 | let m = matchlist( 8 | \ a:line, 9 | \ '^@@ -\(\d\+\)\%(,\(\d\+\)\)\? +\(\d\+\)\%(,\(\d\+\)\)\? @@' 10 | \) 11 | if empty(m) 12 | throw printf( 13 | \ 'vim-unified-diff: invalid format "%s" was specified', 14 | \ a:line 15 | \) 16 | endif 17 | let m = m[1 : 4] 18 | " find correct action 19 | if m[1] ==# '0' 20 | let a = 'a' 21 | elseif m[3] ==# '0' 22 | let a = 'd' 23 | else 24 | let a = 'c' 25 | endif 26 | " find correct endpoint 27 | let send = '' 28 | if !empty(m[1]) && m[1] !=# '0' 29 | let send = printf(',%d', m[0] + m[1] - 1) 30 | endif 31 | let dend = '' 32 | if !empty(m[3]) && m[3] !=# '0' 33 | let dend = printf(',%d', m[2] + m[3] - 1) 34 | endif 35 | return join([m[0], send, a, m[2], dend], '') 36 | endfunction 37 | 38 | function! s:parse_unified(unified) abort 39 | let _normal = [] 40 | for line in a:unified 41 | if line =~# '^\%(+++\|---\)' 42 | continue 43 | elseif line =~# '^@@ -\d\+\%(,\d\+\)\? +\d\+\%(,\d\+\)\? @@' 44 | call add(_normal, s:parse_unified_region(line)) 45 | elseif line =~# '^-' 46 | call add(_normal, substitute(line, '^-', '< ', '')) 47 | elseif line =~# '^+' 48 | if _normal[-1] =~# '^< ' 49 | call add(_normal, '---') 50 | endif 51 | call add(_normal, substitute(line, '^+', '> ', '')) 52 | endif 53 | endfor 54 | return _normal 55 | endfunction 56 | 57 | function! s:on_stdout(job, msg, event) abort dict 58 | let leading = get(self.stdout, -1, '') 59 | silent! call remove(self.stdout, -1) 60 | call extend(self.stdout, [leading . get(a:msg, 0, '')] + a:msg[1:]) 61 | endfunction 62 | 63 | 64 | function! unified_diff#parse(unified) abort 65 | let unified = type(a:unified) == s:t_string 66 | \ ? s:String.split_posix_text(a:unified) 67 | \ : a:unified 68 | return s:parse_unified(a:unified) 69 | endfunction 70 | 71 | function! unified_diff#diff(fname_in, fname_new) abort 72 | let args = [g:unified_diff#executable] 73 | call extend(args, g:unified_diff#arguments) 74 | if &diffopt =~# 'iwhite' 75 | call extend(args, g:unified_diff#iwhite_arguments) 76 | endif 77 | call extend(args, [a:fname_in, a:fname_new]) 78 | let job = s:Job.start(args, { 79 | \ 'stdout': [], 80 | \ 'on_stdout': function('s:on_stdout'), 81 | \}) 82 | call job.wait() 83 | return job.stdout 84 | endfunction 85 | 86 | function! unified_diff#diffexpr() abort 87 | let unified = unified_diff#diff(v:fname_in, v:fname_new) 88 | let diff = s:parse_unified(unified) 89 | call writefile(diff, v:fname_out) 90 | endfunction 91 | 92 | 93 | let s:settings = { 94 | \ 'executable': 'git', 95 | \ 'arguments': [ 96 | \ 'diff', 97 | \ '--unified=0', 98 | \ '--no-index', '--no-color', '--no-ext-diff', 99 | \ '--histogram', 100 | \ ], 101 | \ 'iwhite_arguments': [ 102 | \ '--ignore-all-space', 103 | \ ], 104 | \} 105 | 106 | function! s:init() abort 107 | for [key, value] in items(s:settings) 108 | if !exists('g:unified_diff#' . key) 109 | let g:unified_diff#{key} = value 110 | endif 111 | unlet value 112 | endfor 113 | endfunction 114 | call s:init() 115 | -------------------------------------------------------------------------------- /autoload/vital/_unified_diff/System/Job.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_unified_diff#System#Job#import() abort 6 | return map({'start': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_unified_diff#System#Job#import() abort', printf("return map({'start': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | let s:t_list = type([]) 17 | 18 | 19 | " NOTE: 20 | " Attributes of {options} dictionary start from double underscore (__) are 21 | " used internally so no custom attributes shall start from that. 22 | if has('nvim') 23 | function! s:start(args, ...) abort 24 | call s:_validate_args(a:args) 25 | " Build options for jobstart 26 | let options = get(a:000, 0, {}) 27 | let job = extend(copy(options), s:job) 28 | if has_key(options, 'on_exit') 29 | let job.__on_exit = options.on_exit 30 | endif 31 | " Start job and return a job instance 32 | let job.__job = jobstart(a:args, job) 33 | let job.__status = job.__job > 0 ? 'run' : 'dead' 34 | let job.args = a:args 35 | return job 36 | endfunction 37 | 38 | 39 | " Instance ------------------------------------------------------------------- 40 | let s:job = {} 41 | 42 | function! s:job.id() abort 43 | return self.__job 44 | endfunction 45 | 46 | function! s:job.status() abort 47 | return self.__status 48 | endfunction 49 | 50 | function! s:job.send(data) abort 51 | return jobsend(self.__job, a:data) 52 | endfunction 53 | 54 | function! s:job.stop() abort 55 | try 56 | call jobstop(self.__job) 57 | let self.__status = 'dead' 58 | catch /^Vim\%((\a\+)\)\=:E900/ 59 | " NOTE: 60 | " Vim does not raise exception even the job has already closed so fail 61 | " silently for 'E900: Invalid job id' exception 62 | endtry 63 | endfunction 64 | 65 | function! s:job.wait(...) abort 66 | let timeout = get(a:000, 0, v:null) 67 | if timeout is# v:null 68 | return jobwait([self.__job])[0] 69 | else 70 | return jobwait([self.__job], timeout)[0] 71 | endif 72 | endfunction 73 | 74 | function! s:job.on_exit(job, msg, event) abort 75 | " Update job status 76 | let self.__status = 'dead' 77 | " Call user specified callback if exists 78 | if has_key(self, '__on_exit') 79 | call call(self.__on_exit, [a:job, a:msg, a:event], self) 80 | endif 81 | endfunction 82 | else 83 | function! s:start(args, ...) abort 84 | call s:_validate_args(a:args) 85 | let job = extend(copy(s:job), get(a:000, 0, {})) 86 | let job_options = { 87 | \ 'mode': 'raw', 88 | \ 'timeout': 10000, 89 | \ 'out_cb': function('s:_on_msg_cb', ['stdout', job]), 90 | \ 'err_cb': function('s:_on_msg_cb', ['stderr', job]), 91 | \ 'exit_cb': function('s:_on_exit_cb', [job]), 92 | \} 93 | let job.__job = job_start(a:args, job_options) 94 | let job.__channel = job_getchannel(job.__job) 95 | let job.args = a:args 96 | return job 97 | endfunction 98 | 99 | function! s:_on_msg_cb(name, job, channel, msg) abort 100 | let cb_name = 'on_' . a:name 101 | if has_key(a:job, cb_name) 102 | call call(a:job[cb_name], [a:channel, split(a:msg, '\r\?\n', 1), a:name]) 103 | endif 104 | endfunction 105 | 106 | function! s:_on_exit_cb(job, job8, exitval) abort 107 | " There might be data remain so read channel and call corresponding 108 | " callbacks to mimic 'on_exit' of Neovim 109 | call s:_read_channel_and_call_callback(a:job, 'stdout', {}) 110 | call s:_read_channel_and_call_callback(a:job, 'stderr', {'part': 'err'}) 111 | if has_key(a:job, 'on_exit') 112 | call call(a:job.on_exit, [a:job8, a:exitval, 'exit']) 113 | endif 114 | endfunction 115 | 116 | function! s:_read_channel_and_call_callback(job, name, options) abort 117 | let status = ch_status(a:job.__channel, a:options) 118 | while status ==# 'open' || status ==# 'buffered' 119 | if status ==# 'buffered' 120 | let msg = ch_read(a:job.__channel, a:options) 121 | call s:_on_msg_cb(a:name, a:job, a:job.__channel, msg) 122 | endif 123 | sleep 1m 124 | let status = ch_status(a:job.__channel, a:options) 125 | endwhile 126 | endfunction 127 | 128 | " Instance ------------------------------------------------------------------- 129 | let s:job = {} 130 | 131 | function! s:job.id() abort 132 | return str2nr(matchstr(string(self.__job), '^process \zs\d\+\ze')) 133 | endfunction 134 | 135 | " NOTE: 136 | " On Unix a non-existing command results in "dead" instead 137 | " So returns "dead" instead of "fail" even in non Unix. 138 | function! s:job.status() abort 139 | let status = job_status(self.__job) 140 | return status ==# 'fail' ? 'dead' : status 141 | endfunction 142 | 143 | " NOTE: 144 | " A Null character (\0) is used as a terminator of a string in Vim. 145 | " Neovim can send \0 by using \n splitted list but in Vim. 146 | " So replace all \n in \n splitted list to '' 147 | function! s:job.send(data) abort 148 | let data = type(a:data) == s:t_list 149 | \ ? join(map(a:data, 'substitute(v:val, "\n", '''', ''g'')'), "\n") 150 | \ : a:data 151 | return ch_sendraw(self.__channel, data) 152 | endfunction 153 | 154 | function! s:job.stop() abort 155 | return job_stop(self.__job) 156 | endfunction 157 | 158 | function! s:job.wait(...) abort 159 | if !has('patch-8.0.0027') 160 | throw 'vital: System.Job: Vim 8.0.0026 and earlier is not supported.' 161 | endif 162 | let timeout = get(a:000, 0, v:null) 163 | let timeout = timeout is# v:null ? v:null : timeout / 1000.0 164 | let start_time = reltime() 165 | try 166 | while timeout is# v:null || timeout > reltimefloat(reltime(start_time)) 167 | let status = self.status() 168 | if status ==# 'fail' 169 | return -3 170 | elseif status ==# 'dead' 171 | let info = job_info(self.__job) 172 | return info.exitval 173 | endif 174 | sleep 1m 175 | endwhile 176 | catch /^Vim:Interrupt$/ 177 | call self.stop() 178 | return 1 179 | endtry 180 | return -1 181 | endfunction 182 | endif 183 | 184 | 185 | " Note: 186 | " A string {args} is not permitted while Vim/Neovim treat that a bit 187 | " differently and makes thing complicated. 188 | " Note: 189 | " Vim does not raise E902 on Unix system even the prog is not found so use a 190 | " custom exception instead to make the method compatible. 191 | function! s:_validate_args(args) abort 192 | if type(a:args) != s:t_list 193 | throw 'vital: System.Job: Argument requires to be a List instance.' 194 | elseif len(a:args) == 0 195 | throw 'vital: System.Job: Argument vector must have at least one item.' 196 | endif 197 | let prog = a:args[0] 198 | if !executable(prog) 199 | throw printf('vital: System.Job: "%s" is not an executable', prog) 200 | endif 201 | endfunction 202 | -------------------------------------------------------------------------------- /autoload/vital/unified_diff.vim: -------------------------------------------------------------------------------- 1 | let s:plugin_name = expand(':t:r') 2 | let s:vital_base_dir = expand(':h') 3 | let s:project_root = expand(':h:h:h') 4 | let s:is_vital_vim = s:plugin_name is# 'vital' 5 | 6 | let s:loaded = {} 7 | let s:cache_sid = {} 8 | 9 | " function() wrapper 10 | if v:version > 703 || v:version == 703 && has('patch1170') 11 | function! s:_function(fstr) abort 12 | return function(a:fstr) 13 | endfunction 14 | else 15 | function! s:_SID() abort 16 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 17 | endfunction 18 | let s:_s = '' . s:_SID() . '_' 19 | function! s:_function(fstr) abort 20 | return function(substitute(a:fstr, 's:', s:_s, 'g')) 21 | endfunction 22 | endif 23 | 24 | function! vital#{s:plugin_name}#new() abort 25 | return s:new(s:plugin_name) 26 | endfunction 27 | 28 | function! vital#{s:plugin_name}#import(...) abort 29 | if !exists('s:V') 30 | let s:V = s:new(s:plugin_name) 31 | endif 32 | return call(s:V.import, a:000, s:V) 33 | endfunction 34 | 35 | let s:Vital = {} 36 | 37 | function! s:new(plugin_name) abort 38 | let base = deepcopy(s:Vital) 39 | let base._plugin_name = a:plugin_name 40 | return base 41 | endfunction 42 | 43 | function! s:vital_files() abort 44 | if !exists('s:vital_files') 45 | let s:vital_files = map( 46 | \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), 47 | \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') 48 | endif 49 | return copy(s:vital_files) 50 | endfunction 51 | let s:Vital.vital_files = s:_function('s:vital_files') 52 | 53 | function! s:import(name, ...) abort dict 54 | let target = {} 55 | let functions = [] 56 | for a in a:000 57 | if type(a) == type({}) 58 | let target = a 59 | elseif type(a) == type([]) 60 | let functions = a 61 | endif 62 | unlet a 63 | endfor 64 | let module = self._import(a:name) 65 | if empty(functions) 66 | call extend(target, module, 'keep') 67 | else 68 | for f in functions 69 | if has_key(module, f) && !has_key(target, f) 70 | let target[f] = module[f] 71 | endif 72 | endfor 73 | endif 74 | return target 75 | endfunction 76 | let s:Vital.import = s:_function('s:import') 77 | 78 | function! s:load(...) abort dict 79 | for arg in a:000 80 | let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] 81 | let target = split(join(as, ''), '\W\+') 82 | let dict = self 83 | let dict_type = type({}) 84 | while !empty(target) 85 | let ns = remove(target, 0) 86 | if !has_key(dict, ns) 87 | let dict[ns] = {} 88 | endif 89 | if type(dict[ns]) == dict_type 90 | let dict = dict[ns] 91 | else 92 | unlet dict 93 | break 94 | endif 95 | endwhile 96 | if exists('dict') 97 | call extend(dict, self._import(name)) 98 | endif 99 | unlet arg 100 | endfor 101 | return self 102 | endfunction 103 | let s:Vital.load = s:_function('s:load') 104 | 105 | function! s:unload() abort dict 106 | let s:loaded = {} 107 | let s:cache_sid = {} 108 | unlet! s:vital_files 109 | endfunction 110 | let s:Vital.unload = s:_function('s:unload') 111 | 112 | function! s:exists(name) abort dict 113 | if a:name !~# '\v^\u\w*%(\.\u\w*)*$' 114 | throw 'vital: Invalid module name: ' . a:name 115 | endif 116 | return s:_module_path(a:name) isnot# '' 117 | endfunction 118 | let s:Vital.exists = s:_function('s:exists') 119 | 120 | function! s:search(pattern) abort dict 121 | let paths = s:_extract_files(a:pattern, self.vital_files()) 122 | let modules = sort(map(paths, 's:_file2module(v:val)')) 123 | return s:_uniq(modules) 124 | endfunction 125 | let s:Vital.search = s:_function('s:search') 126 | 127 | function! s:plugin_name() abort dict 128 | return self._plugin_name 129 | endfunction 130 | let s:Vital.plugin_name = s:_function('s:plugin_name') 131 | 132 | function! s:_self_vital_files() abort 133 | let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) 134 | let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) 135 | let base = builtin . ',' . installed 136 | return split(globpath(base, '**/*.vim', 1), "\n") 137 | endfunction 138 | 139 | function! s:_global_vital_files() abort 140 | let pattern = 'autoload/vital/__*__/**/*.vim' 141 | return split(globpath(&runtimepath, pattern, 1), "\n") 142 | endfunction 143 | 144 | function! s:_extract_files(pattern, files) abort 145 | let tr = {'.': '/', '*': '[^/]*', '**': '.*'} 146 | let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') 147 | let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) 148 | return filter(a:files, 'v:val =~# regexp') 149 | endfunction 150 | 151 | function! s:_file2module(file) abort 152 | let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') 153 | let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') 154 | return join(split(tail, '[\\/]\+'), '.') 155 | endfunction 156 | 157 | " @param {string} name e.g. Data.List 158 | function! s:_import(name) abort dict 159 | if has_key(s:loaded, a:name) 160 | return copy(s:loaded[a:name]) 161 | endif 162 | let module = self._get_module(a:name) 163 | if has_key(module, '_vital_created') 164 | call module._vital_created(module) 165 | endif 166 | let export_module = filter(copy(module), 'v:key =~# "^\\a"') 167 | " Cache module before calling module.vital_loaded() to avoid cyclic 168 | " dependences but remove the cache if module._vital_loaded() fails. 169 | " let s:loaded[a:name] = export_module 170 | let s:loaded[a:name] = export_module 171 | if has_key(module, '_vital_loaded') 172 | try 173 | call module._vital_loaded(vital#{s:plugin_name}#new()) 174 | catch 175 | unlet s:loaded[a:name] 176 | throw 'vital: fail to call ._vital_loaded(): ' . v:exception 177 | endtry 178 | endif 179 | return copy(s:loaded[a:name]) 180 | endfunction 181 | let s:Vital._import = s:_function('s:_import') 182 | 183 | " s:_get_module() returns module object wihch has all script local functions. 184 | function! s:_get_module(name) abort dict 185 | let funcname = s:_import_func_name(self.plugin_name(), a:name) 186 | if s:_exists_autoload_func_with_source(funcname) 187 | return call(funcname, []) 188 | else 189 | return s:_get_builtin_module(a:name) 190 | endif 191 | endfunction 192 | 193 | function! s:_get_builtin_module(name) abort 194 | return s:sid2sfuncs(s:_module_sid(a:name)) 195 | endfunction 196 | 197 | if s:is_vital_vim 198 | " For vital.vim, we can use s:_get_builtin_module directly 199 | let s:Vital._get_module = s:_function('s:_get_builtin_module') 200 | else 201 | let s:Vital._get_module = s:_function('s:_get_module') 202 | endif 203 | 204 | function! s:_import_func_name(plugin_name, module_name) abort 205 | return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) 206 | endfunction 207 | 208 | function! s:_module_sid(name) abort 209 | let path = s:_module_path(a:name) 210 | if !filereadable(path) 211 | throw 'vital: module not found: ' . a:name 212 | endif 213 | let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) 214 | let base = join([vital_dir, ''], '[/\\]\+') 215 | let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') 216 | let sid = s:_sid(path, p) 217 | if !sid 218 | call s:_source(path) 219 | let sid = s:_sid(path, p) 220 | if !sid 221 | throw printf('vital: cannot get from path: %s', path) 222 | endif 223 | endif 224 | return sid 225 | endfunction 226 | 227 | function! s:_module_path(name) abort 228 | return get(s:_extract_files(a:name, s:vital_files()), 0, '') 229 | endfunction 230 | 231 | function! s:_module_sid_base_dir() abort 232 | return s:is_vital_vim ? &rtp : s:project_root 233 | endfunction 234 | 235 | function! s:_dot_to_sharp(name) abort 236 | return substitute(a:name, '\.', '#', 'g') 237 | endfunction 238 | 239 | " It will sources autoload file if a given func is not already defined. 240 | function! s:_exists_autoload_func_with_source(funcname) abort 241 | if exists('*' . a:funcname) 242 | " Return true if a given func is already defined 243 | return 1 244 | endif 245 | " source a file which may include a given func definition and try again. 246 | let path = 'autoload/' . substitute(substitute(a:funcname, '#[^#]*$', '.vim', ''), '#', '/', 'g') 247 | call s:_runtime(path) 248 | return exists('*' . a:funcname) 249 | endfunction 250 | 251 | function! s:_runtime(path) abort 252 | execute 'runtime' fnameescape(a:path) 253 | endfunction 254 | 255 | function! s:_source(path) abort 256 | execute 'source' fnameescape(a:path) 257 | endfunction 258 | 259 | " @vimlint(EVL102, 1, l:_) 260 | " @vimlint(EVL102, 1, l:__) 261 | function! s:_sid(path, filter_pattern) abort 262 | let unified_path = s:_unify_path(a:path) 263 | if has_key(s:cache_sid, unified_path) 264 | return s:cache_sid[unified_path] 265 | endif 266 | for line in filter(split(s:_redir(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') 267 | let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') 268 | if s:_unify_path(path) is# unified_path 269 | let s:cache_sid[unified_path] = sid 270 | return s:cache_sid[unified_path] 271 | endif 272 | endfor 273 | return 0 274 | endfunction 275 | 276 | function! s:_redir(cmd) abort 277 | let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] 278 | set verbose=0 verbosefile= 279 | redir => res 280 | silent! execute a:cmd 281 | redir END 282 | let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] 283 | return res 284 | endfunction 285 | 286 | if filereadable(expand(':r') . '.VIM') " is case-insensitive or not 287 | let s:_unify_path_cache = {} 288 | " resolve() is slow, so we cache results. 289 | " Note: On windows, vim can't expand path names from 8.3 formats. 290 | " So if getting full path via and $HOME was set as 8.3 format, 291 | " vital load duplicated scripts. Below's :~ avoid this issue. 292 | function! s:_unify_path(path) abort 293 | if has_key(s:_unify_path_cache, a:path) 294 | return s:_unify_path_cache[a:path] 295 | endif 296 | let value = tolower(fnamemodify(resolve(fnamemodify( 297 | \ a:path, ':p')), ':~:gs?[\\/]?/?')) 298 | let s:_unify_path_cache[a:path] = value 299 | return value 300 | endfunction 301 | else 302 | function! s:_unify_path(path) abort 303 | return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) 304 | endfunction 305 | endif 306 | 307 | " copied and modified from Vim.ScriptLocal 308 | let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') 309 | function! s:sid2sfuncs(sid) abort 310 | let fs = split(s:_redir(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") 311 | let r = {} 312 | let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) 313 | for fname in map(fs, 'matchstr(v:val, pattern)') 314 | let r[fname] = function(s:_sfuncname(a:sid, fname)) 315 | endfor 316 | return r 317 | endfunction 318 | 319 | "" Return funcname of script local functions with SID 320 | function! s:_sfuncname(sid, funcname) abort 321 | return printf('%s_%s', a:sid, a:funcname) 322 | endfunction 323 | 324 | if exists('*uniq') 325 | function! s:_uniq(list) abort 326 | return uniq(a:list) 327 | endfunction 328 | else 329 | function! s:_uniq(list) abort 330 | let i = len(a:list) - 1 331 | while 0 < i 332 | if a:list[i] ==# a:list[i - 1] 333 | call remove(a:list, i) 334 | endif 335 | let i -= 1 336 | endwhile 337 | return a:list 338 | endfunction 339 | endif 340 | -------------------------------------------------------------------------------- /autoload/vital/_unified_diff/Data/List.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_unified_diff#Data#List#import() abort 6 | return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'shift': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'drop_while': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'map_accum': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_unified_diff#Data#List#import() abort', printf("return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'shift': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'drop_while': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'map_accum': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | " Utilities for list. 17 | 18 | let s:save_cpo = &cpo 19 | set cpo&vim 20 | 21 | function! s:pop(list) abort 22 | return remove(a:list, -1) 23 | endfunction 24 | 25 | function! s:push(list, val) abort 26 | call add(a:list, a:val) 27 | return a:list 28 | endfunction 29 | 30 | function! s:shift(list) abort 31 | return remove(a:list, 0) 32 | endfunction 33 | 34 | function! s:unshift(list, val) abort 35 | return insert(a:list, a:val) 36 | endfunction 37 | 38 | function! s:cons(x, xs) abort 39 | return [a:x] + a:xs 40 | endfunction 41 | 42 | function! s:conj(xs, x) abort 43 | return a:xs + [a:x] 44 | endfunction 45 | 46 | " Removes duplicates from a list. 47 | function! s:uniq(list) abort 48 | return s:uniq_by(a:list, 'v:val') 49 | endfunction 50 | 51 | " Removes duplicates from a list. 52 | function! s:uniq_by(list, f) abort 53 | let list = map(copy(a:list), printf('[v:val, %s]', a:f)) 54 | let i = 0 55 | let seen = {} 56 | while i < len(list) 57 | let key = string(list[i][1]) 58 | if has_key(seen, key) 59 | call remove(list, i) 60 | else 61 | let seen[key] = 1 62 | let i += 1 63 | endif 64 | endwhile 65 | return map(list, 'v:val[0]') 66 | endfunction 67 | 68 | function! s:clear(list) abort 69 | if !empty(a:list) 70 | unlet! a:list[0 : len(a:list) - 1] 71 | endif 72 | return a:list 73 | endfunction 74 | 75 | " Concatenates a list of lists. 76 | " XXX: Should we verify the input? 77 | function! s:concat(list) abort 78 | let memo = [] 79 | for Value in a:list 80 | let memo += Value 81 | endfor 82 | return memo 83 | endfunction 84 | 85 | " Take each elements from lists to a new list. 86 | function! s:flatten(list, ...) abort 87 | let limit = a:0 > 0 ? a:1 : -1 88 | let memo = [] 89 | if limit == 0 90 | return a:list 91 | endif 92 | let limit -= 1 93 | for Value in a:list 94 | let memo += 95 | \ type(Value) == type([]) ? 96 | \ s:flatten(Value, limit) : 97 | \ [Value] 98 | unlet! Value 99 | endfor 100 | return memo 101 | endfunction 102 | 103 | " Sorts a list with expression to compare each two values. 104 | " a:a and a:b can be used in {expr}. 105 | function! s:sort(list, expr) abort 106 | if type(a:expr) == type(function('function')) 107 | return sort(a:list, a:expr) 108 | endif 109 | let s:expr = a:expr 110 | return sort(a:list, 's:_compare') 111 | endfunction 112 | 113 | function! s:_compare(a, b) abort 114 | return eval(s:expr) 115 | endfunction 116 | 117 | " Sorts a list using a set of keys generated by mapping the values in the list 118 | " through the given expr. 119 | " v:val is used in {expr} 120 | function! s:sort_by(list, expr) abort 121 | let pairs = map(a:list, printf('[v:val, %s]', a:expr)) 122 | return map(s:sort(pairs, 123 | \ 'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]') 124 | endfunction 125 | 126 | " Returns a maximum value in {list} through given {expr}. 127 | " Returns 0 if {list} is empty. 128 | " v:val is used in {expr} 129 | function! s:max_by(list, expr) abort 130 | if empty(a:list) 131 | return 0 132 | endif 133 | let list = map(copy(a:list), a:expr) 134 | return a:list[index(list, max(list))] 135 | endfunction 136 | 137 | " Returns a minimum value in {list} through given {expr}. 138 | " Returns 0 if {list} is empty. 139 | " v:val is used in {expr} 140 | " FIXME: -0x80000000 == 0x80000000 141 | function! s:min_by(list, expr) abort 142 | return s:max_by(a:list, '-(' . a:expr . ')') 143 | endfunction 144 | 145 | " Returns List of character sequence between [a:from, a:to] 146 | " e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] 147 | function! s:char_range(from, to) abort 148 | return map( 149 | \ range(char2nr(a:from), char2nr(a:to)), 150 | \ 'nr2char(v:val)' 151 | \) 152 | endfunction 153 | 154 | " Returns true if a:list has a:value. 155 | " Returns false otherwise. 156 | function! s:has(list, value) abort 157 | return index(a:list, a:value) isnot -1 158 | endfunction 159 | 160 | " Returns true if a:list[a:index] exists. 161 | " Returns false otherwise. 162 | " NOTE: Returns false when a:index is negative number. 163 | function! s:has_index(list, index) abort 164 | " Return true when negative index? 165 | " let index = a:index >= 0 ? a:index : len(a:list) + a:index 166 | return 0 <= a:index && a:index < len(a:list) 167 | endfunction 168 | 169 | " similar to Haskell's Data.List.span 170 | function! s:span(f, xs) abort 171 | let border = len(a:xs) 172 | for i in range(len(a:xs)) 173 | if !eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 174 | let border = i 175 | break 176 | endif 177 | endfor 178 | return border == 0 ? [[], copy(a:xs)] : [a:xs[: border - 1], a:xs[border :]] 179 | endfunction 180 | 181 | " similar to Haskell's Data.List.break 182 | function! s:break(f, xs) abort 183 | return s:span(printf('!(%s)', a:f), a:xs) 184 | endfunction 185 | 186 | " similar to Haskell's Data.List.takeWhile 187 | function! s:take_while(f, xs) abort 188 | return s:span(a:f, a:xs)[0] 189 | endfunction 190 | 191 | " similar to Haskell's Data.List.dropWhile 192 | function! s:drop_while(f, xs) abort 193 | return s:span(a:f, a:xs)[1] 194 | endfunction 195 | 196 | " similar to Haskell's Data.List.partition 197 | function! s:partition(f, xs) abort 198 | return [filter(copy(a:xs), a:f), filter(copy(a:xs), '!(' . a:f . ')')] 199 | endfunction 200 | 201 | " similar to Haskell's Prelude.all 202 | function! s:all(f, xs) abort 203 | return !s:any(printf('!(%s)', a:f), a:xs) 204 | endfunction 205 | 206 | " similar to Haskell's Prelude.any 207 | function! s:any(f, xs) abort 208 | return !empty(filter(map(copy(a:xs), a:f), 'v:val')) 209 | endfunction 210 | 211 | " similar to Haskell's Prelude.and 212 | function! s:and(xs) abort 213 | return s:all('v:val', a:xs) 214 | endfunction 215 | 216 | " similar to Haskell's Prelude.or 217 | function! s:or(xs) abort 218 | return s:any('v:val', a:xs) 219 | endfunction 220 | 221 | function! s:map_accum(expr, xs, init) abort 222 | let memo = [] 223 | let init = a:init 224 | for x in a:xs 225 | let expr = substitute(a:expr, 'v:memo', init, 'g') 226 | let expr = substitute(expr, 'v:val', x, 'g') 227 | let [tmp, init] = eval(expr) 228 | call add(memo, tmp) 229 | endfor 230 | return memo 231 | endfunction 232 | 233 | " similar to Haskell's Prelude.foldl 234 | function! s:foldl(f, init, xs) abort 235 | let memo = a:init 236 | for x in a:xs 237 | let expr = substitute(a:f, 'v:val', string(x), 'g') 238 | let expr = substitute(expr, 'v:memo', string(memo), 'g') 239 | unlet memo 240 | let memo = eval(expr) 241 | endfor 242 | return memo 243 | endfunction 244 | 245 | " similar to Haskell's Prelude.foldl1 246 | function! s:foldl1(f, xs) abort 247 | if len(a:xs) == 0 248 | throw 'vital: Data.List: foldl1' 249 | endif 250 | return s:foldl(a:f, a:xs[0], a:xs[1:]) 251 | endfunction 252 | 253 | " similar to Haskell's Prelude.foldr 254 | function! s:foldr(f, init, xs) abort 255 | return s:foldl(a:f, a:init, reverse(copy(a:xs))) 256 | endfunction 257 | 258 | " similar to Haskell's Prelude.fold11 259 | function! s:foldr1(f, xs) abort 260 | if len(a:xs) == 0 261 | throw 'vital: Data.List: foldr1' 262 | endif 263 | return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) 264 | endfunction 265 | 266 | " similar to python's zip() 267 | function! s:zip(...) abort 268 | return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") 269 | endfunction 270 | 271 | " similar to zip(), but goes until the longer one. 272 | function! s:zip_fill(xs, ys, filler) abort 273 | if empty(a:xs) && empty(a:ys) 274 | return [] 275 | elseif empty(a:ys) 276 | return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) 277 | elseif empty(a:xs) 278 | return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) 279 | else 280 | return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) 281 | endif 282 | endfunction 283 | 284 | " Inspired by Ruby's with_index method. 285 | function! s:with_index(list, ...) abort 286 | let base = a:0 > 0 ? a:1 : 0 287 | return map(copy(a:list), '[v:val, v:key + base]') 288 | endfunction 289 | 290 | " similar to Ruby's detect or Haskell's find. 291 | function! s:find(list, default, f) abort 292 | let l:Call = type(a:f) is type(function('function')) 293 | \ ? function('call') 294 | \ : function('s:_call_string_expr') 295 | 296 | for x in a:list 297 | if l:Call(a:f, [x]) 298 | return x 299 | endif 300 | endfor 301 | return a:default 302 | endfunction 303 | 304 | function! s:_call_string_expr(expr, args) abort 305 | return map([a:args[0]], a:expr)[0] 306 | endfunction 307 | 308 | " Returns the index of the first element which satisfies the given expr. 309 | function! s:find_index(xs, f, ...) abort 310 | let len = len(a:xs) 311 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 312 | let default = a:0 > 1 ? a:2 : -1 313 | if start >=# len || start < 0 314 | return default 315 | endif 316 | for i in range(start, len - 1) 317 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 318 | return i 319 | endif 320 | endfor 321 | return default 322 | endfunction 323 | 324 | " Returns the index of the last element which satisfies the given expr. 325 | function! s:find_last_index(xs, f, ...) abort 326 | let len = len(a:xs) 327 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : len - 1 328 | let default = a:0 > 1 ? a:2 : -1 329 | if start >=# len || start < 0 330 | return default 331 | endif 332 | for i in range(start, 0, -1) 333 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 334 | return i 335 | endif 336 | endfor 337 | return default 338 | endfunction 339 | 340 | " Similar to find_index but returns the list of indices satisfying the given expr. 341 | function! s:find_indices(xs, f, ...) abort 342 | let len = len(a:xs) 343 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 344 | let result = [] 345 | if start >=# len || start < 0 346 | return result 347 | endif 348 | for i in range(start, len - 1) 349 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 350 | call add(result, i) 351 | endif 352 | endfor 353 | return result 354 | endfunction 355 | 356 | " Return non-zero if a:list1 and a:list2 have any common item(s). 357 | " Return zero otherwise. 358 | function! s:has_common_items(list1, list2) abort 359 | return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) 360 | endfunction 361 | 362 | function! s:intersect(list1, list2) abort 363 | let items = [] 364 | " for funcref 365 | for X in a:list1 366 | if index(a:list2, X) != -1 && index(items, X) == -1 367 | let items += [X] 368 | endif 369 | endfor 370 | return items 371 | endfunction 372 | 373 | " similar to Ruby's group_by. 374 | function! s:group_by(xs, f) abort 375 | let result = {} 376 | let list = map(copy(a:xs), printf('[v:val, %s]', a:f)) 377 | for x in list 378 | let Val = x[0] 379 | let key = type(x[1]) !=# type('') ? string(x[1]) : x[1] 380 | if has_key(result, key) 381 | call add(result[key], Val) 382 | else 383 | let result[key] = [Val] 384 | endif 385 | unlet Val 386 | endfor 387 | return result 388 | endfunction 389 | 390 | function! s:_default_compare(a, b) abort 391 | return a:a <# a:b ? -1 : a:a ># a:b ? 1 : 0 392 | endfunction 393 | 394 | function! s:binary_search(list, value, ...) abort 395 | let Predicate = a:0 >= 1 ? a:1 : 's:_default_compare' 396 | let dic = a:0 >= 2 ? a:2 : {} 397 | let start = 0 398 | let end = len(a:list) - 1 399 | 400 | while 1 401 | if start > end 402 | return -1 403 | endif 404 | 405 | let middle = (start + end) / 2 406 | 407 | let compared = call(Predicate, [a:value, a:list[middle]], dic) 408 | 409 | if compared < 0 410 | let end = middle - 1 411 | elseif compared > 0 412 | let start = middle + 1 413 | else 414 | return middle 415 | endif 416 | endwhile 417 | endfunction 418 | 419 | function! s:product(lists) abort 420 | let result = [[]] 421 | for pool in a:lists 422 | let tmp = [] 423 | for x in result 424 | let tmp += map(copy(pool), 'x + [v:val]') 425 | endfor 426 | let result = tmp 427 | endfor 428 | return result 429 | endfunction 430 | 431 | function! s:permutations(list, ...) abort 432 | if a:0 > 1 433 | throw 'vital: Data.List: too many arguments' 434 | endif 435 | let r = a:0 == 1 ? a:1 : len(a:list) 436 | if r > len(a:list) 437 | return [] 438 | elseif r < 0 439 | throw 'vital: Data.List: {r} must be non-negative integer' 440 | endif 441 | let n = len(a:list) 442 | let result = [] 443 | for indices in s:product(map(range(r), 'range(n)')) 444 | if len(s:uniq(indices)) == r 445 | call add(result, map(indices, 'a:list[v:val]')) 446 | endif 447 | endfor 448 | return result 449 | endfunction 450 | 451 | function! s:combinations(list, r) abort 452 | if a:r > len(a:list) 453 | return [] 454 | elseif a:r < 0 455 | throw 'vital: Data.List: {r} must be non-negative integer' 456 | endif 457 | let n = len(a:list) 458 | let result = [] 459 | for indices in s:permutations(range(n), a:r) 460 | if s:sort(copy(indices), 'a:a - a:b') == indices 461 | call add(result, map(indices, 'a:list[v:val]')) 462 | endif 463 | endfor 464 | return result 465 | endfunction 466 | 467 | let &cpo = s:save_cpo 468 | unlet s:save_cpo 469 | 470 | " vim:set et ts=2 sts=2 sw=2 tw=0: 471 | -------------------------------------------------------------------------------- /autoload/vital/_unified_diff/Data/String.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_unified_diff#Data#String#import() abort 6 | 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': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_unified_diff#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': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | " Utilities for string. 17 | 18 | let s:save_cpo = &cpo 19 | set cpo&vim 20 | 21 | function! s:_vital_loaded(V) abort 22 | let s:V = a:V 23 | let s:L = s:V.import('Data.List') 24 | endfunction 25 | 26 | function! s:_vital_depends() abort 27 | return ['Data.List'] 28 | endfunction 29 | 30 | function! s:_vital_created(module) abort 31 | " Expose script-local funcref 32 | if exists('s:strchars') 33 | let a:module.strchars = s:strchars 34 | endif 35 | if exists('s:wcswidth') 36 | let a:module.wcswidth = s:wcswidth 37 | endif 38 | endfunction 39 | 40 | " Substitute a:from => a:to by string. 41 | " To substitute by pattern, use substitute() instead. 42 | function! s:replace(str, from, to) abort 43 | return s:_replace(a:str, a:from, a:to, 'g') 44 | endfunction 45 | 46 | " Substitute a:from => a:to only once. 47 | " cf. s:replace() 48 | function! s:replace_first(str, from, to) abort 49 | return s:_replace(a:str, a:from, a:to, '') 50 | endfunction 51 | 52 | " implement of replace() and replace_first() 53 | function! s:_replace(str, from, to, flags) abort 54 | return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags) 55 | endfunction 56 | 57 | function! s:scan(str, pattern) abort 58 | let list = [] 59 | call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g') 60 | return list 61 | endfunction 62 | 63 | function! s:reverse(str) abort 64 | return join(reverse(split(a:str, '.\zs')), '') 65 | endfunction 66 | 67 | function! s:starts_with(str, prefix) abort 68 | return stridx(a:str, a:prefix) == 0 69 | endfunction 70 | 71 | function! s:ends_with(str, suffix) abort 72 | let idx = strridx(a:str, a:suffix) 73 | return 0 <= idx && idx + len(a:suffix) == len(a:str) 74 | endfunction 75 | 76 | function! s:common_head(strs) abort 77 | if empty(a:strs) 78 | return '' 79 | endif 80 | let len = len(a:strs) 81 | if len == 1 82 | return a:strs[0] 83 | endif 84 | let strs = len == 2 ? a:strs : sort(copy(a:strs)) 85 | let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g') 86 | return pat ==# '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']') 87 | endfunction 88 | 89 | " Split to two elements of List. ([left, right]) 90 | " e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache'] 91 | function! s:split_leftright(expr, pattern) abort 92 | let [left, _, right] = s:split3(a:expr, a:pattern) 93 | return [left, right] 94 | endfunction 95 | 96 | function! s:split3(expr, pattern) abort 97 | let ERROR = ['', '', ''] 98 | if a:expr ==# '' || a:pattern ==# '' 99 | return ERROR 100 | endif 101 | let begin = match(a:expr, a:pattern) 102 | if begin is -1 103 | return ERROR 104 | endif 105 | let end = matchend(a:expr, a:pattern) 106 | let left = begin <=# 0 ? '' : a:expr[: begin - 1] 107 | let right = a:expr[end :] 108 | return [left, a:expr[begin : end-1], right] 109 | endfunction 110 | 111 | " Slices into strings determines the number of substrings. 112 | " e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache'] 113 | function! s:nsplit(expr, n, ...) abort 114 | let pattern = get(a:000, 0, '\s') 115 | let keepempty = get(a:000, 1, 1) 116 | let ret = [] 117 | let expr = a:expr 118 | if a:n <= 1 119 | return [expr] 120 | endif 121 | while 1 122 | let pos = match(expr, pattern) 123 | if pos == -1 124 | if expr !~ pattern || keepempty 125 | call add(ret, expr) 126 | endif 127 | break 128 | elseif pos >= 0 129 | let left = pos > 0 ? expr[:pos-1] : '' 130 | if pos > 0 || keepempty 131 | call add(ret, left) 132 | endif 133 | let ml = len(matchstr(expr, pattern)) 134 | if pos == 0 && ml == 0 135 | let pos = 1 136 | endif 137 | let expr = expr[pos+ml :] 138 | endif 139 | if len(expr) == 0 140 | break 141 | endif 142 | if len(ret) == a:n - 1 143 | call add(ret, expr) 144 | break 145 | endif 146 | endwhile 147 | return ret 148 | endfunction 149 | 150 | " Returns the number of character in a:str. 151 | " NOTE: This returns proper value 152 | " even if a:str contains multibyte character(s). 153 | " s:strchars(str) {{{ 154 | if exists('*strchars') 155 | let s:strchars = function('strchars') 156 | else 157 | function! s:strchars(str) abort 158 | return strlen(substitute(copy(a:str), '.', 'x', 'g')) 159 | endfunction 160 | endif "}}} 161 | 162 | " Returns the bool of contains any multibyte character in s:str 163 | function! s:contains_multibyte(str) abort "{{{ 164 | return strlen(a:str) != s:strchars(a:str) 165 | endfunction "}}} 166 | 167 | " Remove last character from a:str. 168 | " NOTE: This returns proper value 169 | " even if a:str contains multibyte character(s). 170 | function! s:chop(str) abort "{{{ 171 | return substitute(a:str, '.$', '', '') 172 | endfunction "}}} 173 | 174 | " Remove last \r,\n,\r\n from a:str. 175 | function! s:chomp(str) abort "{{{ 176 | return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '') 177 | endfunction "}}} 178 | 179 | " wrap() and its internal functions 180 | " * _split_by_wcswidth_once() 181 | " * _split_by_wcswidth() 182 | " * _concat() 183 | " * wrap() 184 | " 185 | " NOTE _concat() is just a copy of Data.List.concat(). 186 | " FIXME don't repeat yourself 187 | function! s:_split_by_wcswidth_once(body, x) abort 188 | let fst = s:strwidthpart(a:body, a:x) 189 | let snd = s:strwidthpart_reverse(a:body, s:wcswidth(a:body) - s:wcswidth(fst)) 190 | return [fst, snd] 191 | endfunction 192 | 193 | function! s:_split_by_wcswidth(body, x) abort 194 | let memo = [] 195 | let body = a:body 196 | while s:wcswidth(body) > a:x 197 | let [tmp, body] = s:_split_by_wcswidth_once(body, a:x) 198 | call add(memo, tmp) 199 | endwhile 200 | call add(memo, body) 201 | return memo 202 | endfunction 203 | 204 | function! s:trim(str) abort 205 | return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') 206 | endfunction 207 | 208 | function! s:trim_start(str) abort 209 | return matchstr(a:str,'^\s*\zs.\{-}$') 210 | endfunction 211 | 212 | function! s:trim_end(str) abort 213 | return matchstr(a:str,'^.\{-}\ze\s*$') 214 | endfunction 215 | 216 | function! s:wrap(str,...) abort 217 | let _columns = a:0 > 0 ? a:1 : &columns 218 | return s:L.concat( 219 | \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)')) 220 | endfunction 221 | 222 | function! s:nr2byte(nr) abort 223 | if a:nr < 0x80 224 | return nr2char(a:nr) 225 | elseif a:nr < 0x800 226 | return nr2char(a:nr/64+192).nr2char(a:nr%64+128) 227 | else 228 | return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128) 229 | endif 230 | endfunction 231 | 232 | function! s:nr2enc_char(charcode) abort 233 | if &encoding ==# 'utf-8' 234 | return nr2char(a:charcode) 235 | endif 236 | let char = s:nr2byte(a:charcode) 237 | if strlen(char) > 1 238 | let char = strtrans(iconv(char, 'utf-8', &encoding)) 239 | endif 240 | return char 241 | endfunction 242 | 243 | function! s:nr2hex(nr) abort 244 | let n = a:nr 245 | let r = '' 246 | while n 247 | let r = '0123456789ABCDEF'[n % 16] . r 248 | let n = n / 16 249 | endwhile 250 | return r 251 | endfunction 252 | 253 | " If a ==# b, returns -1. 254 | " If a !=# b, returns first index of different character. 255 | function! s:diffidx(a, b) abort 256 | return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b])) 257 | endfunction 258 | 259 | function! s:substitute_last(expr, pat, sub) abort 260 | return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '') 261 | endfunction 262 | 263 | function! s:dstring(expr) abort 264 | let x = substitute(string(a:expr), "^'\\|'$", '', 'g') 265 | let x = substitute(x, "''", "'", 'g') 266 | return printf('"%s"', escape(x, '"')) 267 | endfunction 268 | 269 | function! s:lines(str) abort 270 | return split(a:str, '\r\?\n') 271 | endfunction 272 | 273 | function! s:_pad_with_char(str, left, right, char) abort 274 | return repeat(a:char, a:left). a:str. repeat(a:char, a:right) 275 | endfunction 276 | 277 | function! s:pad_left(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 left = max([0, a:width - strdisplaywidth(a:str)]) 283 | return s:_pad_with_char(a:str, left, 0, char) 284 | endfunction 285 | 286 | function! s:pad_right(str, width, ...) abort 287 | let char = get(a:, 1, ' ') 288 | if strdisplaywidth(char) != 1 289 | throw "vital: Data.String: Can't use non-half-width characters for padding." 290 | endif 291 | let right = max([0, a:width - strdisplaywidth(a:str)]) 292 | return s:_pad_with_char(a:str, 0, right, char) 293 | endfunction 294 | 295 | function! s:pad_both_sides(str, width, ...) abort 296 | let char = get(a:, 1, ' ') 297 | if strdisplaywidth(char) != 1 298 | throw "vital: Data.String: Can't use non-half-width characters for padding." 299 | endif 300 | let space = max([0, a:width - strdisplaywidth(a:str)]) 301 | let left = space / 2 302 | let right = space - left 303 | return s:_pad_with_char(a:str, left, right, char) 304 | endfunction 305 | 306 | function! s:pad_between_letters(str, width, ...) abort 307 | let char = get(a:, 1, ' ') 308 | if strdisplaywidth(char) != 1 309 | throw "vital: Data.String: Can't use non-half-width characters for padding." 310 | endif 311 | let letters = split(a:str, '\zs') 312 | let each_width = a:width / len(letters) 313 | let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '') 314 | if a:width - strdisplaywidth(str) > 0 315 | return char. s:pad_both_sides(str, a:width - 1, char) 316 | endif 317 | return str 318 | endfunction 319 | 320 | function! s:justify_equal_spacing(str, width, ...) abort 321 | let char = get(a:, 1, ' ') 322 | if strdisplaywidth(char) != 1 323 | throw "vital: Data.String: Can't use non-half-width characters for padding." 324 | endif 325 | let letters = split(a:str, '\zs') 326 | let first_letter = letters[0] 327 | " {width w/o the first letter} / {length w/o the first letter} 328 | let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1) 329 | let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1) 330 | return first_letter. join(s:L.concat([ 331 | \ map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'), 332 | \ map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)') 333 | \ ]), '') 334 | endfunction 335 | 336 | function! s:levenshtein_distance(str1, str2) abort 337 | let letters1 = split(a:str1, '\zs') 338 | let letters2 = split(a:str2, '\zs') 339 | let length1 = len(letters1) 340 | let length2 = len(letters2) 341 | let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), ''0'')') 342 | 343 | for i1 in range(0, length1) 344 | let distances[i1][0] = i1 345 | endfor 346 | for i2 in range(0, length2) 347 | let distances[0][i2] = i2 348 | endfor 349 | 350 | for i1 in range(1, length1) 351 | for i2 in range(1, length2) 352 | let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1 353 | 354 | let distances[i1][i2] = min([ 355 | \ distances[i1 - 1][i2 ] + 1, 356 | \ distances[i1 ][i2 - 1] + 1, 357 | \ distances[i1 - 1][i2 - 1] + cost, 358 | \]) 359 | endfor 360 | endfor 361 | 362 | return distances[length1][length2] 363 | endfunction 364 | 365 | function! s:padding_by_displaywidth(expr, width, float) abort 366 | let padding_char = ' ' 367 | let n = a:width - strdisplaywidth(a:expr) 368 | if n <= 0 369 | let n = 0 370 | endif 371 | if a:float < 0 372 | return a:expr . repeat(padding_char, n) 373 | elseif 0 < a:float 374 | return repeat(padding_char, n) . a:expr 375 | else 376 | if n % 2 is 0 377 | return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2) 378 | else 379 | return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char 380 | endif 381 | endif 382 | endfunction 383 | 384 | function! s:split_by_displaywidth(expr, width, float, is_wrap) abort 385 | if a:width is 0 386 | return [''] 387 | endif 388 | 389 | let lines = [] 390 | 391 | let cs = split(a:expr, '\zs') 392 | let cs_index = 0 393 | 394 | let text = '' 395 | while cs_index < len(cs) 396 | if cs[cs_index] is# "\n" 397 | let text = s:padding_by_displaywidth(text, a:width, a:float) 398 | let lines += [text] 399 | let text = '' 400 | else 401 | let w = strdisplaywidth(text . cs[cs_index]) 402 | 403 | if w < a:width 404 | let text .= cs[cs_index] 405 | elseif a:width < w 406 | let text = s:padding_by_displaywidth(text, a:width, a:float) 407 | else 408 | let text .= cs[cs_index] 409 | endif 410 | 411 | if a:width <= w 412 | let lines += [text] 413 | let text = '' 414 | if a:is_wrap 415 | if a:width < w 416 | if a:width < strdisplaywidth(cs[cs_index]) 417 | while get(cs, cs_index, "\n") isnot# "\n" 418 | let cs_index += 1 419 | endwhile 420 | continue 421 | else 422 | let text = cs[cs_index] 423 | endif 424 | endif 425 | else 426 | while get(cs, cs_index, "\n") isnot# "\n" 427 | let cs_index += 1 428 | endwhile 429 | continue 430 | endif 431 | endif 432 | 433 | endif 434 | let cs_index += 1 435 | endwhile 436 | 437 | if !empty(text) 438 | let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ] 439 | endif 440 | 441 | return lines 442 | endfunction 443 | 444 | function! s:hash(str) abort 445 | if exists('*sha256') 446 | return sha256(a:str) 447 | else 448 | " This gives up sha256ing but just adds up char with index. 449 | let sum = 0 450 | for i in range(len(a:str)) 451 | let sum += char2nr(a:str[i]) * (i + 1) 452 | endfor 453 | 454 | return printf('%x', sum) 455 | endif 456 | endfunction 457 | 458 | function! s:truncate(str, width) abort 459 | " Original function is from mattn. 460 | " http://github.com/mattn/googlereader-vim/tree/master 461 | 462 | if a:str =~# '^[\x00-\x7f]*$' 463 | return len(a:str) < a:width 464 | \ ? printf('%-' . a:width . 's', a:str) 465 | \ : strpart(a:str, 0, a:width) 466 | endif 467 | 468 | let ret = a:str 469 | let width = s:wcswidth(a:str) 470 | if width > a:width 471 | let ret = s:strwidthpart(ret, a:width) 472 | let width = s:wcswidth(ret) 473 | endif 474 | 475 | if width < a:width 476 | let ret .= repeat(' ', a:width - width) 477 | endif 478 | 479 | return ret 480 | endfunction 481 | 482 | function! s:truncate_skipping(str, max, footer_width, separator) abort 483 | let width = s:wcswidth(a:str) 484 | if width <= a:max 485 | let ret = a:str 486 | else 487 | let header_width = a:max - s:wcswidth(a:separator) - a:footer_width 488 | let ret = s:strwidthpart(a:str, header_width) . a:separator 489 | \ . s:strwidthpart_reverse(a:str, a:footer_width) 490 | endif 491 | return s:truncate(ret, a:max) 492 | endfunction 493 | 494 | function! s:strwidthpart(str, width) abort 495 | let str = tr(a:str, "\t", ' ') 496 | let vcol = a:width + 2 497 | return matchstr(str, '.*\%<' . (vcol < 0 ? 0 : vcol) . 'v') 498 | endfunction 499 | 500 | function! s:strwidthpart_reverse(str, width) abort 501 | let str = tr(a:str, "\t", ' ') 502 | let vcol = s:wcswidth(str) - a:width 503 | return matchstr(str, '\%>' . (vcol < 0 ? 0 : vcol) . 'v.*') 504 | endfunction 505 | 506 | if v:version >= 703 507 | " Use builtin function. 508 | let s:wcswidth = function('strwidth') 509 | else 510 | function! s:wcswidth(str) abort 511 | if a:str =~# '^[\x00-\x7f]*$' 512 | return strlen(a:str) 513 | endif 514 | let mx_first = '^\(.\)' 515 | let str = a:str 516 | let width = 0 517 | while 1 518 | let ucs = char2nr(substitute(str, mx_first, '\1', '')) 519 | if ucs == 0 520 | break 521 | endif 522 | let width += s:_wcwidth(ucs) 523 | let str = substitute(str, mx_first, '', '') 524 | endwhile 525 | return width 526 | endfunction 527 | 528 | " UTF-8 only. 529 | function! s:_wcwidth(ucs) abort 530 | let ucs = a:ucs 531 | if (ucs >= 0x1100 532 | \ && (ucs <= 0x115f 533 | \ || ucs == 0x2329 534 | \ || ucs == 0x232a 535 | \ || (ucs >= 0x2e80 && ucs <= 0xa4cf 536 | \ && ucs != 0x303f) 537 | \ || (ucs >= 0xac00 && ucs <= 0xd7a3) 538 | \ || (ucs >= 0xf900 && ucs <= 0xfaff) 539 | \ || (ucs >= 0xfe30 && ucs <= 0xfe6f) 540 | \ || (ucs >= 0xff00 && ucs <= 0xff60) 541 | \ || (ucs >= 0xffe0 && ucs <= 0xffe6) 542 | \ || (ucs >= 0x20000 && ucs <= 0x2fffd) 543 | \ || (ucs >= 0x30000 && ucs <= 0x3fffd) 544 | \ )) 545 | return 2 546 | endif 547 | return 1 548 | endfunction 549 | endif 550 | 551 | function! s:remove_ansi_sequences(text) abort 552 | return substitute(a:text, '\e\[\%(\%(\d\+;\)*\d\+\)\?[mK]', '', 'g') 553 | endfunction 554 | 555 | function! s:escape_pattern(str) abort 556 | " escape characters for no-magic 557 | return escape(a:str, '^$~.*[]\') 558 | endfunction 559 | 560 | function! s:unescape_pattern(str) abort 561 | " unescape characters for no-magic 562 | return s:unescape(a:str, '^$~.*[]\') 563 | endfunction 564 | 565 | function! s:unescape(str, chars) abort 566 | let chars = map(split(a:chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')') 567 | return substitute(a:str, '\\\(' . join(chars, '\|') . '\)', '\1', 'g') 568 | endfunction 569 | 570 | function! s:iconv(expr, from, to) abort 571 | if a:from ==# '' || a:to ==# '' || a:from ==? a:to 572 | return a:expr 573 | endif 574 | let result = iconv(a:expr, a:from, a:to) 575 | return empty(result) ? a:expr : result 576 | endfunction 577 | 578 | " NOTE: 579 | " A definition of a TEXT file is "A file that contains characters organized 580 | " into one or more lines." 581 | " A definition of a LINE is "A sequence of zero or more non- s 582 | " plus a terminating " 583 | " That's why {stdin} always ends with ideally. However, there are 584 | " some programs which does not follow the POSIX rule and a Vim's way to join 585 | " List into TEXT; join({text}, "\n"); does not add to the end of 586 | " the last line. 587 | " That's why add a trailing if it does not exist. 588 | " REF: 589 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 590 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 591 | " :help split() 592 | " NOTE: 593 | " it does nothing if the text is a correct POSIX text 594 | function! s:repair_posix_text(text, ...) abort 595 | let newline = get(a:000, 0, "\n") 596 | return a:text =~# '\n$' ? a:text : a:text . newline 597 | endfunction 598 | 599 | " NOTE: 600 | " A definition of a TEXT file is "A file that contains characters organized 601 | " into one or more lines." 602 | " A definition of a LINE is "A sequence of zero or more non- s 603 | " plus a terminating " 604 | " REF: 605 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 606 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 607 | function! s:join_posix_lines(lines, ...) abort 608 | let newline = get(a:000, 0, "\n") 609 | return join(a:lines, newline) . newline 610 | endfunction 611 | 612 | " NOTE: 613 | " A definition of a TEXT file is "A file that contains characters organized 614 | " into one or more lines." 615 | " A definition of a LINE is "A sequence of zero or more non- s 616 | " plus a terminating " 617 | " TEXT into List; split({text}, '\r\?\n', 1); add an extra empty line at the 618 | " end of List because the end of TEXT ends with and keepempty=1 is 619 | " specified. (btw. keepempty=0 cannot be used because it will remove 620 | " emptylines in the head and the tail). 621 | " That's why removing a trailing before proceeding to 'split' is required 622 | " REF: 623 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 624 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 625 | function! s:split_posix_text(text, ...) abort 626 | let newline = get(a:000, 0, '\r\?\n') 627 | let text = substitute(a:text, newline . '$', '', '') 628 | return split(text, newline, 1) 629 | endfunction 630 | 631 | let &cpo = s:save_cpo 632 | unlet s:save_cpo 633 | " vim:set et ts=2 sts=2 sw=2 tw=0: 634 | --------------------------------------------------------------------------------