sign.vim 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. " Vim doesn't namespace sign ids so every plugin shares the same
  2. " namespace. Sign ids are simply integers so to avoid clashes with other
  3. " signs we guess at a clear run.
  4. "
  5. " Note also we currently never reset s:next_sign_id.
  6. let s:first_sign_id = 3000
  7. let s:next_sign_id = s:first_sign_id
  8. let s:dummy_sign_id = s:first_sign_id - 1
  9. " Remove-all-signs optimisation requires Vim 7.3.596+.
  10. let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596"))
  11. " Removes gitgutter's signs (excluding dummy sign) from the buffer being processed.
  12. function! gitgutter#sign#clear_signs()
  13. let bufnr = gitgutter#utility#bufnr()
  14. call gitgutter#sign#find_current_signs()
  15. let sign_ids = map(values(getbufvar(bufnr, 'gitgutter_gitgutter_signs')), 'v:val.id')
  16. call gitgutter#sign#remove_signs(sign_ids, 1)
  17. call setbufvar(bufnr, 'gitgutter_gitgutter_signs', {})
  18. endfunction
  19. " Updates gitgutter's signs in the buffer being processed.
  20. "
  21. " modified_lines: list of [<line_number (number)>, <name (string)>]
  22. " where name = 'added|removed|modified|modified_removed'
  23. function! gitgutter#sign#update_signs(modified_lines)
  24. call gitgutter#sign#find_current_signs()
  25. let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]')
  26. let obsolete_signs = gitgutter#sign#obsolete_gitgutter_signs_to_remove(new_gitgutter_signs_line_numbers)
  27. let flicker_possible = s:remove_all_old_signs && !empty(a:modified_lines)
  28. if flicker_possible
  29. call gitgutter#sign#add_dummy_sign()
  30. endif
  31. call gitgutter#sign#remove_signs(obsolete_signs, s:remove_all_old_signs)
  32. call gitgutter#sign#upsert_new_gitgutter_signs(a:modified_lines)
  33. if flicker_possible
  34. call gitgutter#sign#remove_dummy_sign(0)
  35. endif
  36. endfunction
  37. function! gitgutter#sign#add_dummy_sign()
  38. let bufnr = gitgutter#utility#bufnr()
  39. if !getbufvar(bufnr, 'gitgutter_dummy_sign')
  40. execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . bufnr
  41. call setbufvar(bufnr, 'gitgutter_dummy_sign', 1)
  42. endif
  43. endfunction
  44. function! gitgutter#sign#remove_dummy_sign(force)
  45. let bufnr = gitgutter#utility#bufnr()
  46. if getbufvar(bufnr, 'gitgutter_dummy_sign') && (a:force || !g:gitgutter_sign_column_always)
  47. execute "sign unplace" s:dummy_sign_id "buffer=" . bufnr
  48. call setbufvar(bufnr, 'gitgutter_dummy_sign', 0)
  49. endif
  50. endfunction
  51. "
  52. " Internal functions
  53. "
  54. function! gitgutter#sign#find_current_signs()
  55. let bufnr = gitgutter#utility#bufnr()
  56. let gitgutter_signs = {} " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>}
  57. let other_signs = [] " [<line_number (number),...]
  58. let dummy_sign_placed = 0
  59. redir => signs
  60. silent execute "sign place buffer=" . bufnr
  61. redir END
  62. for sign_line in filter(split(signs, '\n')[2:], 'v:val =~# "="')
  63. " Typical sign line: line=88 id=1234 name=GitGutterLineAdded
  64. " We assume splitting is faster than a regexp.
  65. let components = split(sign_line)
  66. let name = split(components[2], '=')[1]
  67. if name =~# 'GitGutterDummy'
  68. let dummy_sign_placed = 1
  69. else
  70. let line_number = str2nr(split(components[0], '=')[1])
  71. if name =~# 'GitGutter'
  72. let id = str2nr(split(components[1], '=')[1])
  73. " Remove orphaned signs (signs placed on lines which have been deleted).
  74. " (When a line is deleted its sign lingers. Subsequent lines' signs'
  75. " line numbers are decremented appropriately.)
  76. if has_key(gitgutter_signs, line_number)
  77. execute "sign unplace" gitgutter_signs[line_number].id
  78. endif
  79. let gitgutter_signs[line_number] = {'id': id, 'name': name}
  80. else
  81. call add(other_signs, line_number)
  82. endif
  83. end
  84. endfor
  85. call setbufvar(bufnr, 'gitgutter_dummy_sign', dummy_sign_placed)
  86. call setbufvar(bufnr, 'gitgutter_gitgutter_signs', gitgutter_signs)
  87. call setbufvar(bufnr, 'gitgutter_other_signs', other_signs)
  88. endfunction
  89. " Returns a list of [<id (number)>, ...]
  90. " Sets `s:remove_all_old_signs` as a side-effect.
  91. function! gitgutter#sign#obsolete_gitgutter_signs_to_remove(new_gitgutter_signs_line_numbers)
  92. let bufnr = gitgutter#utility#bufnr()
  93. let signs_to_remove = [] " list of [<id (number)>, ...]
  94. let remove_all_signs = 1
  95. let old_gitgutter_signs = getbufvar(bufnr, 'gitgutter_gitgutter_signs')
  96. for line_number in keys(old_gitgutter_signs)
  97. if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1
  98. call add(signs_to_remove, old_gitgutter_signs[line_number].id)
  99. else
  100. let remove_all_signs = 0
  101. endif
  102. endfor
  103. let s:remove_all_old_signs = remove_all_signs
  104. return signs_to_remove
  105. endfunction
  106. function! gitgutter#sign#remove_signs(sign_ids, all_signs)
  107. let bufnr = gitgutter#utility#bufnr()
  108. if a:all_signs && s:supports_star && empty(getbufvar(bufnr, 'gitgutter_other_signs'))
  109. let dummy_sign_present = getbufvar(bufnr, 'gitgutter_dummy_sign')
  110. execute "sign unplace * buffer=" . bufnr
  111. if dummy_sign_present
  112. execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . bufnr
  113. endif
  114. else
  115. for id in a:sign_ids
  116. execute "sign unplace" id
  117. endfor
  118. endif
  119. endfunction
  120. function! gitgutter#sign#upsert_new_gitgutter_signs(modified_lines)
  121. let bufnr = gitgutter#utility#bufnr()
  122. let other_signs = getbufvar(bufnr, 'gitgutter_other_signs')
  123. let old_gitgutter_signs = getbufvar(bufnr, 'gitgutter_gitgutter_signs')
  124. for line in a:modified_lines
  125. let line_number = line[0] " <number>
  126. if index(other_signs, line_number) == -1 " don't clobber others' signs
  127. let name = gitgutter#utility#highlight_name_for_change(line[1])
  128. if !has_key(old_gitgutter_signs, line_number) " insert
  129. let id = gitgutter#sign#next_sign_id()
  130. execute "sign place" id "line=" . line_number "name=" . name "buffer=" . bufnr
  131. else " update if sign has changed
  132. let old_sign = old_gitgutter_signs[line_number]
  133. if old_sign.name !=# name
  134. execute "sign place" old_sign.id "name=" . name "buffer=" . bufnr
  135. end
  136. endif
  137. endif
  138. endfor
  139. " At this point b:gitgutter_gitgutter_signs is out of date.
  140. endfunction
  141. function! gitgutter#sign#next_sign_id()
  142. let next_id = s:next_sign_id
  143. let s:next_sign_id += 1
  144. return next_id
  145. endfunction