1
0
mirror of https://github.com/amix/vimrc synced 2025-07-07 00:15:00 +08:00

gitignore sources_non_forked_cache

This commit is contained in:
Wu Tingfeng
2022-11-21 23:06:50 +08:00
parent aff5c8f75a
commit 378168c979
1752 changed files with 208511 additions and 1 deletions

View File

@ -0,0 +1,107 @@
let s:available = has('nvim') || (
\ has('job') && (
\ (has('patch-7.4.1826') && !has('gui_running')) ||
\ (has('patch-7.4.1850') && has('gui_running')) ||
\ (has('patch-7.4.1832') && has('gui_macvim'))
\ )
\ )
let s:jobs = {}
function! gitgutter#async#available()
return s:available
endfunction
function! gitgutter#async#execute(cmd, bufnr, handler) abort
call gitgutter#debug#log('[async] '.a:cmd)
let options = {
\ 'stdoutbuffer': [],
\ 'buffer': a:bufnr,
\ 'handler': a:handler
\ }
let command = s:build_command(a:cmd)
if has('nvim')
call jobstart(command, extend(options, {
\ 'on_stdout': function('s:on_stdout_nvim'),
\ 'on_stderr': function('s:on_stderr_nvim'),
\ 'on_exit': function('s:on_exit_nvim')
\ }))
else
let job = job_start(command, {
\ 'out_cb': function('s:on_stdout_vim', options),
\ 'err_cb': function('s:on_stderr_vim', options),
\ 'close_cb': function('s:on_exit_vim', options)
\ })
let s:jobs[s:job_id(job)] = 1
endif
endfunction
function! s:build_command(cmd)
if has('unix')
return ['sh', '-c', a:cmd]
endif
if has('win32')
return has('nvim') ? ['cmd.exe', '/c', a:cmd] : 'cmd.exe /c '.a:cmd
endif
throw 'unknown os'
endfunction
function! s:on_stdout_nvim(_job_id, data, _event) dict abort
if empty(self.stdoutbuffer)
let self.stdoutbuffer = a:data
else
let self.stdoutbuffer = self.stdoutbuffer[:-2] +
\ [self.stdoutbuffer[-1] . a:data[0]] +
\ a:data[1:]
endif
endfunction
function! s:on_stderr_nvim(_job_id, data, _event) dict abort
if a:data != [''] " With Neovim there is always [''] reported on stderr.
call self.handler.err(self.buffer)
endif
endfunction
function! s:on_exit_nvim(_job_id, exit_code, _event) dict abort
if !a:exit_code
call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
endif
endfunction
function! s:on_stdout_vim(_channel, data) dict abort
call add(self.stdoutbuffer, a:data)
endfunction
function! s:on_stderr_vim(channel, _data) dict abort
call self.handler.err(self.buffer)
endfunction
function! s:on_exit_vim(channel) dict abort
let job = ch_getjob(a:channel)
let jobid = s:job_id(job)
if has_key(s:jobs, jobid) | unlet s:jobs[jobid] | endif
while 1
if job_status(job) == 'dead'
let exit_code = job_info(job).exitval
break
endif
sleep 5m
endwhile
if !exit_code
call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
endif
endfunction
function! s:job_id(job)
" Vim
return job_info(a:job).process
endfunction

View File

@ -0,0 +1,109 @@
let s:plugin_dir = expand('<sfile>:p:h:h:h').'/'
let s:log_file = s:plugin_dir.'gitgutter.log'
let s:channel_log = s:plugin_dir.'channel.log'
let s:new_log_session = 1
function! gitgutter#debug#debug()
" Open a scratch buffer
vsplit __GitGutter_Debug__
normal! ggdG
setlocal buftype=nofile
setlocal bufhidden=delete
setlocal noswapfile
call s:vim_version()
call s:separator()
call s:git_version()
call s:separator()
call s:grep_version()
call s:separator()
call s:option('updatetime')
endfunction
function! s:separator()
call s:output('')
endfunction
function! s:vim_version()
redir => version_info
silent execute 'version'
redir END
call s:output(split(version_info, '\n')[0:2])
endfunction
function! s:git_version()
let v = system(g:gitgutter_git_executable.' --version')
call s:output( substitute(v, '\n$', '', '') )
endfunction
function! s:grep_version()
let v = system(g:gitgutter_grep.' --version')
call s:output( substitute(v, '\n$', '', '') )
let v = system(g:gitgutter_grep.' --help')
call s:output( substitute(v, '\%x00', '', 'g') )
endfunction
function! s:option(name)
if exists('+' . a:name)
let v = eval('&' . a:name)
call s:output(a:name . '=' . v)
" redir => output
" silent execute "verbose set " . a:name . "?"
" redir END
" call s:output(a:name . '=' . output)
else
call s:output(a:name . ' [n/a]')
end
endfunction
function! s:output(text)
call append(line('$'), a:text)
endfunction
" assumes optional args are calling function's optional args
function! gitgutter#debug#log(message, ...) abort
if g:gitgutter_log
if s:new_log_session && gitgutter#async#available()
if exists('*ch_logfile')
call ch_logfile(s:channel_log, 'w')
endif
endif
execute 'redir >> '.s:log_file
if s:new_log_session
let s:start = reltime()
silent echo "\n==== start log session ===="
endif
let elapsed = reltimestr(reltime(s:start)).' '
silent echo ''
" callers excluding this function
silent echo elapsed.expand('<sfile>')[:-22].':'
silent echo elapsed.s:format_for_log(a:message)
if a:0 && !empty(a:1)
for msg in a:000
silent echo elapsed.s:format_for_log(msg)
endfor
endif
redir END
let s:new_log_session = 0
endif
endfunction
function! s:format_for_log(data) abort
if type(a:data) == 1
return join(split(a:data,'\n'),"\n")
elseif type(a:data) == 3
return '['.join(a:data,"\n").']'
else
return a:data
endif
endfunction

View File

@ -0,0 +1,421 @@
scriptencoding utf8
let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
" True for git v1.7.2+.
function! s:git_supports_command_line_config_override() abort
call gitgutter#utility#system(g:gitgutter_git_executable.' '.g:gitgutter_git_args.' -c foo.bar=baz --version')
return !v:shell_error
endfunction
let s:c_flag = s:git_supports_command_line_config_override()
let s:temp_from = tempname()
let s:temp_buffer = tempname()
let s:counter = 0
" Returns a diff of the buffer against the index or the working tree.
"
" After running the diff we pass it through grep where available to reduce
" subsequent processing by the plugin. If grep is not available the plugin
" does the filtering instead.
"
" When diffing against the index:
"
" The buffer contents is not the same as the file on disk so we need to pass
" two instances of the file to git-diff:
"
" git diff myfileA myfileB
"
" where myfileA comes from
"
" git show :myfile > myfileA
"
" and myfileB is the buffer contents.
"
" Regarding line endings:
"
" git-show does not convert line endings.
" git-diff FILE FILE does convert line endings for the given files.
"
" If a file has CRLF line endings and git's core.autocrlf is true,
" the file in git's object store will have LF line endings. Writing
" it out via git-show will produce a file with LF line endings.
"
" If this last file is one of the files passed to git-diff, git-diff will
" convert its line endings to CRLF before diffing -- which is what we want --
" but also by default output a warning on stderr.
"
" warning: LF will be replace by CRLF in <temp file>.
" The file will have its original line endings in your working directory.
"
" When running the diff asynchronously, the warning message triggers the stderr
" callbacks which assume the overall command has failed and reset all the
" signs. As this is not what we want, and we can safely ignore the warning,
" we turn it off by passing the '-c "core.safecrlf=false"' argument to
" git-diff.
"
" When writing the temporary files we preserve the original file's extension
" so that repos using .gitattributes to control EOL conversion continue to
" convert correctly.
"
" Arguments:
"
" bufnr - the number of the buffer to be diffed
" from - 'index' or 'working_tree'; what the buffer is diffed against
" preserve_full_diff - truthy to return the full diff or falsey to return only
" the hunk headers (@@ -x,y +m,n @@); only possible if
" grep is available.
function! gitgutter#diff#run_diff(bufnr, from, preserve_full_diff) abort
if gitgutter#utility#repo_path(a:bufnr, 0) == -1
throw 'gitgutter path not set'
endif
if gitgutter#utility#repo_path(a:bufnr, 0) == -2
throw 'gitgutter not tracked'
endif
if gitgutter#utility#repo_path(a:bufnr, 0) == -3
throw 'gitgutter assume unchanged'
endif
" Wrap compound commands in parentheses to make Windows happy.
" bash doesn't mind the parentheses.
let cmd = '('
" Append buffer number to temp filenames to avoid race conditions between
" writing and reading the files when asynchronously processing multiple
" buffers.
" Without the buffer number, buff_file would have a race between the
" second gitgutter#process_buffer() writing the file (synchronously, below)
" and the first gitgutter#process_buffer()'s async job reading it (with
" git-diff).
let buff_file = s:temp_buffer.'.'.a:bufnr
" Add a counter to avoid a similar race with two quick writes of the same buffer.
" Use a modulus greater than a maximum reasonable number of visible buffers.
let s:counter = (s:counter + 1) % 20
let buff_file .= '.'.s:counter
let extension = gitgutter#utility#extension(a:bufnr)
if !empty(extension)
let buff_file .= '.'.extension
endif
" Write buffer to temporary file.
" Note: this is synchronous.
call s:write_buffer(a:bufnr, buff_file)
if a:from ==# 'index'
" Without the buffer number, from_file would have a race in the shell
" between the second process writing it (with git-show) and the first
" reading it (with git-diff).
let from_file = s:temp_from.'.'.a:bufnr
" Add a counter to avoid a similar race with two quick writes of the same buffer.
let from_file .= '.'.s:counter
if !empty(extension)
let from_file .= '.'.extension
endif
" Write file from index to temporary file.
let index_name = gitgutter#utility#get_diff_base(a:bufnr).':'.gitgutter#utility#repo_path(a:bufnr, 1)
let cmd .= g:gitgutter_git_executable.' '.g:gitgutter_git_args.' --no-pager show '.index_name.' > '.from_file.' && '
elseif a:from ==# 'working_tree'
let from_file = gitgutter#utility#repo_path(a:bufnr, 1)
endif
" Call git-diff.
let cmd .= g:gitgutter_git_executable.' '.g:gitgutter_git_args.' --no-pager'
if s:c_flag
let cmd .= ' -c "diff.autorefreshindex=0"'
let cmd .= ' -c "diff.noprefix=false"'
let cmd .= ' -c "core.safecrlf=false"'
endif
let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.from_file.' '.buff_file
" Pipe git-diff output into grep.
if !a:preserve_full_diff && !empty(g:gitgutter_grep)
let cmd .= ' | '.g:gitgutter_grep.' '.gitgutter#utility#shellescape('^@@ ')
endif
" grep exits with 1 when no matches are found; git-diff exits with 1 when
" differences are found. However we want to treat non-matches and
" differences as non-erroneous behaviour; so we OR the command with one
" which always exits with success (0).
let cmd .= ' || exit 0'
let cmd .= ')'
let cmd = gitgutter#utility#cd_cmd(a:bufnr, cmd)
if g:gitgutter_async && gitgutter#async#available()
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': function('gitgutter#diff#handler'),
\ 'err': function('gitgutter#hunk#reset'),
\ })
return 'async'
else
let diff = gitgutter#utility#system(cmd)
if v:shell_error
call gitgutter#debug#log(diff)
throw 'gitgutter diff failed'
endif
return diff
endif
endfunction
function! gitgutter#diff#handler(bufnr, diff) abort
call gitgutter#debug#log(a:diff)
if !bufexists(a:bufnr)
return
endif
call gitgutter#hunk#set_hunks(a:bufnr, gitgutter#diff#parse_diff(a:diff))
let modified_lines = gitgutter#diff#process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr))
let signs_count = len(modified_lines)
if g:gitgutter_max_signs != -1 && signs_count > g:gitgutter_max_signs
call gitgutter#utility#warn_once(a:bufnr, printf(
\ 'exceeded maximum number of signs (%d > %d, configured by g:gitgutter_max_signs).',
\ signs_count, g:gitgutter_max_signs), 'max_signs')
call gitgutter#sign#clear_signs(a:bufnr)
else
if g:gitgutter_signs || g:gitgutter_highlight_lines || g:gitgutter_highlight_linenrs
call gitgutter#sign#update_signs(a:bufnr, modified_lines)
endif
endif
call s:save_last_seen_change(a:bufnr)
if exists('#User#GitGutter')
let g:gitgutter_hook_context = {'bufnr': a:bufnr}
execute 'doautocmd' s:nomodeline 'User GitGutter'
unlet g:gitgutter_hook_context
endif
endfunction
function! gitgutter#diff#parse_diff(diff) abort
let hunks = []
for line in split(a:diff, '\n')
let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4
call add(hunks, hunk_info)
endif
endfor
return hunks
endfunction
function! gitgutter#diff#parse_hunk(line) abort
let matches = matchlist(a:line, s: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])
return [from_line, from_count, to_line, to_count]
else
return []
end
endfunction
" This function is public so it may be used by other plugins
" e.g. vim-signature.
function! gitgutter#diff#process_hunks(bufnr, hunks) abort
let modified_lines = []
for hunk in a:hunks
call extend(modified_lines, s:process_hunk(a:bufnr, hunk))
endfor
return modified_lines
endfunction
" Returns [ [<line_number (number)>, <name (string)>], ...]
function! s:process_hunk(bufnr, hunk) abort
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)
call gitgutter#hunk#increment_lines_added(a:bufnr, to_count)
elseif s:is_removed(from_count, to_count)
call s:process_removed(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count)
elseif s:is_modified(from_count, to_count)
call s:process_modified(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_modified(a:bufnr, 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)
call gitgutter#hunk#increment_lines_added(a:bufnr, to_count - from_count)
call gitgutter#hunk#increment_lines_modified(a:bufnr, 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)
call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count - to_count)
endif
return modifications
endfunction
function! s:is_added(from_count, to_count) abort
return a:from_count == 0 && a:to_count > 0
endfunction
function! s:is_removed(from_count, to_count) abort
return a:from_count > 0 && a:to_count == 0
endfunction
function! s:is_modified(from_count, to_count) abort
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) abort
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) abort
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) abort
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) abort
if a:to_line == 0
call add(a:modifications, [1, 'removed_first_line'])
else
call add(a:modifications, [a:to_line, 'removed'])
endif
endfunction
function! s:process_modified(modifications, from_count, to_count, to_line) abort
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) abort
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) abort
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
let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
endfunction
" Returns a diff for the current hunk.
" Assumes there is only 1 current hunk unless the optional argument is given,
" in which case the cursor is in two hunks and the argument specifies the one
" to choose.
"
" Optional argument: 0 (to use the first hunk) or 1 (to use the second).
function! gitgutter#diff#hunk_diff(bufnr, full_diff, ...)
let modified_diff = []
let hunk_index = 0
let keep_line = 1
" Don't keepempty when splitting because the diff we want may not be the
" final one. Instead add trailing NL at end of function.
for line in split(a:full_diff, '\n')
let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4 " start of new hunk
let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info)
if a:0 && hunk_index != a:1
let keep_line = 0
endif
let hunk_index += 1
endif
if keep_line
call add(modified_diff, line)
endif
endfor
return join(modified_diff, "\n")."\n"
endfunction
function! s:write_buffer(bufnr, file)
let bufcontents = getbufline(a:bufnr, 1, '$')
if bufcontents == [''] && line2byte(1) == -1
" Special case: completely empty buffer.
" A nearly empty buffer of only a newline has line2byte(1) == 1.
call writefile([], a:file)
return
endif
if getbufvar(a:bufnr, '&fileformat') ==# 'dos'
call map(bufcontents, 'v:val."\r"')
endif
if getbufvar(a:bufnr, '&endofline')
call add(bufcontents, '')
endif
let fenc = getbufvar(a:bufnr, '&fileencoding')
if fenc !=# &encoding
call map(bufcontents, 'iconv(v:val, &encoding, "'.fenc.'")')
endif
if getbufvar(a:bufnr, '&bomb')
let bufcontents[0]=''.bufcontents[0]
endif
" The file we are writing to is a temporary file. Sometimes the parent
" directory is deleted outside Vim but, because Vim caches the directory
" name at startup and does not check for its existence subsequently, Vim
" does not realise. This causes E482 errors.
try
call writefile(bufcontents, a:file, 'b')
catch /E482/
call mkdir(fnamemodify(a:file, ':h'), '', '0700')
call writefile(bufcontents, a:file, 'b')
endtry
endfunction
function! s:save_last_seen_change(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'tick', getbufvar(a:bufnr, 'changedtick'))
endfunction

View File

@ -0,0 +1,201 @@
" This is the minimum number of characters required between regions of change
" in a line. It's somewhat arbitrary: higher values mean less visual busyness;
" lower values mean more detail.
let s:gap_between_regions = 5
" Calculates the changed portions of lines.
"
" Based on:
"
" - diff-highlight (included with git)
" https://github.com/git/git/blob/master/contrib/diff-highlight/DiffHighlight.pm
"
" - Diff Strategies, Neil Fraser
" https://neil.fraser.name/writing/diff/
" Returns a list of intra-line changed regions.
" Each element is a list:
"
" [
" line number (1-based),
" type ('+' or '-'),
" start column (1-based, inclusive),
" stop column (1-based, inclusive),
" ]
"
" Args:
" hunk_body - list of lines
function! gitgutter#diff_highlight#process(hunk_body)
" Check whether we have the same number of lines added as removed.
let [removed, added] = [0, 0]
for line in a:hunk_body
if line[0] == '-'
let removed += 1
elseif line[0] == '+'
let added += 1
endif
endfor
if removed != added
return []
endif
let regions = []
for i in range(removed)
" pair lines by position
let rline = a:hunk_body[i]
let aline = a:hunk_body[i + removed]
call s:diff(rline, aline, i, i+removed, 0, 0, regions, 1)
endfor
return regions
endfunction
function! s:diff(rline, aline, rlinenr, alinenr, rprefix, aprefix, regions, whole_line)
" diff marker does not count as a difference in prefix
let start = a:whole_line ? 1 : 0
let prefix = s:common_prefix(a:rline[start:], a:aline[start:])
if a:whole_line
let prefix += 1
endif
let [rsuffix, asuffix] = s:common_suffix(a:rline, a:aline, prefix+1)
" region of change (common prefix and suffix removed)
let rtext = a:rline[prefix+1:rsuffix-1]
let atext = a:aline[prefix+1:asuffix-1]
" singular insertion
if empty(rtext)
if !a:whole_line || len(atext) != len(a:aline) " not whole line
call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+asuffix+1-1])
endif
return
endif
" singular deletion
if empty(atext)
if !a:whole_line || len(rtext) != len(a:rline) " not whole line
call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+rsuffix+1-1])
endif
return
endif
" two insertions
let j = stridx(atext, rtext)
if j != -1
call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+prefix+j+1])
call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1+j+len(rtext), a:aprefix+asuffix+1-1])
return
endif
" two deletions
let j = stridx(rtext, atext)
if j != -1
call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+prefix+j+1])
call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1+j+len(atext), a:rprefix+rsuffix+1-1])
return
endif
" two edits
let lcs = s:lcs(rtext, atext)
" TODO do we need to ensure we don't get more than 2 elements when splitting?
if len(lcs) > s:gap_between_regions
let redits = s:split(rtext, lcs)
let aedits = s:split(atext, lcs)
call s:diff(redits[0], aedits[0], a:rlinenr, a:alinenr, a:rprefix+prefix+1, a:aprefix+prefix+1, a:regions, 0)
call s:diff(redits[1], aedits[1], a:rlinenr, a:alinenr, a:rprefix+prefix+1+len(redits[0])+len(lcs), a:aprefix+prefix+1+len(aedits[0])+len(lcs), a:regions, 0)
return
endif
" fall back to highlighting entire changed area
" if a change (but not the whole line)
if !a:whole_line || ((prefix != 0 || rsuffix != len(a:rline)) && prefix+1 < rsuffix)
call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+rsuffix+1-1])
endif
" if a change (but not the whole line)
if !a:whole_line || ((prefix != 0 || asuffix != len(a:aline)) && prefix+1 < asuffix)
call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+asuffix+1-1])
endif
endfunction
function! s:lcs(s1, s2)
if empty(a:s1) || empty(a:s2)
return ''
endif
let matrix = map(repeat([repeat([0], len(a:s2)+1)], len(a:s1)+1), 'copy(v:val)')
let maxlength = 0
let endindex = len(a:s1)
for i in range(1, len(a:s1))
for j in range(1, len(a:s2))
if a:s1[i-1] ==# a:s2[j-1]
let matrix[i][j] = 1 + matrix[i-1][j-1]
if matrix[i][j] > maxlength
let maxlength = matrix[i][j]
let endindex = i - 1
endif
endif
endfor
endfor
return a:s1[endindex - maxlength + 1 : endindex]
endfunction
" Returns 0-based index of last character of common prefix
" If there is no common prefix, returns -1.
"
" a, b - strings
"
function! s:common_prefix(a, b)
let len = min([len(a:a), len(a:b)])
if len == 0
return -1
endif
for i in range(len)
if a:a[i:i] !=# a:b[i:i]
return i - 1
endif
endfor
return i
endfunction
" Returns 0-based indices of start of common suffix
"
" a, b - strings
" start - 0-based index to start from
function! s:common_suffix(a, b, start)
let [sa, sb] = [len(a:a), len(a:b)]
while sa >= a:start && sb >= a:start
if a:a[sa] ==# a:b[sb]
let sa -= 1
let sb -= 1
else
break
endif
endwhile
return [sa+1, sb+1]
endfunction
" Split a string on another string.
" Assumes 1 occurrence of the delimiter.
function! s:split(str, delimiter)
let i = stridx(a:str, a:delimiter)
if i == 0
return ['', a:str[len(a:delimiter):]]
endif
return [a:str[:i-1], a:str[i+len(a:delimiter):]]
endfunction

View File

@ -0,0 +1,115 @@
function! gitgutter#fold#enable()
call s:save_fold_state()
call s:set_fold_levels()
setlocal foldexpr=gitgutter#fold#level(v:lnum)
setlocal foldmethod=expr
setlocal foldlevel=0
setlocal foldenable
call gitgutter#utility#setbufvar(bufnr(''), 'folded', 1)
endfunction
function! gitgutter#fold#disable()
call s:restore_fold_state()
call gitgutter#utility#setbufvar(bufnr(''), 'folded', 0)
endfunction
function! gitgutter#fold#toggle()
if s:folded()
call gitgutter#fold#disable()
else
call gitgutter#fold#enable()
endif
endfunction
function! gitgutter#fold#level(lnum)
return gitgutter#utility#getbufvar(bufnr(''), 'fold_levels')[a:lnum]
endfunction
function! gitgutter#fold#foldtext()
if !gitgutter#fold#is_changed()
return foldtext()
endif
return substitute(foldtext(), ':', ' (*):', '')
endfunction
" Returns 1 if any of the folded lines have been changed
" (added, removed, or modified), 0 otherwise.
function! gitgutter#fold#is_changed()
for hunk in gitgutter#hunk#hunks(bufnr(''))
let hunk_begin = hunk[2]
let hunk_end = hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
if hunk_end < v:foldstart
continue
endif
if hunk_begin > v:foldend
break
endif
return 1
endfor
return 0
endfunction
" A line in a hunk has a fold level of 0.
" A line within 3 lines of a hunk has a fold level of 1.
" All other lines have a fold level of 2.
function! s:set_fold_levels()
let fold_levels = ['']
for lnum in range(1, line('$'))
let in_hunk = gitgutter#hunk#in_hunk(lnum)
call add(fold_levels, (in_hunk ? 0 : 2))
endfor
let lines_of_context = 3
for lnum in range(1, line('$'))
if fold_levels[lnum] == 2
let pre = lnum >= 3 ? lnum - lines_of_context : 0
let post = lnum + lines_of_context
if index(fold_levels[pre:post], 0) != -1
let fold_levels[lnum] = 1
endif
endif
endfor
call gitgutter#utility#setbufvar(bufnr(''), 'fold_levels', fold_levels)
endfunction
function! s:save_fold_state()
let bufnr = bufnr('')
call gitgutter#utility#setbufvar(bufnr, 'foldlevel', &foldlevel)
call gitgutter#utility#setbufvar(bufnr, 'foldmethod', &foldmethod)
if &foldmethod ==# 'manual'
mkview
endif
endfunction
function! s:restore_fold_state()
let bufnr = bufnr('')
let &foldlevel = gitgutter#utility#getbufvar(bufnr, 'foldlevel')
let &foldmethod = gitgutter#utility#getbufvar(bufnr, 'foldmethod')
if &foldmethod ==# 'manual'
loadview
else
normal! zx
endif
endfunction
function! s:folded()
return gitgutter#utility#getbufvar(bufnr(''), 'folded')
endfunction

View File

@ -0,0 +1,245 @@
function! gitgutter#highlight#line_disable() abort
let g:gitgutter_highlight_lines = 0
call s:define_sign_line_highlights()
if !g:gitgutter_signs
call gitgutter#sign#clear_signs(bufnr(''))
endif
redraw!
endfunction
function! gitgutter#highlight#line_enable() abort
let old_highlight_lines = g:gitgutter_highlight_lines
let g:gitgutter_highlight_lines = 1
call s:define_sign_line_highlights()
if !old_highlight_lines && !g:gitgutter_signs
call gitgutter#all(1)
endif
redraw!
endfunction
function! gitgutter#highlight#line_toggle() abort
if g:gitgutter_highlight_lines
call gitgutter#highlight#line_disable()
else
call gitgutter#highlight#line_enable()
endif
endfunction
function! gitgutter#highlight#linenr_disable() abort
let g:gitgutter_highlight_linenrs = 0
call s:define_sign_linenr_highlights()
if !g:gitgutter_signs
call gitgutter#sign#clear_signs(bufnr(''))
endif
redraw!
endfunction
function! gitgutter#highlight#linenr_enable() abort
let old_highlight_linenrs = g:gitgutter_highlight_linenrs
let g:gitgutter_highlight_linenrs = 1
call s:define_sign_linenr_highlights()
if !old_highlight_linenrs && !g:gitgutter_signs
call gitgutter#all(1)
endif
redraw!
endfunction
function! gitgutter#highlight#linenr_toggle() abort
if g:gitgutter_highlight_linenrs
call gitgutter#highlight#linenr_disable()
else
call gitgutter#highlight#linenr_enable()
endif
endfunction
function! gitgutter#highlight#define_highlights() abort
let [guibg, ctermbg] = s:get_background_colors('SignColumn')
" Highlights used by the signs.
" When they are invisible.
execute "highlight GitGutterAddInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
execute "highlight GitGutterChangeInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
execute "highlight GitGutterDeleteInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
highlight default link GitGutterChangeDeleteInvisible GitGutterChangeInvisible
" When they are visible.
for type in ["Add", "Change", "Delete"]
if hlexists("GitGutter".type) && s:get_foreground_colors("GitGutter".type) != ['NONE', 'NONE']
if g:gitgutter_set_sign_backgrounds
execute "highlight GitGutter".type." guibg=".guibg." ctermbg=".ctermbg
endif
continue
elseif s:useful_diff_colours()
let [guifg, ctermfg] = s:get_foreground_colors('Diff'.type)
else
let [guifg, ctermfg] = s:get_foreground_fallback_colors(type)
endif
execute "highlight GitGutter".type." guifg=".guifg." guibg=".guibg." ctermfg=".ctermfg." ctermbg=".ctermbg
endfor
if hlexists("GitGutterChangeDelete") && g:gitgutter_set_sign_backgrounds
execute "highlight GitGutterChangeDelete guibg=".guibg." ctermbg=".ctermbg
endif
highlight default link GitGutterChangeDelete GitGutterChange
" 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 GitGutterChangeLine
highlight default link GitGutterAddLineNr CursorLineNr
highlight default link GitGutterChangeLineNr CursorLineNr
highlight default link GitGutterDeleteLineNr CursorLineNr
highlight default link GitGutterChangeDeleteLineNr CursorLineNr
" Highlights used intra line.
highlight default GitGutterAddIntraLine gui=reverse cterm=reverse
highlight default GitGutterDeleteIntraLine gui=reverse cterm=reverse
" Set diff syntax colours (used in the preview window) - diffAdded,diffChanged,diffRemoved -
" to match the signs, if not set aleady.
for [dtype,type] in [['Added','Add'], ['Changed','Change'], ['Removed','Delete']]
if !hlexists('diff'.dtype)
let [guifg, ctermfg] = s:get_foreground_colors('GitGutter'.type)
execute "highlight diff".dtype." guifg=".guifg." ctermfg=".ctermfg." guibg=NONE ctermbg=NONE"
endif
endfor
endfunction
function! gitgutter#highlight#define_signs() abort
sign define GitGutterLineAdded
sign define GitGutterLineModified
sign define GitGutterLineRemoved
sign define GitGutterLineRemovedFirstLine
sign define GitGutterLineRemovedAboveAndBelow
sign define GitGutterLineModifiedRemoved
call s:define_sign_text()
call gitgutter#highlight#define_sign_text_highlights()
call s:define_sign_line_highlights()
call s:define_sign_linenr_highlights()
endfunction
function! s:define_sign_text() abort
execute "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added
execute "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified
execute "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed
execute "sign define GitGutterLineRemovedFirstLine text=" . g:gitgutter_sign_removed_first_line
execute "sign define GitGutterLineRemovedAboveAndBelow text=" . g:gitgutter_sign_removed_above_and_below
execute "sign define GitGutterLineModifiedRemoved text=" . g:gitgutter_sign_modified_removed
endfunction
function! gitgutter#highlight#define_sign_text_highlights() abort
" Once a sign's text attribute has been defined, it cannot be undefined or
" set to an empty value. So to make signs' text disappear (when toggling
" off or disabling) we make them invisible by setting their foreground colours
" to the background's.
if g:gitgutter_signs
sign define GitGutterLineAdded texthl=GitGutterAdd
sign define GitGutterLineModified texthl=GitGutterChange
sign define GitGutterLineRemoved texthl=GitGutterDelete
sign define GitGutterLineRemovedFirstLine texthl=GitGutterDelete
sign define GitGutterLineRemovedAboveAndBelow texthl=GitGutterDelete
sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDelete
else
sign define GitGutterLineAdded texthl=GitGutterAddInvisible
sign define GitGutterLineModified texthl=GitGutterChangeInvisible
sign define GitGutterLineRemoved texthl=GitGutterDeleteInvisible
sign define GitGutterLineRemovedFirstLine texthl=GitGutterDeleteInvisible
sign define GitGutterLineRemovedAboveAndBelow texthl=GitGutterDeleteInvisible
sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDeleteInvisible
endif
endfunction
function! s:define_sign_line_highlights() abort
if g:gitgutter_highlight_lines
sign define GitGutterLineAdded linehl=GitGutterAddLine
sign define GitGutterLineModified linehl=GitGutterChangeLine
sign define GitGutterLineRemoved linehl=GitGutterDeleteLine
sign define GitGutterLineRemovedFirstLine linehl=GitGutterDeleteLine
sign define GitGutterLineRemovedAboveAndBelow linehl=GitGutterDeleteLine
sign define GitGutterLineModifiedRemoved linehl=GitGutterChangeDeleteLine
else
sign define GitGutterLineAdded linehl=NONE
sign define GitGutterLineModified linehl=NONE
sign define GitGutterLineRemoved linehl=NONE
sign define GitGutterLineRemovedFirstLine linehl=NONE
sign define GitGutterLineRemovedAboveAndBelow linehl=NONE
sign define GitGutterLineModifiedRemoved linehl=NONE
endif
endfunction
function! s:define_sign_linenr_highlights() abort
if has('nvim-0.3.2')
try
if g:gitgutter_highlight_linenrs
sign define GitGutterLineAdded numhl=GitGutterAddLineNr
sign define GitGutterLineModified numhl=GitGutterChangeLineNr
sign define GitGutterLineRemoved numhl=GitGutterDeleteLineNr
sign define GitGutterLineRemovedFirstLine numhl=GitGutterDeleteLineNr
sign define GitGutterLineRemovedAboveAndBelow numhl=GitGutterDeleteLineNr
sign define GitGutterLineModifiedRemoved numhl=GitGutterChangeDeleteLineNr
else
sign define GitGutterLineAdded numhl=NONE
sign define GitGutterLineModified numhl=NONE
sign define GitGutterLineRemoved numhl=NONE
sign define GitGutterLineRemovedFirstLine numhl=NONE
sign define GitGutterLineRemovedAboveAndBelow numhl=NONE
sign define GitGutterLineModifiedRemoved numhl=NONE
endif
catch /E475/
endtry
endif
endfunction
function! s:get_hl(group, what, mode) abort
let r = synIDattr(synIDtrans(hlID(a:group)), a:what, a:mode)
if empty(r) || r == -1
return 'NONE'
endif
return r
endfunction
function! s:get_foreground_colors(group) abort
let ctermfg = s:get_hl(a:group, 'fg', 'cterm')
let guifg = s:get_hl(a:group, 'fg', 'gui')
return [guifg, ctermfg]
endfunction
function! s:get_background_colors(group) abort
let ctermbg = s:get_hl(a:group, 'bg', 'cterm')
let guibg = s:get_hl(a:group, 'bg', 'gui')
return [guibg, ctermbg]
endfunction
function! s:useful_diff_colours()
let [guifg_add, ctermfg_add] = s:get_foreground_colors('DiffAdd')
let [guifg_del, ctermfg_del] = s:get_foreground_colors('DiffDelete')
return guifg_add != guifg_del && ctermfg_add != ctermfg_del
endfunction
function! s:get_foreground_fallback_colors(type)
if a:type == 'Add'
return ['#009900', '2']
elseif a:type == 'Change'
return ['#bbbb00', '3']
elseif a:type == 'Delete'
return ['#ff2222', '1']
endif
endfunction

View File

@ -0,0 +1,629 @@
let s:winid = 0
let s:preview_bufnr = 0
let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
function! gitgutter#hunk#set_hunks(bufnr, hunks) abort
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', a:hunks)
call s:reset_summary(a:bufnr)
endfunction
function! gitgutter#hunk#hunks(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'hunks', [])
endfunction
function! gitgutter#hunk#reset(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', [])
call s:reset_summary(a:bufnr)
endfunction
function! gitgutter#hunk#summary(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0])
endfunction
function! s:reset_summary(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'summary', [0,0,0])
endfunction
function! gitgutter#hunk#increment_lines_added(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[0] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#increment_lines_modified(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[1] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#increment_lines_removed(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[2] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#next_hunk(count) abort
let bufnr = bufnr('')
if !gitgutter#utility#is_active(bufnr) | return | endif
let hunks = gitgutter#hunk#hunks(bufnr)
if empty(hunks)
call gitgutter#utility#warn('No hunks in file')
return
endif
let current_line = line('.')
let hunk_count = 0
for hunk in hunks
if hunk[2] > current_line
let hunk_count += 1
if hunk_count == a:count
execute 'normal!' hunk[2] . 'Gzv'
if g:gitgutter_show_msg_on_hunk_jumping
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
endif
if gitgutter#hunk#is_preview_window_open()
call gitgutter#hunk#preview()
endif
return
endif
endif
endfor
call gitgutter#utility#warn('No more hunks')
endfunction
function! gitgutter#hunk#prev_hunk(count) abort
let bufnr = bufnr('')
if !gitgutter#utility#is_active(bufnr) | return | endif
let hunks = gitgutter#hunk#hunks(bufnr)
if empty(hunks)
call gitgutter#utility#warn('No hunks in file')
return
endif
let current_line = line('.')
let hunk_count = 0
for hunk in reverse(copy(hunks))
if hunk[2] < current_line
let hunk_count += 1
if hunk_count == a:count
let target = hunk[2] == 0 ? 1 : hunk[2]
execute 'normal!' target . 'Gzv'
if g:gitgutter_show_msg_on_hunk_jumping
redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
endif
if gitgutter#hunk#is_preview_window_open()
call gitgutter#hunk#preview()
endif
return
endif
endif
endfor
call gitgutter#utility#warn('No previous hunks')
endfunction
" Returns the hunk the cursor is currently in or an empty list if the cursor
" isn't in a hunk.
function! s:current_hunk() abort
let bufnr = bufnr('')
let current_hunk = []
for hunk in gitgutter#hunk#hunks(bufnr)
if gitgutter#hunk#cursor_in_hunk(hunk)
let current_hunk = hunk
break
endif
endfor
return current_hunk
endfunction
" Returns truthy if the cursor is in two hunks (which can only happen if the
" cursor is on the first line and lines above have been deleted and lines
" immediately below have been deleted) or falsey otherwise.
function! s:cursor_in_two_hunks()
let hunks = gitgutter#hunk#hunks(bufnr(''))
if line('.') == 1 && len(hunks) > 1 && hunks[0][2:3] == [0, 0] && hunks[1][2:3] == [1, 0]
return 1
endif
return 0
endfunction
" A line can be in 0 or 1 hunks, with the following exception: when the first
" line(s) of a file has been deleted, and the new second line (and
" optionally below) has been deleted, the new first line is in two hunks.
function! gitgutter#hunk#cursor_in_hunk(hunk) abort
let current_line = line('.')
if current_line == 1 && a:hunk[2] == 0
return 1
endif
if current_line >= a:hunk[2] && current_line < a:hunk[2] + (a:hunk[3] == 0 ? 1 : a:hunk[3])
return 1
endif
return 0
endfunction
function! gitgutter#hunk#in_hunk(lnum)
" Hunks are sorted in the order they appear in the buffer.
for hunk in gitgutter#hunk#hunks(bufnr(''))
" if in a hunk on first line of buffer
if a:lnum == 1 && hunk[2] == 0
return 1
endif
" if in a hunk generally
if a:lnum >= hunk[2] && a:lnum < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
return 1
endif
" if hunk starts after the given line
if a:lnum < hunk[2]
return 0
endif
endfor
return 0
endfunction
function! gitgutter#hunk#text_object(inner) abort
let hunk = s:current_hunk()
if empty(hunk)
return
endif
let [first_line, last_line] = [hunk[2], hunk[2] + hunk[3] - 1]
if ! a:inner
let lnum = last_line
let eof = line('$')
while lnum < eof && empty(getline(lnum + 1))
let lnum +=1
endwhile
let last_line = lnum
endif
execute 'normal! 'first_line.'GV'.last_line.'G'
endfunction
function! gitgutter#hunk#stage(...) abort
if !s:in_hunk_preview_window() && !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
if a:0 && (a:1 != 1 || a:2 != line('$'))
call s:hunk_op(function('s:stage'), a:1, a:2)
else
call s:hunk_op(function('s:stage'))
endif
silent! call repeat#set("\<Plug>(GitGutterStageHunk)", -1)
endfunction
function! gitgutter#hunk#undo() abort
if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
call s:hunk_op(function('s:undo'))
silent! call repeat#set("\<Plug>(GitGutterUndoHunk)", -1)
endfunction
function! gitgutter#hunk#preview() abort
if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
call s:hunk_op(function('s:preview'))
silent! call repeat#set("\<Plug>(GitGutterPreviewHunk)", -1)
endfunction
function! s:hunk_op(op, ...)
let bufnr = bufnr('')
if s:in_hunk_preview_window()
if string(a:op) =~ '_stage'
" combine hunk-body in preview window with updated hunk-header
let hunk_body = getline(1, '$')
let [removed, added] = [0, 0]
for line in hunk_body
if line[0] == '-'
let removed += 1
elseif line[0] == '+'
let added += 1
endif
endfor
let hunk_header = b:hunk_header
" from count
let hunk_header[4] = substitute(hunk_header[4], '\(-\d\+\)\(,\d\+\)\?', '\=submatch(1).",".removed', '')
" to count
let hunk_header[4] = substitute(hunk_header[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".added', '')
let hunk_diff = join(hunk_header + hunk_body, "\n")."\n"
call s:goto_original_window()
call gitgutter#hunk#close_hunk_preview_window()
call s:stage(hunk_diff)
endif
return
endif
if gitgutter#utility#is_active(bufnr)
" Get a (synchronous) diff.
let [async, g:gitgutter_async] = [g:gitgutter_async, 0]
let diff = gitgutter#diff#run_diff(bufnr, g:gitgutter_diff_relative_to, 1)
let g:gitgutter_async = async
call gitgutter#hunk#set_hunks(bufnr, gitgutter#diff#parse_diff(diff))
call gitgutter#diff#process_hunks(bufnr, gitgutter#hunk#hunks(bufnr)) " so the hunk summary is updated
if empty(s:current_hunk())
call gitgutter#utility#warn('Cursor is not in a hunk')
elseif s:cursor_in_two_hunks()
let choice = input('Choose hunk: upper or lower (u/l)? ')
" Clear input
normal! :<ESC>
if choice =~ 'u'
call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 0))
elseif choice =~ 'l'
call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 1))
else
call gitgutter#utility#warn('Did not recognise your choice')
endif
else
let hunk_diff = gitgutter#diff#hunk_diff(bufnr, diff)
if a:0
let hunk_first_line = s:current_hunk()[2]
let hunk_diff = s:part_of_diff(hunk_diff, a:1-hunk_first_line, a:2-hunk_first_line)
endif
call a:op(hunk_diff)
endif
endif
endfunction
function! s:stage(hunk_diff)
let bufnr = bufnr('')
let diff = s:adjust_header(bufnr, a:hunk_diff)
" Apply patch to index.
call gitgutter#utility#system(
\ gitgutter#utility#cd_cmd(bufnr, g:gitgutter_git_executable.' '.g:gitgutter_git_args.' apply --cached --unidiff-zero - '),
\ diff)
if v:shell_error
call gitgutter#utility#warn('Patch does not apply')
else
if exists('#User#GitGutterStage')
execute 'doautocmd' s:nomodeline 'User GitGutterStage'
endif
endif
" Refresh gitgutter's view of buffer.
call gitgutter#process_buffer(bufnr, 1)
endfunction
function! s:undo(hunk_diff)
" Apply reverse patch to buffer.
let hunk = gitgutter#diff#parse_hunk(split(a:hunk_diff, '\n')[4])
let lines = map(split(a:hunk_diff, '\r\?\n')[5:], 'v:val[1:]')
let lnum = hunk[2]
let added_only = hunk[1] == 0 && hunk[3] > 0
let removed_only = hunk[1] > 0 && hunk[3] == 0
if removed_only
call append(lnum, lines)
elseif added_only
execute lnum .','. (lnum+len(lines)-1) .'d _'
else
call append(lnum-1, lines[0:hunk[1]])
execute (lnum+hunk[1]) .','. (lnum+hunk[1]+hunk[3]) .'d _'
endif
endfunction
function! s:preview(hunk_diff)
let lines = split(a:hunk_diff, '\r\?\n')
let header = lines[0:4]
let body = lines[5:]
call s:open_hunk_preview_window()
call s:populate_hunk_preview_window(header, body)
call s:enable_staging_from_hunk_preview_window()
if &previewwindow
call s:goto_original_window()
endif
endfunction
" Returns a new hunk diff using the specified lines from the given one.
" Assumes all lines are additions.
" a:first, a:last - 0-based indexes into the body of the hunk.
function! s:part_of_diff(hunk_diff, first, last)
let diff_lines = split(a:hunk_diff, '\n', 1)
" adjust 'to' line count in header
let diff_lines[4] = substitute(diff_lines[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".(a:last-a:first+1)', '')
return join(diff_lines[0:4] + diff_lines[5+a:first:5+a:last], "\n")."\n"
endfunction
function! s:adjust_header(bufnr, hunk_diff)
let filepath = gitgutter#utility#repo_path(a:bufnr, 0)
return s:adjust_hunk_summary(s:fix_file_references(filepath, a:hunk_diff))
endfunction
" Replaces references to temp files with the actual file.
function! s:fix_file_references(filepath, hunk_diff)
let lines = split(a:hunk_diff, '\n')
let left_prefix = matchstr(lines[2], '[abciow12]').'/'
let right_prefix = matchstr(lines[3], '[abciow12]').'/'
let quote = lines[0][11] == '"' ? '"' : ''
let left_file = quote.left_prefix.a:filepath.quote
let right_file = quote.right_prefix.a:filepath.quote
let lines[0] = 'diff --git '.left_file.' '.right_file
let lines[2] = '--- '.left_file
let lines[3] = '+++ '.right_file
return join(lines, "\n")."\n"
endfunction
function! s:adjust_hunk_summary(hunk_diff) abort
let line_adjustment = s:line_adjustment_for_current_hunk()
let diff = split(a:hunk_diff, '\n', 1)
let diff[4] = substitute(diff[4], '+\zs\(\d\+\)', '\=submatch(1)+line_adjustment', '')
return join(diff, "\n")
endfunction
" Returns the number of lines the current hunk is offset from where it would
" be if any changes above it in the file didn't exist.
function! s:line_adjustment_for_current_hunk() abort
let bufnr = bufnr('')
let adj = 0
for hunk in gitgutter#hunk#hunks(bufnr)
if gitgutter#hunk#cursor_in_hunk(hunk)
break
else
let adj += hunk[1] - hunk[3]
endif
endfor
return adj
endfunction
function! s:in_hunk_preview_window()
if g:gitgutter_preview_win_floating
return win_id2win(s:winid) == winnr()
else
return &previewwindow
endif
endfunction
" Floating window: does not move cursor to floating window.
" Preview window: moves cursor to preview window.
function! s:open_hunk_preview_window()
if g:gitgutter_preview_win_floating
if exists('*nvim_open_win')
call gitgutter#hunk#close_hunk_preview_window()
let buf = nvim_create_buf(v:false, v:false)
" Set default width and height for now.
let s:winid = nvim_open_win(buf, v:false, g:gitgutter_floating_window_options)
call nvim_buf_set_option(buf, 'filetype', 'diff')
call nvim_buf_set_option(buf, 'buftype', 'acwrite')
call nvim_buf_set_option(buf, 'bufhidden', 'delete')
call nvim_buf_set_option(buf, 'swapfile', v:false)
call nvim_buf_set_name(buf, 'gitgutter://hunk-preview')
" Assumes cursor is in original window.
autocmd CursorMoved <buffer> ++once call gitgutter#hunk#close_hunk_preview_window()
if g:gitgutter_close_preview_on_escape
" Map <Esc> to close the floating preview.
nnoremap <buffer> <silent> <Esc> :<C-U>call gitgutter#hunk#close_hunk_preview_window()<CR>
" Ensure that when the preview window is closed, the map is removed.
autocmd User GitGutterPreviewClosed silent! nunmap <buffer> <Esc>
autocmd CursorMoved <buffer> ++once silent! nunmap <buffer> <Esc>
execute "autocmd WinClosed <buffer=".winbufnr(s:winid)."> doautocmd" s:nomodeline "User GitGutterPreviewClosed"
endif
return
endif
if exists('*popup_create')
if g:gitgutter_close_preview_on_escape
let g:gitgutter_floating_window_options.filter = function('s:close_popup_on_escape')
endif
let s:winid = popup_create('', g:gitgutter_floating_window_options)
call setbufvar(winbufnr(s:winid), '&filetype', 'diff')
return
endif
endif
if exists('&previewpopup')
let [previewpopup, &previewpopup] = [&previewpopup, '']
endif
" Specifying where to open the preview window can lead to the cursor going
" to an unexpected window when the preview window is closed (#769).
silent! noautocmd execute g:gitgutter_preview_win_location 'pedit gitgutter://hunk-preview'
silent! wincmd P
setlocal statusline=%{''}
doautocmd WinEnter
if exists('*win_getid')
let s:winid = win_getid()
else
let s:preview_bufnr = bufnr('')
endif
setlocal filetype=diff buftype=acwrite bufhidden=delete
" Reset some defaults in case someone else has changed them.
setlocal noreadonly modifiable noswapfile
if g:gitgutter_close_preview_on_escape
" Ensure cursor goes to the expected window.
nnoremap <buffer> <silent> <Esc> :<C-U>wincmd p<Bar>pclose<CR>
endif
if exists('&previewpopup')
let &previewpopup=previewpopup
endif
endfunction
function! s:close_popup_on_escape(winid, key)
if a:key == "\<Esc>"
call popup_close(a:winid)
return 1
endif
return 0
endfunction
" Floating window: does not care where cursor is.
" Preview window: assumes cursor is in preview window.
function! s:populate_hunk_preview_window(header, body)
if g:gitgutter_preview_win_floating
if exists('*nvim_open_win')
" Assumes cursor is not in previewing window.
call nvim_buf_set_var(winbufnr(s:winid), 'hunk_header', a:header)
let [_scrolloff, &scrolloff] = [&scrolloff, 0]
let [width, height] = s:screen_lines(a:body)
let height = min([height, g:gitgutter_floating_window_options.height])
call nvim_win_set_width(s:winid, width)
call nvim_win_set_height(s:winid, height)
let &scrolloff=_scrolloff
call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, [])
call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, a:body)
call nvim_buf_set_option(winbufnr(s:winid), 'modified', v:false)
let ns_id = nvim_create_namespace('GitGutter')
call nvim_buf_clear_namespace(winbufnr(s:winid), ns_id, 0, -1)
for region in gitgutter#diff_highlight#process(a:body)
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
call nvim_buf_add_highlight(winbufnr(s:winid), ns_id, group, region[0]-1, region[2]-1, region[3])
endfor
call nvim_win_set_cursor(s:winid, [1,0])
endif
if exists('*popup_create')
call popup_settext(s:winid, a:body)
for region in gitgutter#diff_highlight#process(a:body)
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
call win_execute(s:winid, "call matchaddpos('".group."', [[".region[0].", ".region[2].", ".(region[3]-region[2]+1)."]])")
endfor
endif
else
let b:hunk_header = a:header
%delete _
call setline(1, a:body)
setlocal nomodified
let [_, height] = s:screen_lines(a:body)
execute 'resize' height
1
call clearmatches()
for region in gitgutter#diff_highlight#process(a:body)
let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
call matchaddpos(group, [[region[0], region[2], region[3]-region[2]+1]])
endfor
1
endif
endfunction
" Calculates the number of columns and the number of screen lines the given
" array of lines will take up, taking account of wrapping.
function! s:screen_lines(lines)
let [_virtualedit, &virtualedit]=[&virtualedit, 'all']
let cursor = getcurpos()
normal! g$
let available_width = virtcol('.')
call setpos('.', cursor)
let &virtualedit=_virtualedit
let width = min([max(map(copy(a:lines), 'strdisplaywidth(v:val)')), available_width])
if exists('*reduce')
let height = reduce(a:lines, { acc, val -> acc + strdisplaywidth(val) / width + (strdisplaywidth(val) % width == 0 ? 0 : 1) }, 0)
else
let height = eval(join(map(copy(a:lines), 'strdisplaywidth(v:val) / width + (strdisplaywidth(v:val) % width == 0 ? 0 : 1)'), '+'))
endif
return [width, height]
endfunction
function! s:enable_staging_from_hunk_preview_window()
augroup gitgutter_hunk_preview
autocmd!
let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
execute 'autocmd BufWriteCmd <buffer='.bufnr.'> GitGutterStageHunk'
augroup END
endfunction
function! s:goto_original_window()
noautocmd wincmd p
doautocmd WinEnter
endfunction
function! gitgutter#hunk#close_hunk_preview_window()
let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
call setbufvar(bufnr, '&modified', 0)
if g:gitgutter_preview_win_floating
if win_id2win(s:winid) > 0
execute win_id2win(s:winid).'wincmd c'
endif
else
pclose
endif
let s:winid = 0
let s:preview_bufnr = 0
endfunction
function gitgutter#hunk#is_preview_window_open()
if g:gitgutter_preview_win_floating
if win_id2win(s:winid) > 0
execute win_id2win(s:winid).'wincmd c'
endif
else
for i in range(1, winnr('$'))
if getwinvar(i, '&previewwindow')
return 1
endif
endfor
endif
return 0
endfunction

View File

@ -0,0 +1,250 @@
" For older Vims without sign_place() the plugin has to manaage the sign ids.
let s:first_sign_id = 3000
let s:next_sign_id = s:first_sign_id
" Remove-all-signs optimisation requires Vim 7.3.596+.
let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596"))
function! gitgutter#sign#enable() abort
let old_signs = g:gitgutter_signs
let g:gitgutter_signs = 1
call gitgutter#highlight#define_sign_text_highlights()
if !old_signs && !g:gitgutter_highlight_lines && !g:gitgutter_highlight_linenrs
call gitgutter#all(1)
endif
endfunction
function! gitgutter#sign#disable() abort
let g:gitgutter_signs = 0
call gitgutter#highlight#define_sign_text_highlights()
if !g:gitgutter_highlight_lines && !g:gitgutter_highlight_linenrs
call gitgutter#sign#clear_signs(bufnr(''))
endif
endfunction
function! gitgutter#sign#toggle() abort
if g:gitgutter_signs
call gitgutter#sign#disable()
else
call gitgutter#sign#enable()
endif
endfunction
" Removes gitgutter's signs from the buffer being processed.
function! gitgutter#sign#clear_signs(bufnr) abort
if exists('*sign_unplace')
call sign_unplace('gitgutter', {'buffer': a:bufnr})
return
endif
call s:find_current_signs(a:bufnr)
let sign_ids = map(values(gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')), 'v:val.id')
call s:remove_signs(a:bufnr, sign_ids, 1)
call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', {})
endfunction
" Updates gitgutter's signs in the buffer being processed.
"
" modified_lines: list of [<line_number (number)>, <name (string)>]
" where name = 'added|removed|modified|modified_removed'
function! gitgutter#sign#update_signs(bufnr, modified_lines) abort
if exists('*sign_unplace')
" Vim is (hopefully) now quick enough to remove all signs then place new ones.
call sign_unplace('gitgutter', {'buffer': a:bufnr})
let modified_lines = s:handle_double_hunk(a:modified_lines)
let signs = map(copy(modified_lines), '{'.
\ '"buffer": a:bufnr,'.
\ '"group": "gitgutter",'.
\ '"name": s:highlight_name_for_change(v:val[1]),'.
\ '"lnum": v:val[0],'.
\ '"priority": g:gitgutter_sign_priority'.
\ '}')
if exists('*sign_placelist')
call sign_placelist(signs)
return
endif
for sign in signs
call sign_place(0, sign.group, sign.name, sign.buffer, {'lnum': sign.lnum, 'priority': sign.priority})
endfor
return
endif
" Derive a delta between the current signs and the ones we want.
" Remove signs from lines that no longer need a sign.
" Upsert the remaining signs.
call s:find_current_signs(a:bufnr)
let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]')
let obsolete_signs = s:obsolete_gitgutter_signs_to_remove(a:bufnr, new_gitgutter_signs_line_numbers)
call s:remove_signs(a:bufnr, obsolete_signs, s:remove_all_old_signs)
call s:upsert_new_gitgutter_signs(a:bufnr, a:modified_lines)
endfunction
"
" Internal functions
"
function! s:find_current_signs(bufnr) abort
let gitgutter_signs = {} " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>}
if !g:gitgutter_sign_allow_clobber
let other_signs = [] " [<line_number (number),...]
endif
if exists('*getbufinfo')
let bufinfo = getbufinfo(a:bufnr)[0]
let signs = has_key(bufinfo, 'signs') ? bufinfo.signs : []
else
let signs = []
redir => signlines
silent execute "sign place buffer=" . a:bufnr
redir END
for signline in filter(split(signlines, '\n')[2:], 'v:val =~# "="')
" Typical sign line before v8.1.0614: line=88 id=1234 name=GitGutterLineAdded
" We assume splitting is faster than a regexp.
let components = split(signline)
call add(signs, {
\ 'lnum': str2nr(split(components[0], '=')[1]),
\ 'id': str2nr(split(components[1], '=')[1]),
\ 'name': split(components[2], '=')[1]
\ })
endfor
endif
for sign in signs
if sign.name =~# 'GitGutter'
" Remove orphaned signs (signs placed on lines which have been deleted).
" (When a line is deleted its sign lingers. Subsequent lines' signs'
" line numbers are decremented appropriately.)
if has_key(gitgutter_signs, sign.lnum)
execute "sign unplace" gitgutter_signs[sign.lnum].id
endif
let gitgutter_signs[sign.lnum] = {'id': sign.id, 'name': sign.name}
else
if !g:gitgutter_sign_allow_clobber
call add(other_signs, sign.lnum)
endif
endif
endfor
call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', gitgutter_signs)
if !g:gitgutter_sign_allow_clobber
call gitgutter#utility#setbufvar(a:bufnr, 'other_signs', other_signs)
endif
endfunction
" Returns a list of [<id (number)>, ...]
" Sets `s:remove_all_old_signs` as a side-effect.
function! s:obsolete_gitgutter_signs_to_remove(bufnr, new_gitgutter_signs_line_numbers) abort
let signs_to_remove = [] " list of [<id (number)>, ...]
let remove_all_signs = 1
let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
for line_number in keys(old_gitgutter_signs)
if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1
call add(signs_to_remove, old_gitgutter_signs[line_number].id)
else
let remove_all_signs = 0
endif
endfor
let s:remove_all_old_signs = remove_all_signs
return signs_to_remove
endfunction
function! s:remove_signs(bufnr, sign_ids, all_signs) abort
if a:all_signs && s:supports_star && (g:gitgutter_sign_allow_clobber || empty(gitgutter#utility#getbufvar(a:bufnr, 'other_signs')))
execute "sign unplace * buffer=" . a:bufnr
else
for id in a:sign_ids
execute "sign unplace" id
endfor
endif
endfunction
function! s:upsert_new_gitgutter_signs(bufnr, modified_lines) abort
if !g:gitgutter_sign_allow_clobber
let other_signs = gitgutter#utility#getbufvar(a:bufnr, 'other_signs')
endif
let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
let modified_lines = s:handle_double_hunk(a:modified_lines)
for line in modified_lines
let line_number = line[0] " <number>
if g:gitgutter_sign_allow_clobber || index(other_signs, line_number) == -1 " don't clobber others' signs
let name = s:highlight_name_for_change(line[1])
if !has_key(old_gitgutter_signs, line_number) " insert
let id = s:next_sign_id()
execute "sign place" id "line=" . line_number "name=" . name "buffer=" . a:bufnr
else " update if sign has changed
let old_sign = old_gitgutter_signs[line_number]
if old_sign.name !=# name
execute "sign place" old_sign.id "name=" . name "buffer=" . a:bufnr
end
endif
endif
endfor
" At this point b:gitgutter_gitgutter_signs is out of date.
endfunction
" Handle special case where the first line is the site of two hunks:
" lines deleted above at the start of the file, and lines deleted
" immediately below.
function! s:handle_double_hunk(modified_lines)
if a:modified_lines[0:1] == [[1, 'removed_first_line'], [1, 'removed']]
return [[1, 'removed_above_and_below']] + a:modified_lines[2:]
endif
return a:modified_lines
endfunction
function! s:next_sign_id() abort
let next_id = s:next_sign_id
let s:next_sign_id += 1
return next_id
endfunction
" Only for testing.
function! gitgutter#sign#reset()
let s:next_sign_id = s:first_sign_id
endfunction
function! s:highlight_name_for_change(text) abort
if a:text ==# 'added'
return 'GitGutterLineAdded'
elseif a:text ==# 'removed'
return 'GitGutterLineRemoved'
elseif a:text ==# 'removed_first_line'
return 'GitGutterLineRemovedFirstLine'
elseif a:text ==# 'modified'
return 'GitGutterLineModified'
elseif a:text ==# 'modified_removed'
return 'GitGutterLineModifiedRemoved'
elseif a:text ==# 'removed_above_and_below'
return 'GitGutterLineRemovedAboveAndBelow'
endif
endfunction

View File

@ -0,0 +1,255 @@
function! gitgutter#utility#supports_overscore_sign()
if gitgutter#utility#windows()
return &encoding ==? 'utf-8'
else
return &termencoding ==? &encoding || &termencoding == ''
endif
endfunction
function! gitgutter#utility#setbufvar(buffer, varname, val)
let buffer = +a:buffer
" Default value for getbufvar() was introduced in Vim 7.3.831.
let ggvars = getbufvar(buffer, 'gitgutter')
if type(ggvars) == type('')
unlet ggvars
let ggvars = {}
call setbufvar(buffer, 'gitgutter', ggvars)
endif
let ggvars[a:varname] = a:val
endfunction
function! gitgutter#utility#getbufvar(buffer, varname, ...)
let ggvars = getbufvar(a:buffer, 'gitgutter')
if type(ggvars) == type({}) && has_key(ggvars, a:varname)
return ggvars[a:varname]
endif
if a:0
return a:1
endif
endfunction
function! gitgutter#utility#warn(message) abort
echohl WarningMsg
echo a:message
echohl None
let v:warningmsg = a:message
endfunction
function! gitgutter#utility#warn_once(bufnr, message, key) abort
if empty(gitgutter#utility#getbufvar(a:bufnr, a:key))
call gitgutter#utility#setbufvar(a:bufnr, a:key, '1')
echohl WarningMsg
redraw | echom a:message
echohl None
let v:warningmsg = a:message
endif
endfunction
" Returns truthy when the buffer's file should be processed; and falsey when it shouldn't.
" This function does not and should not make any system calls.
function! gitgutter#utility#is_active(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'enabled') &&
\ !pumvisible() &&
\ s:is_file_buffer(a:bufnr) &&
\ s:exists_file(a:bufnr) &&
\ s:not_git_dir(a:bufnr)
endfunction
function! s:not_git_dir(bufnr) abort
return s:dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
endfunction
function! s:is_file_buffer(bufnr) abort
return empty(getbufvar(a:bufnr, '&buftype'))
endfunction
" From tpope/vim-fugitive
function! s:winshell()
return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
endfunction
" From tpope/vim-fugitive
function! gitgutter#utility#shellescape(arg) abort
if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
return a:arg
elseif s:winshell()
return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"'
else
return shellescape(a:arg)
endif
endfunction
function! gitgutter#utility#file(bufnr)
return s:abs_path(a:bufnr, 1)
endfunction
" Not shellescaped
function! gitgutter#utility#extension(bufnr) abort
return fnamemodify(s:abs_path(a:bufnr, 0), ':e')
endfunction
function! gitgutter#utility#system(cmd, ...) abort
call gitgutter#debug#log(a:cmd, a:000)
call s:use_known_shell()
silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
call s:restore_shell()
return output
endfunction
function! gitgutter#utility#has_repo_path(bufnr)
return index(['', -1, -2], gitgutter#utility#repo_path(a:bufnr, 0)) == -1
endfunction
" Path of file relative to repo root.
"
" * empty string - not set
" * non-empty string - path
" * -1 - pending
" * -2 - not tracked by git
" * -3 - assume unchanged
function! gitgutter#utility#repo_path(bufnr, shellesc) abort
let p = gitgutter#utility#getbufvar(a:bufnr, 'path', '')
return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction
let s:set_path_handler = {}
function! s:set_path_handler.out(buffer, listing) abort
let listing = s:strip_trailing_new_line(a:listing)
let [status, path] = [listing[0], listing[2:]]
if status =~# '[a-z]'
call gitgutter#utility#setbufvar(a:buffer, 'path', -3)
else
call gitgutter#utility#setbufvar(a:buffer, 'path', path)
endif
if type(self.continuation) == type(function('tr'))
call self.continuation()
else
call call(self.continuation.function, self.continuation.arguments)
endif
endfunction
function! s:set_path_handler.err(buffer) abort
call gitgutter#utility#setbufvar(a:buffer, 'path', -2)
endfunction
" continuation - a funcref or hash to call after setting the repo path asynchronously.
"
" Returns 'async' if the the path is set asynchronously, 0 otherwise.
function! gitgutter#utility#set_repo_path(bufnr, continuation) abort
" Values of path:
" * non-empty string - path
" * -1 - pending
" * -2 - not tracked by git
" * -3 - assume unchanged
call gitgutter#utility#setbufvar(a:bufnr, 'path', -1)
let cmd = gitgutter#utility#cd_cmd(a:bufnr,
\ g:gitgutter_git_executable.' '.g:gitgutter_git_args.
\ ' ls-files -v --error-unmatch --full-name -z -- '.
\ gitgutter#utility#shellescape(s:filename(a:bufnr)))
if g:gitgutter_async && gitgutter#async#available() && !has('vim_starting')
let handler = copy(s:set_path_handler)
let handler.continuation = a:continuation
call gitgutter#async#execute(cmd, a:bufnr, handler)
return 'async'
endif
let listing = gitgutter#utility#system(cmd)
if v:shell_error
call gitgutter#utility#setbufvar(a:bufnr, 'path', -2)
return
endif
let listing = s:strip_trailing_new_line(listing)
let [status, path] = [listing[0], listing[2:]]
if status =~# '[a-z]'
call gitgutter#utility#setbufvar(a:bufnr, 'path', -3)
else
call gitgutter#utility#setbufvar(a:bufnr, 'path', path)
endif
endfunction
function! gitgutter#utility#cd_cmd(bufnr, cmd) abort
let cd = s:unc_path(a:bufnr) ? 'pushd' : (gitgutter#utility#windows() && s:dos_shell() ? 'cd /d' : 'cd')
return cd.' '.s:dir(a:bufnr).' && '.a:cmd
endfunction
function! s:unc_path(bufnr)
return s:abs_path(a:bufnr, 0) =~ '^\\\\'
endfunction
function! s:dos_shell()
return &shell == 'cmd.exe' || &shell == 'command.com'
endfunction
function! s:use_known_shell() abort
if has('unix') && &shell !=# 'sh'
let [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote] = [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote]
let &shell = 'sh'
set shellcmdflag=-c shellredir=>%s\ 2>&1
endif
if has('win32') && (&shell =~# 'pwsh' || &shell =~# 'powershell')
let [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote] = [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote]
let &shell = 'cmd.exe'
set shellcmdflag=/s\ /c shellredir=>%s\ 2>&1 shellpipe=>%s\ 2>&1 shellquote= shellxquote="
endif
endfunction
function! s:restore_shell() abort
if (has('unix') || has('win32')) && exists('s:shell')
let [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote] = [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote]
endif
endfunction
function! gitgutter#utility#get_diff_base(bufnr)
let p = resolve(expand('#'.a:bufnr.':p'))
let ml = matchlist(p, '\v^fugitive:/.*/(\x{40,})/')
if !empty(ml) && !empty(ml[1])
return ml[1].'^'
endif
return g:gitgutter_diff_base
endfunction
function! s:abs_path(bufnr, shellesc)
let p = resolve(expand('#'.a:bufnr.':p'))
" Remove extra parts from fugitive's filepaths
let p = substitute(substitute(p, '^fugitive:', '', ''), '\v\.git/\x{40,}/', '', '')
return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction
function! s:dir(bufnr) abort
return gitgutter#utility#shellescape(fnamemodify(s:abs_path(a:bufnr, 0), ':h'))
endfunction
" Not shellescaped.
function! s:filename(bufnr) abort
return fnamemodify(s:abs_path(a:bufnr, 0), ':t')
endfunction
function! s:exists_file(bufnr) abort
return filereadable(s:abs_path(a:bufnr, 0))
endfunction
" Get rid of any trailing new line or SOH character.
"
" git ls-files -z produces output with null line termination.
" Vim's system() replaces any null characters in the output
" with SOH (start of header), i.e. ^A.
function! s:strip_trailing_new_line(line) abort
return substitute(a:line, '[[:cntrl:]]$', '', '')
endfunction
function! gitgutter#utility#windows()
return has('win64') || has('win32') || has('win16')
endfunction