summaryrefslogtreecommitdiff
path: root/start/signify/autoload/sy/sign.vim
blob: 23b70382f38e775352827e13fe65e9a720192471 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
" vim: et sw=2 sts=2

scriptencoding utf-8

" Init: values {{{1
if get(g:, 'signify_sign_show_text', 1)
  let s:sign_delete = get(g:, 'signify_sign_delete', '_')
else
  let s:sign_delete = ' '
endif

let s:sign_show_count  = get(g:, 'signify_sign_show_count', 1)
let s:delete_highlight = ['', 'SignifyLineDelete']

" Function: #id_next {{{1
function! sy#sign#id_next(sy) abort
  let id = a:sy.signid
  let a:sy.signid += 1
  return id
endfunction

" Function: #get_current_signs {{{1
function! sy#sign#get_current_signs(sy) abort
  let a:sy.internal = {}
  let a:sy.external = {}

  let signlist = sy#util#execute('sign place buffer='. a:sy.buffer)

  for signline in split(signlist, '\n')[2:]
    let tokens = matchlist(signline, '\v^\s+\S+\=(\d+)\s+\S+\=(\d+)\s+\S+\=(.*)$')
    let line   = str2nr(tokens[1])
    let id     = str2nr(tokens[2])
    let type   = tokens[3]

    if type =~# '^Signify'
      " Handle ambiguous signs. Assume you have signs on line 3 and 4.
      " Removing line 3 would lead to the second sign to be shifted up
      " to line 3. Now there are still 2 signs, both one line 3.
      if has_key(a:sy.internal, line)
        execute 'sign unplace' a:sy.internal[line].id 'buffer='.a:sy.buffer
      endif
      let a:sy.internal[line] = { 'type': type, 'id': id }
    else
      let a:sy.external[line] = id
    endif
  endfor
endfunction


" Function: #process_diff {{{1
function! sy#sign#process_diff(sy, vcs, diff) abort
  let a:sy.signtable             = {}
  let a:sy.hunks                 = []
  let [added, modified, deleted] = [0, 0, 0]

  call sy#sign#get_current_signs(a:sy)

  " Determine where we have to put our signs.
  for line in filter(a:diff, 'v:val =~ "^@@ "')
    let a:sy.lines = []
    let ids        = []

    let tokens = matchlist(line, '^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)')

    let old_line = str2nr(tokens[1])
    let new_line = str2nr(tokens[3])

    let old_count = empty(tokens[2]) ? 1 : str2nr(tokens[2])
    let new_count = empty(tokens[4]) ? 1 : str2nr(tokens[4])

    " Workaround for non-conventional diff output in older Fossil versions:
    " https://fossil-scm.org/forum/forumpost/834ce0f1e1
    " Fixed as of: https://fossil-scm.org/index.html/info/7fd2a3652ea7368a
    if a:vcs == 'fossil' && new_line == 0
      let new_line = old_line - 1 - deleted
    endif

    " 2 lines added:

    " @@ -5,0 +6,2 @@ this is line 5
    " +this is line 5
    " +this is line 5
    if (old_count == 0) && (new_count >= 1)
      let added += new_count
      let offset = 0
      while offset < new_count
        let line    = new_line + offset
        let offset += 1
        if s:external_sign_present(a:sy, line) | continue | endif
        call add(ids, s:add_sign(a:sy, line, 'SignifyAdd'))
      endwhile

    " 2 lines removed:

    " @@ -6,2 +5,0 @@ this is line 5
    " -this is line 6
    " -this is line 7
    elseif (old_count >= 1) && (new_count == 0)
      if s:external_sign_present(a:sy, new_line) | continue | endif
      let deleted += old_count
      if new_line == 0
        call add(ids, s:add_sign(a:sy, 1, 'SignifyRemoveFirstLine'))
      elseif s:sign_show_count
        let text = s:sign_delete . (old_count <= 99 ? old_count : '>')
        while strwidth(text) > 2
          let text = substitute(text, '.', '', '')
        endwhile
        call add(ids, s:add_sign(a:sy, new_line, 'SignifyDelete'. old_count, text))
      else
        call add(ids, s:add_sign(a:sy, new_line, 'SignifyDeleteMore', s:sign_delete))
      endif

    " 2 lines changed:

    " @@ -5,2 +5,2 @@ this is line 4
    " -this is line 5
    " -this is line 6
    " +this os line 5
    " +this os line 6
    elseif old_count == new_count
      let modified += old_count
      let offset    = 0
      while offset < new_count
        let line    = new_line + offset
        let offset += 1
        if s:external_sign_present(a:sy, line) | continue | endif
        call add(ids, s:add_sign(a:sy, line, 'SignifyChange'))
      endwhile
    else

      " 2 lines changed; 2 lines removed:

      " @@ -5,4 +5,2 @@ this is line 4
      " -this is line 5
      " -this is line 6
      " -this is line 7
      " -this is line 8
      " +this os line 5
      " +this os line 6
      if old_count > new_count
        let modified += new_count
        let removed   = old_count - new_count
        let deleted  += removed
        let offset    = 0
        while offset < new_count - 1
          let line    = new_line + offset
          let offset += 1
          if s:external_sign_present(a:sy, line) | continue | endif
          call add(ids, s:add_sign(a:sy, line, 'SignifyChange'))
        endwhile
        let line = new_line + offset
        if s:external_sign_present(a:sy, line) | continue | endif
        call add(ids, s:add_sign(a:sy, line, (removed > 9)
              \ ? 'SignifyChangeDeleteMore'
              \ : 'SignifyChangeDelete'. removed))

      " lines changed and added:

      " @@ -5 +5,3 @@ this is line 4
      " -this is line 5
      " +this os line 5
      " +this is line 42
      " +this is line 666
      else
        let modified += old_count
        let offset    = 0
        while offset < old_count
          let line    = new_line + offset
          let offset += 1
          if s:external_sign_present(a:sy, line) | continue | endif
          call add(ids, s:add_sign(a:sy, line, 'SignifyChange'))
        endwhile
        while offset < new_count
          let added  += 1
          let line    = new_line + offset
          let offset += 1
          if s:external_sign_present(a:sy, line) | continue | endif
          call add(ids, s:add_sign(a:sy, line, 'SignifyAdd'))
        endwhile
      endif
    endif

    if !empty(ids)
      call add(a:sy.hunks, {
            \ 'ids'  : ids,
            \ 'start': a:sy.lines[0],
            \ 'end'  : a:sy.lines[-1] })
    endif
  endfor

  " Remove obsoleted signs.
  for line in filter(keys(a:sy.internal), '!has_key(a:sy.signtable, v:val)')
    execute 'sign unplace' a:sy.internal[line].id 'buffer='.a:sy.buffer
  endfor

  if has('gui_macvim') && has('gui_running') && mode() == 'n'
    " MacVim needs an extra kick in the butt, when setting signs from the
    " exit handler. :redraw would trigger a "hanging cursor" issue.
    call feedkeys("\<c-l>", 'n')
  endif

  if empty(a:sy.updated_by) && empty(a:sy.hunks)
    call sy#verbose('Successful exit value, but no diff. Keep VCS for time being.', a:vcs)
    return
  endif

  call sy#verbose('Signs updated.', a:vcs)
  let a:sy.updated_by = a:vcs
  if len(a:sy.vcs) > 1
    call sy#verbose('Disable all other VCS.', a:vcs)
    let a:sy.vcs = [a:vcs]
  endif

  let a:sy.stats = [added, modified, deleted]
endfunction

" Function: #remove_all_signs {{{1
function! sy#sign#remove_all_signs(bufnr) abort
  let sy = getbufvar(a:bufnr, 'sy')

  for hunk in sy.hunks
    for id in hunk.ids
      execute 'sign unplace' id 'buffer='.a:bufnr
    endfor
  endfor

  let sy.hunks = []
endfunction

" Function: s:add_sign {{{1
function! s:add_sign(sy, line, type, ...) abort
  call add(a:sy.lines, a:line)
  let a:sy.signtable[a:line] = 1

  if has_key(a:sy.internal, a:line)
    " There is a sign on this line already.
    if a:type == a:sy.internal[a:line].type
      " Keep current sign since the new one is of the same type.
      return a:sy.internal[a:line].id
    else
      " Update sign by overwriting the ID of the current sign.
      let id = a:sy.internal[a:line].id
    endif
  endif

  if !exists('id')
    let id = sy#sign#id_next(a:sy)
  endif

  if a:type =~# 'SignifyDelete'
    execute printf('sign define %s text=%s texthl=SignifySignDelete linehl=%s',
          \ a:type,
          \ a:1,
          \ s:delete_highlight[g:signify_line_highlight])
  endif
  execute printf('sign place %d line=%d name=%s buffer=%s',
        \ id,
        \ a:line,
        \ a:type,
        \ a:sy.buffer)

  return id
endfunction

" Function: s:external_sign_present {{{1
function! s:external_sign_present(sy, line) abort
  if has_key(a:sy.external, a:line)
    if has_key(a:sy.internal, a:line)
      " Remove Sy signs from lines with other signs.
      execute 'sign unplace' a:sy.internal[a:line].id 'buffer='.a:sy.buffer
    endif
    return 1
  endif
endfunction