├── .gitignore ├── Makefile ├── README.md ├── autoload ├── changes.vim └── changes_icons │ ├── add.bmp │ ├── add1.bmp │ ├── delete.bmp │ ├── delete1.bmp │ ├── license_designkode.txt │ ├── warning.bmp │ └── warning1.bmp ├── changes.zip ├── doc └── ChangesPlugin.txt ├── plugin └── changesPlugin.vim ├── screencast.gif └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | post.pl 2 | vim_passfile 3 | .*.un~ 4 | .*.sw* 5 | # ignore vimballs 6 | *.vba 7 | *.vmb 8 | # ignore *.orig files 9 | *.orig 10 | # ignore some zip files 11 | *-*.zip 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SCRIPT=$(wildcard plugin/*.vim) 2 | AUTOL =$(wildcard autoload/*.vim) 3 | DOC=$(wildcard doc/*.txt) 4 | PLUGIN=$(shell basename "$$PWD") 5 | VERSION=$(shell sed -n '/Version:/{s/^.*\(\S\.\S\+\)$$/\1/;p}' $(SCRIPT)) 6 | 7 | .PHONY: $(PLUGIN).vmb 8 | 9 | all: uninstall vimball install 10 | 11 | vimball: $(PLUGIN).vmb 12 | 13 | zip: $(PLUGIN).zip 14 | 15 | release: uninstall version zip 16 | 17 | clean: 18 | rm -rf *.vmb *.vba */*.orig *.~* .VimballRecord doc/tags 19 | find . -type f \( -name "*.vba" -o -name "*.orig" -o -name "*.~*" \ 20 | -o -name ".VimballRecord" -o -name ".*.un~" -o -name "*.sw*" -o \ 21 | -name tags -o -name "*.vmb" \) -delete 22 | 23 | dist-clean: clean 24 | 25 | install: 26 | vim -N -i NONE -u NONE -c 'ru! plugin/vimballPlugin.vim' -c':so %' -c':q!' $(PLUGIN)-$(VERSION).vmb 27 | 28 | uninstall: 29 | vim -N -i NONE -u NONE -c 'ru! plugin/vimballPlugin.vim' -c':RmVimball' -c':q!' $(PLUGIN)-$(VERSION).vmb 30 | 31 | undo: 32 | for i in */*.orig; do mv -f "$$i" "$${i%.*}"; done 33 | 34 | archive: $(PLUGIN).zip 35 | 36 | $(PLUGIN).zip: 37 | -@/bin/sh -c "if [ -f $(PLUGIN).zip ]; then \ 38 | zip -u --verbose $(PLUGIN).zip ${SCRIPT} ${AUTOL} ${DOC} autoload/*/* ; \ 39 | else \ 40 | zip $(PLUGIN).zip ${SCRIPT} ${AUTOL} ${DOC} autoload/*/* ; \ 41 | fi " 42 | ln -f $(PLUGIN).zip $(PLUGIN)-$(VERSION).zip 43 | 44 | $(PLUGIN).vmb: 45 | rm -f $(PLUGIN)-$(VERSION).vmb 46 | vim -N -i NONE -u NONE -c 'ru! plugin/vimballPlugin.vim' -c ':call append("0", [ "$(SCRIPT)", "$(AUTOL)", "$(DOC)"])' -c '$$d' -c ":%MkVimball $(PLUGIN)-$(VERSION) ." -c':q!' 47 | ln -f $(PLUGIN)-$(VERSION).vmb $(PLUGIN).vmb 48 | 49 | release: version all 50 | 51 | version: 52 | perl -i.orig -pne 'if (/Version:/) {s/\.(\d*)/sprintf(".%d", 1+$$1)/e}' ${SCRIPT} ${AUTOL} 53 | perl -i -pne 'if (/GetLatestVimScripts:/) {s/(\d+)\s+:AutoInstall:/sprintf("%d :AutoInstall:", 1+$$1)/e}' ${SCRIPT} ${AUTOL} 54 | #perl -i -pne 'if (/Last Change:/) {s/\d+\.\d+\.\d\+$$/sprintf("%s", `date -R`)/e}' ${SCRIPT} 55 | perl -i -pne 'if (/Last Change:/) {s/(:\s+).*\n/sprintf(": %s", `date -R`)/e}' ${SCRIPT} ${AUTOL} 56 | perl -i.orig -pne 'if (/Version:/) {s/\.(\d+).*\n/sprintf(".%d %s", 1+$$1, `date -R`)/e}' ${DOC} 57 | VERSION=$(shell sed -n '/Version:/{s/^.*\(\S\.\S\+\)$$/\1/;p}' $(SCRIPT)) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Changes plugin [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/cb%40256bit.org) 2 | > A Vim plugin for displaying changes in a buffer 3 | 4 | This plugin was written to help visualize which lines have been changed since 5 | editing started for a file. The plugin was inspired by so called changed-bars, 6 | available in other editors, such as Embarcadero C++ Builder (there it is 7 | called [Change Bars](http://edn.embarcadero.com/article/33453#6PersonalDeveloperProductivity) 8 | or Visual Studio where it is called [indicator margin](http://blog.eveningcreek.com/?p=151). 9 | 10 | ChangesPlugin.vim uses the diff feature of vim and compares the actual 11 | buffer with its saved state (or possibly against a state in a VCS repository). 12 | It displays signs in the signcolumn to indicate any changes, with optional line highlighting. 13 | 14 | See also the following screencast showing several of the features available: 15 | ![screencast of the plugin](screencast.gif "Screencast") 16 | 17 | Note, that a '-' indicates that at least one line was deleted between that 18 | particular line and the following line. 19 | 20 | Features: 21 | * Shows signs for added, modified, and deleted lines 22 | * Quick jumping between changed blocks ("hunks") 23 | * Tries to update signs as fast as possible 24 | * Optional line highlighting 25 | * Customisable 26 | * Many different Commands (fold all non changed lines, show changed lines in Quickfix window, opens a diff split...) 27 | * Preserves signs from other plugins 28 | * Built-in integration with [vim-airline](https://github.com/vim-airline/vim-airline/) 29 | * Good documentation 30 | * Quick response 31 | * Nice icons for gvim 32 | * Uses Vim 8's async feature for parsing the diff 33 | 34 | #### Why another git-gutter/vim-signify clone? 35 | To be fair, there were a lot of other vim plugins that offered the same functionality as those two. They just didn't get the same attention as those two. ChangesPlugin was one of those plugins. 36 | 37 | ### Installation 38 | Vim 8 comes with package support. Simply clone it into your packpath like this: 39 | ``` 40 | cd ~/.vim/pack/custom/start/ 41 | git clone https://github.com/chrisbra/changesPlugin.git 42 | ``` 43 | (Remember to run `:helptags ALL` after restarting Vim to regenerate the help files) 44 | 45 | Other installation methods: 46 | 47 | | Plugin Manager | Install with... | 48 | | ------------- | ------------- | 49 | | [Pathogen][1] | `git clone https://github.com/chrisbra/changesPlugin ~/.vim/bundle/changesPlugin`
Remember to run `:Helptags` to generate help tags | 50 | | [NeoBundle][2] | `NeoBundle 'chrisbra/changesPlugin'` | 51 | | [Vundle][3] | `Plugin 'chrisbra/changesPlugin'` | 52 | | [Plug][4] | `Plug 'chrisbra/changesPlugin'` | 53 | | [VAM][5] | `call vam#ActivateAddons([ 'changesPlugin' ])` | 54 | | [Dein][6] | `call dein#add('chrisbra/changesPlugin')` | 55 | | [minpac][7] | `call minpac#add('chrisbra/changesPlugin')` | 56 | | manual | copy all of the directories into your `~/.vim` directory (preserve existing directories)| 57 | 58 | Other package managers work similarly, please refer to their documentation. 59 | 60 | ### Usage 61 | Once installed, take a look at the help at `:h ChangesPlugin` 62 | 63 | Here is a short overview of the functionality provided by the plugin: 64 | #### Ex commands: 65 | :EC - Activate the plugin (display indicators of changes for the current buffer) 66 | :DC - Disable the plugin 67 | :TCV - Toggle the plugin 68 | :CC - Show a small help window 69 | :CL - Open the Quickfix window with all changes for the current buffer 70 | :CD - Open a diff view for the current buffer 71 | :CF - Fold away all non-changed lines 72 | :CT - Toggle how the highlighting is displayed 73 | #### Mappings 74 | ]h - Moves forward to the next changed line 75 | [h - Moves backwards to the previous changed line 76 | ah - Selects the current hunk (TextObject) 77 | h - Stage the hunk that the cursor is on (works only for git) 78 | 79 | ### Configuration 80 | 81 | `g:changes_autocmd` (default: 1) - update the signs automatically using InsertLeave and TextChanged autocommands. 82 | 83 | `g:changes_vcs_check` (default: 0) 84 | 85 | `g:changes_vcs_system` (default: '') - Check against a version in a repository (e.g. git/mercurial) and specify VCS to use (if not specified, will try to autodetect). 86 | 87 | `g:changes_diff_preview` (default: 0) - Display diff in the preview window 88 | 89 | `g:changes_respect_SignColumn` (default 0) - If set, will use the SignColumn Highlighting group, else uses the Normal Highlighting group 90 | 91 | `g:changes_sign_text_utf8` (default 1) - If set, will display nice little utf-8 signs. 92 | 93 | `g:changes_linehi_diff` (default: 0) - If set, will overlay the text with highlighting for the difference in the line. 94 | 95 | `g:changes_use_icons` (default: 1) - If set, will display graphical icons if support is available. 96 | 97 | `g:changes_add_sign` (default: '+') - If set, will display custom sign 98 | 99 | `g:changes_delete_sign` (default: '-') - If set, will display custom sign 100 | 101 | `g:changes_modified_sign` (default: '*') - If set, will display custom sign 102 | 103 | `g:changes_utf8_add_sign` (default '➕') - If set, will display nice little utf-8 plus signs. 104 | 105 | `g:changes_utf8_delete_sign` (default '➖') - If set, will display nice little utf-8 minus signs. 106 | 107 | `g:changes_utf8_modifed_sign` (default '★') - If set, will display nice little utf-8 star signs. 108 | 109 | #### Similar Work 110 | [vim-gitgutter](https://github.com/airblade/vim-gitgutter) 111 | Only works for git. 112 | 113 | [vim-signify](https://github.com/mhinz/vim-signify/) 114 | Supports several VCS, only updates the sign on write. 115 | 116 | ### License & Copyright 117 | 118 | © 2009-2014 by Christian Brabandt. The Vim License applies. See `:h license` 119 | 120 | __NO WARRANTY, EXPRESS OR IMPLIED. USE AT-YOUR-OWN-RISK__ 121 | 122 | [1]: https://github.com/tpope/vim-pathogen 123 | [2]: https://github.com/Shougo/neobundle.vim 124 | [3]: https://github.com/VundleVim/Vundle.vim 125 | [4]: https://github.com/junegunn/vim-plug 126 | [5]: https://github.com/MarcWeber/vim-addon-manager 127 | [6]: https://github.com/Shougo/dein.vim 128 | [7]: https://github.com/k-takata/minpac/ 129 | -------------------------------------------------------------------------------- /autoload/changes.vim: -------------------------------------------------------------------------------- 1 | " Changes.vim - Using Signs for indicating changed lines 2 | " ------------------------------------------------------ 3 | " Version: 0.16 4 | " Author: Christian Brabandt 5 | " Last Change: Thu, 15 Jan 2015 21:16:40 +0100 6 | " License: Vim License 7 | " Documentation: see :help changesPlugin.txt 8 | " GetLatestVimScripts: 3052 15 :AutoInstall: ChangesPlugin.vim 9 | " Documentation: "{{{1 10 | " TODO: Remove old in-efficient sign handling, once 8.2 or so is stable 11 | " See :h ChangesPlugin.txt 12 | 13 | "{{{1script-level variables 14 | scriptencoding utf-8 15 | let s:i_path = fnamemodify(expand(""), ':p:h'). '/changes_icons/' 16 | let s:sign_api = v:version > 801 || (v:version == 801 && has("patch614")) 17 | let s:sign_api_list = exists("*sign_placelist") 18 | let s:sign_api_group = 'ChangesSignGroup' 19 | 20 | fu! GetSID() "{{{1 21 | return matchstr(expand(''), '\zs\d\+\ze_GetSID$') 22 | endfu 23 | 24 | let s:sid = GetSID() 25 | delf GetSID "not needed anymore 26 | 27 | " Check preconditions 28 | fu! s:Check() "{{{1 29 | if !exists("s:msg") 30 | let s:msg=[] 31 | endif 32 | if !has("diff") 33 | call s:StoreMessage("Diff support not available in your Vim version.") 34 | throw 'changes:abort' 35 | endif 36 | 37 | if !has("signs") 38 | call s:StoreMessage("Sign Support support not available in your Vim.") 39 | throw 'changes:abort' 40 | endif 41 | 42 | if !executable("diff") || executable("diff") == -1 43 | call s:StoreMessage("No diff executable found") 44 | throw 'changes:abort' 45 | endif 46 | 47 | let s:ids={} 48 | let s:ids["add"] = hlID("DiffAdd") 49 | let s:ids["del"] = hlID("DiffDelete") 50 | let s:ids["cha"] = hlID("DiffChange") 51 | let s:ids["ch2"] = hlID("DiffText") 52 | 53 | call s:SetupSignTextHl() 54 | call s:DefineSigns(0) 55 | endfu 56 | fu! s:GetSignDef(def) "{{{1 57 | " Returns sign definition as string for use as `:sign command` 58 | " not used when s:sign_api is defined 59 | return (has_key(a:def, 'text') ? ' text='.get(a:def, 'text', '') : ''). 60 | \ (has_key(a:def, 'texthl') ? ' texthl='.get(a:def, 'texthl', '') : ''). 61 | \ (has_key(a:def, 'icon') ? ' icon='.get(a:def, 'icon', '') : ''). 62 | \ (has_key(a:def, 'linehl') ? ' linehl='.get(a:def, 'linehl', '') : '') 63 | endfu 64 | fu! s:DefineSigns(undef) "{{{1 65 | if s:sign_api_list 66 | if a:undef 67 | call sign_undefine(keys(s:signs)) 68 | endif 69 | call sign_define(values(s:signs)) 70 | return 71 | endif 72 | for key in keys(s:signs) 73 | if a:undef 74 | try 75 | " Try undefining first, so that refining will actually work! 76 | if s:sign_api 77 | call sign_undefine(s:signs[key].name) 78 | else 79 | exe "sil! sign undefine " key 80 | endif 81 | catch /^Vim\%((\a\+)\)\=:E155/ " sign does not exist 82 | endtry 83 | endif 84 | try 85 | if s:sign_api 86 | call sign_define(key, s:signs[key]) 87 | else 88 | exe "sil! sign define" key. s:GetSignDef(s:signs[key]) 89 | endif 90 | catch /^Vim\%((\a\+)\)\=:E255/ " Can't read icons 91 | if s:sign_api 92 | call sign_undefine(key) 93 | unlet! s:signs[key]['icon'] 94 | call sign_define(key, s:signs[key]) 95 | else 96 | exe "sil! sign undefine " key 97 | unlet! s:signs[key]['icon'] 98 | exe "sign define" key s:GetSignDef(s:signs[key]) 99 | endif 100 | endtry 101 | endfor 102 | endfu 103 | fu! s:CheckLines(arg) "{{{1 104 | " OLD: not needed any more. 105 | " a:arg 1: check original buffer 106 | " 0: check diffed scratch buffer 107 | let line=1 108 | while line <= line('$') 109 | let id=diff_hlID(line,1) 110 | if (id == 0) 111 | let line+=1 112 | continue 113 | " in the original buffer, there won't be any lines accessible 114 | " that have been 'marked' deleted, so we need to check scratch 115 | " buffer for added lines 116 | elseif (id == s:ids['add']) && !a:arg 117 | let s:temp['del'] = s:temp['del'] + [ line ] 118 | elseif (id == s:ids['add']) && a:arg 119 | let b:diffhl['add'] = b:diffhl['add'] + [ line ] 120 | elseif ((id == s:ids['cha']) || (id == s:ids['ch2'])) && a:arg 121 | let b:diffhl['cha'] = b:diffhl['cha'] + [ line ] 122 | endif 123 | let line+=1 124 | endw 125 | endfu 126 | fu! s:UpdateView(...) "{{{1 127 | " if a:1 is given, force update! 128 | let force = a:0 129 | let did_source_init = 0 130 | let b:changes_chg_tick = get(b:, 'changes_chg_tick', 0) 131 | try 132 | call changes#Init() 133 | catch 134 | call changes#CleanUp() 135 | return 136 | endtry 137 | let did_source_init = 1 138 | if !s:IsUpdateAllowed(1) 139 | return 140 | endif 141 | let b:changes_last_line = get(b:, 'changes_last_line', line('$')) 142 | if get(g:, 'gitgutter_enabled', 0) && 143 | \ exists('b:gitgutter_gitgutter_signs') 144 | " Gitgutter plugin is available, stop here 145 | call changes#IgnoreCurrentBuffer() 146 | let force = 0 147 | endif 148 | " Only update, if there have been changes to the buffer 149 | if exists("b:diffhl") && 150 | \ get(g:, 'changes_fast', 1) && 151 | \ line("'[") == line("']") && 152 | \ !empty(b:diffhl) && 153 | \ index(b:diffhl['add'] + b:diffhl['cha'] + b:diffhl['del'], line("'[")) > -1 && 154 | \ b:changes_last_line == line('$') 155 | " there already is a sign on the current line, so 156 | " skip an expensive call to create diff (might happen with many 157 | " rx commands on the same line and triggered TextChanged autocomands) 158 | " and should make Vim more responsive (at the cost of being a little 159 | " bit more unprecise.) 160 | " If you don't like this, set the g:changes_fast variable to zero 161 | let b:changes_chg_tick = b:changedtick 162 | endif 163 | 164 | if b:changes_chg_tick != b:changedtick || force 165 | try 166 | if !did_source_init 167 | call changes#Init() 168 | endif 169 | call s:GetDiff(1, '') 170 | call s:HighlightTextChanges() 171 | let b:changes_chg_tick = b:changedtick 172 | let b:changes_last_line = line('$') 173 | catch 174 | call s:StoreMessage(v:exception) 175 | " Make sure, the message is actually displayed! 176 | verbose call changes#WarningMsg() 177 | call changes#CleanUp() 178 | endtry 179 | else 180 | " if nothing has been added, remove the sign, that has been added 181 | " using the InsertEnter autocommand 182 | if exists("s:changes_last_inserted_sign") 183 | let name=s:PrevDictHasKey(line('.')) 184 | if get(name, 'name', '') ==# s:changes_last_inserted_sign.name 185 | call s:UnPlaceSpecificSigns([{'id': get(name, 'id', s:SignIdRemove())}]) 186 | endif 187 | endif 188 | endif 189 | endfu 190 | fu! s:SetupSignTextHl() "{{{1 191 | if &bg == 'dark' 192 | hi default ChangesSignTextAdd ctermbg=NONE ctermfg=darkgreen guibg=NONE guifg=darkgreen 193 | hi default ChangesSignTextDel ctermbg=NONE ctermfg=darkred guibg=NONE guifg=darkred 194 | hi default ChangesSignTextCh ctermbg=NONE ctermfg=lightblue guibg=NONE guifg=lightblue 195 | else 196 | hi default ChangesSignTextAdd ctermbg=NONE ctermfg=green guibg=NONE guifg=green 197 | hi default ChangesSignTextDel ctermbg=NONE ctermfg=red guibg=NONE guifg=red 198 | hi default ChangesSignTextCh ctermbg=NONE ctermfg=darkblue guibg=NONE guifg=darkblue 199 | endif 200 | hi default link ChangesSignTextDummyCh ChangesSignTextCh 201 | hi default link ChangesSignTextDummyAdd ChangesSignTextAdd 202 | endfu 203 | fu! s:ChangesSignsLines() "{{{1 204 | " returns a list of all changes placed signs 205 | return sort(map(copy(s:placed_signs[0]), 'get(v:val, "line")+0'), 'N') 206 | endfu 207 | fu! s:PrevDictHasKey(line) "{{{1 208 | " Return existing sign on the current line 209 | if s:sign_api 210 | let result=sign_getplaced(bufnr(''), {'group': s:sign_api_group, 'lnum': a:line})[0]['signs'] 211 | return len(result) ? result[0] : {} 212 | endif 213 | let result=filter(copy(s:placed_signs[0]), 'v:val.line==?a:line') 214 | return result == [] ? {} : result[0] 215 | endfu 216 | fu! s:PrevDictHasKeyType(line, id) "{{{1 217 | " Return existing sign on the current line 218 | " If a:1, return complete dict item 219 | let result=filter(copy(s:placed_signs[0]), 'v:val.line ==? a:line && v:val.name[0:2] ==? a:id') 220 | return result == [] ? '' : result[0].name 221 | endfu 222 | fu! s:Write(name) "{{{1 223 | " turn off fsync, so writing is faster 224 | let _fsync=&fsync 225 | set nofsync 226 | exe ":sil keepalt noa :w! ++ff=unix" a:name 227 | let &fsync=_fsync 228 | endfu 229 | fu! s:PlaceSigns(dict) "{{{1 230 | " signs by other plugins 231 | let b = copy(s:placed_signs[1]) 232 | let changes_signs_lines=s:ChangesSignsLines() 233 | for id in ['add', 'cha', 'del'] 234 | let prev_line = -1 235 | if !has_key(a:dict, id) 236 | " safety check 237 | break 238 | endif 239 | for item in a:dict[id] 240 | " One special case could occur: 241 | " You could delete the last lines. In that case, we couldn't place 242 | " here the deletion marks. If this happens, place the deletion 243 | " marks on the last line 244 | if item > line('$') 245 | let item=line('$') 246 | endif 247 | " There already exists a sign in this line, skip now 248 | if index(b, item) > -1 249 | continue 250 | endif 251 | let name=id 252 | " check if there is already a changes sign on the current line 253 | let sign_exists = index(changes_signs_lines, item) > -1 254 | let sign_exists_prev = index(changes_signs_lines, (item-1)) > -1 255 | " Make sure, 'dummych' ==? 'ch' 256 | " or 'dummydel' ==? 'del' 257 | if prev_line+1 == item || (sign_exists_prev && s:SignType(s:PrevDictHasKeyType(item-1, id)) ==? id) 258 | if id=='del' 259 | " don't need to place more deleted signs on those lines, 260 | " skip 261 | let prev_line = item 262 | continue 263 | else 264 | let name=id.'_dummy' 265 | endif 266 | endif 267 | if sign_exists && get(s:PrevDictHasKey(item), 'name', '') ==? name 268 | " There is already a Changes sign placed 269 | continue 270 | endif 271 | let sid=b:sign_prefix.s:SignId() 272 | call s:PlaceSpecificSign(sid, item, name, bufnr('')) 273 | " remember line number, so that we don't place a second sign 274 | " there! 275 | call add(s:placed_signs[0], {'id': sid, 'line':item, 'type': name}) 276 | let prev_line = item 277 | endfor 278 | endfor 279 | endfu 280 | fu! s:UnPlaceSigns(force) "{{{1 281 | if !exists("b:sign_prefix") 282 | return 283 | endif 284 | if a:force 285 | " only changes sign present, can remove all of them now 286 | if s:sign_api 287 | call sign_unplace(s:sign_api_group, {'buffer': bufnr('')}) 288 | else 289 | exe "sign unplace * buffer=".bufnr('') 290 | endif 291 | return 292 | endif 293 | if !exists("b:diffhl") 294 | return 295 | endif 296 | let s:placed_signs = s:PlacedSigns() 297 | call s:UnPlaceSpecificSigns(s:placed_signs[0]) 298 | endfu 299 | fu! s:StoreMessage(msg) "{{{1 300 | call add(s:msg, a:msg) 301 | endfu 302 | fu! s:PreviewDiff(file) "{{{1 303 | try 304 | if !get(g:, 'changes_diff_preview', 0) || &diff 305 | return 306 | endif 307 | if getfsize(a:file) <= 0 308 | " getfsize() returns -1 on error (e.g. file does not exists!) 309 | if bufwinnr(a:file) > 0 310 | sil! pclose 311 | endif 312 | return 313 | endif 314 | let cur = exists("b:current_line") ? b:current_line : 0 315 | let _ar=&g:ar 316 | " Avoid W11 message (:h W11) 317 | set ar 318 | try 319 | exe printf(':noa keepp noswap sil pedit +/@@\ -%d.*\\n\\zs %s', cur, a:file) 320 | let &g:ar=_ar 321 | noa wincmd P 322 | " execute commands in preview window 323 | setl nofoldenable syntax=diff bt=nofile ft=diff 324 | sil keepp noa g/^[+-]\{3\}/d_ 325 | catch 326 | finally 327 | " Be sure, to stay in the original window 328 | noa wincmd p 329 | endtry 330 | if get(g:, 'neocomplcache_enable_auto_close_preview', 0) 331 | " Neocomplache closes preview window, GRR! 332 | " don't close preview window 333 | let g:neocomplcache_enable_auto_close_preview = 0 334 | endif 335 | catch 336 | endtry 337 | endfu 338 | fu! s:ChangeDir() "{{{1 339 | let _pwd = fnameescape(getcwd()) 340 | exe "lcd " fnameescape(fnamemodify(expand("%"), ':h')) 341 | return _pwd 342 | endfu 343 | fu! s:Output(list) "{{{1 344 | let eol="\n" 345 | if &ff ==? 'dos' 346 | let eol = "\r\n" 347 | elseif &ff ==? 'mac' 348 | let eol = "\n\r" 349 | endif 350 | return join(a:list, eol).eol 351 | endfu 352 | fu! s:DeleteTempFiles() "{{{1 353 | for file in glob(s:diff_out.'*', 1, 1) 354 | call delete(file) 355 | endfor 356 | endfu 357 | fu! s:MakeDiff_new(file, type) "{{{1 358 | " Parse Diff output and place signs 359 | " Needs unified diff output 360 | let outfile = printf("%s.%d", s:diff_out, s:jobid) 361 | try 362 | let _pwd = s:ChangeDir() 363 | unlet! b:current_line 364 | call s:Write(s:diff_in_cur) " writes in Unix format 365 | if !s:vcs || !empty(a:file) 366 | let file = !empty(a:file) ? a:file : bufname('') 367 | if empty(file) 368 | throw "changes:abort" 369 | endif 370 | let cp = (!s:Is('unix') ? 'copy ' : 'cp ') 371 | let output = system(cp. shellescape(file). " ". s:diff_in_old) 372 | if v:shell_error 373 | call s:StoreMessage(output[:-2]) 374 | throw "changes:abort" 375 | endif 376 | else 377 | if b:vcs_type == 'git' 378 | let git_rep_p = s:ReturnGitRepPath() 379 | if !empty(git_rep_p) 380 | exe 'lcd' git_rep_p 381 | endif 382 | elseif b:vcs_type == 'cvs' 383 | " I am not sure, if this is the best way 384 | " to query CVS. But just to make sure, 385 | " we are in the right path and we don't have 386 | " to consider CVSROOT 387 | exe 'lcd' fnamemodify(expand('%'), ':p:h') 388 | endif 389 | let cmd = printf("%s %s%s > %s", (b:vcs_type==?'rcs'?'': 390 | \ b:vcs_type), s:vcs_cat[b:vcs_type], 391 | \ shellescape(fnamemodify(resolve(expand('%')), ':.')), 392 | \ s:diff_in_old) 393 | let output = system(cmd) 394 | if v:shell_error 395 | call s:StoreMessage(output[:-2]) 396 | throw "changes:abort" 397 | endif 398 | " if &ff ==? 'dos' 399 | " " need to postprocess the resulting file 400 | " call system('vim --clean -c ":w! ++ff=dos" -c ":q!" ' . s:diff_in_old) 401 | " endif 402 | endif 403 | let cmd = printf("diff -a -U0 -N %s %s %s > %s", 404 | \ s:diff_in_old, s:diff_in_cur, 405 | \ (get(g:, 'changes_grep_diff', 0) ? '|fgrep "@@"' : ''), 406 | \ outfile) 407 | if s:Is('win') && &shell =~? 'cmd.exe$' 408 | let cmd = '( '. cmd. ' )' 409 | endif 410 | if has('job') 411 | call s:ChangesDoAsync(cmd, fnamemodify(bufname(''), ':p'), a:type, outfile) 412 | else 413 | let output = system(cmd) 414 | if v:shell_error >= 2 || v:shell_error < 0 415 | " diff returns 2 on errors 416 | call s:StoreMessage(output[:-2]) 417 | throw "changes:abort" 418 | endif 419 | if getfsize(outfile) <= 0 420 | call s:StoreMessage("File not found or no differences found!") 421 | return 422 | endif 423 | call s:ParseDiffOutput(outfile) 424 | endif 425 | finally 426 | if filereadable(outfile) 427 | call s:PreviewDiff(outfile) 428 | endif 429 | exe 'lcd' _pwd 430 | endtry 431 | endfu 432 | fu! s:MakeDiff(...) "{{{1 433 | " Old version, only needed, when GetDiff(3) is called (or argument 1 is non-empty) 434 | " Get diff for current buffer with original 435 | let o_pwd = s:ChangeDir() 436 | let bnr = bufnr('%') 437 | let ft = &l:ft 438 | noa vert new 439 | set bt=nofile 440 | let scratchbuf = bufnr('') 441 | if !s:vcs 442 | exe ":silent :noa :r " (exists("a:1") && !empty(a:1) ? a:1 : '#') 443 | let &l:ft=ft 444 | else 445 | let vcs=getbufvar(bnr, 'vcs_type') 446 | try 447 | if vcs == 'git' 448 | let git_rep_p = s:ReturnGitRepPath() 449 | if !empty(git_rep_p) 450 | exe 'lcd' git_rep_p 451 | endif 452 | elseif vcs == 'cvs' 453 | " I am not sure, if this is the best way 454 | " to query CVS. But just to make sure, 455 | " we are in the right path and we don't have 456 | " to consider CVSROOT 457 | exe 'lcd' fnamemodify(expand('#'), ':p:h') 458 | endif 459 | let output = system((vcs==?'rcs'?'':vcs) . ' '. s:vcs_cat[vcs] . expand("#") . '>'. s:diff_out) 460 | if v:shell_error 461 | call s:StoreMessage(output[:-2]) 462 | throw "changes:abort" 463 | endif 464 | let fsize=getfsize(s:diff_out) 465 | if fsize == 0 466 | call s:StoreMessage("Couldn't get VCS output, aborting") 467 | "call s:MoveToPrevWindow() 468 | exe "noa" bufwinnr(bnr) "wincmd w" 469 | throw "changes:abort" 470 | endif 471 | exe ':silent :noa :r' s:diff_out 472 | catch /^changes: No git Repository found/ 473 | call s:StoreMessage("Unable to find git Top level repository.") 474 | echo v:errmsg 475 | exe "noa" bufwinnr(bnr) "wincmd w" 476 | throw "changes:abort" 477 | catch 478 | if bufnr('%') != bnr 479 | "call s:MoveToPrevWindow() 480 | exe "noa" bufwinnr(bnr) "wincmd w" 481 | endif 482 | throw "changes:abort" 483 | finally 484 | endtry 485 | endif 486 | 0d_ 487 | diffthis 488 | exe "noa" bufwinnr(bnr) "wincmd w" 489 | diffthis 490 | exe "lcd " o_pwd 491 | return scratchbuf 492 | endfu 493 | fu! s:ParseDiffOutput(file) "{{{1 494 | let b:current_line = 1000000 495 | let file=readfile(a:file) 496 | if !get(g:, 'changes_grep_diff', 0) 497 | let file=filter(file, 'v:val=~''^@@''') 498 | endif 499 | for line in file 500 | " TODO: matchlist seems slow, can this be made faster by manually parsing? 501 | let submatch = matchlist(line, '@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@') 502 | if empty(submatch) 503 | " There was probably an error, skip parsing now 504 | return 505 | else 506 | let old_line = submatch[1] + 0 507 | let old_count = (empty(submatch[2]) ? 1 : submatch[2] + 0) 508 | let new_line = submatch[3] + 0 509 | let new_count = (empty(submatch[4]) ? 1 : submatch[4] + 0) 510 | if b:current_line > (old_line - line('.')) 511 | let b:current_line = old_line 512 | endif 513 | endif 514 | 515 | " 2 Lines added 516 | " @@ -4,0 +4,2 @@ 517 | if old_count == 0 && new_count > 0 518 | let b:diffhl.add += range(new_line, new_line + new_count - 1) 519 | 520 | " 2 lines deleted: 521 | " @@ -4,2 +3,0 @@ 522 | elseif old_count > 0 && new_count == 0 523 | if new_line == 0 524 | let new_line = 1 525 | endif 526 | let b:diffhl.del += range(new_line, new_line + old_count - 1) 527 | 528 | " Lines changed 529 | " 2 deleted, 2 changed 530 | " @@ -3,4 +3,2 @@ 531 | " 532 | " But this is just a Change: 533 | " @@ -1010,5 +1020,5 @@ 534 | elseif old_count >= new_count 535 | let b:diffhl.cha += range(new_line, new_line + new_count - 1) 536 | " if old and new count is not given, can't be deletion 537 | " I think this is wrong: 538 | " if new_line + new_count < old_line+old_count && !(empty(submatch[2]) && empty(submatch[4])) 539 | " should probably rather be: 540 | if new_line + new_count < old_line + old_count && new_count != old_count 541 | let b:diffhl.del+= range(new_count + new_line, old_line + old_count - 1) 542 | endif 543 | 544 | " Lines changed 545 | " 3 added, 2 changed 546 | " @@ -4,2 +4,5 @@ 547 | else " old_count < new_count 548 | let b:diffhl.cha += range(new_line, new_line + old_count - 1) 549 | if new_line + old_count <= new_line+new_count-1 550 | let b:diffhl.add += range(new_line + old_count, new_line + new_count - 1) 551 | endif 552 | endif 553 | endfor 554 | endfu 555 | fu! s:ReturnGitRepPath() "{{{1 556 | " return the top level of the repository path. This is needed, so 557 | " git show will correctly return the file 558 | exe 'lcd' fnamemodify(resolve(expand('%')), ':h') 559 | let git_path = system('git rev-parse --git-dir') 560 | if !v:shell_error 561 | " we need the directory right above the .git metatdata directory 562 | return escape(git_path[:-2].'/..', ' ') 563 | else 564 | return '' 565 | endif 566 | endfu 567 | fu! s:ShowDifferentLines() "{{{1 568 | if !exists("b:diffhl") 569 | return 570 | else 571 | let list=[] 572 | let placed={} 573 | let tline = -1 574 | let types={'cha':'*', 'add': '+', 'del': '-'} 575 | for type in ['cha', 'del', 'add'] 576 | for line in b:diffhl[type] 577 | if has_key(placed, line) 578 | continue 579 | endif 580 | if line == tline+1 581 | let tline=line 582 | continue 583 | endif 584 | call add(list, {'bufnr': bufnr(''), 585 | \ 'lnum': line, 'text': getline(line), 586 | \ 'type': types[type]}) 587 | let placed.line=1 588 | let tline=line 589 | endfor 590 | endfor 591 | endif 592 | if !empty(list) 593 | call setloclist(winnr(), list) 594 | lopen 595 | endif 596 | endfun 597 | let g:Changes_funcref=funcref("ShowDifferentLines") 598 | fu! s:GetSignId() "{{{1 599 | " Not called for s:sign_api 600 | let signs = s:GetPlacedSigns() 601 | if empty(signs) 602 | " No signs placed yet... 603 | return 10 604 | endif 605 | let list=[] 606 | for val in signs[1:] 607 | " get 'id' value of signs 608 | let id = split(val, '=\d\+\zs')[1] 609 | call add(list, (split(id, '=')[1] + 0)) 610 | endfor 611 | return max(list) 612 | endfu 613 | fu! s:GetPlacedSigns() "{{{1 614 | if s:sign_api 615 | return sign_getplaced(bufnr(''), {'group': s:sign_api_group}) 616 | elseif exists("s:all_signs") 617 | return s:all_signs 618 | else 619 | " set local to english 620 | let lang=v:lang 621 | if lang isnot# 'C' 622 | sil lang mess C 623 | endif 624 | redir => a| exe "silent sign place buffer=".bufnr('')|redir end 625 | let s:all_signs = split(a,"\n")[1:] 626 | if lang != 'C' 627 | exe "sil lang mess" lang 628 | endif 629 | endif 630 | return s:all_signs 631 | endfu 632 | fu! s:PlacedSigns() "{{{1 633 | if empty(bufname('')) 634 | " empty(bufname): unnamed buffer, can't get diff of it, 635 | " anyhow, so stop expansive call here 636 | return [[],[]] 637 | endif 638 | let b = s:GetPlacedSigns() 639 | if s:sign_api 640 | return [[], b] 641 | endif 642 | if empty(b) 643 | return [[],[]] 644 | endif 645 | let dict = {} 646 | let own = [] 647 | let other = [] 648 | " Filter from the second item. The first one contains the buffer name: 649 | " Signs for [NULL]: or Signs for : 650 | let b=b[1:] 651 | let c=filter(copy(b), 'v:val =~ "id=".b:sign_prefix') 652 | for item in b 653 | if item =~ "id=".b:sign_prefix 654 | " Signs placed by this plugin 655 | let t = split(item) 656 | let dict.line = split(t[0], '=')[1] 657 | let dict.id = split(t[1], '=')[1] 658 | let dict.name = split(t[2], '=')[1] 659 | call add(own, copy(dict)) 660 | else 661 | call add(other, matchstr(item, '^\s*\w\+=\zs\d\+\ze')+0) 662 | endif 663 | endfor 664 | 665 | " Make sure s:GetPlacedSigns() reruns correctly 666 | unlet! s:all_signs 667 | return [own, other] 668 | endfu 669 | fu! s:GuessVCSSystem() "{{{1 670 | " Check global config variable 671 | for var in [ b:, g:] 672 | let pat='\c\vgit|hg|bzr\|cvs|svn|subversion|mercurial|rcs|fossil|darcs' 673 | let vcs=matchstr(get(var, 'changes_vcs_system', ''), pat) 674 | if vcs 675 | return vcs 676 | endif 677 | endfor 678 | let file = fnamemodify(resolve(expand("%")), ':p') 679 | let path = escape(fnamemodify(file, ':h'), ' ') 680 | " First try git and hg, they seem to be the most popular ones these days 681 | " also check that the file actually exists in the repository 682 | if !empty(finddir('.git',path.';')) && 683 | \ !empty(system('git ls-tree -r HEAD --name-only '. file)) 684 | return 'git' 685 | elseif !empty(finddir('.hg',path.';')) && 686 | \ empty(system('hg status -ui '. file)) 687 | return 'hg' 688 | elseif isdirectory(path . '/CVS') 689 | return 'cvs' 690 | elseif isdirectory(path . '/.svn') 691 | return 'svn' 692 | elseif !empty(finddir('.bzr',path.';')) 693 | return 'bzr' 694 | elseif !empty(findfile('_FOSSIL_', path.';')) 695 | return 'fossil' 696 | elseif !empty(finddir('_darcs', path.';')) 697 | return 'darcs' 698 | elseif !empty(finddir('RCS', path.';')) 699 | return 'rcs' 700 | else 701 | return '' 702 | endif 703 | endfu 704 | fu! s:Is(os) "{{{1 705 | if (a:os == "win") 706 | return has("win32") || has("win16") || has("win64") 707 | elseif (a:os == "mac") 708 | return has("mac") || has("macunix") 709 | elseif (a:os == "unix") 710 | return has("unix") || has("macunix") 711 | endif 712 | endfu 713 | fu! s:RemoveConsecutiveLines(fwd, list) "{{{1 714 | " only keep the start/end of a bunch of successive lines 715 | let temp = -1 716 | let lines = a:fwd ? a:list : reverse(a:list) 717 | for item in lines 718 | if (a:fwd && temp == item - 1) || (!a:fwd && temp == item + 1) 719 | call remove(lines, index(lines, item)) 720 | endif 721 | let temp = item 722 | endfor 723 | return lines 724 | endfu 725 | fu! s:GetDiff(arg, file) "{{{1 726 | " a:arg == 1 Create signs 727 | " a:arg == 2 Show changed lines in locationlist 728 | " a:arg == 3 Stay in diff mode 729 | " a:file -> which file to diff against 730 | 731 | " If error happened, don't try to get a diff list 732 | try 733 | if changes#CurrentBufferIsIgnored() || 734 | \ !empty(&l:bt) || line2byte(line('$')) == -1 735 | call s:StoreMessage('Buffer is ignored') 736 | return 737 | endif 738 | 739 | " Save some settings 740 | let _wsv = winsaveview() 741 | " Lazy redraw 742 | setl lz 743 | let scratchbuf = 0 744 | 745 | try 746 | if !filereadable(bufname('')) 747 | call s:StoreMessage("You've opened a new file so viewing changes ". 748 | \ "is disabled until the file is saved ") 749 | return 750 | endif 751 | 752 | " Does not make sense to check an empty buffer 753 | if empty(bufname('')) 754 | call s:StoreMessage("The buffer does not contain a name. Aborted!") 755 | " don't ignore buffer, it could get a name later... 756 | return 757 | endif 758 | 759 | " do not generate signs for special buffers 760 | if !empty(&buftype) 761 | call s:StoreMessage("Not generating diff for special buffer!") 762 | call changes#IgnoreCurrentBuffer() 763 | return 764 | endif 765 | 766 | let b:diffhl={'add': [], 'del': [], 'cha': []} 767 | if a:arg == 3 768 | let s:temp = {'del': []} 769 | let curbuf = bufnr('%') 770 | let _ft = &ft 771 | let scratchbuf = s:MakeDiff(a:file) 772 | call s:CheckLines(1) 773 | exe "noa" bufwinnr(scratchbuf) "wincmd w" 774 | exe "setl ft=". _ft 775 | call s:CheckLines(0) 776 | " Switch to other buffer and check for deleted lines 777 | exe "noa" bufwinnr(curbuf) "wincmd w" 778 | let b:diffhl['del'] = s:temp['del'] 779 | else 780 | " parse diff output 781 | call s:MakeDiff_new(a:file, a:arg) 782 | endif 783 | if !has("job") 784 | call s:AfterDiff(bufnr('')) 785 | if a:arg != 3 || s:nodiff 786 | let b:changes_view_enabled=1 787 | endif 788 | if a:arg ==# 2 789 | call s:ShowDifferentLines() 790 | endif 791 | endif 792 | catch /^Vim\%((\a\+)\)\=:E139/ " catch error E139 793 | return 794 | catch /^changes/ 795 | let b:changes_view_enabled=0 796 | call changes#IgnoreCurrentBuffer() 797 | catch 798 | call s:StoreMessage("Error occured: ".v:exception) 799 | call s:StoreMessage("Trace: ". v:throwpoint) 800 | finally 801 | if s:vcs && get(b:, "b:changes_view_enabled", 0) 802 | " only add info here, when 'verbose' > 1 803 | call s:StoreMessage("Check against ". 804 | \ fnamemodify(expand("%"),':t') . " from " . b:vcs_type) 805 | endif 806 | " redraw (there seems to be some junk left) 807 | " disabled, redraw is expensive 808 | "redr! 809 | endtry 810 | finally 811 | if exists("_wsv") 812 | call winrestview(_wsv) 813 | endif 814 | " Make sure, the message is actually displayed! 815 | call changes#WarningMsg() 816 | " restore change marks 817 | call s:SaveRestoreChangeMarks(0) 818 | endtry 819 | endfu 820 | fu! s:AfterDiffAPI(bufnr) "{{{1 821 | " Get all currently placed signs in the buffer 822 | let placed_signs = sign_getplaced(a:bufnr, {'group': s:sign_api_group}) 823 | if len(placed_signs[0]['signs']) > 0 824 | " remove signs, that are no longer needed anymore 825 | let newly_signs = b:diffhl['add'] + b:diffhl['cha'] + b:diffhl['del'] 826 | let to_remove = filter(placed_signs[0]['signs'], { idx, val -> index(newly_signs, val.lnum) == -1}) 827 | call map(to_remove, {idx, val -> sign_unplace(s:sign_api_group, {'id': val.id})}) 828 | endif 829 | for name in ['del', 'add', 'cha'] 830 | " Del delete first, since those refer to the old line numbers 831 | let previous=-1 832 | let id = 0 833 | for line in b:diffhl[name] 834 | let actual_name = name 835 | let cur_sign = sign_getplaced(a:bufnr, {'group': s:sign_api_group, 'lnum':line}) 836 | let length=len(cur_sign[0]['signs']) 837 | let cur_id = 0 838 | let cur_name = '' 839 | while length > 0 840 | " In case there are several signs on the current line, 841 | " remove all except for the last one 842 | let cur_id = cur_sign[0]['signs'][length-1]['id'] 843 | let cur_name = cur_sign[0]['signs'][length-1]['name'] 844 | if length > 1 845 | call sign_unplace(s:sign_api_group, {'id':cur_id}) 846 | endif 847 | let length -= 1 848 | endwhile 849 | if line==previous+1 850 | " There was a sign on the previous line 851 | if index(['add', 'cha'], name) > -1 852 | let actual_name = name.'_dummy' 853 | " Only place sign, if the old existing sign is not of the 854 | " same type (name) 855 | if actual_name isnot# cur_name 856 | call s:PlaceSpecificSign(cur_id, line, actual_name, a:bufnr) 857 | endif 858 | else 859 | " nothing to do for deleted lines, 860 | " but remove previously placed lines 861 | if cur_id > 0 862 | call sign_unplace(s:sign_api_group, {'id': cur_id}) 863 | endif 864 | let previous=line 865 | continue 866 | endif 867 | else 868 | " Only place sign, if the old existing sign is not of the same type (name) 869 | if actual_name isnot# cur_name 870 | call s:PlaceSpecificSign(cur_id, line, actual_name, a:bufnr) 871 | endif 872 | endif 873 | let previous=line 874 | endfor 875 | endfor 876 | endfu 877 | fu! s:AfterDiff(bufnr) "{{{1 878 | call s:SortDiffHl() 879 | if s:sign_api 880 | call s:AfterDiffAPI(a:bufnr) 881 | return 882 | endif 883 | " Check for empty dict of signs 884 | if !exists("b:diffhl") || 885 | \ ((b:diffhl ==? {'add': [], 'del': [], 'cha': []}) 886 | \ && empty(s:placed_signs[0])) 887 | " Make sure, diff and previous diff are different, 888 | " otherwise, we might forget to update the signs 889 | call s:StoreMessage('No differences found!') 890 | let s:nodiff=1 891 | else 892 | let s:diffhl = s:CheckInvalidSigns() 893 | " remove invalid signs 894 | " s:diffhl[0] - invalid signs, that need to be removed 895 | " s:diffhl[1] - valid signs, that need to be added 896 | " safety check 897 | if type(s:diffhl) == type([]) && len(s:diffhl) == 2 && type(s:diffhl[0]) == type([]) 898 | call s:UnPlaceSpecificSigns(s:diffhl[0]) 899 | call s:PlaceSigns(s:diffhl[1]) 900 | endif 901 | endif 902 | endfu 903 | fu! s:SortDiffHl() "{{{1 904 | for i in ['add', 'cha', 'del'] 905 | call sort(b:diffhl[i], 'N') 906 | call uniq(b:diffhl[i]) 907 | endfor 908 | endfu 909 | fu! s:SignType(string) "{{{1 910 | " returns type but skips dummy type 911 | return a:string[0:2] 912 | "return matchstr(a:string, '\(dummy\)\?\zs.*$') 913 | endfu 914 | fu! s:CheckInvalidSigns() "{{{1 915 | " list[0]: signs to remove 916 | " list[1]: signs to add 917 | let list=[[],{'add': [], 'del': [], 'cha': []}] 918 | let ind=0 919 | let last={} 920 | try 921 | " 1) check, if there are signs to delete 922 | for item in s:placed_signs[0] 923 | if (item.name ==? '[Deleted]') 924 | call add(list[0], item) 925 | continue 926 | elseif (item.line == get(last, 'line', 0)) 927 | " remove duplicate signs 928 | call add(list[0], item) 929 | continue 930 | endif 931 | let type=s:SignType(item.name) 932 | if match(keys(b:diffhl), type) < 0 933 | continue 934 | endif 935 | if !empty(type) && index(b:diffhl[type], item.line+0) == -1 936 | call add(list[0], item) 937 | " remove item from the placed sign list, so that we 938 | " don't erroneously place a dummy sign later on 939 | let next = get(s:placed_signs[0], ind+1, {}) 940 | if item.name !~? 'dummy' && !empty(next) && next.name =~? 'dummy' 941 | " The next line should not be of type dummy, so add it to the 942 | " delete list and to the add list 943 | call add(list[0], next) 944 | if index(b:diffhl[type], next.line+0) > -1 945 | call add(list[1][type], next.line+0) 946 | endif 947 | call remove(s:placed_signs[0], ind+1) 948 | endif 949 | call remove(s:placed_signs[0], ind) 950 | else 951 | if item.name =~? 'dummy' && s:SignType(get(last, 'name', '')) != type 952 | call add(list[0], item) 953 | if index(b:diffhl[type], item.line+0) > -1 954 | call add(list[1][type], item.line+0) 955 | endif 956 | endif 957 | if s:SignType(item.name) == s:SignType(get(last, 'name', '')) 958 | \ && item.name !~ 'dummy' 959 | \ && item.line+0 == get(last, 'line', 0)+1 960 | " Current type needs to be of type dummy, but isn't, 961 | " remove and re-add the sign 962 | call add(list[0], item) 963 | call add(list[1][type], item.line+0) 964 | endif 965 | let ind+=1 966 | let last = item 967 | endif 968 | endfor 969 | " Check, which signs are to be placed 970 | let changes_signs_lines=s:ChangesSignsLines() 971 | for id in ['add', 'cha', 'del'] 972 | for line in b:diffhl[id] 973 | let has_sign = index(changes_signs_lines, line) > -1 974 | let cur = index(list[1][id], line) 975 | let prev = index(b:diffhl[id], (line-1)) 976 | if !has_sign && cur == -1 977 | call add(list[1][id], line) 978 | elseif prev > -1 && index(list[1][id], (line-1)) > -1 979 | " if a new line is inserted above an already existing line 980 | " with sign type 'add' make sure, that the already existing 981 | " sign type 'add' will be set to 'dummyadd' so that the '+' 982 | " sign appears at the correct line 983 | if cur == -1 984 | call add(list[1][id], line) 985 | endif 986 | if has_sign 987 | let previtem = s:PrevDictHasKey(line) 988 | if previtem.name == id 989 | call add(list[0], previtem) 990 | endif 991 | endif 992 | endif 993 | endfor 994 | endfor 995 | catch 996 | finally 997 | " in any case, return an empty list 998 | return list 999 | endtry 1000 | endfu 1001 | fu! s:UnPlaceSpecificSigns(dict) "{{{1 1002 | for sign in a:dict 1003 | let ind = index(s:placed_signs[0], sign) 1004 | if ind > -1 1005 | call remove(s:placed_signs[0], ind) 1006 | endif 1007 | if s:sign_api 1008 | call sign_unplace(s:sign_api_group, {'id': sign.id, 'buffer': bufnr('')}) 1009 | else 1010 | exe "sign unplace ". sign.id. " buffer=".bufnr('') 1011 | endif 1012 | endfor 1013 | endfu 1014 | fu! s:PlaceSpecificSign(id, line, type, bufnr) "{{{1 1015 | if s:sign_api 1016 | call sign_place(a:id, s:sign_api_group, a:type, a:bufnr, {'lnum': a:line}) 1017 | else 1018 | exe printf("sil sign place %d line=%d name=%s buffer=%d", 1019 | \ a:id, a:line, a:type, a:bufnr) 1020 | endif 1021 | endfu 1022 | fu! s:InitSignDef() "{{{1 1023 | let signs={} 1024 | let s:changes_sign_hi_style = get(s:, 'changes_sign_hi_style', 0) 1025 | let sign_hi = s:changes_sign_hi_style 1026 | if sign_hi == 2 1027 | for name in ['add', 'del', 'ch'] 1028 | let signs.name = {'text': "\", 'texthl': 'SignColumn'} 1029 | endfor 1030 | else 1031 | " Added options for custom signs 1032 | let plus = (get(g:, 'changes_sign_text_utf8', 0) ? get(g:, 'changes_utf8_add_sign','➕') : get(g:,'changes_add_sign','+')) 1033 | let del = (get(g:, 'changes_sign_text_utf8', 0) ? get(g:, 'changes_utf8_delete_sign','➖') : get(g:,'changes_delete_sign','-')) 1034 | let mod = (get(g:, 'changes_sign_text_utf8', 0) ? get(g:, 'changes_utf8_modified_sign','★') : get(g:,'changes_modified_sign','*')) 1035 | 1036 | let signs['add'] = { 1037 | \ 'text': plus, 1038 | \ 'texthl': (sign_hi<2 ? "ChangesSignTextAdd" : "SignColumn"), 1039 | \ 'icon': s:MakeSignIcon(s:i_path.'add1.bmp'), 1040 | \ 'linehl': sign_hi > 0 ? 'DiffAdd' : ''} 1041 | let signs['del'] = { 1042 | \ 'text': del, 1043 | \ 'texthl': (sign_hi<2 ? "ChangesSignTextDel" : "SignColumn"), 1044 | \ 'icon': s:MakeSignIcon(s:i_path.'delete1.bmp'), 1045 | \ 'linehl': sign_hi > 0 ? 'DiffDelete' : ''} 1046 | let signs['cha'] = { 1047 | \ 'text': mod, 1048 | \ 'texthl': (sign_hi<2 ? "ChangesSignTextCh" : "SignColumn"), 1049 | \ 'icon': s:MakeSignIcon(s:i_path.'warning1.bmp'), 1050 | \ 'linehl': sign_hi > 0 ? 'DiffChange' : ''} 1051 | endif 1052 | let signs['add_dummy'] = { 1053 | \ 'text': plus, 1054 | \ 'texthl': (sign_hi<2 ? "ChangesSignTextDummyAdd": "SignColumn"), 1055 | \ 'linehl': sign_hi > 0 ? 'DiffAdd' : ''} 1056 | let signs['cha_dummy'] = { 1057 | \ 'text': mod, 1058 | \ 'texthl': (sign_hi<2 ? "ChangesSignTextDummyCh": "SignColumn"), 1059 | \ 'linehl': sign_hi > 0 ? 'DiffChange' : ''} 1060 | 1061 | for name in keys(signs) 1062 | " remove empty keys from dictionary 1063 | call filter(signs[name], {key, val -> !empty(val)}) 1064 | " add name of sign to the definition, required for sign_define([list]) 1065 | let signs[name].name = name 1066 | endfor 1067 | 1068 | return signs 1069 | endfu 1070 | fu! s:MakeSignIcon(path) "{{{1 1071 | " Windows seems to have problems with the gui 1072 | if has("gui_running") && !s:Is("win") && 1073 | \ get(g:, 'changes_use_icons', 1) && 1074 | \ filereadable(a:path) 1075 | return a:path 1076 | else 1077 | return '' 1078 | endif 1079 | endfu 1080 | fu! s:SaveRestoreChangeMarks(save) "{{{1 1081 | if a:save 1082 | let s:_change_mark = [getpos("'["), getpos("']")] 1083 | else 1084 | for i in [0,1] 1085 | call setpos("'".(i?']':'['), s:_change_mark[i]) 1086 | endfor 1087 | endif 1088 | endfu 1089 | fu! s:HighlightTextChanges() "{{{1 1090 | " use the '[ and '] marks (if they are valid) 1091 | " and highlight changes 1092 | let seq_last=undotree()['seq_last'] 1093 | let seq_cur =undotree()['seq_cur'] 1094 | if seq_last > seq_cur && exists("b:changes_linehi_diff_match") && 1095 | \ len(b:changes_linehi_diff_match) > 0 1096 | " change was undo 1097 | " remove last highlighting (this is just a guess!) 1098 | for [change, val] in items(b:changes_linehi_diff_match) 1099 | if change > seq_cur 1100 | sil call matchdelete(val) 1101 | unlet! b:changes_linehi_diff_match[change] 1102 | endif 1103 | endfor 1104 | return 1105 | endif 1106 | if get(g:, 'changes_linehi_diff', 0) && 1107 | \ (getpos("'[")[1] !=? 1 || 1108 | \ getpos("']")[1] !=? line('$')) && 1109 | \ getpos("']") !=? getpos("'[") 1110 | " ignore those marks, if they are 1111 | " - not set (e.g. they are [0,0,0,0] 1112 | " - or '[ and '] are the same (happens when deleting lines) 1113 | " - or they do not match the complete buffer 1114 | call s:AddMatches( 1115 | \ s:GenerateHiPattern(getpos("'[")[1:2], getpos("']")[1:2])) 1116 | elseif get(g:, 'changes_linehi_diff', 0) && getpos("']") ==? getpos("'[") 1117 | " highlight the complete line 1118 | let pos1=[line("'[")] + [1] 1119 | let pos2=[line("'[")] + [1000] " just a guess 1120 | call s:AddMatches(s:GenerateHiPattern(pos1, pos2)) 1121 | endif 1122 | endfu 1123 | fu! s:GenerateHiPattern(startl, endl) "{{{1 1124 | " startl - Start Position [line, col] 1125 | " endl - End Position [line, col] 1126 | " Easy way: match within a line 1127 | if a:startl[0] == a:endl[0] 1128 | return '\%'.a:startl[0]. 'l\%>'.(a:startl[1]-1).'c.*\%<'.(a:endl[1]+1).'c' 1129 | else 1130 | " Need to generate concat 3 patterns: 1131 | " 1) from startline, startcolumn till end of line 1132 | " 2) all lines between startline and end line 1133 | " 3) from start of endline until end column 1134 | " 1135 | " example: Start at line 1 col. 6 until line 3 column 12: 1136 | " \%(\%1l\%>6v.*\)\|\(\%>1l\%<3l.*\)\|\(\%3l.*\%<12v\) 1137 | return '\%(\%'. a:startl[0]. 'l\%>'. (a:startl[1]-1). 'c.*\)\|'. 1138 | \ '\%(\%>'. a:startl[0]. 'l\%<'. a:endl[0]. 'l.*\)\|'. 1139 | \ '\%(\%'. a:endl[0]. 'l.*\%<'. (a:endl[1]+1). 'c\)' 1140 | endif 1141 | endfu 1142 | fu! s:AddMatches(pattern) "{{{1 1143 | if !empty(a:pattern) 1144 | if !exists("b:changes_linehi_diff_match") 1145 | let b:changes_linehi_diff_match = {} 1146 | endif 1147 | let b:changes_linehi_diff_match[changenr()] = matchadd('CursorLine', a:pattern) 1148 | endif 1149 | endfu 1150 | fu! s:SignIdRemove() "{{{1 1151 | if !exists("b:changes_sign_id") 1152 | return 1153 | endif 1154 | " return the last id, that has been used for placing a sign 1155 | " and decrement id, so that the next call to s:SignId() will 1156 | " get a valid ID 1157 | let b:changes_sign_id -= 1 1158 | return printf("%d%02d", b:sign_prefix, b:changes_sign_id + 1) 1159 | endfu 1160 | fu! s:SignId() "{{{1 1161 | let b:changes_sign_id = get(b:, 'changes_sign_id', 0) + 1 1162 | return b:changes_sign_id 1163 | endfu 1164 | fu! s:IsUpdateAllowed(empty) "{{{1 1165 | if !empty(&buftype) || &ro || &diff || changes#CurrentBufferIsIgnored() 1166 | " Don't make a diff out of an unnamed buffer 1167 | " or of a special buffer or of a read-only buffer 1168 | if !empty(&buftype) 1169 | call s:StoreMessage("Special Buffer, not performing diffs!") 1170 | elseif &ro 1171 | call s:StoreMessage("Buffer read-only, not performing diffs!") 1172 | elseif &diff 1173 | call s:StoreMessage("disabled for diff-mode!") 1174 | else 1175 | call s:StoreMessage("Buffer is currently ignored, not performing diffs!") 1176 | endif 1177 | return 0 1178 | " only check for empty buffer when a:empty is true 1179 | elseif a:empty && empty(bufname('')) 1180 | call s:StoreMessage("Buffer hasn't been written yet. Can't diff!") 1181 | return 0 1182 | elseif !empty(bufname('')) && ((get(g:, 'changes_max_filesize', 0) > 0 && 1183 | \ g:changes_max_filesize < getfsize(bufname(''))) || 1184 | \ getfsize(bufname('')) < 0) 1185 | call s:StoreMessage('FileSize too large, skipping check') 1186 | return 0 1187 | endif 1188 | return 1 1189 | endfu 1190 | 1191 | if has("job") "{{{1 1192 | let s:jobs = {} 1193 | 1194 | function! s:on_exit(channel) dict abort "{{{2 1195 | if getfsize(self.output) <= 0 1196 | call s:StoreMessage("File not found or no differences found!") 1197 | if exists("b:diffhl") 1198 | " might need to remove invalid signs 1199 | call s:AfterDiff(self.bufnr) 1200 | endif 1201 | call changes#WarningMsg() 1202 | return 1203 | endif 1204 | if !exists("b:diffhl") 1205 | let b:diffhl={'add': [], 'del': [], 'cha': []} 1206 | endif 1207 | call s:ParseDiffOutput(self.output) 1208 | call s:AfterDiff(self.bufnr) 1209 | " redrawing is expansive, skipped 1210 | "redr! 1211 | if self.type != 3 || s:nodiff 1212 | let b:changes_view_enabled=1 1213 | endif 1214 | if self.type ==# 2 1215 | call s:ShowDifferentLines() 1216 | endif 1217 | call changes#WarningMsg() 1218 | call s:SaveRestoreChangeMarks(0) 1219 | " Remove from s:jobs 1220 | if has_key(s:jobs, self.file) 1221 | call remove(s:jobs, self.file) 1222 | endif 1223 | let s:jobid -= 1 1224 | endfunction 1225 | 1226 | function! s:ChangesDoAsync(cmd, file, type, outfile) "{{{2 1227 | if s:Is("win") 1228 | let cmd = (&sh =~# 'cmd.exe' ? 'cmd.exe /c ' : 'sh -c ') . a:cmd 1229 | else 1230 | let cmd = ['sh', '-c', a:cmd] 1231 | endif 1232 | if !empty(a:outfile) 1233 | let outfile=s:diff_out.'.'.s:jobid 1234 | else 1235 | let outfile=a:outfile 1236 | endif 1237 | if empty(a:file) 1238 | return 1239 | endif 1240 | 1241 | let options = {'file': a:file, 'cmd': a:cmd, 'type': a:type, 'output': outfile, 'bufnr': bufnr('')} 1242 | if has_key(s:jobs, a:file) 1243 | if job_status(get(s:jobs, a:file)) == 'run' 1244 | return 1245 | else 1246 | call job_stop(get(s:jobs, a:file)) 1247 | call remove(s:jobs, a:file) 1248 | if s:jobid > 1 1249 | let s:jobid -= 1 1250 | endif 1251 | endif 1252 | endif 1253 | let id = job_start(cmd, { 1254 | \ 'err_io': 'out', 1255 | \ 'close_cb': function('s:on_exit', options)}) 1256 | let s:jobs[a:file] = id 1257 | let s:jobid += 1 1258 | endfu 1259 | endif 1260 | 1261 | fu! changes#SetSignColumn() "{{{1 1262 | if exists("s:old_signcolumn") && &scl isnot# 'yes' 1263 | " user changed the setting, do not change it back again 1264 | if &scl is# 'no' 1265 | throw 'changes:abort' 1266 | else 1267 | return 1268 | endif 1269 | elseif &scl isnot# 'yes' && !&buftype ==# 'terminal' 1270 | set signcolumn=yes 1271 | let s:old_signcolumn = 1 1272 | endif 1273 | endfu 1274 | fu! changes#GetStats() "{{{1 1275 | return [ len(get(get(b:, 'diffhl', []), 'add', [])), 1276 | \ len(get(get(b:, 'diffhl', []), 'cha', [])), 1277 | \ len(get(get(b:, 'diffhl', []), 'del', []))] 1278 | endfu 1279 | fu! changes#WarningMsg() "{{{1 1280 | if !&vbs 1281 | " Set verbose to 1 to have messages displayed! 1282 | return 1283 | endif 1284 | if !empty(s:msg) 1285 | redraw! 1286 | let msg=["Changes.vim: " . s:msg[0]] + (len(s:msg) > 1 ? s:msg[1:] : []) 1287 | echohl WarningMsg 1288 | for mess in reverse(msg) 1289 | echomsg mess 1290 | endfor 1291 | 1292 | echohl Normal 1293 | let v:errmsg=msg[0] 1294 | let s:msg = [] 1295 | endif 1296 | endfu 1297 | fu! changes#Output() "{{{1 1298 | let add = '+' 1299 | let ch = '*' 1300 | let del = '-' 1301 | let sign_def = s:signs 1302 | if !empty(sign_def) 1303 | let add = matchstr(sign_def['add'], 'text=\zs..') 1304 | let ch = matchstr(sign_def['cha'], 'text=\zs..') 1305 | let del = matchstr(sign_def['del'], 'text=\zs..') 1306 | endif 1307 | echohl Title 1308 | echo "Differences will be highlighted like this:" 1309 | echohl Normal 1310 | echo "=========================================" 1311 | echohl ChangesSignTextAdd 1312 | echo add. " Added Lines" 1313 | echohl ChangesSignTextDel 1314 | echo del. " Deleted Lines" 1315 | echohl ChangesSignTextCh 1316 | echo ch. " Changed Lines" 1317 | echohl Normal 1318 | endfu 1319 | fu! changes#Init() "{{{1 1320 | " Message queue, that will be displayed. 1321 | let s:msg = [] 1322 | " save change marks 1323 | call s:SaveRestoreChangeMarks(1) 1324 | " Ignore buffer 1325 | if !exists("s:ignore") 1326 | let s:ignore = {} 1327 | endif 1328 | let s:changes_signs_undefined=0 1329 | let s:autocmd = get(g:, 'changes_autocmd', 1) 1330 | " Check against a file in a vcs system 1331 | let s:vcs = get(g:, 'changes_vcs_check', 0) 1332 | if !exists("b:vcs_type") 1333 | if exists("g:changes_vcs_system") 1334 | let b:vcs_type = g:changes_vcs_system 1335 | endif 1336 | if exists("b:changes_vcs_system") 1337 | let b:vcs_type = b:changes_vcs_system 1338 | endif 1339 | if !exists("b:vcs_type") 1340 | let b:vcs_type = s:GuessVCSSystem() 1341 | endif 1342 | endif 1343 | if s:vcs && empty(b:vcs_type) 1344 | " disable VCS checking... 1345 | let s:vcs=0 1346 | endif 1347 | if !exists("s:vcs_cat") 1348 | let s:vcs_cat = {'git': 'show :', 1349 | \'bzr': 'cat ', 1350 | \'cvs': '-q update -p ', 1351 | \'darcs': '--show-contents ', 1352 | \'fossil': 'finfo -p ', 1353 | \'rcs': 'co -p ', 1354 | \'svn': 'cat ', 1355 | \'hg': 'cat '} 1356 | 1357 | " Define aliases... 1358 | let s:vcs_cat.mercurial = s:vcs_cat.hg 1359 | let s:vcs_cat.subversion = s:vcs_cat.svn 1360 | let s:vcs_diff = {'git': ' diff -U0 --no-ext-diff --no-color ', 1361 | \ 'hg' : ' diff -U0 '} 1362 | let s:vcs_apply = {'git': ' apply --cached --unidiff-zero ', 1363 | \ 'hg' : ' import - '} 1364 | endif 1365 | 1366 | " Settings for Version Control 1367 | if s:vcs && !empty(b:vcs_type) 1368 | if get(s:vcs_cat, b:vcs_type, 'NONE') == 'NONE' 1369 | call s:StoreMessage("Don't know VCS " . b:vcs_type) 1370 | call s:StoreMessage("VCS check will be disabled for now.") 1371 | let s:vcs=0 1372 | " Probably file not in a repository/working dir 1373 | endif 1374 | if !executable(b:vcs_type) 1375 | call s:StoreMessage("Guessing VCS: ". b:vcs_type) 1376 | call s:StoreMessage("Executable " . b:vcs_type . " not found! Aborting.") 1377 | call s:StoreMessage("You might want to set the g:changes_vcs_system variable to override!") 1378 | let s:vcs=0 1379 | " Probably file not in a repository/working dir 1380 | endif 1381 | endif 1382 | if !exists("s:diff_out") 1383 | let s:diff_out = tempname() 1384 | let s:diff_in_cur = s:diff_out.'.cur.txt' 1385 | let s:diff_in_old = s:diff_out.'.old.txt' 1386 | let s:jobid = 1 " job id 1387 | endif 1388 | let s:nodiff=0 1389 | " Make sure, we are fetching all placed signs again 1390 | unlet! s:all_signs 1391 | let s:old_signs = get(s:, 'signs', {}) 1392 | let s:signs=s:InitSignDef() 1393 | " Only check the first time this file is loaded 1394 | " It should not be neccessary to check every time 1395 | if !exists("s:precheck") 1396 | try 1397 | call s:Check() 1398 | catch 1399 | call s:StoreMessage("changes plugin will not be working!") 1400 | " Rethrow exception 1401 | let s:autocmd = 0 1402 | throw "changes:abort" 1403 | return 1404 | endtry 1405 | let s:precheck=1 1406 | endif 1407 | if !get(g:, 'changes_respect_SignColumn', 0) 1408 | " Make the Sign column not standout 1409 | hi! clear SignColumn 1410 | if &l:nu || &l:rnu 1411 | hi! link SignColumn LineNr 1412 | else 1413 | hi! link SignColumn Normal 1414 | endif 1415 | endif 1416 | " This variable is a prefix for all placed signs. 1417 | " This is needed, to not mess with signs placed by the user 1418 | if !exists("b:sign_prefix") 1419 | let b:sign_prefix = (s:sign_api ? '' : s:GetSignId() + 10) 1420 | endif 1421 | 1422 | let s:placed_signs = s:PlacedSigns() 1423 | if s:old_signs !=? s:signs && !empty(s:old_signs) 1424 | " Sign definition changed, redefine them 1425 | call s:UnPlaceSpecificSigns(s:placed_signs[0]) 1426 | call s:DefineSigns(1) 1427 | " need to parse placed signs again... 1428 | let s:placed_signs = s:PlacedSigns() 1429 | endif 1430 | call changes#SetSignColumn() 1431 | call changes#AuCmd(s:autocmd) 1432 | " map to update sign column, if g:changes_fast == 0 1433 | if !hasmapto('', 'i') && !get(g:, 'changes_fast', 1) 1434 | call ChangesMap('') 1435 | endif 1436 | endfu 1437 | fu! changes#CurrentBufferIsIgnored() "{{{1 1438 | return exists("s:ignore") && get(s:ignore, bufnr('%'), 0) 1439 | endfu 1440 | fu! changes#IgnoreCurrentBuffer() "{{{1 1441 | if !exists("s:ignore") 1442 | let s:ignore = {} 1443 | endif 1444 | let s:ignore[bufnr('%')]=1 1445 | endfu 1446 | fu! changes#UnignoreCurrentBuffer() "{{{1 1447 | if changes#CurrentBufferIsIgnored() 1448 | call remove(s:ignore, bufnr('%')) 1449 | endif 1450 | endfu 1451 | fu! changes#EnableChanges(arg, ...) "{{{1 1452 | " if a:1 given, make a diff against the given file 1453 | call changes#UnignoreCurrentBuffer() 1454 | try 1455 | let savevar = get(g:, 'changes_max_filesize', 0) 1456 | unlet! g:changes_max_filesize 1457 | call changes#Init() 1458 | if a:arg 1459 | call s:UnPlaceSigns(1) 1460 | endif 1461 | verbose call s:GetDiff(a:arg, (a:0 ? a:1 : '')) 1462 | catch 1463 | call changes#WarningMsg() 1464 | call changes#CleanUp() 1465 | finally 1466 | if savevar > 0 1467 | let g:changes_max_filesize = savevar 1468 | endif 1469 | endtry 1470 | endfu 1471 | fu! changes#CleanUp() "{{{1 1472 | " only delete signs, that have been set by this plugin 1473 | call s:UnPlaceSigns(1) 1474 | call changes#IgnoreCurrentBuffer() 1475 | for key in keys(get(s:, 'signs', {})) 1476 | exe "sil! sign undefine " key 1477 | endfor 1478 | if s:autocmd 1479 | call changes#AuCmd(0) 1480 | endif 1481 | let b:changes_view_enabled = 0 1482 | if exists("b:changes_linehi_diff_match") 1483 | for val in values(b:changes_linehi_diff_match) 1484 | " ignore possible errors 1485 | sil! call matchdelete(val) 1486 | endfor 1487 | endif 1488 | unlet! b:diffhl s:signs s:old_signs b:changes_linehi_diff_match b:changes_sign_id s:precheck 1489 | endfu 1490 | fu! changes#AuCmd(arg) "{{{1 1491 | if a:arg 1492 | if !exists("#Changes") 1493 | augroup Changes 1494 | autocmd! 1495 | au TextChanged,InsertLeave,FilterReadPost * :call s:UpdateView() 1496 | " make sure, hightlighting groups are not cleared 1497 | au ColorScheme,GUIEnter * :try|call s:Check() |catch|endtry 1498 | if s:Is('unix') 1499 | " FocusGained does not work well on Windows 1500 | " because calling background processess triggers 1501 | " FocusGAined autocommands recursively 1502 | au FocusGained * :call s:UpdateView(1) 1503 | endif 1504 | if !get(g:, 'changes_fast', 1) 1505 | au InsertEnter * :call changes#InsertSignOnEnter() 1506 | endif 1507 | au BufWritePost,BufWinEnter * :call s:UpdateView(1) 1508 | au VimLeave * call s:DeleteTempFiles() 1509 | augroup END 1510 | endif 1511 | else 1512 | augroup Changes 1513 | autocmd! 1514 | augroup END 1515 | augroup! Changes 1516 | endif 1517 | endfu 1518 | fu! changes#TCV() "{{{1 1519 | if get(b:, "changes_view_enabled", 0) 1520 | call s:UnPlaceSigns(0) 1521 | let b:changes_view_enabled = 0 1522 | echo "Hiding changes since last save" 1523 | else 1524 | try 1525 | call changes#Init() 1526 | call s:GetDiff(1, '') 1527 | let b:changes_view_enabled = 1 1528 | echo "Showing changes since last save" 1529 | catch 1530 | " Make sure, the message is actually displayed! 1531 | verbose call changes#WarningMsg() 1532 | call changes#CleanUp() 1533 | endtry 1534 | endif 1535 | endfu 1536 | fu! changes#MoveToNextChange(fwd, cnt) "{{{1 1537 | " Make sure, the hunks are up to date 1538 | let _fen = &fen 1539 | set nofen 1540 | call s:UpdateView() 1541 | let &fen = _fen 1542 | 1543 | let cur = line('.') 1544 | let dict = get(b:, "diffhl", {}) 1545 | let lines = get(dict, "add", []) + 1546 | \ get(dict, "del", []) + 1547 | \ get(dict, "cha", []) 1548 | let lines = sort(lines, 'n') 1549 | " remove duplicates 1550 | let lines = uniq(lines) 1551 | if mode() =~? '[vs]' && index(lines, cur) == -1 1552 | " in visual mode and not within a hunk! 1553 | return "\" 1554 | endif 1555 | 1556 | let suffix = '0' " move to start of hunk 1557 | let cnt = a:cnt-1 1558 | 1559 | " only keep the start/end of a bunch of successive lines 1560 | let lines = s:RemoveConsecutiveLines(1, copy(lines)) + 1561 | \ s:RemoveConsecutiveLines(0, copy(lines)) 1562 | " sort again... 1563 | let lines = sort(lines, 'n') 1564 | 1565 | if empty(lines) 1566 | echomsg "There are no ". (a:fwd ? "next" : "previous"). 1567 | \ " differences!" 1568 | return "\" 1569 | elseif (a:fwd && max(lines) <= cur) || 1570 | \ (!a:fwd && min(lines) >= cur) 1571 | echomsg "There are no more ". (a:fwd ? "next" : "previous"). 1572 | \ " differences!" 1573 | return "\" 1574 | endif 1575 | if a:fwd 1576 | call filter(lines, 'v:val > cur') 1577 | if empty(lines) 1578 | return "\" 1579 | endif 1580 | else 1581 | call filter(lines, 'v:val < cur') 1582 | if empty(lines) 1583 | return "\" 1584 | else 1585 | call reverse(lines) 1586 | endif 1587 | endif 1588 | if cnt > len(lines) 1589 | let cnt=length(lines) 1590 | endif 1591 | 1592 | " Cancel the user given count 1593 | " otherwise the count would be multiplied with 1594 | " the given line number 1595 | let prefix=(cnt > 0 ? "\" : "") 1596 | return prefix.lines[cnt]. "G".suffix 1597 | endfu 1598 | fu! changes#CurrentHunk() "{{{1 1599 | if changes#MoveToNextChange(0,1) == "\" 1600 | " outside of a hunk 1601 | return "\" 1602 | else 1603 | return "[ho]h$" 1604 | endif 1605 | endfu 1606 | fu! changes#FoldDifferences(...) "{{{1 1607 | if &fde!=?'index(g:lines,v:lnum)>-1?0:1' 1608 | let b:chg_folds = {'fen': &fen, 'fdm': &fdm, 'fde': &fde} 1609 | " Use context from 'diffopt' setting, if it is set 1610 | let context = matchstr(&diffopt, 'context:\zs\d\+\ze')+0 1611 | if context == 0 1612 | let context = 3 1613 | endif 1614 | let context = empty(a:000) ? context : a:1 1615 | let g:lines = [] 1616 | let dict=get(b:, 'diffhl', {}) 1617 | for line in sort(get(dict, 'add', []) + get(dict, 'cha' , []) + get(dict, 'del', []), 'n') 1618 | for item in range(line-context,line+context) 1619 | " Add some context 1620 | if index(g:lines, item) > -1 || item < 1 || item > line('$') 1621 | continue 1622 | endif 1623 | call add(g:lines, item) 1624 | endfor 1625 | endfor 1626 | let g:lines=uniq(g:lines) 1627 | if !empty(g:lines) 1628 | setl fen fdm=expr fde=index(g:lines,v:lnum)>-1?0:1 1629 | else 1630 | let s:msg=[] 1631 | call s:StoreMessage('Not folding, no lines changes!') 1632 | verbose call changes#WarningMsg() 1633 | endif 1634 | else 1635 | for items in items(get(b:, 'chg_folds', {})) 1636 | exe "let &".items[0]."=".string(items[1]) 1637 | endfor 1638 | endif 1639 | endfu 1640 | fu! changes#ToggleHiStyle() "{{{1 1641 | let s:changes_sign_hi_style += 1 1642 | if s:changes_sign_hi_style > 2 1643 | let s:changes_sign_hi_style = 0 1644 | endif 1645 | try 1646 | call changes#Init() 1647 | call s:GetDiff(1, '') 1648 | catch 1649 | " Make sure, the message is actually displayed! 1650 | verbose call changes#WarningMsg() 1651 | call changes#CleanUp() 1652 | endtry 1653 | endfu 1654 | fu! changes#MapCR() "{{{1 1655 | if !( pumvisible() || 1656 | \ (exists("*wildmenumode") && wildmenumode())) 1657 | call changes#InsertSignOnEnter() 1658 | endif 1659 | return '' 1660 | endfu 1661 | fu! changes#InsertSignOnEnter() "{{{1 1662 | " prevent an expansive call to create a diff, 1663 | " simply check, if the current line has a sign 1664 | " and if not, add one 1665 | unlet! s:changes_last_inserted_sign 1666 | if !s:IsUpdateAllowed(1) || &cpo =~ '$' 1667 | " If '$' is inside 'cpo' setting, than the redraw by placing a sign 1668 | " will overwrite the '$' placed from the change command. So return 1669 | " if the user has '$' inside the cpoptions. 1670 | return 1671 | endif 1672 | try 1673 | call changes#Init() 1674 | catch changes:abort 1675 | endtry 1676 | let line = line('.') 1677 | let prev = line - 1 1678 | let next = line + 1 1679 | let name=s:PrevDictHasKey(line) 1680 | if name == {} 1681 | " no sign yet on current line, add one. 1682 | let prevname = s:PrevDictHasKey(prev) 1683 | let name = ((get(prevname, 'name', '') =~? 'add') ? 'add_dummy' : 'add') 1684 | let id=b:sign_prefix.s:SignId() 1685 | call s:PlaceSpecificSign(id, line, name, bufnr('')) 1686 | " on o in normal mode, we should keep the sign 1687 | if get(b:, 'changes_last_line', 0) == line('$') 1688 | let s:changes_last_inserted_sign={'id': id, 'line':line, 'type':name} 1689 | endif 1690 | endif 1691 | if get(s:PrevDictHasKey(next),'name', '') ==? 'add' 1692 | let item = filter(copy(s:placed_signs[0]), 'v:val.line ==? next') 1693 | call s:UnPlaceSpecificSigns(item) 1694 | call s:PlaceSpecificSign(item[0].id, next, 'add_dummy', bufnr('')) 1695 | endif 1696 | let b:changes_last_line = line('$') 1697 | endfu 1698 | fu! changes#StageHunk(line, revert) "{{{1 1699 | try 1700 | let cur = a:line 1701 | let _pwd = s:ChangeDir() 1702 | let _wsv = winsaveview() 1703 | let _vbs = &verbose 1704 | call changes#Init() 1705 | if get(b:, 'vcs_type', '') !=? 'git' 1706 | let &vbs=1 1707 | call s:StoreMessage("Sorry, staging Hunks is only supported for git!") 1708 | return 1709 | elseif !a:revert && changes#GetStats() ==? [0,0,0] 1710 | let &vbs=1 1711 | call s:StoreMessage('No changes detected, nothing to do!') 1712 | return 1713 | endif 1714 | if changes#GetStats() !=? [0,0,0] || a:revert 1715 | if &mod 1716 | sil noa write 1717 | endif 1718 | let git_rep_p = s:ReturnGitRepPath() 1719 | exe "lcd" git_rep_p 1720 | " When reverting, need to get cached diff 1721 | let diff = split(system(b:vcs_type. 1722 | \ s:vcs_diff[b:vcs_type]. 1723 | \ (a:revert ? ' --cached ' : ''). 1724 | \ expand('%')), "\n") 1725 | if v:shell_error 1726 | let &vbs=1 1727 | call s:StoreMessage("Error occured: ". join(diff, "\n")) 1728 | return 1729 | endif 1730 | let file='' 1731 | let found=0 1732 | let hunk=[] 1733 | let index = match(diff, '^+++') 1734 | for line in diff[index + 1 : ] 1735 | if line =~? '^@@.*@@' 1736 | if found 1737 | break 1738 | endif 1739 | let temp = split(line)[2] 1740 | let lines = split(temp, ',') 1741 | call map(lines, 'matchstr(v:val, ''\d\+'')+0') 1742 | if (len(lines) == 2 && 1743 | \ cur >= lines[0] && cur < lines[0]+lines[1]) || 1744 | \ (len(lines) == 1 && cur == lines[0]) || 1745 | \ (len(lines) == 2 && lines[1] == 0 && cur == lines[0]) 1746 | " this is the hunk the cursor is on 1747 | let found=1 1748 | endif 1749 | endif 1750 | if found 1751 | call add(hunk, line) 1752 | endif 1753 | endfor 1754 | if empty(hunk) 1755 | call s:StoreMessage('Cursor not on a diff hunk, aborting!') 1756 | let &vbs=1 1757 | return 1758 | endif 1759 | " Add filename to hunk 1760 | let hunk = diff[0:index] + hunk 1761 | let output=system(b:vcs_type. s:vcs_apply[b:vcs_type]. 1762 | \ (a:revert ? ' --reverse - ' : ' - '), 1763 | \ s:Output(hunk)) 1764 | if v:shell_error 1765 | call s:StoreMessage(output) 1766 | endif 1767 | call s:GetDiff(1, '') 1768 | endif 1769 | catch 1770 | let &vbs=1 1771 | call s:StoreMessage("Exception occured") 1772 | call s:StoreMessage(string(v:exception)) 1773 | finally 1774 | exe "lcd " _pwd 1775 | call changes#WarningMsg() 1776 | if &vbs != _vbs 1777 | let &vbs = _vbs 1778 | endif 1779 | call winrestview(_wsv) 1780 | endtry 1781 | endfu 1782 | " Modeline "{{{1 1783 | " vi:fdm=marker fdl=0 ts=4 sw=4 et 1784 | -------------------------------------------------------------------------------- /autoload/changes_icons/add.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/add.bmp -------------------------------------------------------------------------------- /autoload/changes_icons/add1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/add1.bmp -------------------------------------------------------------------------------- /autoload/changes_icons/delete.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/delete.bmp -------------------------------------------------------------------------------- /autoload/changes_icons/delete1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/delete1.bmp -------------------------------------------------------------------------------- /autoload/changes_icons/license_designkode.txt: -------------------------------------------------------------------------------- 1 | URL: http://www.designkode.com/blog/free-developer-icons 2 | License: Creative Commons (Attribution 3.0 Unported) 3 | "DesignKode is releasing this set of 40 free high quality icons for your web site and application GUI designs. All icons in this set are 32 x 32 pixel PNG image files. You may freely use these icons in your commercial or personal projects without attribution." 4 | -------------------------------------------------------------------------------- /autoload/changes_icons/warning.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/warning.bmp -------------------------------------------------------------------------------- /autoload/changes_icons/warning1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/autoload/changes_icons/warning1.bmp -------------------------------------------------------------------------------- /changes.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisbra/changesPlugin/f75e659b92a32e6653371888c3e6d0697018144a/changes.zip -------------------------------------------------------------------------------- /doc/ChangesPlugin.txt: -------------------------------------------------------------------------------- 1 | *ChangesPlugin.txt* Print indication of changed lines for a buffer 2 | 3 | Author: Christian Brabandt 4 | Version: 0.16 Thu, 15 Jan 2015 21:16:40 +0100 5 | Copyright: (c) 2010-2019 by Christian Brabandt 6 | 7 | *ChangesPlugin-copyright* 8 | The VIM LICENSE applies to ChangesPlugin.txt (see |copyright|) 9 | except use ChangesPlugin instead of "Vim". NO WARRANTY, EXPRESS OR 10 | IMPLIED. USE AT-YOUR-OWN-RISK. 11 | 12 | ============================================================================== 13 | 1. Contents *ChangesPlugin* 14 | 15 | 1. Contents......................................: |ChangesPlugin| 16 | 2. Manual........................................: |ChangesPlugin-manual| 17 | 3. Configuration.................................: |ChangesPlugin-Config| 18 | 4. ChangesPlugin Feedback........................: |ChangesPlugin-Feedback| 19 | 5. ChangesPlugin History.........................: |ChangesPlugin-history| 20 | ============================================================================== 21 | 22 | *ChangesPlugin-manual* 23 | 2. Functionality 24 | ---------------- 25 | 26 | This plugin was written to help visualize which lines have been changes since 27 | editing started for a file. The plugin was inspired by so called changed-bars, 28 | available at other editors, such as Embarcadero C++ Builder (there it is 29 | called Change Bars, see: 30 | http://edn.embarcadero.com/article/33453#6PersonalDeveloperProductivity) 31 | or Visual Studio where it is called indicator margin (see 32 | http://blog.eveningcreek.com/?p=151). 33 | 34 | ChangesPlugin.vim uses the |diff|-feature of vim and compares the actual 35 | buffer with it's saved state (or possibly against a state in a repository). 36 | In order to highlight the indicator signs at the first column, its using 37 | |signs|. For a description of the highlighting and how to customize it see 38 | |ChangesPlugin-colors| 39 | 40 | Note, that a '-' indicates, that at least one line was deleted between that 41 | particular line and the following line. 42 | 43 | This means, that in order to use this plugin you need a vim, that was built 44 | with |+signs|-support and |+diff|-support and you also need an executable diff 45 | command. If neither of these conditions are met, changePlugin.vim will issue a 46 | warning and abort. 47 | 48 | The plugin does not place signs on lines, on which a sign was already placed 49 | (by e.g. another plugin). 50 | 51 | *:EC* *:EnableChanges* 52 | By default the plugin is not enabled. To enable it enter > 53 | :EnableChanges 54 | When you run this command, ChangesPlugin.vim diffs the current file agains 55 | its saved file on disk and displays the changes in the first column. 56 | 57 | If you like to display the Changes against a different file, simply supply its 58 | filename as optional argument to that command, e.g. > 59 | :EnableChanges ~/project/foobar.c 60 | would display the Signs as if your current file has been diffed against the 61 | file ~/project/foobar.c 62 | 63 | Alternatively, you can enter the shortcut > 64 | :EC 65 | which basically calls :EnableChanges and supports the same arguments. 66 | 67 | If errors happen, the current buffer will be silently ignored when :EC 68 | is called later. 69 | 70 | *:DC* *:DisableChanges* 71 | If you want to disable the plugin, enter > 72 | :DisableChanges 73 | or alternatively, you can enter the shortcut > 74 | :DC 75 | and the Display of Changes will be disabled. 76 | 77 | *:TCV* *:ToggleChangeView* 78 | You can toggle, between turning on and off the indicator bars, using > 79 | :ToggleChangeView 80 | or alternatively: > 81 | :TCV 82 | to toggle the display of indicator bars. 83 | 84 | *:CC* *:ChangesCaption* 85 | You are probably wondering, what those strange looking signs mean. You can 86 | use either > 87 | :CC 88 | or > 89 | :ChangesCaptions 90 | to let the Plugin display a small caption, so you know what each sign means 91 | and how they are colored. 92 | 93 | *:CL* *:ChangesLineOverview* 94 | If you are editing a huge file with several hundreds of lines, it may be hard 95 | to find the lines that have been changed. > 96 | :CL 97 | or > 98 | :ChangesLineOverview 99 | provide an easy view to only see all modified lines. This will open the 100 | |location-list| buffer where you can easily see the affected lines. Pushing 101 | enter on any line, allows you to easily jump to that line in the buffer. 102 | Note: Only the first line of a hunk will be added. This makes it easier to use 103 | |:lnext| to jump from hunk to hunk. 104 | 105 | *:CD* *:ChangesDiffMode* 106 | You might want to keep the diff-window open, so you can use it for modifying 107 | your buffer using e.g. |:diffput| or |:diffget| 108 | Therefore ChangesPlugin defines the two commands > 109 | :CD 110 | and > 111 | :ChangesDiffMode 112 | If you issue any of these commands, a vertical split buffer will open, 113 | that contains the changes from either your VCS or from the unmodified buffer 114 | as it is saved on disk. See |copy-diffs| for how to merge changes between 115 | those two buffers. 116 | 117 | *:CF* *:ChangesFoldDiff* 118 | To temporarily fold aways all non-modified lines, you can use the :CF 119 | command > 120 | :CF 121 | and > 122 | :ChangesFoldDiff 123 | 124 | An optional number argument can be given to select the number of context lines 125 | to be shown (default: context value from 'diffopt' or 3 if not given). 126 | Issuing the command again, will restore the previous state. 127 | 128 | *:CT* *:ChangesStyleToggle* 129 | This command changes how changed lines will be highlighted. It toggles between 130 | three states. Initially changed lines will only be indicated by a sign in the 131 | first column. After toggling, the sign will be displayed and additionally, all 132 | lines will be highlighted. After calling it a second time, all lines will be 133 | highlighted, but there won't be an indicator sign in the first line. Use: > 134 | :CT 135 | and > 136 | :ChangesStyleToggle 137 | 138 | To toggle between all states. 139 | *:ChangesStageCurrentHunk* 140 | Stages the hunk, the cursor is currently on (works only for git). This is like 141 | incrementally save only part of a diff and commit it separately so that your 142 | commit histoy is cleaner. Use: > 143 | 144 | :ChangesStageCurrentHunk 145 | < 146 | to stage the hunk the cursor is currently on. Use: > 147 | 148 | :ChangesStageCurrentHunk! 149 | 150 | to revert a staged hunk. Note: The cursor needs to be on a line, that has been 151 | staged or that is on a diff. 152 | 153 | *ChangesPlugin-Maps* *[h* *]h* 154 | ChangesPlugin also sets up some mappings to move from one changed line to 155 | another. By default, those mappings are defined: 156 | 157 | `[h` move backwards to the next changed line, 158 | `]h` moves forward to the next changed line. 159 | *v_ah* 160 | `ah` selects the current hunk (only in Visual and Operator pending 161 | mode) 162 | *h* 163 | `h` selects the current hunk and stages it. (only possible for git). 164 | `H` selects the staged hunk and reverts it. (only possible for git). 165 | 166 | ============================================================================== 167 | *ChangesPlugin-Config* 168 | 3. Configuring ChangesPlugin.vim 169 | 170 | There are several different configuration options available. 171 | 172 | *ChangesPlugin-aucmd* 173 | g:changes_autocmd (default: 1) 174 | ------------------------------ 175 | 176 | By default ChangesPlugin.vim will automatically update the view. You can 177 | however configure it to do not so. To disable this, set this variable in your 178 | |.vimrc| > 179 | 180 | let g:changes_autocmd=0 181 | 182 | The autocommands check, whether there have been changes to the file, or else 183 | they won't update the view. 184 | *ChangesPlugin-VCS* 185 | g:changes_vcs_check (default: 0) 186 | g:changes_vcs_system (default: '') 187 | ---------------------------------- 188 | 189 | Those variables allow to check for differences agains a VCS. 190 | Warning: This feature is rather experimental. So use it with care. 191 | 192 | You can configure ChangesPlugin to check the differrences of the current 193 | buffer not only against the file stored on disk, but rather query a Version 194 | Control System (VCS) for its latest version and indicate changes based on 195 | this version. 196 | 197 | Currently, ChangesPlugin supports these VCS Systems: 198 | - git 199 | - hg 200 | - svn 201 | - cvs 202 | - bzr 203 | - darcs 204 | - fossil 205 | - RCS 206 | 207 | To enable this feature, you need to set the variable g:changes_vcs_check to 1. 208 | ChangesPlugin will then try to auto-detect, which of the above supported 209 | VCS-Systems is in use. This may fail obviously, so you can always force 210 | ChangesPlugin to use any of the above by setting the g:changes_vcs_system 211 | Variable. 212 | 213 | To enable this feature, you need to set the g:changes_vcs_check variable to 1. 214 | The following example enables this feature and ensures, EnablePlugin is using 215 | git as VCS, so these lines have been entered in the |.vimrc| > 216 | 217 | :let g:changes_vcs_check=1 218 | :let g:changes_vcs_system='git' 219 | 220 | Note, that depending on the VCS System you use, this might slow down 221 | ChangesPlugin significantly. Especially CVS seems to be very slow. 222 | 223 | To only specify a specific VCS for a certain buffer, set the buffer-local 224 | variable `b:changes_vcs_system` in that particular buffer. That overrides the 225 | global variable `g:changes_vcs_system`. 226 | 227 | Note also, that setting g:changes_vcs_system is setting a global variable (see 228 | |g:-var|) and therefore would set the VCS for every buffer opened in vim (thus 229 | you could use changesPlugin only with one single VCS). However, guessing the 230 | VCS System should work fairely well and in case it doesn't, please report a 231 | bug to the maintainer of the plugin. Setting g:changes_vcs_check will however 232 | disable the check against the on-disk version of a buffer. 233 | 234 | *ChangesPlugin-preview* 235 | g:changes_diff_preview (default: 0) 236 | ---------------------------------- 237 | 238 | If you'd like to see the diff of the changes, you can set the variable 239 | g:changes_diff_preview. e.g. in your |.vimrc| set > 240 | 241 | :let g:changes_diff_preview = 1 242 | < 243 | This will display and update the preview window, whenever the indicator signs 244 | are displayed. 245 | 246 | 247 | g:changes_respect_SignColumn (default 0) 248 | ---------------------------------------- 249 | 250 | If set, will not reset the highlighting color of the Sign Column to that of 251 | the Normal Highlighting group. 252 | 253 | g:changes_sign_text_utf8 (default 1) 254 | ------------------------------------ 255 | 256 | If set, will use nice little UTF8 symbols for displaying the indicator 257 | signs. 258 | 259 | g:changes_max_filesize (default 0) 260 | ------------------------------------ 261 | 262 | Maximum size in kbytes of a buffer, for which to perform the check. Larger 263 | buffers will likely slow down Vim considerably, since it needs to perform 264 | diffs and parse the result often. (default: off) 265 | 266 | *ChangesPlugin-colors* 267 | Specify different colors 268 | ------------------------ 269 | 270 | By default, changes Plugin uses 3 different colors for highlighting changes 271 | to the buffer. Added Lines will be highlighted in green, Modified lines in 272 | blue and deleted lines will be highlighted in yellow, where the first line 273 | of a number of consecutive lines will be additionally marked by a letter 274 | (or a nice gui icon, if your Vim version supports it [usually only the gui 275 | version]). 276 | 277 | If you want to changes those colors, you can define your own custom 278 | highlighting. For example, you can place the following lines in your 279 | |.vimrc| > 280 | 281 | hi ChangesSignTextAdd ctermbg=yellow ctermfg=black guibg=green 282 | hi ChangesSignTextDel ctermbg=white ctermfg=black guibg=red 283 | hi ChangesSignTextCh ctermbg=black ctermfg=white guibg=blue 284 | hi ChangesSignTextDummyCh ctermfg=NONE ctermbg=white guifg=NONE guibg=white 285 | hi ChangesSignTextDummyAdd ctermfg=NONE ctermbg=green guifg=NONE guibg=green 286 | 287 | g:changes_linehi_diff (default: 0) 288 | ---------------------------------- 289 | 290 | If set, will use Cursorline highlighting to overlay within a line the 291 | changes. Use |:DC| and |:EC| to reset all highlighting. Note, will only 292 | highlight changes, that have happened within the current editing session 293 | (does not attempt to parse the VCS diff to try to figure the differences 294 | out). Note: Currently very experimental! 295 | 296 | *ChangesPlugin-signs* 297 | You can customize what text will be displayed in the signcolumn by setting the 298 | following variables (this shows the defaults): > 299 | 300 | " Customize Ascii Text for Signs 301 | let g:changes_add_sign='+' 302 | let g:changes_delete_sign='-' 303 | let g:changes_modified_sign='*' 304 | 305 | " Customize UTF-8 Text for Signs 306 | " overrides the g_changes__sign 307 | let g:changes_utf8_add_sign='➕' 308 | let g:changes_utf8_delete_sign='➖' 309 | let g:changes_utf8_modifed_sign='★' 310 | *ChangesPlugin-Signcol* 311 | g:changes_fixed_sign_column (default: 0) 312 | ---------------------------------------- 313 | 314 | When set, ChangesPlugin always tries to have the sign column visible, so that 315 | modifying the buffer won't make it "shift" because of a sudden disappearing or 316 | appearing SignColumn (|hl-SignColumn|) To enable, set in your |.vimrc| > 317 | 318 | let g:changes_fixed_sign_column=1 319 | < 320 | (not used for Vim 8 which has the 'signcolumn' setting.) 321 | 322 | g:changes_fast (default: 1) 323 | -------------------------- 324 | If set (which it is by default), skip creating diffs at a couple occasions. 325 | 326 | Currently the following features are disabled, when g:changes_fast is set: 327 | - update within a row for small changes (e.g. hitting 'rx' repeatedly in 328 | the same row) 329 | - InsertEnter/InsertLeave autocommands to update signs 330 | - When inserting a new line in the buffer, update signs 331 | 332 | While those features are disabled, this also means Vim is more responsive, 333 | since it does not need to diff the buffer that often (at the cost of being 334 | slightly more imprecise). 335 | 336 | If you want to enable those settings, set in your |.vimrc| > 337 | 338 | let g:changes_fast=0 339 | < 340 | *ChangesPlugin-errors* 341 | By default the plugin will not display error messages. If you want to see 342 | them, set the 'verbose' option to 1 (or even 2). 343 | 344 | ============================================================================== 345 | 4. ChangesPlugin Feedback *ChangesPlugin-feedback* 346 | 347 | Feedback is always welcome. If you like the plugin, please rate it at the 348 | vim-page: 349 | http://www.vim.org/scripts/script.php?script_id=3052 350 | 351 | You can also follow the development of the plugin at github: 352 | http://github.com/chrisbra/changesPlugin 353 | 354 | Please don't hesitate to report any bugs to the maintainer, mentioned in the 355 | third line of this document. 356 | 357 | ============================================================================== 358 | 5. ChangesPlugin History *ChangesPlugin-history* 359 | 360 | BF: Bugfix 361 | NF: New Feature 362 | 363 | 0.16: unreleased: 364 | BF: |:DC| should also undefine the dummy sign 365 | BF: Check for flag for mappings 366 | BF: various improvements by Constantin Kulikov, 367 | https://github.com/chrisbra/changesPlugin/pull/14, thanks! 368 | BF: Document that |:ChangesFoldDiff| toggles 369 | BF: Make |:ChangesFoldDiff| show context lines 370 | BF: check, that the file exists in git 371 | NF: use signcolumn function if available 372 | NF: Make use of Sign API (neews Vim 8.1.616) 373 | BF: better detection of modified 374 | NF: better and simpler defaults for signs 375 | NF: Make use of Vim patch 8.1.1682 (to define and place list of signs) 376 | NF: Allow customization of signs 377 | 0.15: Jan 15, 2015: {{{1 378 | NF: use |TextChanged| autocommand to update signs faster 379 | NF: use new utf8 symbols 380 | NF: Hide SignColumn 381 | NF: include some nice looking icons for gvim 382 | BF: Code cleanup 383 | BF: resolve symlinks 384 | NF: airline integration 385 | NF: |:CF| 386 | NF: |:CT| 387 | BF: only draw at most one deleted sign, if a range of several 388 | consecutive lines have been deleted 389 | NF: only place newly changed lines (instead of always removing all 390 | previous signs and adding them again) 391 | NF: if g:changes_linehi_diff is set, tries to highlight changes within 392 | a line by overlaying |CursorColumn| highlighting over that parts. 393 | BF: try to skip expensive TextChanged calls if possible. 394 | BF: detect other signs correctly (didn't work with non-english locale) 395 | BF: do not try to detect changes on vim help files and read only files 396 | BF: calling |:DC| twice would cause an error 397 | BF: Make use of patch 7.4.341 398 | BF: When undefining signs and starting the gui, make sure no 399 | '[Deleted]' signs are left over 400 | BF: Clear SignColumn before linking it to Normal group 401 | BF: Make sure a warning message is shown for |:EC| 402 | BF: Catch |E255| when defining signs 403 | BF: prevent recursively calling |FocusGained| autocommand on windows 404 | NF: |ChangesPlugin-Signcol| 405 | BF: if vim-gitgutter is installed and the current buffer is handled by 406 | vim-gitgutter, let gitgutter handle the current buffer (issue 407 | https://github.com/chrisbra/changesPlugin/issues/12, reported by 408 | Gray-Wind, thanks!) 409 | NF: g:changes_fast variable (to enable more correct at the cost of 410 | being possibly slower placement of signs). 411 | BF: when updating signs, make sure that the first line of a 412 | consecutive number has the correcty type 413 | NF: In insert mode, when is pressed, update the signs immediately 414 | NF: InsertEnter/Leave autocommands to update signs 415 | NF: Stage hunks for git 416 | NF: Ignore special buffers (help window, quickfix buffer, command line 417 | window..) 418 | BF: Make |:CL| only add the first line of each hunk to the location 419 | list. 420 | 0.14: Aug 14, 2013: {{{1 421 | BF: Exception not caught (reported by Marco, thanks!) 422 | BF: Prevent flickering by resetting SignColumn on 423 | updates (reported by Marco, thanks!) 424 | BF: parse diff output instead of relying on vimdiff 425 | and parsing the syntax items (prevents flickering 426 | since no window needs to be split) 427 | BF: avoid error messages, when buffer is ignored 428 | (https://github.com/chrisbra/changesPlugin/issues/6 429 | reported by mpfusion, thanks!) 430 | BF: avoid error messages, using |DC| 431 | (https://github.com/chrisbra/changesPlugin/issues/7 432 | reported by mpfusion, thanks!) 433 | BF: don't mess with the |alternate-file| 434 | (https://github.com/chrisbra/changesPlugin/issues/8 435 | reported by mpfusion, thanks!) 436 | NF: |ChangesPlugin-preview| 437 | NF: |ChangesPlugin-Maps| 438 | [h/]h move to previous/next changed line 439 | 0.13: Feb 16, 2013: {{{1 440 | BF: Plugin used :EnableDisplayChanges, but 441 | documentation talked about EnableChanges 442 | (same for DisableChanges) 443 | BF: Don't load the autoload script when sourcing 444 | the plugin (reported by Sergey Kholev, thanks!) 445 | 0.12: Jan, 31, 2012: {{{1 446 | NF: Fix issue #3 from github (check changes against 447 | any file, suggested by Alessio Bolognino, Thanks!) 448 | BF: Fix E117 error (patch by Mate Gabri, Thanks!) 449 | BF: Check, that the current file is readable, before 450 | diffing (patch by Mate Gabri, Thanks!) 451 | BF: Sometimes, the previous Window is not accessible 452 | anymore 453 | (http://github.com/chrisbra/changesPlugin/issues/5 454 | by Mate Gabri, Thanks!) 455 | 0.11: May 04, 2010: {{{1 456 | BF: Document, that |InsertLeave| autocommand is used 457 | as autocommand 458 | BF: generate the help file with 'et' set, so that the 459 | README at github looks prettier 460 | BF: When staying in diff mode, don't reset 'fdm' 461 | and apply syntax coloring to scratch buffer 462 | BF: the check for the diff executable does not work 463 | as expected (Reported by Sergey Khorev), 464 | additionally outputting the Warnings did not work 465 | in that case 466 | 0.10: Apr 28, 2010: {{{1 467 | NF: Fixed Issue 1 from github 468 | (http://github.com/chrisbra/changesPlugin/issues/1/find) 469 | 0.9: Apr 24, 2010: {{{1 470 | NF: You can now use different VCS Systems for each 471 | buffer you are using. 472 | NF: Stay in diff mode 473 | BF: Fix the display of deleted signs 474 | BF: Undefining old signs, so that changing 475 | g:changes_hl_lines works 476 | BF: Some more error handling. 477 | NF: Show an overview for changed lines in location-list 478 | (|:CL|) 479 | NF: Show what each sign means using |:CC| 480 | 0.8: Apr 22, 2010: {{{1 481 | NF: Renamed the helpfile, to make it more obvious, 482 | that it refers to a plugin 483 | NF: Outputting name of checked file, if checking 484 | against VCS 485 | BF: Don't check for empty files. 486 | BF: Reworked the Message function 487 | BF: Don't try to place signs, if there are no 488 | differences 489 | (unreleased, VCS successfully tested with 490 | git, hg, svn, cvs, bzr) 491 | 0.7: Apr 19, 2010: {{{1 492 | NF: Check against a file in a VCS 493 | (unreleased, first working version, 494 | needs to be checked for each VCS) 495 | 0.6: Apr 12, 2010: {{{1 496 | BF: fixed a missing highlight for DiffText 497 | 0.5: Apr 12, 2010: {{{1 498 | BF: error when trying to access b:diffhl in the 499 | scratch buffer. This should be fixed now (thanks 500 | Jeet Sukumaran!) 501 | BF: Use the correct highlighting groups (thanks Jeet 502 | Sukumaran!) 503 | 0.4: Apr 12, 2010: {{{1 504 | NF: |ToggleChangesView| 505 | NF: The autocommand checks, if the buffer has been 506 | modified, since the last time. 507 | BF: Do not mess with signs, that have not been placed 508 | by ChangesPlugin.vim 509 | BF: CleanUp was seriously messed up (sorry, I must 510 | have been asleep, when writing that) 511 | BF: Take care of 'foldcolumn' setting, which would be 512 | overwritten by the signs-column 513 | 0.3: Apr 11, 2010: {{{1 514 | BF: redraw, so that the diff window will not be 515 | displayed 516 | NF: enabled GLVS (see |GLVS|) 517 | 0.2: Apr 11, 2010: {{{1 518 | Added Documentation 519 | created an autoload version 520 | 0.1: Apr 10, 2010: {{{1 521 | First working version 522 | Modeline: {{{1 523 | ============================================================================== 524 | vim:tw=76:ts=8:ft=help:et:fdm=marker:fdl=0 525 | -------------------------------------------------------------------------------- /plugin/changesPlugin.vim: -------------------------------------------------------------------------------- 1 | " ChangesPlugin.vim - Using Signs for indicating changed lines 2 | " --------------------------------------------------------------- 3 | " Version: 0.16 4 | " Authors: Christian Brabandt 5 | " Last Change: Thu, 15 Jan 2015 21:16:40 +0100 6 | " Script: http://www.vim.org/scripts/script.php?script_id=3052 7 | " License: VIM License 8 | " Documentation: see :help changesPlugin.txt 9 | " GetLatestVimScripts: 3052 15 :AutoInstall: ChangesPlugin.vim 10 | " --------------------------------------------------------------------- 11 | " Load Once: {{{1 12 | if &cp || exists("g:loaded_changes") 13 | finish 14 | endif 15 | let g:loaded_changes = 1 16 | let s:keepcpo = &cpo 17 | set cpo&vim 18 | if v:version < 800 && !has('nvim') 19 | echohl WarningMsg 20 | echomsg "The ChangesPlugin needs at least a Vim version 8" 21 | echohl Normal 22 | finish 23 | endif 24 | 25 | " --------------------------------------------------------------------- 26 | " Public Functions: {{{1 27 | fu! ChangesMap(char) "{{{2 28 | if a:char is '' 29 | imap