├── .gitignore ├── pictures ├── signify-demo.gif └── signify-logo.png ├── showcolors.bash ├── CONTRIBUTING.md ├── autoload ├── sy │ ├── jump.vim │ ├── debug.vim │ ├── fold.vim │ ├── highlight.vim │ ├── util.vim │ ├── sign.vim │ └── repo.vim └── sy.vim ├── LICENSE ├── README.md ├── plugin └── signify.vim └── doc └── signify.txt /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /pictures/signify-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhinz/vim-signify/HEAD/pictures/signify-demo.gif -------------------------------------------------------------------------------- /pictures/signify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhinz/vim-signify/HEAD/pictures/signify-logo.png -------------------------------------------------------------------------------- /showcolors.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! hash tput 2>/dev/null; then 4 | echo "I cannot find the 'tput' program." \ 5 | 'You might find it in one of the ncurses packages.' >&2 6 | exit 1 7 | fi 8 | 9 | for i in {0..255}; do 10 | tput setab $i && echo -n " $i " 11 | done 12 | 13 | tput sgr0 14 | echo 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ============ 3 | 4 | If you intend to contribute to this project, please keep some simple rules in 5 | mind: 6 | 7 | - one commit per feature/fix 8 | - the short commit message shouldn't be longer than 50 characters 9 | - the short commit message should start with an uppercase character 10 | - use the imperative for the short commit message 11 | - don't finish the short commit message with a '.' 12 | - don't use github-specific syntax to close an issue (I'll do that, when 13 | merging into master) 14 | - it's always a good idea to have a look at 'git log' to get an idea how to 15 | format one's own commits 16 | - if you have questions about a certain patch or feature requests, just open 17 | a Github issue 18 | 19 | Examples 20 | -------- 21 | 22 | ``` 23 | Bad: "fixed loop to start from 0 instead of 1" 24 | Good: "Avoid off-by-one issue in skiplist loop" 25 | 26 | Bad: "fixed typo" 27 | Good: "Docs: typo" 28 | ``` 29 | -------------------------------------------------------------------------------- /autoload/sy/jump.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " #next_hunk {{{1 6 | function! sy#jump#next_hunk(count) 7 | execute sy#util#return_if_no_changes() 8 | 9 | let lnum = line('.') 10 | let hunks = filter(copy(b:sy.hunks), 'v:val.start > lnum') 11 | let hunk = get(hunks, a:count - 1, get(hunks, -1, {})) 12 | 13 | if !empty(hunk) 14 | execute 'sign jump '. hunk.ids[0] .' buffer='. b:sy.buffer 15 | endif 16 | 17 | if exists('#User#SignifyHunk') 18 | doautocmd User SignifyHunk 19 | endif 20 | endfunction 21 | 22 | " #prev_hunk {{{1 23 | function! sy#jump#prev_hunk(count) 24 | execute sy#util#return_if_no_changes() 25 | 26 | let lnum = line('.') 27 | let hunks = filter(copy(b:sy.hunks), 'v:val.start < lnum') 28 | let hunk = get(hunks, 0 - a:count, get(hunks, 0, {})) 29 | 30 | if !empty(hunk) 31 | execute 'sign jump '. hunk.ids[0] .' buffer='. b:sy.buffer 32 | endif 33 | 34 | if exists('#User#SignifyHunk') 35 | doautocmd User SignifyHunk 36 | endif 37 | endfunction 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marco Hinz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /autoload/sy/debug.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " #list_active_buffers {{{1 6 | function! sy#debug#list_active_buffers() abort 7 | for b in range(1, bufnr('$')) 8 | if !buflisted(b) || empty(getbufvar(b, 'sy')) 9 | continue 10 | endif 11 | 12 | let sy = copy(getbufvar(b, 'sy')) 13 | let path = remove(sy, 'path') 14 | 15 | echo "\n". path ."\n". repeat('=', strlen(path)) 16 | 17 | for k in ['buffer', 'vcs', 'stats', 'signid'] 18 | if k == 'stats' 19 | echo printf("%10s = %d added, %d changed, %d removed\n", 20 | \ k, 21 | \ sy.stats[0], 22 | \ sy.stats[1], 23 | \ sy.stats[2]) 24 | else 25 | echo printf("%10s = %s\n", k, string(sy[k])) 26 | endif 27 | endfor 28 | 29 | if empty(sy.hunks) 30 | echo printf("%10s = %s\n", 'hunks', '[]') 31 | else 32 | for i in range(len(sy.hunks)) 33 | if i == 0 34 | echo printf("%10s = start: %d, end: %d, IDs: %s\n", 35 | \ 'hunks', 36 | \ sy.hunks[i].start, 37 | \ sy.hunks[i].end, 38 | \ string(sy.hunks[i].ids)) 39 | else 40 | echo printf("%20s: %d, %s: %d, %s: %s\n", 41 | \ 'start', sy.hunks[i].start, 42 | \ 'end', sy.hunks[i].end, 43 | \ 'IDs', string(sy.hunks[i].ids)) 44 | endif 45 | endfor 46 | endif 47 | endfor 48 | endfunction 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![vim-signify](https://raw.githubusercontent.com/mhinz/vim-signify/master/pictures/signify-logo.png) 2 | 3 | --- 4 | 5 | _Signify (or just Sy) uses the sign column to indicate added, modified and 6 | removed lines in a file that is managed by a version control system (VCS)._ 7 | 8 | --- 9 | 10 | - Supports **git**, **mercurial**, **darcs**, **bazaar**, **subversion**, 11 | **cvs**, **rcs**, **fossil**, **accurev**, **perforce**, **tfs**, **yadm**. 12 | - **Asynchronous** execution of VCS tools for Vim 8.0.902+ and Neovim. 13 | - **Preserves signs** from other plugins. 14 | - Handles **nested repositories** controlled by different VCS. 15 | - Provides mappings for **navigating hunks** ("blocks of changed lines"). 16 | - Provides an **operator** that acts on hunks. 17 | - **Preview** changes in the current line in a popup window. 18 | - Show all changes in **diff mode**. 19 | - Alternative workflow: Disable the plugin by default and **toggle it per 20 | buffer** on demand. 21 | - Optional **line highlighting**. 22 | - Optional **skipping of filetypes/filenames**. 23 | - Optional **stats in the statusline**. 24 | - **Works out of the box**, but allows fine-grained configuration. 25 | - **Great documentation** and **handsome maintainers**! 26 | 27 | --- 28 | 29 | _Similar plugin for git: [vim-gitgutter](https://github.com/airblade/vim-gitgutter)_ 30 | 31 | ## Installation 32 | 33 | The `master` branch is async-only and thus requires at least Vim 8.0.902. Use 34 | the `legacy` tag for older Vim versions. 35 | 36 | Using your favorite [plugin 37 | manager](https://github.com/mhinz/vim-galore#managing-plugins), e.g. 38 | [vim-plug](https://github.com/junegunn/vim-plug): 39 | 40 | ```vim 41 | if has('nvim') || has('patch-8.0.902') 42 | Plug 'mhinz/vim-signify' 43 | else 44 | Plug 'mhinz/vim-signify', { 'tag': 'legacy' } 45 | endif 46 | ``` 47 | 48 | ## Configuration for async update 49 | ```vim 50 | " default updatetime 4000ms is not good for async update 51 | set updatetime=100 52 | ``` 53 | 54 | ## Demo 55 | 56 | ![Example:signify in action](https://raw.githubusercontent.com/mhinz/vim-signify/master/pictures/signify-demo.gif) 57 | 58 | ## Author and Feedback 59 | 60 | If you like this plugin, star it! It's a great way of getting feedback. The same 61 | goes for reporting issues or feature requests. 62 | 63 | Contact: [Twitter](https://twitter.com/_mhinz_) 64 | 65 | Co-maintainer: [@jamessan](https://github.com/jamessan) 66 | -------------------------------------------------------------------------------- /plugin/signify.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | if exists('g:loaded_signify') || !has('signs') || &compatible 6 | finish 7 | endif 8 | 9 | let g:loaded_signify = 1 10 | let g:signify_locked = 0 11 | let g:signify_detecting = 0 12 | 13 | " Commands {{{1 14 | command! -nargs=0 -bar SignifyList call sy#debug#list_active_buffers() 15 | command! -nargs=0 -bar SignifyDebug call sy#repo#debug_detection() 16 | command! -nargs=0 -bar -bang SignifyFold call sy#fold#dispatch(1) 17 | command! -nargs=0 -bar -bang SignifyDiff call sy#repo#diffmode(1) 18 | command! -nargs=0 -bar SignifyHunkDiff call sy#repo#diff_hunk() 19 | command! -nargs=0 -bar SignifyHunkUndo call sy#repo#undo_hunk() 20 | command! -nargs=0 -bar SignifyRefresh call sy#util#refresh_windows() 21 | 22 | command! -nargs=0 -bar SignifyEnable call sy#start() 23 | command! -nargs=0 -bar SignifyDisable call sy#stop() 24 | command! -nargs=0 -bar SignifyToggle call sy#toggle() 25 | command! -nargs=0 -bar SignifyToggleHighlight call sy#highlight#line_toggle() 26 | command! -nargs=0 -bar SignifyEnableAll call sy#start_all() 27 | command! -nargs=0 -bar SignifyDisableAll call sy#stop_all() 28 | 29 | " Mappings {{{1 30 | let s:cpoptions = &cpoptions 31 | set cpoptions+=B 32 | 33 | " hunk jumping 34 | nnoremap (signify-next-hunk) &diff 35 | \ ? ']c' 36 | \ : ":\call sy#jump#next_hunk(v:count1)\" 37 | nnoremap (signify-prev-hunk) &diff 38 | \ ? '[c' 39 | \ : ":\call sy#jump#prev_hunk(v:count1)\" 40 | 41 | if empty(maparg(']c', 'n')) && !hasmapto('(signify-next-hunk)', 'n') 42 | nmap ]c (signify-next-hunk) 43 | if empty(maparg(']C', 'n')) && !hasmapto('9999]c', 'n') 44 | nmap ]C 9999]c 45 | endif 46 | endif 47 | if empty(maparg('[c', 'n')) && !hasmapto('(signify-prev-hunk)', 'n') 48 | nmap [c (signify-prev-hunk) 49 | if empty(maparg('[C', 'n')) && !hasmapto('9999[c', 'n') 50 | nmap [C 9999[c 51 | end 52 | endif 53 | 54 | " hunk text object 55 | onoremap (signify-motion-inner-pending) :call sy#util#hunk_text_object(0) 56 | xnoremap (signify-motion-inner-visual) :call sy#util#hunk_text_object(0) 57 | onoremap (signify-motion-outer-pending) :call sy#util#hunk_text_object(1) 58 | xnoremap (signify-motion-outer-visual) :call sy#util#hunk_text_object(1) 59 | 60 | let &cpoptions = s:cpoptions 61 | unlet s:cpoptions 62 | 63 | " Autocmds {{{1 64 | if has('gui_running') && has('win32') && argc() 65 | " Fix 'no signs at start' race. 66 | autocmd GUIEnter * redraw 67 | endif 68 | 69 | autocmd QuickFixCmdPre *vimgrep* let g:signify_locked = 1 70 | autocmd QuickFixCmdPost *vimgrep* let g:signify_locked = 0 71 | 72 | autocmd BufNewFile,BufRead * nested 73 | \ if !get(g:, 'signify_disable_by_default') | 74 | \ call sy#start({'bufnr': bufnr('')}) | 75 | \ endif 76 | " 1}}} 77 | 78 | if exists('#User#SignifySetup') 79 | doautocmd User SignifySetup 80 | endif 81 | -------------------------------------------------------------------------------- /autoload/sy/fold.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | " SignifyFoldExpr {{{1 4 | function! SignifyFoldExpr(lnum) 5 | return s:levels[a:lnum] 6 | endfunction 7 | 8 | " SignifyFoldText {{{1 9 | function! SignifyFoldText() 10 | let linelen = &textwidth ? &textwidth : 80 11 | let marker = &foldmarker[:stridx(&foldmarker, ',')-1] 12 | let range = foldclosedend(v:foldstart) - foldclosed(v:foldstart) + 1 13 | 14 | let left = substitute(getline(v:foldstart), marker, '', '') 15 | let leftlen = len(left) 16 | 17 | let right = printf('%d [%d]', range, v:foldlevel) 18 | let rightlen = len(right) 19 | 20 | let tmp = strpart(left, 0, linelen - rightlen) 21 | let tmplen = len(tmp) 22 | 23 | if leftlen > tmplen 24 | let left = strpart(tmp, 0, tmplen - 4) . '... ' 25 | let leftlen = tmplen 26 | endif 27 | 28 | let fill = repeat(' ', linelen - (leftlen + rightlen)) 29 | 30 | " return left . fill . right . repeat(' ', 100) 31 | return left . fill . right 32 | endfunction 33 | 34 | " #dispatch {{{1 35 | function! sy#fold#dispatch(do_tab) abort 36 | if a:do_tab 37 | call sy#fold#enable(1) 38 | else 39 | call sy#fold#toggle() 40 | endif 41 | endfunction 42 | 43 | " #enable {{{1 44 | function! sy#fold#enable(do_tab) abort 45 | execute sy#util#return_if_no_changes() 46 | 47 | if a:do_tab 48 | tabedit % 49 | endif 50 | 51 | let [s:context0, s:context1] = get(g:, 'signify_fold_context', [3, 8]) 52 | let s:levels = s:get_levels(s:get_lines()) 53 | 54 | setlocal foldexpr=SignifyFoldExpr(v:lnum) 55 | setlocal foldtext=SignifyFoldText() 56 | setlocal foldmethod=expr 57 | setlocal foldlevel=0 58 | endfunction 59 | 60 | " #disable {{{1 61 | function! sy#fold#disable() abort 62 | let &l:foldmethod = b:sy_folded.method 63 | let &l:foldtext = b:sy_folded.text 64 | normal! zv 65 | endfunction 66 | 67 | " #toggle {{{1 68 | function! sy#fold#toggle() abort 69 | if exists('b:sy_folded') 70 | call sy#fold#disable() 71 | if b:sy_folded.method == 'manual' 72 | loadview 73 | endif 74 | unlet b:sy_folded 75 | else 76 | let b:sy_folded = { 'method': &foldmethod, 'text': &foldtext } 77 | if &foldmethod == 'manual' 78 | let old_vop = &viewoptions 79 | mkview 80 | let &viewoptions = old_vop 81 | endif 82 | call sy#fold#enable(0) 83 | endif 84 | 85 | redraw! 86 | call sy#start() 87 | endfunction 88 | 89 | " s:get_lines {{{1 90 | function! s:get_lines() abort 91 | return map(sy#util#get_signs(b:sy.buffer), {_, val -> val.lnum}) 92 | endfunction 93 | 94 | " s:get_levels {{{1 95 | function! s:get_levels(lines) abort 96 | let levels = {} 97 | 98 | for line in range(1, line('$')) 99 | let levels[line] = 2 100 | endfor 101 | 102 | for line in a:lines 103 | for l in range(line - s:context1, line + s:context1) 104 | if (l < 1) || (l > line('$')) 105 | continue 106 | endif 107 | if levels[l] == 2 108 | let levels[l] = 1 109 | endif 110 | for ll in range(line - s:context0, line + s:context0) 111 | let levels[ll] = 0 112 | endfor 113 | endfor 114 | endfor 115 | 116 | return levels 117 | endfunction 118 | -------------------------------------------------------------------------------- /autoload/sy/highlight.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " Variables {{{1 6 | let s:sign_add = get(g:, 'signify_sign_add', '+') 7 | let s:sign_delete_first_line = get(g:, 'signify_sign_delete_first_line', '‾') 8 | let s:sign_change = get(g:, 'signify_sign_change', '!') 9 | let s:sign_change_delete = get(g:, 'signify_sign_change_delete', s:sign_change . s:sign_delete_first_line) 10 | if strdisplaywidth(s:sign_change_delete) > 2 11 | call sy#verbose(printf('Changing g:signify_sign_change_delete from %s to !- to avoid E239', s:sign_change_delete)) 12 | let s:sign_change_delete = '!-' 13 | endif 14 | let s:sign_show_count = get(g:, 'signify_sign_show_count', 1) 15 | " 1}}} 16 | 17 | " #setup {{{1 18 | function! sy#highlight#setup() abort 19 | highlight default link SignifyLineAdd DiffAdd 20 | highlight default link SignifyLineDelete DiffDelete 21 | highlight default link SignifyLineDeleteFirstLine SignifyLineDelete 22 | highlight default link SignifyLineChange DiffChange 23 | highlight default link SignifyLineChangeDelete SignifyLineChange 24 | 25 | highlight default link SignifySignAdd DiffAdd 26 | highlight default link SignifySignDelete DiffDelete 27 | highlight default link SignifySignDeleteFirstLine SignifySignDelete 28 | highlight default link SignifySignChange DiffChange 29 | highlight default link SignifySignChangeDelete SignifySignChange 30 | endfunction 31 | 32 | " #line_enable {{{1 33 | function! sy#highlight#line_enable() abort 34 | execute 'sign define SignifyAdd text='. s:sign_add ' texthl=SignifySignAdd linehl=SignifyLineAdd '. sy#util#numhl('SignifySignAdd') 35 | execute 'sign define SignifyChange text='. s:sign_change ' texthl=SignifySignChange linehl=SignifyLineChange '. sy#util#numhl('SignifySignChange') 36 | execute 'sign define SignifyChangeDelete text='. s:sign_change_delete ' texthl=SignifySignChangeDelete linehl=SignifyLineChangeDelete '. sy#util#numhl('SignifySignChangeDelete') 37 | execute 'sign define SignifyRemoveFirstLine text='. s:sign_delete_first_line ' texthl=SignifySignDeleteFirstLine linehl=SignifyLineDeleteFirstLine '. sy#util#numhl('SignifySignDeleteFirstLine') 38 | let g:signify_line_highlight = 1 39 | endfunction 40 | 41 | " #line_disable {{{1 42 | function! sy#highlight#line_disable() abort 43 | execute 'sign define SignifyAdd text='. s:sign_add ' texthl=SignifySignAdd linehl= '. sy#util#numhl('SignifySignAdd') 44 | execute 'sign define SignifyChange text='. s:sign_change ' texthl=SignifySignChange linehl= '. sy#util#numhl('SignifySignChange') 45 | execute 'sign define SignifyChangeDelete text='. s:sign_change_delete ' texthl=SignifySignChangeDelete linehl= '. sy#util#numhl('SignifySignChangeDelete') 46 | execute 'sign define SignifyRemoveFirstLine text='. s:sign_delete_first_line ' texthl=SignifySignDeleteFirstLine linehl= '. sy#util#numhl('SignifySignDeleteFirstLine') 47 | let g:signify_line_highlight = 0 48 | endfunction 49 | 50 | " #line_toggle {{{1 51 | function! sy#highlight#line_toggle() abort 52 | if get(g:, 'signify_line_highlight') 53 | call sy#highlight#line_disable() 54 | else 55 | call sy#highlight#line_enable() 56 | endif 57 | 58 | redraw! 59 | call sy#start() 60 | endfunction 61 | " }}} 62 | 63 | call sy#highlight#setup() 64 | -------------------------------------------------------------------------------- /autoload/sy.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " #start {{{1 6 | " Optional argument: {'bufnr': X } 7 | function! sy#start(...) abort 8 | if g:signify_locked 9 | call sy#verbose('Locked.') 10 | return 11 | endif 12 | 13 | if g:signify_detecting > 50 14 | call sy#verbose('Too many detection jobs running, deferring detection') 15 | return 16 | endif 17 | 18 | let bufnr = a:0 && has_key(a:1, 'bufnr') ? a:1.bufnr : bufnr('') 19 | let sy = getbufvar(bufnr, 'sy') 20 | 21 | if empty(sy) 22 | let path = s:get_path(bufnr) 23 | if s:skip(bufnr, path) 24 | call sy#verbose('Skip file: '. path) 25 | return 26 | endif 27 | call sy#verbose('Register new file: '. path) 28 | let new_sy = { 29 | \ 'path': path, 30 | \ 'buffer': bufnr, 31 | \ 'detecting': 0, 32 | \ 'vcs': [], 33 | \ 'hunks': [], 34 | \ 'signid': 0x100, 35 | \ 'updated_by': '', 36 | \ 'stats': [-1, -1, -1], 37 | \ 'info': { 38 | \ 'dir': fnamemodify(path, ':p:h'), 39 | \ 'path': sy#util#escape(path), 40 | \ 'file': sy#util#escape(fnamemodify(path, ':t')) 41 | \ }} 42 | call setbufvar(bufnr, 'sy', new_sy) 43 | call sy#set_buflocal_autocmds(bufnr) 44 | call sy#repo#detect(bufnr) 45 | elseif has('vim_starting') 46 | call sy#verbose("Don't run Sy more than once during startup.") 47 | return 48 | else 49 | let path = s:get_path(bufnr) 50 | if !filereadable(path) 51 | call sy#stop() 52 | return 53 | elseif empty(sy.vcs) 54 | if get(sy, 'retry') 55 | let sy.retry = 0 56 | call sy#verbose('Redetecting VCS.') 57 | call sy#repo#detect(sy.buffer) 58 | else 59 | if get(sy, 'detecting') 60 | call sy#verbose('Detection is already in progress.') 61 | else 62 | call sy#verbose('No VCS found. Disabling.') 63 | call sy#stop(sy.buffer) 64 | endif 65 | endif 66 | else 67 | for vcs in sy.vcs 68 | let job_id = getbufvar(sy.buffer, 'sy_job_id_'. vcs, 0) 69 | if type(job_id) != type(0) || job_id > 0 70 | call sy#verbose('Update is already in progress.', vcs) 71 | else 72 | call sy#verbose('Updating signs.', vcs) 73 | call sy#repo#get_diff(sy.buffer, vcs, function('sy#sign#set_signs')) 74 | endif 75 | endfor 76 | endif 77 | endif 78 | endfunction 79 | 80 | " #stop {{{1 81 | function! sy#stop(...) abort 82 | let bufnr = bufnr('') 83 | if empty(getbufvar(a:0 ? a:1 : bufnr, 'sy')) | return | endif 84 | call sy#sign#remove_all_signs(bufnr) 85 | execute printf('autocmd! signify * ', bufnr) 86 | call setbufvar(bufnr, 'sy', {}) 87 | endfunction 88 | 89 | " #toggle {{{1 90 | function! sy#toggle() abort 91 | call call(empty(getbufvar(bufnr(''), 'sy')) ? 'sy#start' : 'sy#stop', []) 92 | endfunction 93 | 94 | " #start_all {{{1 95 | function! sy#start_all() abort 96 | for bufnr in range(1, bufnr("$")) 97 | call sy#start({'bufnr': bufnr}) 98 | endfor 99 | let g:signify_disable_by_default = 0 100 | endfunction 101 | 102 | " #stop_all {{{1 103 | function! sy#stop_all() abort 104 | for bufnr in range(1, bufnr('')) 105 | if !empty(getbufvar(bufnr, 'sy')) 106 | call sy#stop(bufnr) 107 | endif 108 | endfor 109 | let g:signify_disable_by_default = 1 110 | endfunction 111 | 112 | " #buffer_is_active {{{1 113 | function! sy#buffer_is_active(...) 114 | let bufnr = a:0 ? a:1 : bufnr('') 115 | return !empty(getbufvar(bufnr, 'sy')) 116 | endfunction 117 | 118 | " #verbose {{{1 119 | function! sy#verbose(msg, ...) abort 120 | if &verbose 121 | if type(a:msg) == type([]) 122 | for msg in a:msg 123 | echomsg printf('[sy%s] %s', (a:0 ? ':'.a:1 : ''), msg) 124 | endfor 125 | else 126 | echomsg printf('[sy%s] %s', (a:0 ? ':'.a:1 : ''), a:msg) 127 | endif 128 | endif 129 | endfunction 130 | 131 | " #set_buflocal_autocmds {{{1 132 | function! sy#set_buflocal_autocmds(bufnr) abort 133 | augroup signify 134 | execute printf('autocmd! * ', a:bufnr) 135 | 136 | execute printf('autocmd BufEnter call sy#start()', a:bufnr) 137 | execute printf('autocmd WinEnter call sy#start()', a:bufnr) 138 | execute printf('autocmd BufWritePost call sy#start()', a:bufnr) 139 | 140 | execute printf('autocmd CursorHold call sy#start()', a:bufnr) 141 | execute printf('autocmd CursorHoldI call sy#start()', a:bufnr) 142 | 143 | execute printf('autocmd FocusGained SignifyRefresh', a:bufnr) 144 | 145 | execute printf('autocmd CmdwinEnter let g:signify_cmdwin_active = 1', a:bufnr) 146 | execute printf('autocmd CmdwinLeave let g:signify_cmdwin_active = 0', a:bufnr) 147 | 148 | execute printf('autocmd ShellCmdPost call sy#start()', a:bufnr) 149 | 150 | if exists('##VimResume') 151 | execute printf('autocmd VimResume call sy#start()', a:bufnr) 152 | endif 153 | augroup END 154 | 155 | if exists('#User#SignifyAutocmds') 156 | doautocmd User SignifyAutocmds 157 | endif 158 | endfunction 159 | 160 | " s:get_path {{{1 161 | function! s:get_path(bufnr) 162 | let path = resolve(fnamemodify(bufname(a:bufnr), ':p')) 163 | if has('win32') 164 | let path = substitute(path, '\v^(\w):\\\\', '\1:\\', '') 165 | endif 166 | return path 167 | endfunction 168 | 169 | " s:skip {{{1 170 | function! s:skip(bufnr, path) 171 | if getbufvar(a:bufnr, '&diff') || !filereadable(a:path) 172 | return 1 173 | endif 174 | 175 | if exists('g:signify_skip_filetype') 176 | if has_key(g:signify_skip_filetype, getbufvar(a:bufnr, '&filetype')) 177 | return 1 178 | elseif has_key(g:signify_skip_filetype, 'help') 179 | \ && getbufvar(a:bufnr, '&buftype') == 'help' 180 | return 1 181 | endif 182 | endif 183 | 184 | if exists('g:signify_skip_filename') && has_key(g:signify_skip_filename, a:path) 185 | return 1 186 | endif 187 | 188 | " DEPRECATED: Use g:signify_skip.pattern instead. 189 | if exists('g:signify_skip_filename_pattern') 190 | for pattern in g:signify_skip_filename_pattern 191 | if a:path =~ pattern 192 | return 1 193 | endif 194 | endfor 195 | endif 196 | 197 | if exists('g:signify_skip') 198 | if has_key(g:signify_skip, 'pattern') 199 | for pattern in g:signify_skip.pattern 200 | if a:path =~ pattern 201 | return 1 202 | endif 203 | endfor 204 | endif 205 | endif 206 | 207 | return 0 208 | endfunction 209 | -------------------------------------------------------------------------------- /autoload/sy/util.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " #escape {{{1 6 | function! sy#util#escape(path) abort 7 | if exists('+shellslash') 8 | let old_ssl = &shellslash 9 | if fnamemodify(&shell, ':t') == 'cmd.exe' 10 | set noshellslash 11 | else 12 | set shellslash 13 | endif 14 | endif 15 | 16 | let path = shellescape(a:path) 17 | 18 | if exists('old_ssl') 19 | let &shellslash = old_ssl 20 | endif 21 | 22 | return path 23 | endfunction 24 | 25 | " #refresh_windows {{{1 26 | function! sy#util#refresh_windows() abort 27 | if exists('*win_getid') 28 | let winid = win_getid() 29 | else 30 | let winnr = winnr() 31 | endif 32 | 33 | if !get(g:, 'signify_cmdwin_active') 34 | for bufnr in tabpagebuflist() 35 | if sy#buffer_is_active(bufnr) 36 | call sy#start({'bufnr': bufnr}) 37 | endif 38 | endfor 39 | endif 40 | 41 | if exists('winid') 42 | call win_gotoid(winid) 43 | else 44 | execute winnr .'wincmd w' 45 | endif 46 | endfunction 47 | 48 | " #hunk_text_object {{{1 49 | function! sy#util#hunk_text_object(emptylines) abort 50 | execute sy#util#return_if_no_changes() 51 | 52 | let lnum = line('.') 53 | let hunks = filter(copy(b:sy.hunks), 'v:val.start <= lnum && v:val.end >= lnum') 54 | 55 | if empty(hunks) 56 | echomsg 'signify: Here is no hunk.' 57 | return 58 | endif 59 | 60 | execute hunks[0].start 61 | normal! V 62 | 63 | if a:emptylines 64 | let lnum = hunks[0].end 65 | while getline(lnum+1) =~ '^$' 66 | let lnum += 1 67 | endwhile 68 | execute lnum 69 | else 70 | execute hunks[0].end 71 | endif 72 | endfunction 73 | 74 | " #shell_redirect {{{1 75 | function! sy#util#shell_redirect(path) abort 76 | " if shellredir contains a %s it is replaced with the path 77 | " otherwise, just append it (from :help shellredir: 78 | " The name of the temporary file can be represented by '%s' if necessary 79 | " (the file name is appended automatically if no %s appears in the value 80 | " of this option) 81 | if &shellredir =~# '%s' 82 | return substitute(&shellredir, '\C%s', a:path, 'g') 83 | else 84 | return &shellredir .' '. a:path 85 | endif 86 | endfunction 87 | 88 | " #chdir {{{1 89 | function! sy#util#chdir() abort 90 | let chdir = haslocaldir() 91 | \ ? 'lcd' 92 | \ : (exists(':tcd') && haslocaldir(-1, 0)) ? 'tcd' : 'cd' 93 | return [getcwd(), chdir] 94 | endfunction 95 | 96 | " #return_if_no_changes {{{1 97 | function! sy#util#return_if_no_changes() abort 98 | let sy = getbufvar(bufnr(''), 'sy') 99 | if empty(sy) || empty(sy.hunks) 100 | echomsg 'signify: There are no changes.' 101 | return 'return' 102 | endif 103 | return '' 104 | endfunction 105 | 106 | " #execute {{{1 107 | function! sy#util#execute(cmd) abort 108 | let lang = v:lang 109 | redir => output 110 | silent! execute a:cmd 111 | redir END 112 | silent! execute 'language message' lang 113 | return output 114 | endfunction 115 | 116 | let s:popup_window = 0 117 | 118 | " #get_hunk_stats {{{1 119 | function! sy#util#get_hunk_stats() abort 120 | execute sy#util#return_if_no_changes() 121 | 122 | let curline = line('.') 123 | let total_hunks = len(b:sy.hunks) 124 | 125 | for i in range(total_hunks) 126 | if b:sy.hunks[i].start <= curline && b:sy.hunks[i].end >= curline 127 | return { 'total_hunks': total_hunks, 'current_hunk': i + 1 } 128 | endif 129 | endfor 130 | 131 | return {} 132 | endfunction 133 | 134 | " #popup_close {{{1 135 | function! sy#util#popup_close() abort 136 | if s:popup_window 137 | if nvim_win_is_valid(s:popup_window) 138 | call nvim_win_close(s:popup_window, 1) 139 | endif 140 | let s:popup_window = 0 141 | endif 142 | endfunction 143 | 144 | " #popup_create {{{1 145 | function! sy#util#popup_create(hunkdiff) abort 146 | let offset = s:offset() 147 | let winline = winline() 148 | let min_height = 6 149 | let max_height = winheight('%') - winline 150 | let diff_height = len(a:hunkdiff) 151 | let height = min([diff_height, max_height]) 152 | 153 | if diff_height > max_height && max_height < min_height 154 | let max_scroll = min_height - max_height 155 | let scroll = min([max_scroll, diff_height - max_height]) 156 | " Old versions don't have feedkeys(..., 'x') 157 | execute 'normal!' scroll.'' 158 | let winline -= scroll 159 | let height += scroll 160 | endif 161 | 162 | let padding = repeat(' ', offset - 1) 163 | 164 | if exists('*nvim_open_win') 165 | call sy#util#popup_close() 166 | let buf = nvim_create_buf(0, 1) 167 | call nvim_buf_set_lines(buf, 0, -1, 0, map(a:hunkdiff, 'v:val[0].padding.v:val[1:]')) 168 | call nvim_buf_set_option(buf, 'syntax', 'diff') 169 | let s:popup_window = nvim_open_win(buf, v:false, { 170 | \ 'relative': 'win', 171 | \ 'row': winline, 172 | \ 'col': 0, 173 | \ 'width': winwidth('%'), 174 | \ 'height': height, 175 | \ }) 176 | call nvim_win_set_option(s:popup_window, 'cursorline', v:false) 177 | call nvim_win_set_option(s:popup_window, 'foldcolumn', has('nvim-0.5') ? '0' : 0) 178 | call nvim_win_set_option(s:popup_window, 'foldenable', v:false) 179 | call nvim_win_set_option(s:popup_window, 'number', v:false) 180 | call nvim_win_set_option(s:popup_window, 'relativenumber', v:false) 181 | call nvim_win_set_option(s:popup_window, 'wrap', v:true) 182 | autocmd CursorMoved ++once call sy#util#popup_close() 183 | elseif exists('*popup_create') 184 | let s:popup_window = popup_create(map(a:hunkdiff, 'v:val[0].padding.v:val[1:]'), { 185 | \ 'line': 'cursor+1', 186 | \ 'col': 0, 187 | \ 'minwidth': winwidth('%'), 188 | \ 'maxheight': height, 189 | \ 'moved': 'any', 190 | \ 'zindex': 1000, 191 | \ }) 192 | call setbufvar(winbufnr(s:popup_window), '&syntax', 'diff') 193 | else 194 | return 0 195 | endif 196 | 197 | return 1 198 | endfunction 199 | 200 | " #numhl {{{1 201 | try 202 | sign define SyTest numhl=Number 203 | let s:use_numhl = 1 204 | sign undefine SyTest 205 | catch 206 | let s:use_numhl = 0 207 | endtry 208 | 209 | function! sy#util#numhl(hlgroup) abort 210 | if !s:use_numhl 211 | return '' 212 | endif 213 | 214 | if get(g:, 'signify_number_highlight') 215 | return printf('numhl=%s', a:hlgroup) 216 | else 217 | return 'numhl=' 218 | endif 219 | endfunction 220 | 221 | " s:offset {{{1 222 | function! s:offset() abort 223 | let offset = &foldcolumn 224 | let offset += 2 " FIXME: Find better way to calculate the sign column width. 225 | if &number 226 | let l = len(line('$')) + 1 227 | let offset += (&numberwidth > l) ? &numberwidth : l 228 | elseif &relativenumber 229 | let l = len(winheight('%')) + 1 230 | let offset += (&numberwidth > l) ? &numberwidth : l 231 | endif 232 | return offset 233 | endfunction 234 | 235 | " #get_signs {{{1 236 | if exists('*sign_getplaced') 237 | function! sy#util#get_signs(bufnr) 238 | return sign_getplaced(a:bufnr)[0].signs 239 | endfunction 240 | else 241 | function! sy#util#get_signs(bufnr) 242 | let buf = bufnr(a:bufnr) 243 | let signs = [] 244 | 245 | let signlist = execute('sign place buffer='. buf) 246 | for signline in split(signlist, '\n')[2:] 247 | let tokens = matchlist(signline, '\v^\s+\S+\=(\d+)\s+\S+\=(\d+)\s+\S+\=(.{-})%(\s+\S+\=(\d+))=$') 248 | let line = str2nr(tokens[1]) 249 | let id = str2nr(tokens[2]) 250 | let name = tokens[3] 251 | let priority = tokens[4] 252 | if empty(priority) 253 | " Older Vim versions didn't report priority, so set the default value 254 | " manually 255 | let priority = '10' 256 | endif 257 | let priority = str2nr(priority) 258 | call add(signs, { 259 | \ 'lnum': line, 260 | \ 'id': id, 261 | \ 'name': name, 262 | \ 'priority': priority, 263 | \ 'group': '' 264 | \ }) 265 | endfor 266 | 267 | return signs 268 | endfunction 269 | endif 270 | -------------------------------------------------------------------------------- /autoload/sy/sign.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " Variables {{{1 6 | let s:sign_delete = get(g:, 'signify_sign_delete', '_') 7 | 8 | " Support for sign priority was added together with sign_place(). 9 | if exists('*sign_place') 10 | let s:sign_priority = printf('priority=%d', get(g:, 'signify_priority', 10)) 11 | else 12 | let s:sign_priority = '' 13 | endif 14 | 15 | let s:sign_show_count = get(g:, 'signify_sign_show_count', 1) 16 | let s:delete_highlight = ['', 'SignifyLineDelete'] 17 | " 1}}} 18 | 19 | " #id_next {{{1 20 | function! sy#sign#id_next(sy) abort 21 | let id = a:sy.signid 22 | let a:sy.signid += 1 23 | return id 24 | endfunction 25 | 26 | " #get_current_signs {{{1 27 | function! sy#sign#get_current_signs(sy) abort 28 | let a:sy.internal = {} 29 | let a:sy.external = {} 30 | 31 | let signlist = sy#util#get_signs(a:sy.buffer) 32 | 33 | for sign in signlist 34 | if sign.name =~# '^Signify' 35 | " Handle ambiguous signs. Assume you have signs on line 3 and 4. 36 | " Removing line 3 would lead to the second sign to be shifted up 37 | " to line 3. Now there are still 2 signs, both one line 3. 38 | if has_key(a:sy.internal, sign.lnum) 39 | execute 'sign unplace' a:sy.internal[sign.lnum].id 'buffer='.a:sy.buffer 40 | endif 41 | let a:sy.internal[sign.lnum] = { 'type': sign.name, 'id': sign.id } 42 | else 43 | let a:sy.external[sign.lnum] = sign.id 44 | endif 45 | endfor 46 | endfunction 47 | 48 | 49 | " #process_diff {{{1 50 | function! sy#sign#process_diff(sy, vcs, diff) abort 51 | let a:sy.signtable = {} 52 | let a:sy.hunks = [] 53 | let [added, modified, deleted] = [0, 0, 0] 54 | 55 | call sy#sign#get_current_signs(a:sy) 56 | 57 | " Determine where we have to put our signs. 58 | for line in filter(a:diff, 'v:val =~ "^@@ "') 59 | let a:sy.lines = [] 60 | let ids = [] 61 | 62 | let [old_line, old_count, new_line, new_count] = sy#sign#parse_hunk(line) 63 | 64 | " Workaround for non-conventional diff output in older Fossil versions: 65 | " https://fossil-scm.org/forum/forumpost/834ce0f1e1 66 | " Fixed as of: https://fossil-scm.org/index.html/info/7fd2a3652ea7368a 67 | if a:vcs == 'fossil' && new_line == 0 68 | let new_line = old_line - 1 - deleted 69 | endif 70 | 71 | " Pure add: 72 | 73 | " @@ -5,0 +6,2 @@ this is line 5 74 | " +this is line 5 75 | " +this is line 5 76 | if old_count == 0 && new_count > 0 77 | let added += new_count 78 | let offset = 0 79 | while offset < new_count 80 | let line = new_line + offset 81 | let offset += 1 82 | if s:external_sign_present(a:sy, line) | continue | endif 83 | call add(ids, s:add_sign(a:sy, line, 'SignifyAdd')) 84 | endwhile 85 | 86 | " Pure delete 87 | 88 | " @@ -6,2 +5,0 @@ this is line 5 89 | " -this is line 6 90 | " -this is line 7 91 | elseif old_count > 0 && new_count == 0 92 | if s:external_sign_present(a:sy, new_line) | continue | endif 93 | let deleted += old_count 94 | if new_line == 0 95 | call add(ids, s:add_sign(a:sy, 1, 'SignifyRemoveFirstLine')) 96 | elseif s:sign_show_count 97 | if old_count > 99 98 | let text = s:sign_delete . '>' 99 | elseif old_count < 2 100 | let text = s:sign_delete 101 | else 102 | let text = s:sign_delete . old_count 103 | endif 104 | while strwidth(text) > 2 105 | let text = substitute(text, '.', '', '') 106 | endwhile 107 | call add(ids, s:add_sign(a:sy, new_line, 'SignifyDelete'. old_count, text)) 108 | else 109 | call add(ids, s:add_sign(a:sy, new_line, 'SignifyDeleteMore', s:sign_delete)) 110 | endif 111 | " All lines are modified. 112 | elseif old_count > 0 && new_count > 0 && old_count == new_count 113 | let modified += new_count 114 | let offset = 0 115 | while offset < new_count 116 | let line = new_line + offset 117 | let offset += 1 118 | if s:external_sign_present(a:sy, line) | continue | endif 119 | call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) 120 | endwhile 121 | " Some lines are modified and some new lines are added. 122 | elseif old_count > 0 && new_count > 0 && old_count < new_count 123 | let modified += old_count 124 | let added += new_count - old_count 125 | let offset = 0 126 | while offset < old_count 127 | let line = new_line + offset 128 | let offset += 1 129 | if s:external_sign_present(a:sy, line) | continue | endif 130 | call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) 131 | endwhile 132 | while offset < new_count 133 | let line = new_line + offset 134 | let offset += 1 135 | if s:external_sign_present(a:sy, line) | continue | endif 136 | call add(ids, s:add_sign(a:sy, line, 'SignifyAdd')) 137 | endwhile 138 | " Some lines are modified and some lines are deleted. 139 | elseif old_count > 0 && new_count > 0 && old_count > new_count 140 | let modified += new_count 141 | let deleted_count = old_count - new_count 142 | let deleted += deleted_count 143 | 144 | let prev_line_available = new_line > 1 && !get(a:sy.signtable, new_line - 1, 0) 145 | if prev_line_available 146 | if s:sign_show_count 147 | if deleted_count > 99 148 | let text = s:sign_delete . '>' 149 | elseif deleted_count < 2 150 | let text = s:sign_delete 151 | else 152 | let text = s:sign_delete . deleted_count 153 | endif 154 | while strwidth(text) > 2 155 | let text = substitute(text, '.', '', '') 156 | endwhile 157 | call add(ids, s:add_sign(a:sy, new_line - 1, 'SignifyDelete'. deleted_count, text)) 158 | else 159 | call add(ids, s:add_sign(a:sy, new_line - 1, 'SignifyDeleteMore', s:sign_delete)) 160 | endif 161 | endif 162 | 163 | let offset = 0 164 | while offset < new_count 165 | let line = new_line + offset 166 | if s:external_sign_present(a:sy, line) | continue | endif 167 | if !prev_line_available && offset == 0 168 | call add(ids, s:add_sign(a:sy, line, 'SignifyChangeDelete')) 169 | else 170 | call add(ids, s:add_sign(a:sy, line, 'SignifyChange')) 171 | endif 172 | let offset += 1 173 | endwhile 174 | endif 175 | 176 | if !empty(ids) 177 | call add(a:sy.hunks, { 178 | \ 'ids' : ids, 179 | \ 'start': a:sy.lines[0], 180 | \ 'end' : a:sy.lines[-1] }) 181 | endif 182 | endfor 183 | 184 | " Remove obsoleted signs. 185 | for line in filter(keys(a:sy.internal), '!has_key(a:sy.signtable, v:val)') 186 | execute 'sign unplace' a:sy.internal[line].id 'buffer='.a:sy.buffer 187 | endfor 188 | 189 | if empty(a:sy.updated_by) && empty(a:sy.hunks) 190 | call sy#verbose('Successful exit value, but no diff. Keep VCS for time being.', a:vcs) 191 | return 192 | endif 193 | 194 | call sy#verbose('Signs updated.', a:vcs) 195 | let a:sy.updated_by = a:vcs 196 | if len(a:sy.vcs) > 1 197 | call sy#verbose('Disable all other VCS.', a:vcs) 198 | let a:sy.vcs = [a:vcs] 199 | endif 200 | 201 | let a:sy.stats = [added, modified, deleted] 202 | endfunction 203 | 204 | " #remove_all_signs {{{1 205 | function! sy#sign#remove_all_signs(bufnr) abort 206 | let sy = getbufvar(a:bufnr, 'sy', {}) 207 | 208 | for hunk in get(sy, 'hunks', []) 209 | for id in get(hunk, 'ids', []) 210 | execute 'sign unplace' id 'buffer='.a:bufnr 211 | endfor 212 | endfor 213 | 214 | let sy.hunks = [] 215 | endfunction 216 | 217 | " #parse_hunk {{{1 218 | " Parse a hunk as '@@ -273,3 +267,14' into [old_line, old_count, new_line, new_count] 219 | function! sy#sign#parse_hunk(diffline) abort 220 | let tokens = matchlist(a:diffline, '^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)') 221 | return [ 222 | \ str2nr(tokens[1]), 223 | \ empty(tokens[2]) ? 1 : str2nr(tokens[2]), 224 | \ str2nr(tokens[3]), 225 | \ empty(tokens[4]) ? 1 : str2nr(tokens[4]) 226 | \ ] 227 | endfunction 228 | 229 | " #set_signs {{{1 230 | function! sy#sign#set_signs(sy, vcs, diff) abort 231 | call sy#verbose('sy#sign#set_signs()', a:vcs) 232 | 233 | if a:sy.stats == [-1, -1, -1] 234 | let a:sy.stats = [0, 0, 0] 235 | endif 236 | 237 | if empty(a:diff) 238 | call sy#verbose('No changes found.', a:vcs) 239 | let a:sy.stats = [0, 0, 0] 240 | call sy#sign#remove_all_signs(a:sy.buffer) 241 | return 242 | endif 243 | 244 | if get(g:, 'signify_line_highlight') 245 | call sy#highlight#line_enable() 246 | else 247 | call sy#highlight#line_disable() 248 | endif 249 | 250 | call sy#sign#process_diff(a:sy, a:vcs, a:diff) 251 | 252 | if exists('#User#Signify') 253 | doautocmd User Signify 254 | endif 255 | endfunction 256 | 257 | " s:add_sign {{{1 258 | function! s:add_sign(sy, line, type, ...) abort 259 | call add(a:sy.lines, a:line) 260 | let a:sy.signtable[a:line] = 1 261 | 262 | if has_key(a:sy.internal, a:line) 263 | " There is a sign on this line already. 264 | if a:type == a:sy.internal[a:line].type 265 | " Keep current sign since the new one is of the same type. 266 | return a:sy.internal[a:line].id 267 | else 268 | " Update sign by overwriting the ID of the current sign. 269 | let id = a:sy.internal[a:line].id 270 | endif 271 | endif 272 | 273 | if !exists('id') 274 | let id = sy#sign#id_next(a:sy) 275 | endif 276 | 277 | if a:type =~# 'SignifyDelete' 278 | execute printf('sign define %s text=%s texthl=SignifySignDelete linehl=%s %s', 279 | \ a:type, 280 | \ a:1, 281 | \ s:delete_highlight[g:signify_line_highlight], 282 | \ sy#util#numhl('SignifySignDelete')) 283 | endif 284 | execute printf('sign place %d line=%d name=%s %s buffer=%s', 285 | \ id, 286 | \ a:line, 287 | \ a:type, 288 | \ s:sign_priority, 289 | \ a:sy.buffer) 290 | 291 | return id 292 | endfunction 293 | 294 | " s:external_sign_present {{{1 295 | function! s:external_sign_present(sy, line) abort 296 | " If sign priority is supported, so are multiple signs per line. 297 | " Therefore, we can report no external signs present and let 298 | " g:signify_priority control whether Sy's signs are shown. 299 | if !empty(s:sign_priority) 300 | return 301 | endif 302 | if has_key(a:sy.external, a:line) 303 | if has_key(a:sy.internal, a:line) 304 | " Remove Sy signs from lines with other signs. 305 | execute 'sign unplace' a:sy.internal[a:line].id 'buffer='.a:sy.buffer 306 | endif 307 | return 1 308 | endif 309 | endfunction 310 | -------------------------------------------------------------------------------- /autoload/sy/repo.vim: -------------------------------------------------------------------------------- 1 | " vim: et sw=2 sts=2 fdm=marker 2 | 3 | scriptencoding utf-8 4 | 5 | " #detect {{{1 6 | function! sy#repo#detect(bufnr) abort 7 | let sy = getbufvar(a:bufnr, 'sy') 8 | for vcs in s:vcs_list 9 | let sy.detecting += 1 10 | let g:signify_detecting += 1 11 | call sy#repo#get_diff(a:bufnr, vcs, function('sy#sign#set_signs')) 12 | endfor 13 | endfunction 14 | 15 | " s:callback_nvim_stdout{{{1 16 | function! s:callback_nvim_stdout(_job_id, data, _event) dict abort 17 | let self.stdoutbuf[-1] .= a:data[0] 18 | call extend(self.stdoutbuf, a:data[1:]) 19 | endfunction 20 | 21 | " s:callback_nvim_exit {{{1 22 | function! s:callback_nvim_exit(_job_id, exitval, _event) dict abort 23 | return s:handle_diff(self, a:exitval) 24 | endfunction 25 | 26 | " s:callback_vim_stdout {{{1 27 | function! s:callback_vim_stdout(_job_id, data) dict abort 28 | let self.stdoutbuf += [a:data] 29 | endfunction 30 | 31 | " s:callback_vim_close {{{1 32 | function! s:callback_vim_close(channel) dict abort 33 | let job = ch_getjob(a:channel) 34 | while 1 35 | if job_status(job) == 'dead' 36 | let exitval = job_info(job).exitval 37 | break 38 | endif 39 | sleep 10m 40 | endwhile 41 | return s:handle_diff(self, exitval) 42 | endfunction 43 | 44 | " s:write_buffer {{{1 45 | function! s:write_buffer(bufnr, file) 46 | let bufcontents = getbufline(a:bufnr, 1, '$') 47 | 48 | if bufcontents == [''] && line2byte(1) == -1 49 | " Special case: completely empty buffer. 50 | " A nearly empty buffer of only a newline has line2byte(1) == 1. 51 | call writefile([], a:file) 52 | return 53 | endif 54 | 55 | if getbufvar(a:bufnr, '&fileformat') ==# 'dos' 56 | call map(bufcontents, 'v:val."\r"') 57 | endif 58 | 59 | let fenc = getbufvar(a:bufnr, '&fileencoding') 60 | let enc = getbufvar(a:bufnr, '&encoding') 61 | if fenc !=# enc 62 | call map(bufcontents, 'iconv(v:val, "'.enc.'", "'.fenc.'")') 63 | endif 64 | 65 | if getbufvar(a:bufnr, '&bomb') 66 | let bufcontents[0]=''.bufcontents[0] 67 | endif 68 | 69 | call writefile(bufcontents, a:file) 70 | endfunction 71 | 72 | " sy#get_diff {{{1 73 | function! sy#repo#get_diff(bufnr, vcs, func) abort 74 | call sy#verbose('sy#repo#get_diff()', a:vcs) 75 | 76 | let job_id = getbufvar(a:bufnr, 'sy_job_id_'.a:vcs) 77 | 78 | if getbufvar(a:bufnr, '&modified') 79 | let [cmd, options] = s:initialize_buffer_job(a:bufnr, a:vcs) 80 | let options.difftool = 'diff' 81 | else 82 | let [cmd, options] = s:initialize_job(a:bufnr, a:vcs) 83 | let options.difftool = a:vcs 84 | endif 85 | 86 | let options.func = a:func 87 | 88 | if has('nvim') 89 | if job_id 90 | silent! call jobstop(job_id) 91 | endif 92 | let job_id = jobstart(cmd, extend(options, { 93 | \ 'cwd': getbufvar(a:bufnr, 'sy').info.dir, 94 | \ 'on_stdout': function('s:callback_nvim_stdout'), 95 | \ 'on_exit': function('s:callback_nvim_exit'), 96 | \ })) 97 | call setbufvar(a:bufnr, 'sy_job_id_'.a:vcs, job_id) 98 | elseif has('patch-8.0.902') 99 | if type(job_id) != type(0) 100 | silent! call job_stop(job_id) 101 | endif 102 | let opts = { 103 | \ 'cwd': getbufvar(a:bufnr, 'sy').info.dir, 104 | \ 'in_io': 'null', 105 | \ 'out_cb': function('s:callback_vim_stdout', options), 106 | \ 'close_cb': function('s:callback_vim_close', options), 107 | \ } 108 | let job_id = job_start(cmd, opts) 109 | call setbufvar(a:bufnr, 'sy_job_id_'.a:vcs, job_id) 110 | else 111 | let options.stdoutbuf = split(s:run(a:vcs), '\n') 112 | call s:handle_diff(options, v:shell_error) 113 | endif 114 | endfunction 115 | 116 | " s:handle_diff {{{1 117 | function! s:handle_diff(options, exitval) abort 118 | call sy#verbose('s:handle_diff()', a:options.vcs) 119 | 120 | if has_key(a:options, 'tempfiles') 121 | for f in a:options.tempfiles 122 | call delete(f) 123 | endfor 124 | endif 125 | 126 | let sy = getbufvar(a:options.bufnr, 'sy') 127 | if empty(sy) 128 | call sy#verbose(printf('No b:sy found for %s', bufname(a:options.bufnr)), a:options.vcs) 129 | return 130 | elseif !empty(sy.updated_by) && sy.updated_by != a:options.vcs 131 | call sy#verbose(printf('Signs already got updated by %s.', sy.updated_by), a:options.vcs) 132 | return 133 | elseif empty(sy.vcs) 134 | let g:signify_detecting -= 1 135 | let sy.detecting -= 1 136 | endif 137 | 138 | let fenc = getbufvar(a:options.bufnr, '&fenc') 139 | let enc = getbufvar(a:options.bufnr, '&enc') 140 | if (fenc != enc) && has('iconv') 141 | call map(a:options.stdoutbuf, printf('iconv(v:val, "%s", "%s")', fenc, enc)) 142 | endif 143 | 144 | let [found_diff, diff] = s:check_diff_{a:options.difftool}(a:exitval, a:options.stdoutbuf) 145 | if found_diff 146 | if index(sy.vcs, a:options.vcs) == -1 147 | let sy.vcs += [a:options.vcs] 148 | endif 149 | call a:options.func(sy, a:options.vcs, diff) 150 | else 151 | call sy#verbose('No valid diff found. Disabling this VCS.', a:options.vcs) 152 | endif 153 | 154 | call setbufvar(a:options.bufnr, 'sy_job_id_'.a:options.vcs, 0) 155 | endfunction 156 | 157 | " s:check_diff_diff {{{1 158 | function! s:check_diff_diff(exitval, diff) abort 159 | return a:exitval <= 1 ? [1, a:diff] : [0, []] 160 | endfunction 161 | 162 | " s:check_diff_git {{{1 163 | function! s:check_diff_git(exitval, diff) abort 164 | return a:exitval ? [0, []] : [1, a:diff] 165 | endfunction 166 | 167 | " s:check_diff_yadm {{{1 168 | function! s:check_diff_yadm(exitval, diff) abort 169 | return a:exitval ? [0, []] : [1, a:diff] 170 | endfunction 171 | 172 | " s:check_diff_hg {{{1 173 | function! s:check_diff_hg(exitval, diff) abort 174 | return a:exitval ? [0, []] : [1, a:diff] 175 | endfunction 176 | 177 | " s:check_diff_svn {{{1 178 | function! s:check_diff_svn(exitval, diff) abort 179 | return a:exitval ? [0, []] : [1, a:diff] 180 | endfunction 181 | 182 | " s:check_diff_bzr {{{1 183 | function! s:check_diff_bzr(exitval, diff) abort 184 | return (a:exitval =~ '[012]') ? [1, a:diff] : [0, []] 185 | endfunction 186 | 187 | " s:check_diff_darcs {{{1 188 | function! s:check_diff_darcs(exitval, diff) abort 189 | return a:exitval ? [0, []] : [1, a:diff] 190 | endfunction 191 | 192 | " s:check_diff_fossil {{{1 193 | function! s:check_diff_fossil(exitval, diff) abort 194 | return a:exitval ? [0, []] : [1, a:diff] 195 | endfunction 196 | 197 | " s:check_diff_cvs {{{1 198 | function! s:check_diff_cvs(exitval, diff) abort 199 | let [found_diff, diff] = [0, []] 200 | if a:exitval == 1 201 | for diffline in a:diff 202 | if diffline =~ '^+++' 203 | let [found_diff, diff] = [1, a:diff] 204 | break 205 | endif 206 | endfor 207 | elseif a:exitval == 0 && len(a:diff) == 0 208 | let found_diff = 1 209 | endif 210 | return [found_diff, diff] 211 | endfunction 212 | 213 | " s:check_diff_rcs {{{1 214 | function! s:check_diff_rcs(exitval, diff) abort 215 | return (a:exitval == 2) ? [0, []] : [1, a:diff] 216 | endfunction 217 | 218 | " s:check_diff_accurev {{{1 219 | function! s:check_diff_accurev(exitval, diff) abort 220 | return (a:exitval >= 2) ? [0, []] : [1, a:diff] 221 | endfunction 222 | 223 | " s:check_diff_perforce {{{1 224 | function! s:check_diff_perforce(exitval, diff) abort 225 | return a:exitval ? [0, []] : [1, a:diff] 226 | endfunction 227 | 228 | " s:check_diff_tfs {{{1 229 | function! s:check_diff_tfs(exitval, diff) abort 230 | return a:exitval ? [0, []] : [1, s:strip_context(a:diff)] 231 | endfunction 232 | 233 | " #get_stats {{{1 234 | function! sy#repo#get_stats(...) abort 235 | let sy = getbufvar(a:0 ? a:1 : bufnr(''), 'sy') 236 | return empty(sy) ? [-1, -1, -1] : sy.stats 237 | endfunction 238 | 239 | " #get_stats_decorated {{{1 240 | function! sy#repo#get_stats_decorated(...) 241 | let bufnr = a:0 ? a:1 : bufnr('') 242 | let [added, modified, removed] = sy#repo#get_stats(bufnr) 243 | let symbols = ['+', '-', '~'] 244 | let stats = [added, removed, modified] " reorder 245 | let statline = '' 246 | 247 | for i in range(3) 248 | if stats[i] > 0 249 | let statline .= printf('%s%s ', symbols[i], stats[i]) 250 | endif 251 | endfor 252 | 253 | if !empty(statline) 254 | let statline = printf('[%s]', statline[:-2]) 255 | endif 256 | 257 | return statline 258 | endfunction 259 | 260 | " #debug_detection {{{1 261 | function! sy#repo#debug_detection() 262 | if empty(getbufvar(bufnr(''), 'sy')) 263 | echomsg 'signify: I cannot detect any changes!' 264 | return 265 | endif 266 | 267 | for vcs in s:vcs_list 268 | let cmd = s:get_base_cmd(bufnr(''), vcs, g:signify_vcs_cmds) 269 | echohl Statement 270 | echo cmd 271 | echo repeat('=', len(cmd)) 272 | echohl NONE 273 | 274 | let diff = s:run(vcs) 275 | if v:shell_error 276 | echohl ErrorMsg 277 | echo diff 278 | echohl NONE 279 | else 280 | echo empty(diff) ? "" : diff 281 | endif 282 | echo "\n" 283 | endfor 284 | endfunction 285 | 286 | function! s:system_in_dir(cmd) abort 287 | let [cwd, chdir] = sy#util#chdir() 288 | try 289 | execute chdir fnameescape(b:sy.info.dir) 290 | return system(a:cmd) 291 | finally 292 | execute chdir fnameescape(cwd) 293 | endtry 294 | endfunction 295 | 296 | " #diffmode {{{1 297 | function! sy#repo#diffmode(do_tab) abort 298 | execute sy#util#return_if_no_changes() 299 | 300 | let vcs = b:sy.updated_by 301 | 302 | call sy#verbose('SignifyDiff', vcs) 303 | let ft = &filetype 304 | let fenc = &fenc 305 | if a:do_tab 306 | tabedit % 307 | endif 308 | diffthis 309 | 310 | let base = s:get_base(bufnr(''), vcs) 311 | 312 | leftabove vnew 313 | 314 | let undolevels = &l:undolevels 315 | setlocal undolevels=-1 316 | 317 | if (fenc != &enc) && has('iconv') 318 | silent put =iconv(base, fenc, &enc) 319 | else 320 | silent put =base 321 | endif 322 | 323 | silent 1delete 324 | set buftype=nofile bufhidden=wipe nomodified 325 | let &filetype = ft 326 | let &l:undolevels = undolevels 327 | diffthis 328 | wincmd p 329 | normal! ]czt 330 | endfunction 331 | 332 | " s:extract_current_hunk {{{1 333 | function! s:extract_current_hunk(diff) abort 334 | let header = '' 335 | let hunk = [] 336 | 337 | for line in a:diff 338 | if header != '' 339 | if line[:2] == '@@ ' || empty(line) 340 | break 341 | endif 342 | call add(hunk, line) 343 | elseif line[:2] == '@@ ' && s:is_cur_line_in_hunk(line) 344 | let header = line 345 | endif 346 | endfor 347 | 348 | return [header, hunk] 349 | endfunction 350 | 351 | function! s:is_cur_line_in_hunk(hunkline) abort 352 | let cur_line = line('.') 353 | let [_old_line, old_count, new_line, new_count] = sy#sign#parse_hunk(a:hunkline) 354 | 355 | if cur_line == 1 && new_line == 0 356 | " deleted first line 357 | return 1 358 | endif 359 | 360 | if cur_line == new_line && new_count < old_count 361 | " deleted lines 362 | return 1 363 | endif 364 | 365 | if cur_line >= new_line && cur_line < (new_line + new_count) 366 | " added/changed lines 367 | return 1 368 | endif 369 | 370 | return 0 371 | endfunction 372 | 373 | " #diff_hunk {{{1 374 | function! sy#repo#diff_hunk() abort 375 | let bufnr = bufnr('') 376 | let sy = getbufvar(bufnr, 'sy') 377 | if !empty(sy) && !empty(sy.updated_by) 378 | call sy#repo#get_diff(bufnr, sy.updated_by, function('s:diff_hunk')) 379 | endif 380 | endfunction 381 | 382 | function! s:diff_hunk(_sy, vcs, diff) abort 383 | call sy#verbose('s:preview_hunk()', a:vcs) 384 | 385 | let [_, hunk] = s:extract_current_hunk(a:diff) 386 | if empty(hunk) 387 | return 388 | endif 389 | 390 | if sy#util#popup_create(hunk) 391 | return 392 | endif 393 | 394 | silent! wincmd P 395 | if !&previewwindow 396 | noautocmd botright new 397 | endif 398 | call setline(1, hunk) 399 | silent! %foldopen! 400 | setlocal previewwindow filetype=diff buftype=nofile bufhidden=delete 401 | " With :noautocmd wincmd p, the first line of the preview window would show 402 | " the 'cursorline', although it's not focused. Use feedkeys() instead. 403 | noautocmd call feedkeys("\p", 'nt') 404 | endfunction 405 | 406 | " #undo_hunk {{{1 407 | function! sy#repo#undo_hunk() abort 408 | let bufnr = bufnr('') 409 | let sy = getbufvar(bufnr, 'sy') 410 | if !empty(sy) && !empty(sy.updated_by) 411 | call sy#repo#get_diff(bufnr, sy.updated_by, function('s:undo_hunk')) 412 | endif 413 | endfunction 414 | 415 | function! s:undo_hunk(sy, vcs, diff) abort 416 | call sy#verbose('s:undo_hunk()', a:vcs) 417 | 418 | let [header, hunk] = s:extract_current_hunk(a:diff) 419 | if empty(hunk) 420 | return 421 | endif 422 | 423 | let [_old_line, _old_count, new_line, new_count] = sy#sign#parse_hunk(header) 424 | 425 | for line in hunk 426 | let op = line[0] 427 | let text = line[1:] 428 | if op == ' ' 429 | if text != getline(new_line) 430 | echoerr 'Could not apply context hunk for undo. Try saving the buffer first.' 431 | return 432 | endif 433 | let new_line += 1 434 | elseif op == '-' 435 | call append(new_count == 0 ? new_line : new_line - 1, text) 436 | let new_line += 1 437 | elseif op == '+' 438 | if text != getline(new_line) 439 | echoerr 'Could not apply addition hunk for undo. Try saving the buffer first.' 440 | return 441 | endif 442 | execute 'silent' new_line 'delete _' 443 | else 444 | echoer 'Unknown diff operation ' . line 445 | return 446 | endif 447 | endfor 448 | 449 | " Undoing altered the buffer, so update signs. 450 | call setbufvar(a:sy.buffer, 'sy_job_id_'.a:vcs, 0) 451 | return sy#start() 452 | endfunction 453 | 454 | " s:initialize_job {{{1 455 | function! s:initialize_job(bufnr, vcs) abort 456 | return s:wrap_cmd(a:bufnr, a:vcs, s:get_base_cmd(a:bufnr, a:vcs, g:signify_vcs_cmds)) 457 | endfunction 458 | 459 | " s:initialize_buffer_job {{{1 460 | function! s:initialize_buffer_job(bufnr, vcs) abort 461 | let bufferfile = tempname() 462 | call s:write_buffer(a:bufnr, bufferfile) 463 | 464 | let basefile = tempname() 465 | let base_cmd = s:get_base_cmd(a:bufnr, a:vcs, g:signify_vcs_cmds_diffmode) . '>' . fnameescape(basefile) . ' && ' 466 | 467 | let diff_cmd = base_cmd . s:difftool . ' -U0 ' . fnameescape(basefile) . ' ' . fnameescape(bufferfile) 468 | let [cmd, options] = s:wrap_cmd(a:bufnr, a:vcs, diff_cmd) 469 | 470 | let options.tempfiles = [basefile, bufferfile] 471 | 472 | return [cmd, options] 473 | endfunction 474 | 475 | " s:wrap_cmd {{{1 476 | function! s:wrap_cmd(bufnr, vcs, cmd) abort 477 | if has('win32') 478 | if has('nvim') 479 | let cmd = &shell =~ '\v%(cmd|powershell|pwsh)' ? a:cmd : ['sh', '-c', a:cmd] 480 | else 481 | if &shell =~ 'cmd' 482 | let cmd = join([&shell, &shellcmdflag, '(', a:cmd, ')']) 483 | elseif empty(&shellxquote) 484 | let cmd = join([&shell, &shellcmdflag, &shellquote, a:cmd, &shellquote]) 485 | else 486 | let cmd = join([&shell, &shellcmdflag, &shellxquote, a:cmd, &shellxquote]) 487 | endif 488 | endif 489 | else 490 | let cmd = ['sh', '-c', a:cmd] 491 | endif 492 | let options = { 493 | \ 'stdoutbuf': [''], 494 | \ 'vcs': a:vcs, 495 | \ 'bufnr': a:bufnr, 496 | \ } 497 | return [cmd, options] 498 | endfunction 499 | 500 | " s:get_vcs_path {{{1 501 | function! s:get_vcs_path(bufnr, vcs) abort 502 | return (a:vcs =~# '\v(git|cvs|accurev|tfs|yadm)') 503 | \ ? getbufvar(a:bufnr, 'sy').info.file 504 | \ : getbufvar(a:bufnr, 'sy').info.path 505 | endfunction 506 | 507 | " s:get_base_cmd {{{1 508 | function! s:get_base_cmd(bufnr, vcs, vcs_cmds) abort 509 | let cmd = a:vcs_cmds[a:vcs] 510 | let cmd = s:replace(cmd, '%f', s:get_vcs_path(a:bufnr, a:vcs)) 511 | let cmd = s:replace(cmd, '%d', s:difftool) 512 | let cmd = s:replace(cmd, '%n', s:devnull) 513 | return cmd 514 | endfunction 515 | 516 | " s:get_base {{{1 517 | " Get the "base" version of the current buffer as a string. 518 | function! s:get_base(bufnr, vcs) abort 519 | return s:system_in_dir(s:get_base_cmd(a:bufnr, a:vcs, g:signify_vcs_cmds_diffmode)) 520 | endfunction 521 | 522 | " s:run {{{1 523 | function! s:run(vcs) 524 | try 525 | let ret = s:system_in_dir(s:get_base_cmd(bufnr(''), a:vcs, g:signify_vcs_cmds)) 526 | catch 527 | " This exception message can be seen via :SignifyDebugUnknown. 528 | " E.g. unquoted VCS programs in vcd_cmds can lead to E484. 529 | let ret = v:exception .' at '. v:throwpoint 530 | finally 531 | return ret 532 | endtry 533 | endfunction 534 | 535 | " s:replace {{{1 536 | function! s:replace(cmd, pat, sub) 537 | let parts = split(a:cmd, a:pat, 1) 538 | return join(parts, a:sub) 539 | endfunction 540 | 541 | " s:strip_context {{{1 542 | function! s:strip_context(context) 543 | let diff = [] 544 | let hunk = [] 545 | let state = 0 546 | let lines = a:context 547 | let linenr = 0 548 | 549 | while linenr < len(lines) 550 | let line = lines[linenr] 551 | 552 | if state == 0 553 | if line =~ "^@@ " 554 | let [old_line, old_count, new_line, new_count] = sy#sign#parse_hunk(line) 555 | let hunk = [] 556 | let state = 1 557 | else 558 | call add(diff,line) 559 | endif 560 | let linenr += 1 561 | elseif index([1,2,3],state) >= 0 && index(['\','/'],line[0]) >= 0 562 | let linenr += 1 563 | call add(hunk,line) 564 | elseif state == 1 565 | if line[0] == ' ' 566 | let old_line += 1 567 | let new_line += 1 568 | let old_count -= 1 569 | let new_count -= 1 570 | let linenr += 1 571 | else 572 | let old_count_part = 0 573 | let new_count_part = 0 574 | let state = 2 575 | endif 576 | elseif state == 2 577 | if line[0] == '-' 578 | call add(hunk,line) 579 | let old_count_part += 1 580 | let linenr += 1 581 | else 582 | let state = 3 583 | endif 584 | elseif state == 3 585 | if line[0] == '+' 586 | call add(hunk,line) 587 | let new_count_part += 1 588 | let linenr += 1 589 | else 590 | call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) 591 | let diff += hunk 592 | let hunk = [] 593 | let old_count -= old_count_part 594 | let new_count -= new_count_part 595 | let old_line += old_count_part 596 | let new_line += new_count_part 597 | let state = 1 598 | endif 599 | endif 600 | 601 | if state > 0 && new_count <= 0 && old_count <= 0 602 | if len(hunk) > 0 603 | call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) 604 | let diff = diff + hunk 605 | let hunk = [] 606 | endif 607 | let state = 0 608 | endif 609 | endwhile 610 | if len(hunk) > 0 611 | call add(diff, printf("@@ -%d%s +%d%s @@",(old_count_part == 0 && old_line > 0) ? old_line -1 : old_line, old_count_part == 1 ? "" : printf(",%d", old_count_part), (new_count_part == 0 && new_line > 0) ? new_line - 1 : new_line, new_count_part == 1 ? "" : printf(",%d", new_count_part))) 612 | let diff = diff + hunk 613 | let hunk = [] 614 | endif 615 | return diff 616 | endfunction 617 | " 1}}} 618 | 619 | " Variables {{{1 620 | let s:default_vcs_cmds = { 621 | \ 'git': 'git diff --no-color --no-ext-diff -U0 -- %f', 622 | \ 'yadm': 'yadm diff --no-color --no-ext-diff -U0 -- %f', 623 | \ 'hg': 'hg --config alias.diff=diff diff --color=never --nodates -U0 -- %f', 624 | \ 'svn': 'svn diff --diff-cmd %d -x -U0 -- %f', 625 | \ 'bzr': 'bzr diff --using %d --diff-options=-U0 -- %f', 626 | \ 'darcs': 'darcs diff --no-pause-for-gui --no-unified --diff-opts=-U0 -- %f', 627 | \ 'fossil': 'fossil diff --unified -c 0 -- %f', 628 | \ 'cvs': 'cvs diff -U0 -- %f', 629 | \ 'rcs': 'rcsdiff -U0 %f 2>%n', 630 | \ 'accurev': 'accurev diff %f -- -U0', 631 | \ 'perforce': 'p4 info '. sy#util#shell_redirect('%n') . (has('win32') ? ' && set P4DIFF=&&' : ' && env P4DIFF=') .' p4 diff -du0 %f', 632 | \ 'tfs': 'tf diff -version:W -noprompt -format:Unified %f' 633 | \ } 634 | 635 | let s:default_vcs_cmds_diffmode = { 636 | \ 'git': 'git show HEAD:./%f', 637 | \ 'yadm': 'yadm show HEAD:./%f', 638 | \ 'hg': 'hg cat %f', 639 | \ 'svn': 'svn cat %f', 640 | \ 'bzr': 'bzr cat %f', 641 | \ 'darcs': 'darcs show contents -- %f', 642 | \ 'fossil': 'fossil cat %f', 643 | \ 'cvs': 'cvs up -p -- %f 2>%n', 644 | \ 'rcs': 'co -q -p %f', 645 | \ 'accurev': 'accurev cat %f', 646 | \ 'perforce': 'p4 print -q %f', 647 | \ 'tfs': 'tf view -version:W -noprompt %f', 648 | \ } 649 | 650 | if exists('g:signify_vcs_cmds') 651 | call extend(g:signify_vcs_cmds, s:default_vcs_cmds, 'keep') 652 | else 653 | let g:signify_vcs_cmds = s:default_vcs_cmds 654 | endif 655 | if exists('g:signify_vcs_cmds_diffmode') 656 | call extend(g:signify_vcs_cmds_diffmode, s:default_vcs_cmds_diffmode, 'keep') 657 | else 658 | let g:signify_vcs_cmds_diffmode = s:default_vcs_cmds_diffmode 659 | endif 660 | 661 | let s:vcs_dict = map(copy(g:signify_vcs_cmds), 'split(v:val)[0]') 662 | 663 | if exists('g:signify_skip') && has_key(g:signify_skip, 'vcs') 664 | if has_key(g:signify_skip.vcs, 'allow') 665 | let s:vcs_list = filter(copy(g:signify_skip.vcs.allow), 'executable(s:vcs_dict[v:val])') 666 | elseif has_key(g:signify_skip.vcs, 'deny') 667 | for vcs in g:signify_skip.vcs.deny 668 | silent! call remove(s:vcs_dict, vcs) 669 | endfor 670 | let s:vcs_list = keys(filter(s:vcs_dict, 'executable(v:val)')) 671 | end 672 | else 673 | let s:vcs_list = keys(filter(s:vcs_dict, 'executable(v:val)')) 674 | endif 675 | 676 | let s:difftool = sy#util#escape(get(g:, 'signify_difftool', 'diff')) 677 | let s:devnull = has('win32') || has ('win64') ? 'NUL' : '/dev/null' 678 | -------------------------------------------------------------------------------- /doc/signify.txt: -------------------------------------------------------------------------------- 1 | *signify.txt* Indicate changed lines within a file using a VCS. 2 | *signify* 3 | > 4 | ___ 5 | __ __ /'___\ 6 | ____/\_\ __ ___ /\_\/\ \__/ __ __ 7 | /',__\/\ \ /'_ `\ /' _ `\/\ \ \ ,__\/\ \/\ \ 8 | /\__, `\ \ \/\ \L\ \/\ \/\ \ \ \ \ \_/\ \ \_\ \ 9 | \/\____/\ \_\ \____ \ \_\ \_\ \_\ \_\ \/`____ \ 10 | \/___/ \/_/\/___L\ \/_/\/_/\/_/\/_/ `/___/> \ 11 | /\____/ /\___/ 12 | \_/__/ \/__/ 13 | < 14 | by Marco Hinz~ 15 | 16 | ============================================================================== 17 | TOC *signify-contents* 18 | 19 | INTRO .......................... |signify-intro| 20 | MODUS OPERANDI ................. |signify-modus-operandi| 21 | DEBUG .......................... |signify-debug| 22 | SIGNS .......................... |signify-signs| 23 | OPTIONS ........................ |signify-options| 24 | COMMANDS ....................... |signify-commands| 25 | AUTOCMDS ....................... |signify-autocmds| 26 | EVENTS ......................... |signify-events| 27 | MAPPINGS ....................... |signify-mappings| 28 | COLORS ......................... |signify-colors| 29 | FAQ ............................ |signify-faq| 30 | EXAMPLE ........................ |signify-example| 31 | 32 | ============================================================================== 33 | INTRO *signify-intro* 34 | 35 | Signify uses the sign column to indicate added, modified and removed lines 36 | based on the data of an underlying version control system. 37 | 38 | Supported VCS:~ 39 | > 40 | git 41 | mercurial 42 | darcs 43 | bzr 44 | subversion 45 | cvs 46 | rcs 47 | fossil 48 | accurev 49 | perforce 50 | tfs 51 | yadm 52 | < 53 | ============================================================================== 54 | MODUS OPERANDI *signify-modus-operandi* 55 | 56 | When you open a new buffer, Sy registers it as `active` and tries to figure out 57 | whether the underlying file is controlled by a VCS (version control system) or 58 | not. 59 | 60 | For that, it asynchronously tries all the `diff` sub commands of all the VCS 61 | tools that are supported and executable. 62 | 63 | If one of the checks produces a proper diff, that VCS will automatically be 64 | used for all successive calls to Sy. 65 | 66 | See |signify-autocmds| for when signs get updated. 67 | 68 | If none of the checks produces a proper diff, the VCS will be set to `unknown`. 69 | The next time Sy gets run, the buffer will be marked as `inactive`, so it won't 70 | look for changes anymore. 71 | 72 | Or you set |g:signify_disable_by_default|, which registers all new buffers as 73 | `inactive`, and enable Sy on demand using |signify-:SignifyToggle|. 74 | 75 | Use |signify-:SignifyList| to list all buffers managed by Sy and their current 76 | state. 77 | 78 | ============================================================================== 79 | DEBUG *signify-debug* 80 | 81 | If signs aren't showing up as expected, see if |signify-:SignifyDebug| shows 82 | anything suspicious. It will try all VCS and shows either errors or a diff for 83 | each VCS. 84 | 85 | If the output looks fine though, create an issue on GitHub: 86 | 87 | https://github.com/mhinz/vim-signify/issues/new 88 | 89 | Make sure to mention your Vim version and which Sy version you use (latest 90 | release or master). 91 | 92 | Additionally, include the output of |:messages| after executing one of the 93 | following: 94 | 95 | - opening the buffer via `vim -V1 ` 96 | - or running `:verb w` in the buffer 97 | 98 | ============================================================================== 99 | SIGNS *signify-signs* 100 | 101 | `+` This line was added. 102 | 103 | `!` This line was modified. 104 | 105 | `_1` The number of deleted lines below this sign. If the number is larger 106 | `99` than 9, the `_` will be omitted. For numbers larger than 99, `_>` 107 | `_>` will be shown instead. 108 | 109 | `!1` This line was modified and the lines below were deleted. 110 | `!>` It is a combination of `!` and `_`. If the number is larger than 9, 111 | `!>` will be shown instead. 112 | 113 | `‾` The first line was removed. It's a special case of the `_` sign. 114 | 115 | See |g:signify_sign_add| on how to use different signs. 116 | 117 | ============================================================================== 118 | OPTIONS *signify-options* 119 | 120 | Put these variables into your vimrc. The provided examples also indicate the 121 | default values, as long as no "Default:" section is given. 122 | 123 | |g:signify_vcs_cmds| 124 | |g:signify_vcs_cmds_diffmode| 125 | |g:signify_disable_by_default| 126 | |g:signify_skip| 127 | |g:signify_skip_filetype| 128 | |g:signify_skip_filename| 129 | |g:signify_skip_filename_pattern| 130 | |g:signify_line_highlight| 131 | |g:signify_number_highlight| 132 | |g:signify_sign_add| 133 | |g:signify_sign_delete| 134 | |g:signify_sign_delete_first_line| 135 | |g:signify_sign_change| 136 | |g:signify_sign_change_delete| 137 | |g:signify_sign_show_count| 138 | |g:signify_difftool| 139 | |g:signify_fold_context| 140 | |g:signify_priority| 141 | 142 | ------------------------------------------------------------------------------ 143 | *g:signify_vcs_cmds* > 144 | let g:signify_vcs_cmds = { 145 | \ 'cvs': 'cvs -d '. $CVSROOT .' diff -U0 -- %f' } 146 | < 147 | The command to use for updating signs in an `unmodified` buffer, thus when the 148 | buffer is in sync with the file on disk. 149 | 150 | By default, it returns a diff for all the changes since the last commit. 151 | 152 | The key can be any from this list: 153 | 154 | git 155 | yadm 156 | hg 157 | svn 158 | darcs 159 | bzr 160 | fossil 161 | cvs 162 | rcs 163 | accurev 164 | perforce 165 | tfs 166 | 167 | Modifiers:~ 168 | 169 | %f actual filepath 170 | %d |g:signify_difftool| 171 | %n Unix: `/dev/null`, Windows: `NUL` 172 | 173 | Redirection: Instead of `>foo` use `sy#util#shell_redirect('foo')`. This 174 | helper function takes 'shellredir' into account. 175 | 176 | The output format mustn't change, otherwise Sy won't give any reasonable 177 | results. It's probably wise to start with the respective default values. 178 | 179 | If your VCS program is not in `$PATH` and you have to specify an absolute path, 180 | escape spaces by quoting, e.g.: 181 | > 182 | let g:signify_vcs_cmds = { 183 | \ 'git': '"C:\Program Files (x86)\Git\bin\git.exe" diff --no-color --no-ext-diff -U0 -- %f' 184 | \ } 185 | < 186 | NOTE: Always test these commands on the shell first and make sure that no 187 | colors are emitted. Our parser expects lines in the diff output to start with 188 | "@@", but with colors the line starts with escape sequences instead. 189 | 190 | Default: 191 | > 192 | let g:signify_vcs_cmds = { 193 | \ 'git': 'git diff --no-color --no-ext-diff -U0 -- %f', 194 | \ 'yadm': 'yadm diff --no-color --no-ext-diff -U0 -- %f', 195 | \ 'hg': 'hg --config alias.diff=diff diff --color=never --nodates -U0 -- %f', 196 | \ 'svn': 'svn diff --diff-cmd %d -x -U0 -- %f', 197 | \ 'bzr': 'bzr diff --using %d --diff-options=-U0 -- %f', 198 | \ 'darcs': 'darcs diff --no-pause-for-gui --no-unified --diff-opts=-U0 -- %f', 199 | \ 'fossil': 'fossil diff --unified -c 0 -- %f', 200 | \ 'cvs': 'cvs diff -U0 -- %f', 201 | \ 'rcs': 'rcsdiff -U0 %f 2>%n', 202 | \ 'accurev': 'accurev diff %f -- -U0', 203 | \ 'perforce': 'p4 info '. sy#util#shell_redirect('%n') . (has('win32') ? ' && set P4DIFF=&&' : ' && env P4DIFF=') .' p4 diff -du0 %f', 204 | \ 'tfs': 'tf diff -version:W -noprompt -format:Unified %f' 205 | \ } 206 | < 207 | ------------------------------------------------------------------------------ 208 | *g:signify_vcs_cmds_diffmode* > 209 | let g:signify_vcs_cmds_diffmode = { 210 | \ 'git': 'git show HEAD:./%f', 211 | \ 'yadm': 'yadm show HEAD:./%f', 212 | \ 'hg': 'hg cat %f', 213 | \ 'svn': 'svn cat %f', 214 | \ 'bzr': 'bzr cat %f', 215 | \ 'darcs': 'darcs show contents -- %f', 216 | \ 'fossil': 'fossil cat %f', 217 | \ 'cvs': 'cvs up -p -- %f 2>%n', 218 | \ 'rcs': 'co -q -p %f', 219 | \ 'accurev': 'accurev cat %f', 220 | \ 'perforce': 'p4 print -q %f', 221 | \ 'tfs': 'tf view -version:W -noprompt %f', 222 | \ } 223 | < 224 | The command to use for updating signs in a `modified` buffer, thus when the 225 | buffer is `not` in sync with the file on disk. Also for |signify-:SignifyDiff|. 226 | 227 | By default, it returns the file as of the last commit. Thus without any of the 228 | current changes in the buffer. 229 | 230 | Works the same as |g:signify_vcs_cmds|. 231 | 232 | ------------------------------------------------------------------------------ 233 | *g:signify_disable_by_default* > 234 | let g:signify_disable_by_default = 0 235 | < 236 | Disable Sy by default. You can still enable it later via: 237 | 238 | |signify-:SignifyToggle| 239 | |signify-:SignifyEnable| 240 | |signify-:SignifyEnableAll| 241 | 242 | ------------------------------------------------------------------------------ 243 | *g:signify_skip* 244 | > 245 | let g:signify_skip = { 'vcs': { 'allow': ['git'] } } 246 | let g:signify_skip = { 'vcs': { 'deny': ['perforce'] } } 247 | < 248 | Allow only certain VCS or deny all but the specified ones. Mutually exclusive. 249 | 250 | This can be used to replace the old g:signify_vcs_list. 251 | 252 | Default: 253 | 254 | ------------------------------------------------------------------------------ 255 | *g:signify_skip_filename_pattern* 256 | *g:signify_skip_filename* 257 | *g:signify_skip_filetype* 258 | > 259 | let g:signify_skip_filetype = { 'vim': 1, 'c': 1 } 260 | let g:signify_skip_filename = { '/home/user/.vimrc': 1 } 261 | < 262 | Don't activate the plugin for these filetypes and/or filenames. Filenames have 263 | to be absolute paths. 264 | 265 | These options must be |Dict|s for faster lookup. 266 | > 267 | let g:signify_skip_filename_pattern = [ 'foo.*bar', 'tmp' ] 268 | < 269 | Don't activate the plugin for filenames matching these patterns. 270 | 271 | Default: 272 | 273 | ------------------------------------------------------------------------------ 274 | *g:signify_line_highlight* > 275 | let g:signify_line_highlight = 0 276 | < 277 | Enable line highlighting in addition to using signs by default. 278 | 279 | ------------------------------------------------------------------------------ 280 | *g:signify_number_highlight* > 281 | let g:signify_number_highlight = 0 282 | < 283 | Enable number column highlighting in addition to using signs by default. 284 | 285 | This requires Vim 8.2.3874+ or Neovim 0.3.2+. 286 | 287 | ------------------------------------------------------------------------------ 288 | *g:signify_sign_add* 289 | *g:signify_sign_delete* 290 | *g:signify_sign_delete_first_line* 291 | *g:signify_sign_change* 292 | *g:signify_sign_change_delete* 293 | > 294 | let g:signify_sign_add = '+' 295 | let g:signify_sign_delete = '_' 296 | let g:signify_sign_delete_first_line = '‾' 297 | let g:signify_sign_change = '!' 298 | let g:signify_sign_change_delete = g:signify_sign_change . g:signify_sign_delete_first_line 299 | < 300 | The sign to use if a line was added, deleted or changed or a combination of 301 | these. 302 | 303 | You can use Unicode characters, but signs must not take up more than two 304 | cells. Otherwise, |E239| is thrown. 305 | 306 | Examples:~ 307 | > 308 | let g:signify_sign_add = '▊' " U+258A LEFT THREE QUARTERS BLOCK (1 cell) 309 | let g:signify_sign_change = '██' " U+2588 FULL BLOCK x2 (2 cells) 310 | < 311 | ------------------------------------------------------------------------------ 312 | *g:signify_sign_show_count* > 313 | let g:signify_sign_show_count = 1 314 | < 315 | Add the number of deleted lines to |g:signify_sign_delete| (up to 99). 316 | Otherwise, only the usual sign text will be shown. 317 | 318 | ------------------------------------------------------------------------------ 319 | *g:signify_difftool* > 320 | let g:signify_difftool = 'gnudiff' 321 | < 322 | This will avoid the attempt to find the proper diff tool for version control 323 | systems that rely on an external diff tool that supports the -U0 flag. These 324 | are: svn, bzr, darcs, fossil. 325 | 326 | Default: "diff" 327 | 328 | ------------------------------------------------------------------------------ 329 | *g:signify_fold_context* > 330 | let g:signify_fold_context = [0, 3] 331 | < 332 | This changes the number of lines of context that |signify-:SignifyFold| should 333 | use. The first element describes the context at foldlevel 0 and the second the 334 | context at foldlevel 1. 335 | 336 | Example:~ 337 | 338 | Using "[0,3]" means that after using :SignifyFold, only changed lines will be 339 | unfolded. Using |zo| (and similar |fold-commands|) on a folded line will reveal 340 | 3 more lines of context. Using |zo| a second time will reveal everything. 341 | 342 | Default: [3, 8] 343 | 344 | ------------------------------------------------------------------------------ 345 | *g:signify_priority* > 346 | let g:signify_priority = 5 347 | < 348 | This changes the sign priority used for signs. See |sign-priority|. 349 | 350 | Default: 10 351 | 352 | ============================================================================== 353 | COMMAND *signify-commands* 354 | *signify-:SignifyEnable* > 355 | :SignifyEnable 356 | < 357 | Enable the plugin for the current buffer only. 358 | 359 | Can also be used to when a repository was initialized while Sy was already 360 | loaded. 361 | 362 | ------------------------------------------------------------------------------ 363 | *signify-:SignifyEnableAll* > 364 | :SignifyEnableAll 365 | < 366 | Enable the plugin for all buffers. Sets |g:signify_disable_by_default| to 0. 367 | 368 | ------------------------------------------------------------------------------ 369 | *signify-:SignifyDisable* > 370 | :SignifyDisable 371 | < 372 | Disable the plugin for the current buffer only. 373 | 374 | ------------------------------------------------------------------------------ 375 | *signify-:SignifyDisableAll* > 376 | :SignifyDisableAll 377 | < 378 | Disable the plugin for all buffers. Sets |g:signify_disable_by_default| to 1. 379 | 380 | ------------------------------------------------------------------------------ 381 | *signify-:SignifyToggle* > 382 | :SignifyToggle 383 | < 384 | Toggle the plugin for the current buffer only. 385 | 386 | ------------------------------------------------------------------------------ 387 | *signify-:SignifyToggleHighlight* > 388 | :SignifyToggleHighlight 389 | < 390 | Toggle line highlighting for lines containing changes. 391 | 392 | ------------------------------------------------------------------------------ 393 | *signify-:SignifyRefresh* > 394 | :SignifyRefresh 395 | < 396 | Refresh signs in all windows. 397 | 398 | NOTE: Nothing will happen, if :SignifyRefresh is used from the |cmdline-window|. 399 | 400 | ------------------------------------------------------------------------------ 401 | *signify-:SignifyDiff* > 402 | :SignifyDiff[!] 403 | < 404 | Open a new tab with two windows using |diff-mode| to show the differences 405 | between the current file and its version that was last checked in. 406 | 407 | With [!], no new tab will be opened. 408 | 409 | Also see |g:signify_vcs_cmds_diffmode|. 410 | 411 | ------------------------------------------------------------------------------ 412 | *signify-:SignifyFold* > 413 | :SignifyFold[!] 414 | < 415 | Open the current buffer in a new tab page and set 'foldexpr' so that only 416 | changed lines with their surrounding context are unfolded. 417 | 418 | The number of lines per context can be changed via |g:signify_fold_context|. 419 | 420 | The |foldtext| will be set so that the left side shows the first line in the 421 | fold and the right side shows something like "50 [1]" which whereas "50" 422 | stands for the number of folded lines and the "1" is the 'foldlevel'. 423 | 424 | If [!] is given, Sy will do the same without opening an extra tab page. 425 | Another ":SignifyFold!" will toggle back to the previous settings. 426 | 427 | See |folds| to learn more about folding. 428 | 429 | ------------------------------------------------------------------------------ 430 | *signify-:SignifyHunkDiff* > 431 | :SignifyHunkDiff 432 | < 433 | Used on a line with a change, this will show a diff of the hunk in a floating 434 | window (Nvim), popup (Vim), or preview window (fallback). 435 | 436 | ------------------------------------------------------------------------------ 437 | *signify-:SignifyHunkUndo* > 438 | :SignifyHunkUndo 439 | < 440 | Used on a line with a change, this will restore the hunk to the base version. 441 | This effectively undoes the changes that |signify-:SignifyHunkDiff| would show. 442 | 443 | ------------------------------------------------------------------------------ 444 | *signify-:SignifyList* > 445 | :SignifyList 446 | < 447 | Outputs debug info for all managed buffers. 448 | 449 | ------------------------------------------------------------------------------ 450 | *signify-:SignifyDebug* > 451 | :SignifyDebug 452 | < 453 | In case no signs are shown, although the buffer contains a file controlled by 454 | a supported VCS, use this command. 455 | 456 | It will show all tried commands and their output. Errors will be highlighted 457 | via |hl-ErrorMsg|. 458 | 459 | ============================================================================== 460 | AUTOCMDS *signify-autocmds* 461 | 462 | By default, signs get updated on these events: 463 | 464 | |BufEnter| Opening or switching to another buffer. 465 | |WinEnter| Opening or switching to another window. 466 | |BufWritePost| Saving a buffer. 467 | |FocusGained| When the window containing Vim gains focus. Updates signs 468 | for all Vim windows. This event fires for all GUIs and even 469 | a few terminal emulators. 470 | For tmux, use this: `set-option -g focus-events on` 471 | |CursorHold| After 'updatetime' milliseconds without moving the cursor 472 | in normal mode. 473 | |CursorHoldI| After 'updatetime' milliseconds without moving the cursor 474 | in insert mode. 475 | |ShellCmdPost| After shelling out via |:!|. Think `:!git commit -m foo` 476 | |VimResume| After suspending Vim with ``, then doing things in 477 | the shell, and resuming to Vim via `fg`. Only Neovim. 478 | 479 | See all of them with: 480 | > 481 | :au signify 482 | < 483 | You can disable sign updating for certain events: 484 | > 485 | autocmd User SignifyAutocmds autocmd! signify CursorHold,CursorHoldI 486 | < 487 | If you don't need immediate feedback or responses from your VCS are slow, then 488 | use this to only update signs when writing the buffer: 489 | > 490 | autocmd User SignifyAutocmds 491 | \ exe 'au! signify' | au signify BufWritePost * call sy#start() 492 | < 493 | ============================================================================== 494 | EVENTS *signify-events* 495 | 496 | Signify fires these user events: 497 | 498 | User SignifySetup~ 499 | 500 | This event fires at the end of `plugin/signify.vim`, in case you want to 501 | change any of the default commands, or mappings. 502 | 503 | User SignifyAutocmds~ 504 | 505 | This event fires every time Sy sets autocmds. E.g. when opening a 506 | version-controlled file or when using |signify-:SignifyToggle| a lot. 507 | Useful to change the default |signify-autocmds|. 508 | 509 | User Signify~ 510 | 511 | This event fires when Sy updated the signs. 512 | 513 | Hook into these user events like this: 514 | > 515 | autocmd User Signify echomsg 'updated!' 516 | < 517 | NOTE: Autocmds don't nest by default. If you use any command that triggers 518 | other events, make sure to use |autocmd-nested|. 519 | 520 | ============================================================================== 521 | MAPPINGS *signify-mappings* 522 | 523 | ------------------------------------------------------------------------------ 524 | Hunk jumping:~ 525 | 526 | ]c Jump to the next hunk. 527 | [c Jump to the previous hunk. 528 | 529 | ]C Jump to the last hunk. 530 | [C Jump to the first hunk. 531 | 532 | These keys only get mapped by default when: 533 | 534 | - The keys are not mapped already (by you or another plugin). 535 | - There are no other keys that are mapped to do the same (to avoid duplicate 536 | mappings). 537 | 538 | Mapping other keys: 539 | > 540 | nmap gj (signify-next-hunk) 541 | nmap gk (signify-prev-hunk) 542 | nmap gJ 9999gj 543 | nmap gK 9999gk 544 | < 545 | When you jump to a hunk, show "[Hunk 2/15]" by putting this in your vimrc: 546 | > 547 | autocmd User SignifyHunk call s:show_current_hunk() 548 | 549 | function! s:show_current_hunk() abort 550 | let h = sy#util#get_hunk_stats() 551 | if !empty(h) 552 | echo printf('[Hunk %d/%d]', h.current_hunk, h.total_hunks) 553 | endif 554 | endfunction 555 | < 556 | ------------------------------------------------------------------------------ 557 | Hunk text object:~ 558 | > 559 | omap ic (signify-motion-inner-pending) 560 | xmap ic (signify-motion-inner-visual) 561 | omap ac (signify-motion-outer-pending) 562 | xmap ac (signify-motion-outer-visual) 563 | < 564 | "ic" operates on all lines of the current hunk. "ac" does the same, but also 565 | removes all trailing empty lines. 566 | 567 | NOTE: Don't be surprised that this also works with "deleted lines". 568 | 569 | ============================================================================== 570 | COLORS *signify-colors* 571 | 572 | This plugin defines highlighting groups for two different places: for lines 573 | and signs. Per default these don't exist but are linked to the standard 574 | highlighting groups: |hl-DiffAdd|, |hl-DiffChange|, |hl-DiffDelete|: 575 | > 576 | highlight link SignifyLineAdd DiffAdd 577 | highlight link SignifyLineChange DiffChange 578 | highlight link SignifyLineChangeDelete SignifyLineChange 579 | highlight link SignifyLineDelete DiffDelete 580 | highlight link SignifyLineDeleteFirstLine SignifyLineDelete 581 | 582 | highlight link SignifySignAdd DiffAdd 583 | highlight link SignifySignChange DiffChange 584 | highlight link SignifySignChangeDelete SignifySignChange 585 | highlight link SignifySignDelete DiffDelete 586 | highlight link SignifySignDeleteFirstLine SignifySignDelete 587 | < 588 | Thus if you do not want to change the standard highlighting groups, but want 589 | different colors for either your signs or lines, you can overwrite these 590 | highlighting groups in your vimrc. 591 | 592 | Assuming you prefer |hl-DiffText| over |hl-DiffChange| for changed lines: 593 | > 594 | highlight link SignifyLineChange DiffText 595 | < 596 | Example configuration:~ 597 | > 598 | highlight SignifySignAdd ctermfg=green guifg=#00ff00 cterm=NONE gui=NONE 599 | highlight SignifySignDelete ctermfg=red guifg=#ff0000 cterm=NONE gui=NONE 600 | highlight SignifySignChange ctermfg=yellow guifg=#ffff00 cterm=NONE gui=NONE 601 | < 602 | Note: For Unix people there is a small script in the repo, showcolors.bash, 603 | that shows all 256 colors available in the terminal. That makes picking the 604 | right numbers much easier. 605 | 606 | Default highlight groups:~ 607 | 608 | The sign column (often mistakenly called "gutter") itself (all lines without 609 | signs) is highlighted by |hl-SignColumn|. Some colorschemes define no background 610 | color for |hl-Normal| but for |hl-SignColumn|. To avoid that visible difference: 611 | > 612 | highlight SignColumn ctermbg=NONE cterm=NONE guibg=NONE gui=NONE 613 | < 614 | ============================================================================== 615 | FAQ *signify-faq* 616 | 617 | |signify-faq-01| How to display changes in the statusline? 618 | |signify-faq-02| The plugin is slow! 619 | |signify-faq-03| Line highlighting without showing signs? 620 | 621 | ------------------------------------------------------------------------------ 622 | *signify-faq-01* 623 | How to display changes in the statusline?~ 624 | 625 | Use either of the following two functions. Both take an optional buffer number 626 | and default to the current one. 627 | 628 | - sy#repo#get_stats(...)~ 629 | 630 | Returns a list with the number of added, modified, and removed lines. 631 | 632 | - sy#repo#get_stats_decorated(...)~ 633 | 634 | Similar to sy#repo#get_stats(), but with added decorations. 635 | Example: "[+3 -8 ~5]" 636 | 637 | Using 'statusline': 638 | > 639 | function! MyStatusline() 640 | return ' %f '. sy#repo#get_stats_decorated() 641 | endfunction 642 | 643 | set statusline=%!MyStatusline() 644 | < 645 | The above is the short form of: 646 | > 647 | function! s:sy_stats_wrapper() 648 | let [added, modified, removed] = sy#repo#get_stats() 649 | let symbols = ['+', '-', '~'] 650 | let stats = [added, removed, modified] " reorder 651 | let statline = '' 652 | 653 | for i in range(3) 654 | if stats[i] > 0 655 | let statline .= printf('%s%s ', symbols[i], stats[i]) 656 | endif 657 | endfor 658 | 659 | if !empty(statline) 660 | let statline = printf('[%s]', statline[:-2]) 661 | endif 662 | 663 | return statline 664 | endfunction 665 | 666 | function! MyStatusline() 667 | return ' %f '. s:sy_stats_wrapper() 668 | endfunction 669 | 670 | set statusline=%!MyStatusline() 671 | < 672 | ------------------------------------------------------------------------------ 673 | *signify-faq-02* 674 | The plugin is slow!~ 675 | 676 | * Sy relies on external tools. Check if these are the bottleneck. 677 | 678 | If you use a centralized VCS like Subversion, is the connection to the 679 | server slow? 680 | 681 | If you use a decentralized VCS like Git, are you working on a slow remote 682 | file system? 683 | 684 | * Vim's sign handling code is known to be slow. If the delay is very long, 685 | chances are the diff is just huge. This often happens after adding (or 686 | removing) a huge file to the repo. 687 | 688 | ------------------------------------------------------------------------------ 689 | *signify-faq-03* 690 | Line highlighting without showing signs?~ 691 | 692 | The line highlighting relies on signs being placed. The sign column is being 693 | shown automatically if there are placed signs. 694 | 695 | With a recent Vim, you can change that behavior using 'signcolumn'. 696 | 697 | ============================================================================== 698 | EXAMPLE *signify-example* 699 | 700 | An example configuration for Sy: 701 | > 702 | " Faster sign updates on CursorHold/CursorHoldI 703 | set updatetime=100 704 | 705 | nnoremap gd :SignifyDiff 706 | nnoremap gp :SignifyHunkDiff 707 | nnoremap gu :SignifyHunkUndo 708 | 709 | " hunk jumping 710 | nmap gj (signify-next-hunk) 711 | nmap gk (signify-prev-hunk) 712 | 713 | " hunk text object 714 | omap ic (signify-motion-inner-pending) 715 | xmap ic (signify-motion-inner-visual) 716 | omap ac (signify-motion-outer-pending) 717 | xmap ac (signify-motion-outer-visual) 718 | < 719 | ============================================================================== 720 | vim: et tw=78 721 | --------------------------------------------------------------------------------