@@ -12,29 +12,32 @@ function! lightline#gitdiff#write_to_cache(soft) abort
1212 return
1313 endif
1414
15- let g: lightline #gitdiff#cache[bufnr (' %' )] = s: calculate ()
15+ " let g:lightline#gitdiff#cache[bufnr('%')] = s:calculate_numstat()
16+ let g: lightline #gitdiff#cache[bufnr (' %' )] = s: calculate_porcelain ()
1617endfunction
1718
1819" format returns how many lines were added and/or deleted in a nicely
1920" formatted string. The output can be configured with global variables.
2021function ! lightline#gitdiff#format (data) abort
2122 let l: indicator_added = get (g: , ' lightline#gitdiff#indicator_added' , ' A: ' )
2223 let l: indicator_deleted = get (g: , ' lightline#gitdiff#indicator_deleted' , ' D: ' )
24+ let l: indicator_modified = get (g: , ' lightline#gitdiff#indicator_modified' , ' M: ' )
2325 let l: separator = get (g: , ' lightline#gitdiff#separator' , ' ' )
2426
2527 let l: lines_added = has_key (a: data , ' A' ) ? l: indicator_added . a: data [' A' ] : ' '
2628 let l: lines_deleted = has_key (a: data , ' D' ) ? l: indicator_deleted . a: data [' D' ] : ' '
29+ let l: lines_modified = has_key (a: data , ' M' ) ? l: indicator_modified . a: data [' M' ] : ' '
2730
28- return join ([l: lines_added , l: lines_deleted ], l: separator )
31+ return join ([l: lines_added , l: lines_deleted, l: lines_modified ], l: separator )
2932endfunction
3033
31- " calculate queries git to get the amount of lines that were added and/or
34+ " calculate_numstat queries git to get the amount of lines that were added and/or
3235" deleted. It returns a dict with two keys: 'A' and 'D'. 'A' holds how many
3336" lines were added, 'D' holds how many lines were deleted.
3437"
3538" This function is the most expensive one. It calls git twice: to check
3639" whether the buffer is in a git repository and to do the actual calculation.
37- function ! s: calculate () abort
40+ function ! s: calculate_numstat () abort
3841 if ! executable (' git' ) || ! s: is_inside_work_tree ()
3942 " b/c cannot do anything
4043 return {}
@@ -63,19 +66,125 @@ function! s:calculate() abort
6366 return l: ret
6467endfunction
6568
69+ function ! s: calculate_porcelain () abort
70+ let l: indicator_groups = s: transcode_diff_porcelain (s: get_diff_porcelain ())
71+
72+ let l: indicators = map (copy (l: indicator_groups ), { idx, val - > s: parse_indicator_group (val) })
73+
74+ let l: lines_added = len (filter (copy (l: indicators ), { idx, val - > val == # ' A' }))
75+ let l: lines_deleted = len (filter (copy (l: indicators ), { idx, val - > val == # ' D' }))
76+ let l: lines_modified = len (filter (copy (l: indicators ), { idx, val - > val == # ' M' }))
77+
78+ let l: ret = {}
79+
80+ if l: lines_added > 0
81+ let l: ret [' A' ] = l: lines_added
82+ endif
83+
84+ if l: lines_deleted > 0
85+ let l: ret [' D' ] = l: lines_deleted
86+ endif
87+
88+ if l: lines_modified > 0
89+ let l: ret [' M' ] = l: lines_modified
90+ endif
91+
92+ return l: ret
93+ endfunction
94+
95+ " get_diff_porcelain returns the output of git's word-diff as list. The header
96+ " of the diff is removed b/c it is not needed.
6697function ! s: get_diff_porcelain () abort
6798 " return the ouput of `git diff --word-diff=porcelain --unified=0` linewise
6899 "
69- " systemlist()
100+ let l: porcelain = systemlist (' cd ' . expand (' %:p:h:S' ) . ' && git diff --word-diff=porcelain --unified=0 -- ' . expand (' %:t:S' ))
101+ return l: porcelain [4 :]
102+ endfunction
103+
104+ " transcode_diff_porcelain turns a diff porcelain into a list of lists such as
105+ " the following:
106+ "
107+ " [ [' ', '-', '~'], ['~'], ['+', '~'], ['+', '-', '~' ] ]
108+ "
109+ " This translates to Deletion, Addition, Addition and Modification. The
110+ " characters ' ', '-', '+', '~' are the very first columns of a
111+ " `--word-diff=porcelain` output and include everything we need for
112+ " calculation.
113+ function ! s: transcode_diff_porcelain (porcelain) abort
114+ " b/c we do not need the line identifiers
115+ call filter (a: porcelain , { idx, val - > val !~# ' ^@@' })
116+
117+ " b/c we only need the identifiers at the first char of each diff line
118+ call map (a: porcelain , { idx, val - > strcharpart (val, -1 , 2 ) })
119+
120+ return s: group_at_right ({ el - > el == # ' ~' }, a: porcelain , v: true )
121+ endfunction
122+
123+ " parse_indicator_group parses a group of indicators af a word-diff porcelain
124+ " that describes an Addition, Delition or Modification. It returns a single
125+ " character of either 'A', 'D', 'M' for the type of diff that is recorded by
126+ " the group.
127+ function ! s: parse_indicator_group (indicators) abort
128+ let l: action = ' ' " A_ddition, D_eletion or M_odification
129+ let l: changer = ' '
130+
131+ for el in a: indicators
132+ if el == # ' ' && l: changer == # ' '
133+ " b/c first element with no meaning
134+ continue
135+ endif
136+
137+ if el == # ' +' || el == # ' -'
138+ if l: changer == # ' '
139+ " changer was found
140+ let l: changer = el
141+ continue
142+ else
143+ " 2nd changer found i.e., modification
144+ return ' M'
145+ endif
146+ endif
147+
148+ if el == # ' ~'
149+ if l: changer == # ' ' || l: changer == # ' +'
150+ " a single `~` stands for a new line i.e., an addition
151+ return ' A'
152+ else
153+ " b/c changer must be '-'
154+ return ' D'
155+ endif
156+ endif
157+ endfor
158+
159+ " b/c we should never end up here
160+ echoerr ' Could not parse indicator group of word-diff porcelain'
70161endfunction
71162
72- function ! s: clean_diff_porcelain () abort
73- " return cleaned porcelain (remove header of diff, only lines between `@@`)
163+ " group_at groups a list of elements where `f` evaluates to true returning a
164+ " list of lists. `f` must take a single parameter; each element is used as an
165+ " argument to `f`. If `borders` is true, the matched element is included in
166+ " each group at the beginning.
167+ function ! s: group_at (f , list , borders) abort
168+ let grouped_list = []
169+
170+ for el in a: list
171+ if a: f (el )
172+ call add (l: grouped_list , [])
173+ if ! a: borders
174+ continue
175+ endif
176+ endif
177+
178+ call add (l: grouped_list [len (l: grouped_list )], el )
179+ endfor
180+
181+ return grouped_list
74182endfunction
75183
76- function ! s: group_diff_porcelain () abort
77- " return grouped porcelain by `@@` -> each group is a single change
78- " in `calculate()` each group is analysed and a final result calculated
184+ " group_at_right behaves the same as group_at except that the matched element
185+ " is included in each group /at the end/.
186+ function ! s: group_at_right (f , list , borders) abort
187+ return reverse (s: group_at (a: f , reverse (a: list ), a: borders ))
79188endfunction
80189
81190function ! s: is_inside_work_tree () abort " {{{
0 commit comments