├── .projections.json ├── .travis.yml ├── LICENSE ├── README.md ├── autoload └── lightline │ ├── gitdiff.vim │ └── gitdiff │ ├── algorithms │ ├── numstat.vim │ └── word_diff_porcelain.vim │ └── utils.vim ├── plugin └── lightline │ └── gitdiff.vim └── test ├── algorithm └── parse_indicator_group.vader ├── lightline-gitdiff.vader ├── run-tests.sh ├── utils └── group_at.vader └── vimrc /.projections.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | "make": "sh test/run-tests.sh" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: vim 3 | 4 | before_script: | 5 | git clone https://github.com/junegunn/vader.vim.git 6 | git clone https://github.com/vim/vim.git 7 | cd vim 8 | ./configure --with-features=huge 9 | make 10 | sudo make install 11 | cd - 12 | script: | 13 | /usr/local/bin/vim -Nu <(cat << VIMRC 14 | set rtp+=vader.vim 15 | set rtp+=. 16 | VIMRC) -c 'Vader! test/lightline-gitdiff.vader' > /dev/null 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | lightline-gitdiff 2 | Copyright © 2019 Niklaas Baudet von Gersdorff 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightline-gitdiff 2 | 3 | I had been using [airblade/vim-gitgutter][gitgutter] for a while, however, I 4 | felt distracted by the indicators shown in the sign column in the end. That 5 | said, I wanted some lightweight signal indicating whether the current file 6 | contains uncommitted changes to the repository or not. 7 | 8 | So, this little plugin was born. I myself use 9 | [itchyny/lightline.vim][lightline] to configure the statusline of vim easily, 10 | so this is where the name of the plugin comes from. In addition, I embrace 11 | lightlines's philosophy to provide a lightweight and stable, yet configurable 12 | plugin that "just works". However, you can also integrate the plugin with vim's 13 | vanilla `statusline`. 14 | 15 | By default the plugin shows indicators such as the following: 16 | 17 | ``` 18 | A: 4 D: 6 M: 2 19 | ``` 20 | 21 | This says that, in comparison to the git index, the current buffer contains 12 22 | uncommitted changes: four lines were deleted, six lines were added and two 23 | lines only modified. If there are no uncommitted changes, nothing is shown to 24 | reduce distraction. 25 | 26 | You can see the plugin in action in my statusline/lightline: 27 | 28 | ![screenshot](https://raw.githubusercontent.com/wiki/niklaas/lightline-gitdiff/images/screenshot.png) 29 | 30 | ## Installation 31 | 32 | Use your favorite plugin manager to install the plugin. I personally prefer 33 | vim-plug but feel free to choose another one: 34 | 35 | ```vim 36 | Plug 'niklaas/lightline-gitdiff' 37 | ``` 38 | 39 | ## Configuration 40 | 41 | ### Using vim's vanilla statusline 42 | 43 | ```vim 44 | set statusline=%!lightline#gitdiff#get() 45 | ``` 46 | 47 | which let's your `statusline` consist of `gitdiff`'s indicators only. (Probably 48 | not what you want but you can consult `:h statusline` for further information 49 | on how to include additional elements.) 50 | 51 | ### Using lightline 52 | 53 | ```vim 54 | let g:lightline = { 55 | \ 'active': { 56 | \ 'left': [ [ 'mode', 'paste' ], 57 | \ [ 'gitbranch', 'filename', 'readonly', 'modified' ], 58 | \ [ 'gitdiff' ] ], 59 | \ 'right': [ [ 'lineinfo' ], 60 | \ [ 'percent' ] ] 61 | \ }, 62 | \ 'inactive': { 63 | \ 'left': [ [ 'filename', 'gitversion' ] ], 64 | \ }, 65 | \ 'component_function': { 66 | \ 'gitbranch': 'fugitive#head', 67 | \ }, 68 | \ 'component_expand': { 69 | \ 'gitdiff': 'lightline#gitdiff#get', 70 | \ }, 71 | \ 'component_type': { 72 | \ 'gitdiff': 'middle', 73 | \ }, 74 | \ } 75 | ``` 76 | 77 | which should give you pretty much the same result as shown in the screenshot. 78 | 79 | # Configuration 80 | 81 | You can configure the appearance of the indicators and the separator between 82 | them. The following are the defaults: 83 | 84 | ```vim 85 | let g:lightline#gitdiff#indicator_added = 'A: ' 86 | let g:lightline#gitdiff#indicator_deleted = 'D: ' 87 | let g:lightline#gitdiff#separator = ' ' 88 | ``` 89 | 90 | A callback function is called every time the `diff` is updated and written to 91 | the cache. By default this is `lightline#update()` to update lightline with 92 | the newly calculated `diff`. However, you can also provide you own callback 93 | function in the following way: 94 | 95 | ```vim 96 | let g:lightline#gitdiff#update_callback = { -> MyCustomCallback() } 97 | ``` 98 | 99 | If the callback function is not defined, the error is caught. This allows to 100 | use the plugin with any type of `statusline` plugin. 101 | 102 | You can even change the algorithm that is used to calculate the `diff`. The 103 | plugin comes bundled with two algorithms: `numstat` and `word_diff_porcelain`. 104 | By default, the latter one is used because it allows to display modified lines. 105 | `numstat` is much simpler but only supports showing added and deleted lines. 106 | This resembles the default: 107 | 108 | ```vim 109 | let g:LightlineGitDiffAlgorithm = 110 | \ { buffer -> lightline#gitdiff#algorithms#word_diff_porcelain#calculate(buffer) } 111 | ``` 112 | 113 | Substitute `word_diff_porcelain` with `numstat` if you want to switch -- or 114 | provide your own. Take a look at the source of both functions for inspiration 115 | or consult me if you need help. I am happy to bundle additional faster and more 116 | feature-rich algorithms in the package. 117 | 118 | You can show empty indicators (i.e. `A: 0 D: 0 M: 0`) in the following way: 119 | 120 | ```vim 121 | let g:lightline#gitdiff#show_empty_indicators = 1 122 | ``` 123 | 124 | # How it works / performance 125 | 126 | In the background, `lightline#gitdiff#get()` calls `git --numstat` or `git 127 | --word-diff=porcelain` (depending on the algorithm you choose, the latter being 128 | the default) for the current buffer and caches the result. 129 | 130 | If possible e.g., when an already open buffer is entered, the cache is used and 131 | no call to `git` is made. `git` is only executed when reading or writing to a 132 | buffer. See the `augroup` in [plugin/lightline/gitdiff.vim][augroup]. 133 | 134 | If you have any suggestions to improve the performance, please let me know. I 135 | am happy to implement your suggestions on my own -- or you can create a pull 136 | request. 137 | 138 | # Bugs etc. 139 | 140 | Probably this code has some sharp edges. Feel free to report bugs, suggestions 141 | and pull requests. I'll try to fix them as soon as possible. 142 | 143 | [gitgutter]: https://github.com/airblade/vim-gitgutter 144 | [lightline]: https://github.com/itchyny/lightline.vim 145 | [augroup]: https://github.com/niklaas/lightline-gitdiff/blob/master/plugin/lightline/gitdiff.vim 146 | -------------------------------------------------------------------------------- /autoload/lightline/gitdiff.vim: -------------------------------------------------------------------------------- 1 | function! lightline#gitdiff#get() abort 2 | return lightline#gitdiff#format(g:lightline#gitdiff#cache[bufnr('%')]) 3 | endfunction 4 | 5 | " update() {{{1 is the entry point for *writing* changes of the current buffer 6 | " into the cache. It calls a callback function afterwards. This allows to 7 | " execute arbitrary code every time the cache was updated. 8 | " 9 | " By default, `lightline#update()` is called b/c this plugin was intended for 10 | " Lightline [1] originally. However, /there is no need to use Lightline/. 11 | " Since a callback is provided, you can update any other statusbar et al. 12 | " 13 | " If the provided callback cannot be found, the error is caught. 14 | " 15 | " [1]: https://github.com/itchyny/lightline.vim 16 | function! lightline#gitdiff#update(buffers, soft) 17 | for buffer in a:buffers 18 | call lightline#gitdiff#write_calculation_to_cache(buffer, a:soft) 19 | endfor 20 | 21 | let l:Callback = get(g:, 'lightline#gitdiff#update_callback', { -> lightline#update() }) 22 | 23 | try 24 | call l:Callback() 25 | catch /^Vim\%((\a\+)\)\=:E117/ 26 | endtry 27 | endfunction 28 | 29 | " write_calculation_to_cache() {{{1 writes the information got from an 30 | " algorithm that calculates changes into the cache. There is an option to 31 | " perform a "soft" write to reduce calls to the function that calculates 32 | " changes. This is to minimize overhead. Anyway, the function ensures that 33 | " there is data in the cache for the current buffer. 34 | function! lightline#gitdiff#write_calculation_to_cache(buffer, soft) abort 35 | if a:soft && has_key(g:lightline#gitdiff#cache, a:buffer) 36 | " b/c there is something in the cache already 37 | return 38 | endif 39 | 40 | let l:indicator_values = get(g:, 'LightlineGitDiffAlgorithm', 41 | \ { buffer -> lightline#gitdiff#algorithms#word_diff_porcelain#calculate(buffer) })(a:buffer) 42 | 43 | " If the user doesn't want to show empty indicators, 44 | " then remove the empty indicators returned from the algorithm 45 | if !get(g:, 'lightline#gitdiff#show_empty_indicators', 0) 46 | for key in keys(l:indicator_values) 47 | if l:indicator_values[key] == 0 48 | unlet l:indicator_values[key] 49 | endif 50 | endfor 51 | endif 52 | 53 | let g:lightline#gitdiff#cache[a:buffer] = l:indicator_values 54 | endfunction 55 | 56 | " format() {{{1 returns the calculated changes of the current buffer in a 57 | " nicely formatted string. The output can be configured with the following 58 | " global variables that are exposed as public API: 59 | " 60 | " - lightline#gitdiff#separator 61 | " - lightline#gitdiff#indicator_added 62 | " - lightline#gitdiff#indicator_deleted 63 | " - lightline#gitdiff#indicator_modified 64 | " 65 | " It takes what I call "diff_dict" as input i.e., a Dict that has identifiers 66 | " as keys (`A`, `D` and `M`). Each identifier specifies a type of change. The 67 | " values of the dict specify the amount of changes. The following types of 68 | " changes exist: 69 | " 70 | " - A: Addition 71 | " - D: Deletion 72 | " - M: Modification 73 | " 74 | " In fact, an arbitrary number of changes can be supported. This depends on 75 | " the algorithm that is used for calculation 76 | " (`g:LightlineGitDiffAlgorithm`). However, this function takes only these 77 | " types of changes into account b/c it only provides default indicators for 78 | " these types. If an algorithm does not support a particular type, this is not 79 | " an issue; if it supports more types than this function, the additional types 80 | " must be configured with default values here. 81 | " 82 | " The function maps the values of the diff_dict to the indicators that are 83 | " configured with the global values mentioned above. The `...#separator` 84 | " separates each indicator-value-pair. If none of the global variables are 85 | " set, `format` returns a joined string separates by a single space with the 86 | " amount of each type of change prefixed with its key and a colon e.g., `A: 4 87 | " D: 5`. 88 | function! lightline#gitdiff#format(diff_dict) abort 89 | let l:separator = get(g:, 'lightline#gitdiff#separator', ' ') 90 | 91 | let l:change_types = { 'A': 'added', 'D': 'deleted', 'M': 'modified' } 92 | let l:Formatter = { key, val -> has_key(a:diff_dict, key) ? 93 | \ get(g:, 'lightline#gitdiff#indicator_' . val, key . ': ') . a:diff_dict[key] : '' } 94 | 95 | return join(values(filter(map(l:change_types, l:Formatter), 96 | \ { key, val -> val !=# '' })), l:separator) 97 | endfunction 98 | -------------------------------------------------------------------------------- /autoload/lightline/gitdiff/algorithms/numstat.vim: -------------------------------------------------------------------------------- 1 | " calculate_numstat {{{1 queries git to get the amount of lines that were 2 | " added and/or deleted. It returns a dict with two keys: 'A' and 'D'. 'A' 3 | " holds how many lines were added, 'D' holds how many lines were deleted. 4 | function! lightline#gitdiff#algorithms#numstat#calculate(buffer) abort 5 | if !lightline#gitdiff#utils#is_git_exectuable() || !lightline#gitdiff#utils#is_inside_work_tree() 6 | " b/c there is nothing that can be done here; the algorithm needs git 7 | return {} 8 | endif 9 | 10 | let l:stats = split(system('cd ' . expand('#' . a:buffer . ':p:h:S') 11 | \ . ' && git diff --no-ext-diff --numstat -- ' 12 | \ . expand('#' . a:buffer . ':t:S'))) 13 | 14 | if len(l:stats) < 2 || join(l:stats[:1], '') !~# '^\d\+$' 15 | " b/c there are no changes made, the file is untracked or some error 16 | " occured 17 | return {} 18 | endif 19 | 20 | return { 'A': l:stats[0], 'D': l:stats[1] } 21 | endfunction 22 | -------------------------------------------------------------------------------- /autoload/lightline/gitdiff/algorithms/word_diff_porcelain.vim: -------------------------------------------------------------------------------- 1 | " calculate_porcelain {{{1 transcodes a `git diff --word-diff=porcelain` and 2 | " returns a dictionary that tells how many lines in the diff mean Addition, 3 | " Deletion or Modification. 4 | function! lightline#gitdiff#algorithms#word_diff_porcelain#calculate(buffer) abort 5 | if !lightline#gitdiff#utils#is_git_exectuable() || !lightline#gitdiff#utils#is_inside_work_tree(a:buffer) 6 | " b/c there is nothing that can be done here; the algorithm needs git 7 | return {} 8 | endif 9 | 10 | let l:indicator_groups = s:transcode_diff_porcelain(s:get_diff_porcelain(a:buffer)) 11 | 12 | let l:changes = map(copy(l:indicator_groups), { idx, val -> 13 | \ lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(val) }) 14 | 15 | let l:lines_added = len(filter(copy(l:changes), { idx, val -> val ==# 'A' })) 16 | let l:lines_deleted = len(filter(copy(l:changes), { idx, val -> val ==# 'D' })) 17 | let l:lines_modified = len(filter(copy(l:changes), { idx, val -> val ==# 'M' })) 18 | 19 | return { 'A': l:lines_added, 'D': l:lines_deleted, 'M': l:lines_modified} 20 | endfunction 21 | 22 | " get_diff_porcelain {{{1 returns the output of git's word-diff as list. The 23 | " header of the diff is removed b/c it is not needed. 24 | function! s:get_diff_porcelain(buffer) abort 25 | let l:porcelain = systemlist('cd ' . expand('#' . a:buffer . ':p:h:S') . 26 | \ ' && git diff --no-ext-diff --word-diff=porcelain --unified=0 -- ' . expand('#' . a:buffer . ':t:S')) 27 | return l:porcelain[4:] 28 | endfunction 29 | 30 | " transcode_diff_porcelain() {{{1 turns a diff porcelain into a list of lists 31 | " such as the following: 32 | " 33 | " [ [' ', '-', '~'], ['~'], ['+', '~'], ['+', '-', '~' ] ] 34 | " 35 | " This translates to Deletion, Addition, Addition and Modification eventually, 36 | " see s:parse_indicator_group. The characters ' ', '-', '+', '~' are the very 37 | " first columns of a `--word-diff=porcelain` output and include everything we 38 | " need for calculation. 39 | function! s:transcode_diff_porcelain(porcelain) abort 40 | " b/c we do not need the line identifiers 41 | call filter(a:porcelain, { idx, val -> val !~# '^@@' }) 42 | 43 | " b/c we only need the identifiers at the first char of each diff line 44 | call map(a:porcelain, { idx, val -> strcharpart(val, -1, 2) }) 45 | 46 | return lightline#gitdiff#utils#group_at({ el -> el ==# '~' }, a:porcelain, v:true) 47 | endfunction 48 | 49 | " parse_indicator_group() {{{1 parses a group of indicators af a word-diff 50 | " porcelain that describes an Addition, Delition or Modification. It returns a 51 | " single character of either 'A', 'D', 'M' for the type of diff that is 52 | " recorded by the group respectively. A group looks like the following: 53 | " 54 | " [' ', '+', '~'] 55 | " 56 | " In this case it means A_ddition. The algorithm is rather simple because 57 | " there are only four indicators: ' ', '+', '-', '~'. These are the rules: 58 | " 59 | " 1. Sometimes a group starts with a 'space'. This can be ignored. 60 | " 2. '+' and '-' I call "changers". In combination with other indicators 61 | " they specify what kind of change was made. 62 | " 3. If a '+' or '-' is follwed by a '~' the group means Addition or 63 | " Deletion respectively. 64 | " 4. If a '+' or '-' is followed by anything else than a '~' it is a 65 | " Modification. 66 | " 5. If the group consists of a single '~' it is an Addition. 67 | " 6. There must be one but only one '~' in *every* group. 68 | " 69 | " The method implements this algorithm. It is far from perfect but seems to 70 | " work as some tests showed. 71 | function! lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(indicators) abort 72 | let l:changer = '' 73 | 74 | if len(a:indicators) ==# 1 && a:indicators[0] ==# '~' 75 | return 'A' 76 | endif 77 | 78 | for el in a:indicators 79 | if l:changer ==# '' && ( el ==# '-' || el ==# '+' ) 80 | let l:changer = el 81 | continue 82 | endif 83 | 84 | if l:changer ==# '+' && el ==# '~' 85 | return 'A' 86 | endif 87 | 88 | if l:changer ==# '-' && el ==# '~' 89 | return 'D' 90 | endif 91 | 92 | if l:changer !=# el 93 | return 'M' 94 | endif 95 | endfor 96 | 97 | " b/c we should never end up here 98 | echoerr 'lightline#gitdiff: Error parsing indicator group: [ ' . join(a:indicators, ', ') . ' ]' 99 | endfunction 100 | -------------------------------------------------------------------------------- /autoload/lightline/gitdiff/utils.vim: -------------------------------------------------------------------------------- 1 | " group_at() {{{1 groups a list of elements where `f` evaluates to true returning a 2 | " list of lists. `f` must take a single parameter; each element is used as an 3 | " argument to `f`. If `borders` is true, the matched element is included in 4 | " each group at the end. 5 | function! lightline#gitdiff#utils#group_at(f, list, borders) abort 6 | " for the first element this must be true to initialise the list with an 7 | " empty group at the beginning (if it's not a border that should be 8 | " excluded) 9 | let l:is_previous_border = v:true 10 | let l:grouped_list = [] 11 | 12 | for el in a:list 13 | " element matches but not borders should be included 14 | let l:skip_this = a:f(el) && !a:borders 15 | 16 | if l:is_previous_border && !l:skip_this 17 | call add(l:grouped_list, []) 18 | endif 19 | 20 | let l:is_previous_border = a:f(el) ? v:true : v:false 21 | 22 | if l:skip_this 23 | continue 24 | endif 25 | 26 | call add(l:grouped_list[len(l:grouped_list)-1], el) 27 | endfor 28 | 29 | return l:grouped_list 30 | endfunction 31 | 32 | function! lightline#gitdiff#utils#is_inside_work_tree(buffer) abort "{{{1 33 | call system('cd ' . expand('#' . a:buffer . ':p:h:S') . ' && git rev-parse --is-inside-work-tree --prefix ' . expand('#' . a:buffer . ':h:S')) 34 | return !v:shell_error 35 | endfunction 36 | 37 | function! lightline#gitdiff#utils#is_git_exectuable() abort "{{{1 38 | return executable('git') 39 | endfunction 40 | -------------------------------------------------------------------------------- /plugin/lightline/gitdiff.vim: -------------------------------------------------------------------------------- 1 | " Cache stores the information got from the algorithms that process the 2 | " `diff`s. The key is the buffer number, the value is the amount of lines. 3 | let g:lightline#gitdiff#cache = {} 4 | 5 | augroup lightline#gitdiff 6 | autocmd! 7 | " Update hard b/c buffer is new or changed 8 | autocmd BufReadPost,BufWritePost * :call lightline#gitdiff#update([bufnr('%')], v:false) 9 | " Soft update is possible b/c no buffer new or changed 10 | autocmd BufEnter * :call lightline#gitdiff#update([bufnr('%')], v:true) 11 | " Update all cached buffers hard b/c change to repository was made 12 | autocmd BufDelete COMMIT_EDITMSG :call lightline#gitdiff#update(keys(g:lightline#gitdiff#cache), v:false) 13 | augroup end 14 | -------------------------------------------------------------------------------- /test/algorithm/parse_indicator_group.vader: -------------------------------------------------------------------------------- 1 | # Addition {{{1 2 | 3 | Execute (parse_indicator_group(): given indicator group is [`~`]): 4 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(['~']) 5 | Then (should return A_ddition): 6 | AssertEqual 'A', actual 7 | 8 | Execute (parse_indicator_group(): given indicator group is ['+', '~']): 9 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(['+', '~']) 10 | Then (should return A_ddition): 11 | AssertEqual 'A', actual 12 | 13 | # Deletion {{{1 14 | 15 | Execute (parse_indicator_group(): given indicator group is ['-', '~']): 16 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(['-', '~']) 17 | Then (should return D_eletion): 18 | AssertEqual 'D', actual 19 | 20 | # Modification {{{1 21 | 22 | Execute (parse_indicator_group(): given indicator group is ['-', '+', '~']): 23 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group(['-', '+', '~']) 24 | Then (should return M_odification): 25 | AssertEqual 'M', actual 26 | 27 | Execute (parse_indicator_group(): given indicator group is [' ', '+', ' ', '~']): 28 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group([' ', '+', ' ', '~']) 29 | Then (should return M_odification): 30 | AssertEqual 'M', actual 31 | 32 | Execute (parse_indicator_group(): given indicator group is [' ', '-', ' ', '~']): 33 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group([' ', '-', ' ', '~']) 34 | Then (should return M_odification): 35 | AssertEqual 'M', actual 36 | 37 | Execute (parse_indicator_group(): given indicator group is [' ', '~']): 38 | # because a single space was inserted at the beginning of a line 39 | let actual = lightline#gitdiff#algorithms#word_diff_porcelain#parse_indicator_group([' ', '~']) 40 | Then (should return M_odification): 41 | AssertEqual 'M', actual 42 | 43 | # vim:set fdm=marker: 44 | -------------------------------------------------------------------------------- /test/lightline-gitdiff.vader: -------------------------------------------------------------------------------- 1 | Include (Algorithms): algorithm/parse_indicator_group.vader 2 | Include (Utils): utils/group_at.vader 3 | 4 | Before : 5 | if exists('g:LightlineGitDiffAlgorithm') 6 | unlet g:LightlineGitDiffAlgorithm 7 | endif 8 | 9 | " no show_empty_indicators variable 10 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable and an empty result): 11 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M':0 } } 12 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 13 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 14 | Then (should return no empty indicators): 15 | AssertEqual {}, actual 16 | 17 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with only added lines): 18 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 1, 'D': 0, 'M': 0 } } 19 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 20 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 21 | Then (should remove all indicators but 'A'): 22 | AssertEqual { 'A': 1 }, actual 23 | 24 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with only deleted lines): 25 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 1, 'M': 0 } } 26 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 27 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 28 | Then (should remove all indicators but 'D'): 29 | AssertEqual { 'D': 1 }, actual 30 | 31 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with only modified lines): 32 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M': 1 } } 33 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 34 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 35 | Then (should remove all indicators but 'M'): 36 | AssertEqual { 'M': 1 }, actual 37 | 38 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with added and modified lines): 39 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 3, 'D': 0, 'M': 1 } } 40 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 41 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 42 | Then (should remove only the 'D' indicator): 43 | AssertEqual { 'A': 3, 'M': 1 }, actual 44 | 45 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with deleted and modified lines): 46 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 2, 'M': 1 } } 47 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 48 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 49 | Then (should remove only the 'A' indicator): 50 | AssertEqual { 'D': 2, 'M': 1 }, actual 51 | 52 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with added and deleted lines): 53 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 4, 'D': 5, 'M': 0 } } 54 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 55 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 56 | Then (should remove only the 'M' indicator): 57 | AssertEqual { 'A': 4, 'D': 5 }, actual 58 | 59 | Execute(write_calculation_to_cache(): given no show_empty_indicators variable with added, deleted, and modified lines): 60 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 9, 'D': 10, 'M': 7 } } 61 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 62 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 63 | Then (should not remove any indicators): 64 | AssertEqual { 'A': 9, 'D': 10, 'M': 7 }, actual 65 | 66 | " show_empty_indicators variable == 0 67 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with an empty result): 68 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M':0 } } 69 | let g:lightline#gitdiff#show_empty_indicators = 0 70 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 71 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 72 | Then (should return no empty indicators): 73 | AssertEqual {}, actual 74 | 75 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with only added lines): 76 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 1, 'D': 0, 'M': 0 } } 77 | let g:lightline#gitdiff#show_empty_indicators = 0 78 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 79 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 80 | Then (should remove all indicators but 'A'): 81 | AssertEqual { 'A': 1 }, actual 82 | 83 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with only deleted lines): 84 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 1, 'M': 0 } } 85 | let g:lightline#gitdiff#show_empty_indicators = 0 86 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 87 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 88 | Then (should remove all indicators but 'D'): 89 | AssertEqual { 'D': 1 }, actual 90 | 91 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with only modified lines): 92 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M': 1 } } 93 | let g:lightline#gitdiff#show_empty_indicators = 0 94 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 95 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 96 | Then (should remove all indicators but 'M'): 97 | AssertEqual { 'M': 1 }, actual 98 | 99 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with added and modified lines): 100 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 3, 'D': 0, 'M': 1 } } 101 | let g:lightline#gitdiff#show_empty_indicators = 0 102 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 103 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 104 | Then (should remove only the 'D' indicator): 105 | AssertEqual { 'A': 3, 'M': 1 }, actual 106 | 107 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with deleted and modified lines): 108 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 2, 'M': 1 } } 109 | let g:lightline#gitdiff#show_empty_indicators = 0 110 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 111 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 112 | Then (should remove only the 'A' indicator): 113 | AssertEqual { 'D': 2, 'M': 1 }, actual 114 | 115 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with added and deleted lines): 116 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 4, 'D': 5, 'M': 0 } } 117 | let g:lightline#gitdiff#show_empty_indicators = 0 118 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 119 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 120 | Then (should remove only the 'M' indicator): 121 | AssertEqual { 'A': 4, 'D': 5 }, actual 122 | 123 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 0 with added, deleted, and modified lines): 124 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 9, 'D': 10, 'M': 7 } } 125 | let g:lightline#gitdiff#show_empty_indicators = 0 126 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 127 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 128 | Then (should not remove any indicators): 129 | AssertEqual { 'A': 9, 'D': 10, 'M': 7 }, actual 130 | 131 | " show_empty_indicators variable == 1 132 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with an empty result): 133 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M':0 } } 134 | let g:lightline#gitdiff#show_empty_indicators = 1 135 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 136 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 137 | Then (should return all indicators): 138 | AssertEqual { 'A': 0, 'D': 0, 'M':0 }, actual 139 | 140 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with only added lines): 141 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 1, 'D': 0, 'M': 0 } } 142 | let g:lightline#gitdiff#show_empty_indicators = 1 143 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 144 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 145 | Then (should remove all indicators): 146 | AssertEqual { 'A': 1, 'D': 0, 'M': 0 }, actual 147 | 148 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with only deleted lines): 149 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 1, 'M': 0 } } 150 | let g:lightline#gitdiff#show_empty_indicators = 1 151 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 152 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 153 | Then (should return all indicators): 154 | AssertEqual { 'A': 0, 'D': 1, 'M': 0 }, actual 155 | 156 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with only modified lines): 157 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 0, 'M': 1 } } 158 | let g:lightline#gitdiff#show_empty_indicators = 1 159 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 160 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 161 | Then (should return all indicators): 162 | AssertEqual { 'A': 0, 'D': 0, 'M': 1 }, actual 163 | 164 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with added and modified lines): 165 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 3, 'D': 0, 'M': 1 } } 166 | let g:lightline#gitdiff#show_empty_indicators = 1 167 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 168 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 169 | Then (should return all indicators): 170 | AssertEqual { 'A': 3, 'D': 0, 'M': 1 }, actual 171 | 172 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with deleted and modified lines): 173 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 0, 'D': 2, 'M': 1 } } 174 | let g:lightline#gitdiff#show_empty_indicators = 1 175 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 176 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 177 | Then (should return all indicators): 178 | AssertEqual { 'A': 0, 'D': 2, 'M': 1 }, actual 179 | 180 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with added and deleted lines): 181 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 4, 'D': 5, 'M': 0 } } 182 | let g:lightline#gitdiff#show_empty_indicators = 1 183 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 184 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 185 | Then (should return all indicators): 186 | AssertEqual { 'A': 4, 'D': 5, 'M': 0 }, actual 187 | 188 | Execute(write_calculation_to_cache(): given the show_empty_indicators variable equals 1 with added, deleted, and modified lines): 189 | let g:LightlineGitDiffAlgorithm = { -> { 'A': 9, 'D': 10, 'M': 7 } } 190 | let g:lightline#gitdiff#show_empty_indicators = 1 191 | call g:lightline#gitdiff#write_calculation_to_cache(1, 0) 192 | let actual = get(g:, 'lightline#gitdiff#cache')[1] 193 | Then (should return all indicators): 194 | AssertEqual { 'A': 9, 'D': 10, 'M': 7 }, actual 195 | -------------------------------------------------------------------------------- /test/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | DIR=$(dirname "$0") 4 | cd "$DIR" || exit 5 | 6 | vim -Nu vimrc -Es -c 'Vader! *' 7 | -------------------------------------------------------------------------------- /test/utils/group_at.vader: -------------------------------------------------------------------------------- 1 | Before: 2 | let g:Matcher = { el -> el ==# 'X' } 3 | 4 | # Empty list {{{1 5 | 6 | Execute (group_at(): given an empty list): 7 | let borders = [ v:true, v:false ] 8 | let actuals = map(copy(borders), { idx, b -> 9 | \ lightline#gitdiff#utils#group_at(g:Matcher, [], b) 10 | \ }) 11 | Then (should return an empty list): 12 | AssertEqual [ [], [] ], actuals 13 | 14 | # Single element {{{1 15 | 16 | Execute (group_at(): given a list with single element, not matching): 17 | let borders = [ v:true, v:false ] 18 | let actuals = map(copy(borders), { idx, b -> 19 | \ lightline#gitdiff#utils#group_at(g:Matcher, ['a'], b) 20 | \ }) 21 | Then (should return a list with a single group, including the element): 22 | AssertEqual [ [['a']], [['a']] ], actuals 23 | 24 | Execute (group_at(): given a list with single element, single match, excluding borders): 25 | let actual = lightline#gitdiff#utils#group_at(g:Matcher, ['X'], v:false) 26 | Then (should return an empty list): 27 | AssertEqual [], actual 28 | 29 | Execute (group_at(): given a list with single element, single match, including borders): 30 | let actual = lightline#gitdiff#utils#group_at(g:Matcher, ['X'], v:true) 31 | Then (should return an empty list): 32 | AssertEqual [ ['X'] ], actual 33 | 34 | # Multiple elements {{{1 35 | 36 | Execute (group_at(): given a list with two elements, not matching): 37 | let borders = [ v:true, v:false ] 38 | let actuals = map(copy(borders), { idx, b -> 39 | \ lightline#gitdiff#utils#group_at(g:Matcher, ['a', 'b'], b) 40 | \ }) 41 | Then (should return a list with a single group, including the elements): 42 | AssertEqual [ [['a', 'b']], [['a', 'b']] ], actuals 43 | 44 | 45 | Execute (group_at(): given a list with two elements, single match, excluding borders): 46 | let actual = lightline#gitdiff#utils#group_at(g:Matcher, ['a', 'X'], v:false) 47 | Then (should return a list with a single element, excluding the border): 48 | AssertEqual [ ['a'] ], actual 49 | 50 | Execute (group_at(): given a list with two elements, single match, including borders): 51 | let actual = lightline#gitdiff#utils#group_at(g:Matcher, ['a', 'X'], v:true) 52 | Then (should return a list with a single element, including the border): 53 | AssertEqual [ ['a', 'X'] ], actual 54 | 55 | 56 | Execute (group_at(): given a list with multiple elements, single match, including borders): 57 | let lists = [ ['a', 'b', 'X'], ['a', 'b', 'X', 'c'] ] 58 | let actuals = map(copy(lists), { idx, l -> 59 | \ lightline#gitdiff#utils#group_at(g:Matcher, l, v:true) 60 | \ }) 61 | Then (should return a list with a single element, including the border): 62 | AssertEqual [ [['a', 'b', 'X']], [['a', 'b', 'X'], ['c']] ], actuals 63 | 64 | Execute (group_at(): given a list with multiple elements, multiple matches, including borders): 65 | let lists = [ ['a', 'b', 'X', 'X'], ['a', 'b', 'X', 'c', 'X', 'd'] ] 66 | let actuals = map(copy(lists), { idx, l -> 67 | \ lightline#gitdiff#utils#group_at(g:Matcher, l, v:true) 68 | \ }) 69 | Then (should return a list with a single element, including the border): 70 | AssertEqual [ [['a', 'b', 'X'], ['X']], [['a', 'b', 'X'], ['c', 'X'], ['d']] ], actuals 71 | 72 | 73 | Execute (group_at(): given a list with multiple elements, single match, excluding borders): 74 | let lists = [ ['a', 'b', 'X'], ['a', 'b', 'X', 'c'] ] 75 | let actuals = map(copy(lists), { idx, l -> 76 | \ lightline#gitdiff#utils#group_at(g:Matcher, l, v:false) 77 | \ }) 78 | Then (should return a list with a single element, including the border): 79 | AssertEqual [ [['a', 'b']], [['a', 'b'], ['c']] ], actuals 80 | 81 | Execute (group_at(): given a list with multiple elements, multiple matches, excluding borders): 82 | let lists = [ ['a', 'b', 'X', 'X'], ['a', 'b', 'X', 'c', 'X', 'd'] ] 83 | let actuals = map(copy(lists), { idx, l -> 84 | \ lightline#gitdiff#utils#group_at(g:Matcher, l, v:false) 85 | \ }) 86 | Then (should return a list with a single element, including the border): 87 | AssertEqual [ [['a', 'b']], [['a', 'b'], ['c'], ['d']] ], actuals 88 | -------------------------------------------------------------------------------- /test/vimrc: -------------------------------------------------------------------------------- 1 | filetype off 2 | let &runtimepath .= ',' . expand(':p:h:h') 3 | let &runtimepath .= ',~/.vim/plugged/vader.vim' 4 | filetype plugin indent on 5 | syntax enable 6 | set nomore 7 | set noswapfile 8 | set viminfo= 9 | --------------------------------------------------------------------------------