diff --git a/autoload/diff.vim b/autoload/diff.vim new file mode 100644 index 0000000..62c3855 --- /dev/null +++ b/autoload/diff.vim @@ -0,0 +1,155 @@ +let s:grep_available = executable('grep') +let s:grep_command = ' | ' . (g:gitgutter_escape_grep ? '\grep' : 'grep') . ' -e "^@@ "' + + +function! diff#run_diff(realtime) + if a:realtime + let blob_name = ':./' . fnamemodify(utility#file(),':t') + let blob_file = tempname() + let cmd = 'git show ' . blob_name . ' > ' . blob_file . ' && diff -U0 ' . g:gitgutter_diff_args . ' ' . blob_file . ' - ' + else + let cmd = 'git diff --no-ext-diff --no-color -U0 ' . g:gitgutter_diff_args . ' ' . shellescape(utility#file()) + endif + if s:grep_available + let cmd .= s:grep_command + endif + let cmd = utility#escape(cmd) + if a:realtime + if &fileformat ==# "dos" + let eol = "\r\n" + elseif &fileformat ==# "mac" + let eol = "\r" + else + let eol = "\n" + endif + let buffer_contents = join(getline(1, '$'), eol) . eol + let diff = system(utility#command_in_directory_of_file(cmd), buffer_contents) + else + let diff = system(utility#command_in_directory_of_file(cmd)) + endif + return diff +endfunction + +function! diff#parse_diff(diff) + let hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@' + let hunks = [] + for line in split(a:diff, '\n') + let matches = matchlist(line, hunk_re) + if len(matches) > 0 + let from_line = str2nr(matches[1]) + let from_count = (matches[2] == '') ? 1 : str2nr(matches[2]) + let to_line = str2nr(matches[3]) + let to_count = (matches[4] == '') ? 1 : str2nr(matches[4]) + call add(hunks, [from_line, from_count, to_line, to_count]) + endif + endfor + return hunks +endfunction + +function! diff#process_hunks(hunks) + call hunk#reset() + let modified_lines = [] + for hunk in a:hunks + call extend(modified_lines, diff#process_hunk(hunk)) + endfor + return modified_lines +endfunction + +function! diff#process_hunk(hunk) + let modifications = [] + let from_line = a:hunk[0] + let from_count = a:hunk[1] + let to_line = a:hunk[2] + let to_count = a:hunk[3] + + if diff#is_added(from_count, to_count) + call diff#process_added(modifications, from_count, to_count, to_line) + call hunk#increment_lines_added(to_count) + + elseif diff#is_removed(from_count, to_count) + call diff#process_removed(modifications, from_count, to_count, to_line) + call hunk#increment_lines_removed(from_count) + + elseif diff#is_modified(from_count, to_count) + call diff#process_modified(modifications, from_count, to_count, to_line) + call hunk#increment_lines_modified(to_count) + + elseif diff#is_modified_and_added(from_count, to_count) + call diff#process_modified_and_added(modifications, from_count, to_count, to_line) + call hunk#increment_lines_added(to_count - from_count) + call hunk#increment_lines_modified(from_count) + + elseif diff#is_modified_and_removed(from_count, to_count) + call diff#process_modified_and_removed(modifications, from_count, to_count, to_line) + call hunk#increment_lines_modified(to_count) + call hunk#increment_lines_removed(from_count - to_count) + + endif + return modifications +endfunction + +function! diff#is_added(from_count, to_count) + return a:from_count == 0 && a:to_count > 0 +endfunction + +function! diff#is_removed(from_count, to_count) + return a:from_count > 0 && a:to_count == 0 +endfunction + +function! diff#is_modified(from_count, to_count) + return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count +endfunction + +function! diff#is_modified_and_added(from_count, to_count) + return a:from_count > 0 && a:to_count > 0 && a:from_count < a:to_count +endfunction + +function! diff#is_modified_and_removed(from_count, to_count) + return a:from_count > 0 && a:to_count > 0 && a:from_count > a:to_count +endfunction + +function! diff#process_added(modifications, from_count, to_count, to_line) + let offset = 0 + while offset < a:to_count + let line_number = a:to_line + offset + call add(a:modifications, [line_number, 'added']) + let offset += 1 + endwhile +endfunction + +function! diff#process_removed(modifications, from_count, to_count, to_line) + call add(a:modifications, [a:to_line, 'removed']) +endfunction + +function! diff#process_modified(modifications, from_count, to_count, to_line) + let offset = 0 + while offset < a:to_count + let line_number = a:to_line + offset + call add(a:modifications, [line_number, 'modified']) + let offset += 1 + endwhile +endfunction + +function! diff#process_modified_and_added(modifications, from_count, to_count, to_line) + let offset = 0 + while offset < a:from_count + let line_number = a:to_line + offset + call add(a:modifications, [line_number, 'modified']) + let offset += 1 + endwhile + while offset < a:to_count + let line_number = a:to_line + offset + call add(a:modifications, [line_number, 'added']) + let offset += 1 + endwhile +endfunction + +function! diff#process_modified_and_removed(modifications, from_count, to_count, to_line) + let offset = 0 + while offset < a:to_count + let line_number = a:to_line + offset + call add(a:modifications, [line_number, 'modified']) + let offset += 1 + endwhile + call add(a:modifications, [a:to_line + offset - 1, 'modified_removed']) +endfunction diff --git a/autoload/highlight.vim b/autoload/highlight.vim new file mode 100644 index 0000000..d068b46 --- /dev/null +++ b/autoload/highlight.vim @@ -0,0 +1,65 @@ +function! highlight#define_sign_column_highlight() + highlight default link SignColumn LineNr +endfunction + +function! highlight#define_highlights() + " Highlights used by the signs. + highlight GitGutterAddDefault guifg=#009900 guibg=NONE ctermfg=2 ctermbg=NONE + highlight GitGutterChangeDefault guifg=#bbbb00 guibg=NONE ctermfg=3 ctermbg=NONE + highlight GitGutterDeleteDefault guifg=#ff2222 guibg=NONE ctermfg=1 ctermbg=NONE + highlight default link GitGutterChangeDeleteDefault GitGutterChangeDefault + + highlight default link GitGutterAdd GitGutterAddDefault + highlight default link GitGutterChange GitGutterChangeDefault + highlight default link GitGutterDelete GitGutterDeleteDefault + highlight default link GitGutterChangeDelete GitGutterChangeDeleteDefault + + " Highlights used for the whole line. + highlight default link GitGutterAddLine DiffAdd + highlight default link GitGutterChangeLine DiffChange + highlight default link GitGutterDeleteLine DiffDelete + highlight default link GitGutterChangeDeleteLine GitGutterChangeLineDefault +endfunction + +function! highlight#define_signs() + sign define GitGutterLineAdded + sign define GitGutterLineModified + sign define GitGutterLineRemoved + sign define GitGutterLineModifiedRemoved + sign define GitGutterDummy + + if g:gitgutter_signs + call highlight#define_sign_symbols() + call highlight#define_sign_text_highlights() + endif + call highlight#define_sign_line_highlights() +endfunction + +function! highlight#define_sign_symbols() + exe "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added + exe "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified + exe "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed + exe "sign define GitGutterLineModifiedRemoved text=" . g:gitgutter_sign_modified_removed +endfunction + +function! highlight#define_sign_text_highlights() + sign define GitGutterLineAdded texthl=GitGutterAdd + sign define GitGutterLineModified texthl=GitGutterChange + sign define GitGutterLineRemoved texthl=GitGutterDelete + sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDelete +endfunction + +function! highlight#define_sign_line_highlights() + if g:gitgutter_highlight_lines + sign define GitGutterLineAdded linehl=GitGutterAddLine + sign define GitGutterLineModified linehl=GitGutterChangeLine + sign define GitGutterLineRemoved linehl=GitGutterDeleteLine + sign define GitGutterLineModifiedRemoved linehl=GitGutterChangeDeleteLine + else + sign define GitGutterLineAdded linehl= + sign define GitGutterLineModified linehl= + sign define GitGutterLineRemoved linehl= + sign define GitGutterLineModifiedRemoved linehl= + endif + redraw! +endfunction diff --git a/autoload/hunk.vim b/autoload/hunk.vim new file mode 100644 index 0000000..903e1a7 --- /dev/null +++ b/autoload/hunk.vim @@ -0,0 +1,24 @@ +" number of lines [added, modified, removed] +let s:summary = [0, 0, 0] + +function! hunk#summary() + return s:summary +endfunction + +function! hunk#reset() + let s:summary = [0, 0, 0] " TODO: is bling/airline expecting [-1, -1, -1]? +endfunction + +function! hunk#increment_lines_added(count) + let s:summary[0] += a:count +endfunction + +function! hunk#increment_lines_modified(count) + let s:summary[1] += a:count +endfunction + +function! hunk#increment_lines_removed(count) + let s:summary[2] += a:count +endfunction + + diff --git a/autoload/sign.vim b/autoload/sign.vim new file mode 100644 index 0000000..44725eb --- /dev/null +++ b/autoload/sign.vim @@ -0,0 +1,84 @@ +" Vim doesn't namespace sign ids so every plugin shares the same +" namespace. Sign ids are simply integers so to avoid clashes with other +" signs we guess at a clear run. +" +" Note also we currently never reset s:next_sign_id. +let s:first_sign_id = 3000 +let s:next_sign_id = s:first_sign_id +let s:sign_ids = {} " key: filename, value: list of sign ids +let s:other_signs = [] +let s:dummy_sign_id = 153 + + +function! sign#clear_signs(file_name) + if exists('s:sign_ids') && has_key(s:sign_ids, a:file_name) + for id in s:sign_ids[a:file_name] + exe ":sign unplace" id "file=" . a:file_name + endfor + let s:sign_ids[a:file_name] = [] + endif +endfunction + +" This assumes there are no GitGutter signs in the file. +" If this is untenable we could change the regexp to exclude GitGutter's +" signs. +function! sign#find_other_signs(file_name) + redir => signs + silent exe ":sign place file=" . a:file_name + redir END + let s:other_signs = [] + for sign_line in split(signs, '\n') + if sign_line =~ '^\s\+\w\+=' + let matches = matchlist(sign_line, '^\s\+\w\+=\(\d\+\)') + let line_number = str2nr(matches[1]) + call add(s:other_signs, line_number) + endif + endfor +endfunction + +function! sign#show_signs(file_name, modified_lines) + for line in a:modified_lines + let line_number = line[0] + let type = 'GitGutterLine' . utility#snake_case_to_camel_case(line[1]) + call sign#add_sign(line_number, type, a:file_name) + endfor +endfunction + +function! sign#add_sign(line_number, name, file_name) + let id = sign#next_sign_id() + if !sign#is_other_sign(a:line_number) " Don't clobber other people's signs. + exe ":sign place" id "line=" . a:line_number "name=" . a:name "file=" . a:file_name + call sign#remember_sign(id, a:file_name) + endif +endfunction + +function! sign#next_sign_id() + let next_id = s:next_sign_id + let s:next_sign_id += 1 + return next_id +endfunction + +function! sign#remember_sign(id, file_name) + if has_key(s:sign_ids, a:file_name) + let sign_ids_for_file = s:sign_ids[a:file_name] + call add(sign_ids_for_file, a:id) + else + let sign_ids_for_file = [a:id] + endif + let s:sign_ids[a:file_name] = sign_ids_for_file +endfunction + +function! sign#is_other_sign(line_number) + return index(s:other_signs, a:line_number) == -1 ? 0 : 1 +endfunction + +function! sign#add_dummy_sign() + let last_line = line('$') + exe ":sign place" s:dummy_sign_id "line=" . (last_line + 1) "name=GitGutterDummy file=" . utility#file() +endfunction + +function! sign#remove_dummy_sign() + if exists('s:dummy_sign_id') + exe ":sign unplace" s:dummy_sign_id "file=" . utility#file() + endif +endfunction diff --git a/autoload/utility.vim b/autoload/utility.vim new file mode 100644 index 0000000..37b6868 --- /dev/null +++ b/autoload/utility.vim @@ -0,0 +1,77 @@ +let s:file = '' + +function! utility#is_active() + return g:gitgutter_enabled && utility#exists_file() && utility#is_in_a_git_repo() && utility#is_tracked_by_git() +endfunction + +function! utility#current_file() + return expand('%:p') +endfunction + +function! utility#set_file(file) + let s:file = a:file +endfunction + +function! utility#file() + return s:file +endfunction + +function! utility#exists_file() + return filereadable(utility#file()) +endfunction + +function! utility#directory_of_file() + return shellescape(fnamemodify(utility#file(), ':h')) +endfunction + +function! utility#has_unsaved_changes(file) + return getbufvar(a:file, "&mod") +endfunction + +" https://github.com/tpope/vim-dispatch/blob/9cdd05a87f8a47120335be03dfcd8358544221cd/autoload/dispatch/windows.vim#L8-L17 +function! utility#escape(str) + if &shellxquote ==# '"' + return '"' . substitute(a:str, '"', '""', 'g') . '"' + else + let esc = exists('+shellxescape') ? &shellxescape : '"&|<>()@^' + return &shellquote . + \ substitute(a:str, '['.esc.']', '&', 'g') . + \ get({'(': ')', '"(': ')"'}, &shellquote, &shellquote) + endif +endfunction + +function! utility#discard_stdout_and_stderr() + if !exists('utility#discard') + if &shellredir ==? '>%s 2>&1' + let utility#discard = ' > /dev/null 2>&1' + else + let utility#discard = ' >& /dev/null' + endif + endif + return utility#discard +endfunction + +function! utility#command_in_directory_of_file(cmd) + let utility#cmd_in_dir = 'cd ' . utility#directory_of_file() . ' && ' . a:cmd + return substitute(utility#cmd_in_dir, "'", '"', 'g') +endfunction + +function! utility#is_in_a_git_repo() + let cmd = utility#escape('git rev-parse' . utility#discard_stdout_and_stderr()) + call system(utility#command_in_directory_of_file(cmd)) + return !v:shell_error +endfunction + +function! utility#is_tracked_by_git() + let cmd = utility#escape('git ls-files --error-unmatch' . utility#discard_stdout_and_stderr() . ' ' . shellescape(utility#file())) + call system(utility#command_in_directory_of_file(cmd)) + return !v:shell_error +endfunction + +function! utility#differences(hunks) + return len(a:hunks) != 0 +endfunction + +function! utility#snake_case_to_camel_case(text) + return substitute(a:text, '\v(.)(\a+)(_(.)(.+))?', '\u\1\l\2\u\4\l\5', '') +endfunction diff --git a/plugin/gitgutter.vim b/plugin/gitgutter.vim index 8f57400..61f5581 100644 --- a/plugin/gitgutter.vim +++ b/plugin/gitgutter.vim @@ -18,7 +18,7 @@ endfunction call s:set('g:gitgutter_enabled', 1) call s:set('g:gitgutter_signs', 1) call s:set('g:gitgutter_highlight_lines', 0) -let s:highlight_lines = g:gitgutter_highlight_lines +" let s:highlight_lines = g:gitgutter_highlight_lines call s:set('g:gitgutter_sign_column_always', 0) call s:set('g:gitgutter_realtime', 1) call s:set('g:gitgutter_eager', 1) @@ -29,425 +29,17 @@ call s:set('g:gitgutter_sign_modified_removed', '~_') call s:set('g:gitgutter_diff_args', '') call s:set('g:gitgutter_escape_grep', 0) -let s:file = '' -let s:hunk_summary = [0, 0, 0] - function! s:init() if !exists('g:gitgutter_initialised') - call s:define_sign_column_highlight() - call s:define_highlights() - call s:define_signs() - - " Vim doesn't namespace sign ids so every plugin shares the same - " namespace. Sign ids are simply integers so to avoid clashes with other - " signs we guess at a clear run. - " - " Note also we currently never reset s:next_sign_id. - let s:first_sign_id = 3000 - let s:next_sign_id = s:first_sign_id - let s:sign_ids = {} " key: filename, value: list of sign ids - let s:other_signs = [] - let s:dummy_sign_id = 153 - - let s:grep_available = executable('grep') - let s:grep_command = ' | ' . (g:gitgutter_escape_grep ? '\grep' : 'grep') . ' -e "^@@ "' - + call highlight#define_sign_column_highlight() + call highlight#define_highlights() + call highlight#define_signs() let g:gitgutter_initialised = 1 endif endfunction " }}} -" Utility {{{ - -function! s:is_active() - return g:gitgutter_enabled && s:exists_file() && s:is_in_a_git_repo() && s:is_tracked_by_git() -endfunction - -function! s:current_file() - return expand('%:p') -endfunction - -function! s:set_file(file) - let s:file = a:file -endfunction - -function! s:file() - return s:file -endfunction - -function! s:exists_file() - return filereadable(s:file()) -endfunction - -function! s:directory_of_file() - return shellescape(fnamemodify(s:file(), ':h')) -endfunction - -function! s:has_unsaved_changes(file) - return getbufvar(a:file, "&mod") -endfunction - -function! s:reset_hunk_summary() - let s:hunk_summary = [-1, -1, -1] -endfunction - -" https://github.com/tpope/vim-dispatch/blob/9cdd05a87f8a47120335be03dfcd8358544221cd/autoload/dispatch/windows.vim#L8-L17 -function! s:escape(str) - if &shellxquote ==# '"' - return '"' . substitute(a:str, '"', '""', 'g') . '"' - else - let esc = exists('+shellxescape') ? &shellxescape : '"&|<>()@^' - return &shellquote . - \ substitute(a:str, '['.esc.']', '&', 'g') . - \ get({'(': ')', '"(': ')"'}, &shellquote, &shellquote) - endif -endfunction - -function! s:discard_stdout_and_stderr() - if !exists('s:discard') - if &shellredir ==? '>%s 2>&1' - let s:discard = ' > /dev/null 2>&1' - else - let s:discard = ' >& /dev/null' - endif - endif - return s:discard -endfunction - -function! s:command_in_directory_of_file(cmd) - let s:cmd_in_dir = 'cd ' . s:directory_of_file() . ' && ' . a:cmd - return substitute(s:cmd_in_dir, "'", '"', 'g') -endfunction - -function! s:is_in_a_git_repo() - let cmd = s:escape('git rev-parse' . s:discard_stdout_and_stderr()) - call system(s:command_in_directory_of_file(cmd)) - return !v:shell_error -endfunction - -function! s:is_tracked_by_git() - let cmd = s:escape('git ls-files --error-unmatch' . s:discard_stdout_and_stderr() . ' ' . shellescape(s:file())) - call system(s:command_in_directory_of_file(cmd)) - return !v:shell_error -endfunction - -function! s:differences(hunks) - return len(a:hunks) != 0 -endfunction - -function! s:snake_case_to_camel_case(text) - return substitute(a:text, '\v(.)(\a+)(_(.)(.+))?', '\u\1\l\2\u\4\l\5', '') -endfunction - -" }}} - -" Highlights and signs {{{ - -function! s:define_sign_column_highlight() - highlight default link SignColumn LineNr -endfunction - -function! s:define_highlights() - " Highlights used by the signs. - highlight GitGutterAddDefault guifg=#009900 guibg=NONE ctermfg=2 ctermbg=NONE - highlight GitGutterChangeDefault guifg=#bbbb00 guibg=NONE ctermfg=3 ctermbg=NONE - highlight GitGutterDeleteDefault guifg=#ff2222 guibg=NONE ctermfg=1 ctermbg=NONE - highlight default link GitGutterChangeDeleteDefault GitGutterChangeDefault - - highlight default link GitGutterAdd GitGutterAddDefault - highlight default link GitGutterChange GitGutterChangeDefault - highlight default link GitGutterDelete GitGutterDeleteDefault - highlight default link GitGutterChangeDelete GitGutterChangeDeleteDefault - - " Highlights used for the whole line. - highlight default link GitGutterAddLine DiffAdd - highlight default link GitGutterChangeLine DiffChange - highlight default link GitGutterDeleteLine DiffDelete - highlight default link GitGutterChangeDeleteLine GitGutterChangeLineDefault -endfunction - -function! s:define_signs() - sign define GitGutterLineAdded - sign define GitGutterLineModified - sign define GitGutterLineRemoved - sign define GitGutterLineModifiedRemoved - sign define GitGutterDummy - - if g:gitgutter_signs - call s:define_sign_symbols() - call s:define_sign_text_highlights() - endif - call s:define_sign_line_highlights() -endfunction - -function! s:define_sign_symbols() - exe "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added - exe "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified - exe "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed - exe "sign define GitGutterLineModifiedRemoved text=" . g:gitgutter_sign_modified_removed -endfunction - -function! s:define_sign_text_highlights() - sign define GitGutterLineAdded texthl=GitGutterAdd - sign define GitGutterLineModified texthl=GitGutterChange - sign define GitGutterLineRemoved texthl=GitGutterDelete - sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDelete -endfunction - -function! s:define_sign_line_highlights() - if s:highlight_lines - sign define GitGutterLineAdded linehl=GitGutterAddLine - sign define GitGutterLineModified linehl=GitGutterChangeLine - sign define GitGutterLineRemoved linehl=GitGutterDeleteLine - sign define GitGutterLineModifiedRemoved linehl=GitGutterChangeDeleteLine - else - sign define GitGutterLineAdded linehl= - sign define GitGutterLineModified linehl= - sign define GitGutterLineRemoved linehl= - sign define GitGutterLineModifiedRemoved linehl= - endif - redraw! -endfunction - -" }}} - -" Diff processing {{{ - -function! s:run_diff(realtime) - if a:realtime - let blob_name = ':./' . fnamemodify(s:file(),':t') - let blob_file = tempname() - let cmd = 'git show ' . blob_name . ' > ' . blob_file . ' && diff -U0 ' . g:gitgutter_diff_args . ' ' . blob_file . ' - ' - else - let cmd = 'git diff --no-ext-diff --no-color -U0 ' . g:gitgutter_diff_args . ' ' . shellescape(s:file()) - endif - if s:grep_available - let cmd .= s:grep_command - endif - let cmd = s:escape(cmd) - if a:realtime - if &fileformat ==# "dos" - let eol = "\r\n" - elseif &fileformat ==# "mac" - let eol = "\r" - else - let eol = "\n" - endif - let buffer_contents = join(getline(1, '$'), eol) . eol - let diff = system(s:command_in_directory_of_file(cmd), buffer_contents) - else - let diff = system(s:command_in_directory_of_file(cmd)) - endif - return diff -endfunction - -function! s:parse_diff(diff) - let hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@' - let hunks = [] - for line in split(a:diff, '\n') - let matches = matchlist(line, hunk_re) - if len(matches) > 0 - let from_line = str2nr(matches[1]) - let from_count = (matches[2] == '') ? 1 : str2nr(matches[2]) - let to_line = str2nr(matches[3]) - let to_count = (matches[4] == '') ? 1 : str2nr(matches[4]) - call add(hunks, [from_line, from_count, to_line, to_count]) - endif - endfor - return hunks -endfunction - -function! s:process_hunks(hunks) - let s:hunk_summary = [0, 0, 0] - let modified_lines = [] - for hunk in a:hunks - call extend(modified_lines, s:process_hunk(hunk)) - endfor - return modified_lines -endfunction - -function! s:process_hunk(hunk) - let modifications = [] - let from_line = a:hunk[0] - let from_count = a:hunk[1] - let to_line = a:hunk[2] - let to_count = a:hunk[3] - - if s:is_added(from_count, to_count) - call s:process_added(modifications, from_count, to_count, to_line) - let s:hunk_summary[0] += to_count - - elseif s:is_removed(from_count, to_count) - call s:process_removed(modifications, from_count, to_count, to_line) - let s:hunk_summary[2] += from_count - - elseif s:is_modified(from_count, to_count) - call s:process_modified(modifications, from_count, to_count, to_line) - let s:hunk_summary[1] += to_count - - elseif s:is_modified_and_added(from_count, to_count) - call s:process_modified_and_added(modifications, from_count, to_count, to_line) - let s:hunk_summary[0] += to_count - from_count - let s:hunk_summary[1] += from_count - - elseif s:is_modified_and_removed(from_count, to_count) - call s:process_modified_and_removed(modifications, from_count, to_count, to_line) - let s:hunk_summary[1] += to_count - let s:hunk_summary[2] += from_count - to_count - - endif - return modifications -endfunction - -" }}} - -" Diff utility {{{ - -function! s:is_added(from_count, to_count) - return a:from_count == 0 && a:to_count > 0 -endfunction - -function! s:is_removed(from_count, to_count) - return a:from_count > 0 && a:to_count == 0 -endfunction - -function! s:is_modified(from_count, to_count) - return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count -endfunction - -function! s:is_modified_and_added(from_count, to_count) - return a:from_count > 0 && a:to_count > 0 && a:from_count < a:to_count -endfunction - -function! s:is_modified_and_removed(from_count, to_count) - return a:from_count > 0 && a:to_count > 0 && a:from_count > a:to_count -endfunction - -function! s:process_added(modifications, from_count, to_count, to_line) - let offset = 0 - while offset < a:to_count - let line_number = a:to_line + offset - call add(a:modifications, [line_number, 'added']) - let offset += 1 - endwhile -endfunction - -function! s:process_removed(modifications, from_count, to_count, to_line) - call add(a:modifications, [a:to_line, 'removed']) -endfunction - -function! s:process_modified(modifications, from_count, to_count, to_line) - let offset = 0 - while offset < a:to_count - let line_number = a:to_line + offset - call add(a:modifications, [line_number, 'modified']) - let offset += 1 - endwhile -endfunction - -function! s:process_modified_and_added(modifications, from_count, to_count, to_line) - let offset = 0 - while offset < a:from_count - let line_number = a:to_line + offset - call add(a:modifications, [line_number, 'modified']) - let offset += 1 - endwhile - while offset < a:to_count - let line_number = a:to_line + offset - call add(a:modifications, [line_number, 'added']) - let offset += 1 - endwhile -endfunction - -function! s:process_modified_and_removed(modifications, from_count, to_count, to_line) - let offset = 0 - while offset < a:to_count - let line_number = a:to_line + offset - call add(a:modifications, [line_number, 'modified']) - let offset += 1 - endwhile - call add(a:modifications, [a:to_line + offset - 1, 'modified_removed']) -endfunction - -" }}} - -" Sign processing {{{ - -function! s:clear_signs(file_name) - if exists('s:sign_ids') && has_key(s:sign_ids, a:file_name) - for id in s:sign_ids[a:file_name] - exe ":sign unplace" id "file=" . a:file_name - endfor - let s:sign_ids[a:file_name] = [] - endif -endfunction - -" This assumes there are no GitGutter signs in the file. -" If this is untenable we could change the regexp to exclude GitGutter's -" signs. -function! s:find_other_signs(file_name) - redir => signs - silent exe ":sign place file=" . a:file_name - redir END - let s:other_signs = [] - for sign_line in split(signs, '\n') - if sign_line =~ '^\s\+\w\+=' - let matches = matchlist(sign_line, '^\s\+\w\+=\(\d\+\)') - let line_number = str2nr(matches[1]) - call add(s:other_signs, line_number) - endif - endfor -endfunction - -function! s:show_signs(file_name, modified_lines) - for line in a:modified_lines - let line_number = line[0] - let type = 'GitGutterLine' . s:snake_case_to_camel_case(line[1]) - call s:add_sign(line_number, type, a:file_name) - endfor -endfunction - -function! s:add_sign(line_number, name, file_name) - let id = s:next_sign_id() - if !s:is_other_sign(a:line_number) " Don't clobber other people's signs. - exe ":sign place" id "line=" . a:line_number "name=" . a:name "file=" . a:file_name - call s:remember_sign(id, a:file_name) - endif -endfunction - -function! s:next_sign_id() - let next_id = s:next_sign_id - let s:next_sign_id += 1 - return next_id -endfunction - -function! s:remember_sign(id, file_name) - if has_key(s:sign_ids, a:file_name) - let sign_ids_for_file = s:sign_ids[a:file_name] - call add(sign_ids_for_file, a:id) - else - let sign_ids_for_file = [a:id] - endif - let s:sign_ids[a:file_name] = sign_ids_for_file -endfunction - -function! s:is_other_sign(line_number) - return index(s:other_signs, a:line_number) == -1 ? 0 : 1 -endfunction - -function! s:add_dummy_sign() - let last_line = line('$') - exe ":sign place" s:dummy_sign_id "line=" . (last_line + 1) "name=GitGutterDummy file=" . s:file() -endfunction - -function! s:remove_dummy_sign() - if exists('s:dummy_sign_id') - exe ":sign unplace" s:dummy_sign_id "file=" . s:file() - endif -endfunction - -" }}} " Public interface {{{ @@ -460,45 +52,45 @@ command GitGutterAll call GitGutterAll() " Supply optional argument to use realtime mode. function! GitGutter(file, ...) - call s:set_file(a:file) - if s:is_active() + call utility#set_file(a:file) + if utility#is_active() call s:init() - if (a:0 == 1) || s:has_unsaved_changes(a:file) - let diff = s:run_diff(1) + if (a:0 == 1) || utility#has_unsaved_changes(a:file) + let diff = diff#run_diff(1) else - let diff = s:run_diff(0) + let diff = diff#run_diff(0) endif - let s:hunks = s:parse_diff(diff) - let modified_lines = s:process_hunks(s:hunks) + let s:hunks = diff#parse_diff(diff) + let modified_lines = diff#process_hunks(s:hunks) if g:gitgutter_sign_column_always - call s:add_dummy_sign() + call sign#add_dummy_sign() else - if s:differences(s:hunks) - call s:add_dummy_sign() " prevent flicker + if utility#differences(s:hunks) + call sign#add_dummy_sign() " prevent flicker else - call s:remove_dummy_sign() + call sign#remove_dummy_sign() endif endif - call s:clear_signs(a:file) - call s:find_other_signs(a:file) - call s:show_signs(a:file, modified_lines) + call sign#clear_signs(a:file) + call sign#find_other_signs(a:file) + call sign#show_signs(a:file, modified_lines) else - call s:reset_hunk_summary() + call hunk#reset() endif endfunction -command GitGutter call GitGutter(s:current_file()) +command GitGutter call GitGutter(utility#current_file()) function! GitGutterDisable() let g:gitgutter_enabled = 0 - call s:clear_signs(s:file()) - call s:remove_dummy_sign() - call s:reset_hunk_summary() + call sign#clear_signs(utility#file()) + call sign#remove_dummy_sign() + call hunk#reset() endfunction command GitGutterDisable call GitGutterDisable() function! GitGutterEnable() let g:gitgutter_enabled = 1 - call GitGutter(s:current_file()) + call GitGutter(utility#current_file()) endfunction command GitGutterEnable call GitGutterEnable() @@ -512,25 +104,25 @@ endfunction command GitGutterToggle call GitGutterToggle() function! GitGutterLineHighlightsDisable() - let s:highlight_lines = 0 - call s:define_sign_line_highlights() + let g:gitgutter_highlight_lines = 0 + call highlight#define_sign_line_highlights() endfunction command GitGutterLineHighlightsDisable call GitGutterLineHighlightsDisable() function! GitGutterLineHighlightsEnable() - let s:highlight_lines = 1 - call s:define_sign_line_highlights() + let g:gitgutter_highlight_lines = 1 + call highlight#define_sign_line_highlights() endfunction command GitGutterLineHighlightsEnable call GitGutterLineHighlightsEnable() function! GitGutterLineHighlightsToggle() - let s:highlight_lines = (s:highlight_lines ? 0 : 1) - call s:define_sign_line_highlights() + let g:gitgutter_highlight_lines = (g:gitgutter_highlight_lines ? 0 : 1) + call highlight#define_sign_line_highlights() endfunction command GitGutterLineHighlightsToggle call GitGutterLineHighlightsToggle() function! GitGutterNextHunk(count) - if s:is_active() + if utility#is_active() let current_line = line('.') let hunk_count = 0 for hunk in s:hunks @@ -547,7 +139,7 @@ endfunction command -count=1 GitGutterNextHunk call GitGutterNextHunk() function! GitGutterPrevHunk(count) - if s:is_active() + if utility#is_active() let current_line = line('.') let hunk_count = 0 for hunk in reverse(copy(s:hunks)) @@ -581,14 +173,14 @@ command -count=1 GitGutterPrevHunk call GitGutterPrevHunk() " `line` - refers to the line number where the change starts " `count` - refers to the number of lines the change covers function! GitGutterGetHunks() - return s:is_active() ? s:hunks : [] + return utility#is_active() ? s:hunks : [] endfunction " Returns an array that contains a summary of the current hunk status. " The format is [ added, modified, removed ], where each value represents " the number of lines added/modified/removed respectively. function! GitGutterGetHunkSummary() - return s:hunk_summary + return hunk#summary() endfunction nnoremap GitGutterNextHunk :execute v:count1 . "GitGutterNextHunk" @@ -603,19 +195,19 @@ augroup gitgutter autocmd! if g:gitgutter_realtime - autocmd CursorHold,CursorHoldI * call GitGutter(s:current_file(), 1) + autocmd CursorHold,CursorHoldI * call GitGutter(utility#current_file(), 1) endif if g:gitgutter_eager - autocmd BufEnter,BufWritePost,FileWritePost,FileChangedShellPost * call GitGutter(s:current_file()) + autocmd BufEnter,BufWritePost,FileWritePost,FileChangedShellPost * call GitGutter(utility#current_file()) autocmd TabEnter * call GitGutterAll() if !has('gui_win32') autocmd FocusGained * call GitGutterAll() endif else - autocmd BufReadPost,BufWritePost,FileReadPost,FileWritePost,FileChangedShellPost * call GitGutter(s:current_file()) + autocmd BufReadPost,BufWritePost,FileReadPost,FileWritePost,FileChangedShellPost * call GitGutter(utility#current_file()) endif - autocmd ColorScheme * call s:define_sign_column_highlight() | call s:define_highlights() + autocmd ColorScheme * call highlight#define_sign_column_highlight() | call highlight#define_highlights() augroup END " }}}