scriptencoding utf-8

if exists('g:loaded_magit') || !executable('git') || &cp
  finish
endif
let g:loaded_magit = 1

let g:vimagit_version = [1, 7, 3]

let g:vimagit_minium_vim_version = '7.4.1926'
if ! has('patch-' . g:vimagit_minium_vim_version)
	throw "vimagit needs at least vim " . g:vimagit_minium_vim_version
endif

" Initialisation {{{

" source common file. variables in common file are shared with plugin and
" syntax files
let g:vimagit_path = fnameescape(resolve(expand('<sfile>:p:h')))
execute 'source ' . g:vimagit_path . '/../common/magit_common.vim'

" these mappings are broadly applied, for all vim buffers
let g:magit_show_magit_mapping     = get(g:, 'magit_show_magit_mapping',        '<leader>M' )

" user options
let g:magit_enabled                = get(g:, 'magit_enabled',                   1)
let g:magit_show_help              = get(g:, 'magit_show_help',                 0)
let g:magit_default_show_all_files = get(g:, 'magit_default_show_all_files',    1)
let g:magit_default_fold_level     = get(g:, 'magit_default_fold_level',        1)
let g:magit_auto_close             = get(g:, 'magit_auto_close',                0)
let g:magit_auto_foldopen            = get(g:, 'magit_auto_foldopen',               1)
let g:magit_default_sections       = get(g:, 'magit_default_sections',          ['info', 'global_help', 'commit', 'staged', 'unstaged'])
let g:magit_discard_untracked_do_delete = get(g:, 'magit_discard_untracked_do_delete',        0)

let g:magit_refresh_gutter         = get(g:, 'magit_refresh_gutter'   ,         1)
" Should deprecate the following
let g:magit_refresh_gitgutter      = get(g:, 'magit_refresh_gitgutter',         0)

let g:magit_commit_title_limit     = get(g:, 'magit_commit_title_limit',        50)

let g:magit_scrolloff              = get(g:, 'magit_scrolloff',                 3)

let g:magit_warning_max_lines      = get(g:, 'magit_warning_max_lines',         10000)

let g:magit_git_cmd                = get(g:, 'magit_git_cmd'          ,         "git")

execute "nnoremap <silent> " . g:magit_show_magit_mapping . " :call magit#show_magit('v')<cr>"

if (g:magit_refresh_gutter == 1 || g:magit_refresh_gitgutter == 1)
  autocmd User VimagitUpdateFile
    \ if ( exists("*gitgutter#process_buffer") ) |
    \   call gitgutter#process_buffer(bufnr(g:magit_last_updated_buffer), 0) |
    \ elseif ( exists("*sy#util#refresh_windows") ) |
    \   call sy#util#refresh_windows() |
    \ endif
endif
" }}}

" s:mg_cut_str cut a string given a limit size
" param[in] str string to cut
" param[in] limit maximum number of column
" return string cut on limit
function! s:mg_cut_str(str, limit)
	if ( len(a:str) < a:limit )
		return a:str
	elseif ( ( a:limit - 3 ) < 0 )
		return ""
	else
		return printf("%.*s...", a:limit - 3, a:str)
	endif
endfunction

" s:mg_get_info: this function writes in current buffer current git state
" WARNING: this function writes in file, it should only be called through
" protected functions like magit#update_buffer
function! s:mg_get_info()
	let align_w=12

	let repo_line=printf("%-*s %s",
				\ align_w,
				\ g:magit_section_info.cur_repo,
				\ magit#git#top_dir())

	try
		let head_br=magit#git#get_branch_name("HEAD")
	catch 'shell_error'
		let head_br="Empty repository"
	endtry
	let upstream_br=magit#git#get_remote_branch("HEAD", "upstream")
	let push_br=magit#git#get_remote_branch("HEAD", "push")
	let max_br_w = max([len(head_br), len(upstream_br), len(push_br)])

	let limit=winwidth(0)-align_w-max_br_w-3
	let head_msg=s:mg_cut_str(magit#git#get_commit_subject("HEAD"), limit)
	let upstream_msg=s:mg_cut_str(magit#git#get_commit_subject(upstream_br), limit)
	let push_msg=s:mg_cut_str(magit#git#get_commit_subject(push_br), limit)

	let head_line=magit#utils#strip(printf("%-*s %-*s %s", 
				\ align_w, g:magit_section_info.cur_head,
				\ max_br_w, head_br, head_msg))
	let upstream_line=magit#utils#strip(printf("%-*s %-*s %s",
				\ align_w, g:magit_section_info.cur_upstream,
				\ max_br_w, upstream_br, upstream_msg))
	let push_line=magit#utils#strip(printf("%-*s %-*s %s",
				\ align_w, g:magit_section_info.cur_push,
				\ max_br_w, push_br, push_msg))


	silent put =g:magit_sections.info
	silent put =magit#utils#underline(g:magit_sections.info)
	silent put =''
	silent put =repo_line
	silent put =head_line
	silent put =upstream_line
	silent put =push_line

	if ( b:magit_current_commit_mode != '' )
		let commit_mode_line=printf("%-*s %s",
				\ align_w, g:magit_section_info.commit_mode,
				\ g:magit_commit_mode[b:magit_current_commit_mode])
		silent put =commit_mode_line
	endif
	silent put =''
	silent put ='Press ? to display help'
	silent put =''
	silent put =''
endfunction

" s:mg_display_files: display in current buffer files, filtered by some
" parameters
" param[in] mode: files mode, can be 'staged' or 'unstaged'
" param[in] curdir: directory containing files (only needed for untracked
" directory)
" param[in] depth: current directory depth (only needed for untracked
" directory)
function! s:mg_display_files(mode, curdir, depth)

	" FIXME: ouch, must store subdirs in more efficient way
	for filename in b:state.get_filenames(a:mode)
		let file = b:state.get_file(a:mode, filename, 0)
		if ( file.depth != a:depth || filename !~ a:curdir . '.*' )
			continue
		endif
		silent put =file.get_filename_header()
		let file.line_pos = line('.')

		if ( file.dir != 0 )
			if ( file.visible == 1 )
				call s:mg_display_files(a:mode, filename, a:depth + 1)
				continue
			endif
		endif

		if ( file.visible == 0 )
			silent put =''
			continue
		endif
		if ( file.exists == 0 )
			echoerr "Error, " . filename . " should not exists"
		endif
		let hunks = file.get_hunks()
		for hunk in hunks
			if ( hunk.header != '' )
				silent put =hunk.header
				let hunk.line_pos = line('.')
			endif
			if ( !empty(hunk.lines) )
				silent put =hunk.lines
			endif
		endfor
		silent put =''
	endfor
endfunction

" s:mg_get_staged_section: this function writes in current buffer all staged
" or unstaged files, using b:state.dict information
" WARNING: this function writes in file, it should only be called through
" protected functions like magit#update_buffer
" param[in] mode: 'staged' or 'unstaged'
function! s:mg_get_staged_section(mode)
	silent put =g:magit_sections[a:mode]
	call magit#mapping#get_section_help(a:mode)
	silent put =magit#utils#underline(g:magit_sections[a:mode])
	silent put =''
	call s:mg_display_files(a:mode, '', 0)
	silent put =''
endfunction

" s:mg_get_stashes: this function write in current buffer all stashes
" WARNING: this function writes in file, it should only be called through
" protected functions like magit#update_buffer
function! s:mg_get_stashes()
	try
		silent! let stash_list=magit#sys#systemlist(g:magit_git_cmd . " stash list")
	catch 'shell_error'
		call magit#sys#print_shell_error()
		return
	endtry

	if (!empty(stash_list))
		silent put =g:magit_sections.stash
		silent put =magit#utils#underline(g:magit_sections.stash)
		silent put =''

		for stash in stash_list
			let stash_id=substitute(stash, '^\(stash@{\d\+}\):.*$', '\1', '')
			silent put =stash
			silent! execute "read !git stash show -p " . stash_id
		endfor
		silent put =''
		silent put =''
	endif
endfunction

" b:magit_current_commit_msg: this variable store the current commit message,
" saving it among refreshes (remember? the whole buffer is wiped at each
" refresh).
let b:magit_current_commit_msg = []

" s:mg_get_commit_section: this function writes in current buffer the commit
" section. It is a commit message, depending on b:magit_current_commit_mode
" WARNING: this function writes in file, it should only be called through
" protected functions like magit#update_buffer
" param[in] b:magit_current_commit_mode: this function uses global commit mode
"       'CC': prepare a brand new commit message
"       'CA': get the last commit message
function! s:mg_get_commit_section()
	if ( b:magit_current_commit_mode != '' )
		silent put =g:magit_sections.commit
		silent put =magit#utils#underline(g:magit_sections.commit)

		let git_dir=magit#git#git_dir()
		" refresh the COMMIT_EDITMSG file
		if ( b:magit_current_commit_mode == 'CC' )
			silent! call magit#sys#system_noraise("GIT_EDITOR=/bin/false " .
						\ g:magit_git_cmd . " -c commit.verbose=no commit --cleanup=verbatim --no-status -e 2> /dev/null")
		elseif ( b:magit_current_commit_mode == 'CA' )
			silent! call magit#sys#system_noraise("GIT_EDITOR=/bin/false " .
						\ g:magit_git_cmd . " -c commit.verbose=no commit --cleanup=verbatim --no-status --amend -e 2> /dev/null")
		endif
		if ( !empty(b:magit_current_commit_msg) )
			silent put =b:magit_current_commit_msg
		elseif ( filereadable(git_dir . 'COMMIT_EDITMSG') )
			let commit_msg=readfile(git_dir . 'COMMIT_EDITMSG')
			silent put =commit_msg
		endif
		silent put =''
		silent put =''
	endif
endfunction

" s:mg_search_block: helper function, to get start and end line of a block,
" giving a start and multiple end pattern
" a "pattern parameter" is a List:
"   @[0]: end pattern regex
"   @[1]: number of line to exclude above (negative), below (positive) or none (0)
" param[in] start_pattern: start "pattern parameter", which will be search
" backward (cursor position is set to end of line before searching, to find the
" pattern if on the current line)
" param[in] end_pattern: list of end "pattern parameter". Each pattern is 
" searched in order. It'll choose the match with the minimum line number
" (smallest region search)
" param[in] upperlimit_pattern: regex of upper limit. If start_pattern line is
" inferior to upper_limit line, block is discarded
" param[in]: end_pattern_on_cursor: boolean, if true end pattern is also
" search on cursor position
" return: [startline, endline]
function! s:mg_search_block(start_pattern, end_pattern, upper_limit_pattern,
			\ end_pattern_on_cursor)

	let upper_limit=0
	if ( a:upper_limit_pattern != "" )
		let upper_limit=search(a:upper_limit_pattern, "cbnW")
	endif

	let start=search(a:start_pattern[0], "cbnW")
	if ( start == 0 || start < upper_limit )
		throw "out_of_block"
	endif
	let start+=a:start_pattern[1]

	let end=0
	let min=line('$')
	for end_p in a:end_pattern
		let curr_end=search(end_p[0], a:end_pattern_on_cursor ? "c" : "" . "nW")
		if ( curr_end != 0 && curr_end <= min )
			let end=curr_end + end_p[1]
			let min=curr_end
		endif
	endfor
	if ( end == 0 )
		throw "out_of_block"
	endif

	return [start,end]
endfunction

" s:mg_get_commit_msg: get the commit meesgae currently in commit section
" return a string containg the commit message
" \param[in] out_of_block (optional): if set, will first move the cursor to
" the commit block before getting content
function! s:mg_get_commit_msg(...)
	let commit_section_pat_start='^'.g:magit_sections.commit.'$'
	" Get next section pattern with g:magit_default_sections order
	let commit_position = match(g:magit_default_sections, 'commit')
	if ( commit_position + 1 == len(g:magit_default_sections) )
	  let commit_section_pat_end='\%$'
	else
	  let commit_section_pat_end='^'.g:magit_sections[g:magit_default_sections[commit_position+1]].'$'
	endif

	let commit_jump_line = 2
	let out_of_block = a:0 == 1 ? a:1 : 0
	if ( out_of_block )
		let old_pos=line('.')
		let commit_pos=search(commit_section_pat_start, "cw")
		if ( commit_pos != 0 )
			call cursor(commit_pos+1, 0)
		endif
	endif
	try
		let [start, end] = <SID>mg_search_block(
					\ [commit_section_pat_start, commit_jump_line],
					\ [ [commit_section_pat_end, -1] ], "", 1)
	finally
		if ( out_of_block && commit_pos != 0 )
			call cursor(old_pos, 0)
		endif
	endtry
	return magit#utils#strip_array(getline(start, end))
endfunction

" s:mg_git_commit: commit staged stuff with message prepared in commit section
" param[in] mode: mode to commit
"       'CF': don't use commit section, just amend previous commit with staged
"       stuff, without modifying message
"       'CC': commit staged stuff with message in commit section to a brand new
"       commit
"       'CA': commit staged stuff with message in commit section amending last
"       commit
" return no
function! s:mg_git_commit(mode) abort
	if ( a:mode == 'CF' )
		try
			silent let git_result=magit#sys#system(g:magit_git_cmd .
						\ " commit --amend -C HEAD")
		catch 'shell_error'
			call magit#sys#print_shell_error()
			echoerr "Commit fix failed"
		endtry
	else
		let cleanup_cfg = magit#git#get_config('commit.cleanup', 'strip')
		let commit_flag=" --cleanup=" . cleanup_cfg . " "
		if ( a:mode != 'CA' && empty( magit#get_staged_files() ) )
			let choice = confirm(
				\ "Do you really want to commit without any staged files?",
				\ "&Yes\n&No", 2)
			if ( choice != 1 )
				return
			else
				let commit_flag.=" --allow-empty "
			endif
		endif

		let commit_msg=s:mg_get_commit_msg()
		if ( empty( commit_msg ) )
			let choice = confirm(
				\ "Do you really want to commit with an empty message?",
				\ "&Yes\n&No", 2)
			if ( choice != 1 )
				return
			else
				let commit_flag.=" --allow-empty-message "
			endif
		endif

		if ( a:mode == 'CA' )
			let commit_flag.=" --amend "
		endif
		let commit_cmd=g:magit_git_cmd . " commit " . commit_flag .
					\ " --file - "
		try
			silent! let git_result=magit#sys#system(commit_cmd, commit_msg)
		catch 'shell_error'
			call magit#sys#print_shell_error()
			echoerr "Commit failed"
		endtry
		let b:magit_current_commit_mode=''
		let b:magit_current_commit_msg=[]
	endif
	let b:magit_just_commited = 1
endfunction

" s:mg_select_file_block: select the whole diff file, relative to the current
" cursor position
" nota: if the cursor is not in a diff file when the function is called, this
" function will fail
" return: a List
"         @[0]: return value
"         @[1]: List of lines containing the patch for the whole file
function! s:mg_select_file_block()
	return <SID>mg_search_block(
				\ [g:magit_file_re, 1],
				\ [ [g:magit_end_diff_re, 0],
				\   [g:magit_file_re, -1],
				\   [g:magit_stash_re, -1],
				\   [g:magit_section_re, -2],
				\   [g:magit_bin_re, 0],
				\   [g:magit_eof_re, 0 ]
				\ ],
				\ "",
				\ 0)
endfunction

" s:mg_select_hunk_block: select a hunk, from the current cursor position
" nota: if the cursor is not in a hunk when the function is called, this
" function will fail
" return: a List
"         @[0]: return value
"         @[1]: List of lines containing the hunk
function! s:mg_select_hunk_block()
	return <SID>mg_search_block(
				\ [g:magit_hunk_re, 0],
				\ [ [g:magit_hunk_re, -1],
				\   [g:magit_end_diff_re, 0],
				\   [g:magit_file_re, -1],
				\   [g:magit_stash_re, -1],
				\   [g:magit_section_re, -2],
				\   [g:magit_eof_re, 0 ]
				\ ],
				\ g:magit_file_re,
				\ 0)
endfunction

" s:mg_create_diff_from_select: craft the diff to apply from a selection
" in a chunk
" remarks: it works with full lines, and can not span over multiple chunks
" param[in] select_lines: List containing all selected line numbers
" return: List containing the diff to apply, including the chunk header (must
" be applied with git apply --recount)
function! s:mg_create_diff_from_select(select_lines)
	let start_select_line = a:select_lines[0]
	let end_select_line = a:select_lines[-1]
	let [starthunk,endhunk] = <SID>mg_select_hunk_block()
	if ( start_select_line < starthunk || end_select_line > endhunk )
		throw 'out of hunk selection'
	endif
	let section=magit#helper#get_section()
	let filename=magit#helper#get_filename()
	let hunks = b:state.get_file(section, filename).get_hunks()
	for hunk in hunks
		if ( hunk.header == getline(starthunk) )
			let current_hunk = hunk
			break
		endif
	endfor
	let selection = []
	call add(selection, current_hunk.header)

	let current_line = starthunk + 1
	" when staging by visual selection, lines out of selection must be
	" ignored. To do so, + lines are simply ignored, - lines are considered as
	" untouched.
	" For unstaging, - lines must be ignored and + lines considered untouched.
	if ( section == 'unstaged' )
		let remove_line_char = '+'
		let replace_line_char = '-'
	else
		let remove_line_char = '-'
		let replace_line_char = '+'
	endif
	for hunk_line in current_hunk.lines
		if ( index(a:select_lines, current_line) != -1 )
			call add(selection, getline(current_line))
		elseif ( hunk_line =~ '^'.remove_line_char.'.*' )
			" just ignore these lines
		elseif ( hunk_line =~ '^'.replace_line_char.'.*' )
			call add(selection, substitute(hunk_line,
						\ '^'.replace_line_char.'\(.*\)$', ' \1', ''))
		elseif ( hunk_line =~ '^ .*' )
			call add(selection, hunk_line)
		else
			throw 'visual selection error: ' . hunk_line
		endif
		let current_line += 1
	endfor
	return selection
endfunction

" s:mg_mark_lines_in_hunk: this function toggle marks for selected lines in a
" hunk.
" if a hunk contains marked lines, only these lines will be (un)staged on next
" (un)stage command
" param[in] start_select_line,end_select_line: limits of the selection
function! s:mg_mark_lines_in_hunk(start_select_line, end_select_line)
	let [starthunk,endhunk] = <SID>mg_select_hunk_block()
	if ( a:start_select_line < starthunk || a:end_select_line > endhunk )
		throw 'out of hunk selection'
	endif
	return magit#sign#toggle_signs('M', a:start_select_line, a:end_select_line)
endfunction

" }}}

" {{{ User functions and commands

" magit#open_close_folding()
" param[in] visible : boolean, force visible value. If not set, toggle
" visibility
function! magit#open_close_folding(...)
	let list = matchlist(getline("."), g:magit_file_re)
	if ( empty(list) )
		throw 'non file header line: ' . getline(".")
	endif
	let filename = list[2]
	let section=magit#helper#get_section()
	" if first param is set, force visible to this value
	" else, toggle value
	let file = b:state.get_file(section, filename, 0)
	if ( a:0 == 1 )
		call file.set_visible(a:1)
	else
		call file.toggle_visible()
	endif
	call magit#update_buffer()
endfunction

let g:magit_last_updated_buffer = ''

" s:mg_display_functions: Dict wrapping all display related functions
" This Dict should be accessed through g:magit_default_sections
let s:mg_display_functions = {
	\ 'info':        { 'fn': function("s:mg_get_info"), 'arg': []},
	\ 'global_help': { 'fn': function("magit#mapping#get_section_help"), 'arg': ['global']},
	\ 'commit':      { 'fn': function("s:mg_get_commit_section"), 'arg': []},
	\ 'staged':      { 'fn': function("s:mg_get_staged_section"), 'arg': ['staged']},
	\ 'unstaged':    { 'fn': function("s:mg_get_staged_section"), 'arg': ['unstaged']},
	\ 'stash':       { 'fn': function("s:mg_get_stashes"), 'arg': []},
\ }

" magit#update_buffer: this function:
" 1. checks that current buffer is the wanted one
" 2. save window state (cursor position...)
" 3. delete buffer
" 4. fills with unstage stuff
" 5. restore window state
" param[in] updated file (optional): this filename is updated to absolute
" path, set in g:magit_last_updated_buffer and the User autocmd
" param[in] current section (optional)
" param[in] current hunk id
" when params 1 & 2 & 3 are set, it means
" that a stage/unstage action occured. We try to smartly set the cursor
" position after the refresh
"  - on current file on closest hunk if still contains hunks in current section
"  - else on next file if any
"  - else on previous file if any
"  - or cursor stay where it is
" VimagitUpdateFile event is raised
function! magit#update_buffer(...)
	let buffer_name=bufname("%")
	" (//|\\\\) is to handle old vim 7.4-0 fnameescape behavior on Windows
	if ( buffer_name !~ "\\v^magit:(//|\\\\).*" )
		echoerr "Not in magit buffer but in " . buffer_name
		return
	endif
	
	if ( a:0 >= 1 )
		let cur_filename = a:1
	endif
	if ( a:0 >= 2 )
		let cur_section = a:2
	endif
	if ( a:0 >= 3 )
		let cur_hunk_id = a:3
	endif

	if ( b:magit_current_commit_mode != '' )
		try
			let b:magit_current_commit_msg = s:mg_get_commit_msg(1)
		catch /^out_of_block$/
			let b:magit_current_commit_msg = []
		endtry
		call s:set_mode_write()
	else
		call s:set_mode_read()
	endif
	" FIXME: find a way to save folding state. According to help, this won't
	" help:
	" > This does not save fold information.
	" Playing with foldenable around does not help.
	" mkview does not help either.
	let l:winview = winsaveview()

	" remove all signs (needed as long as we wipe buffer)
	call magit#sign#remove_all()

	" remove folding while we are updating the buffer. writing to the buffer
	" while the folding is enabled can be veryyyyy slow.
	" One last strange thing is that, the first time the buffer is written, it
	" is not slow at all. It is slow the second time, at first refresh (can be
	" order of seconds). Then at third time (2nd refresh), it is fast again.
	" refs:
	" https://github.com/jreybert/vimagit/issues/170
	" https://github.com/jreybert/vimagit/issues/36 (maybe)
	setlocal foldmethod=manual
	" delete buffer
	silent! execute "silent :%delete _"

	" be smart for the cursor position after refresh, if stage/unstaged
	" occured
	if ( a:0 >= 2 )
		let filenames = b:state.get_filenames(cur_section)
		let pos = match(filenames, cur_filename)
		let next_filename = (pos < len(filenames) - 1) ? filenames[pos+1] : ''
		let prev_filename = (pos > 0) ? filenames[pos-1] : ''
	endif

	call b:state.update()

	if ( g:magit_auto_close == 1 &&
				\ b:magit_just_commited == 1 &&
				\ empty(b:state.get_filenames('staged')) &&
				\ empty(b:state.get_filenames('unstaged')) )
		let b:magit_just_commited = 0
		call magit#close_magit()
		return
	endif

	for section in g:magit_default_sections
		try
			let func = s:mg_display_functions[section]
		catch
			echohl WarningMsg
			echom 'unknown section to display: ' . section
			echom 'please check your redefinition of g:magit_default_sections'
			echohl None
		endtry
		call call(func.fn, func.arg)
	endfor

	call winrestview(l:winview)

	call magit#utils#clear_undo()

	setlocal filetype=magit
	setlocal foldmethod=syntax

	if ( b:magit_current_commit_mode != '' && b:magit_commit_newly_open == 1 )
		let commit_section_pat_start='^'.g:magit_sections.commit.'$'
		silent! let section_line=search(commit_section_pat_start, "w")
		silent! call cursor(section_line+2+magit#mapping#get_section_help_line_nb('commit'), 0)
		if exists('#User#VimagitEnterCommit')
			doautocmd User VimagitEnterCommit
		endif
		let b:magit_commit_newly_open = 0
	endif

	let g:magit_last_updated_buffer = ''
	if ( a:0 >= 1 )
		let abs_filename = magit#git#top_dir() . cur_filename
		if ( bufexists(abs_filename) )
			let g:magit_last_updated_buffer = abs_filename
			if exists('#User#VimagitUpdateFile')
				doautocmd User VimagitUpdateFile
			endif
		endif
	endif

	if exists('#User#VimagitRefresh')
		doautocmd User VimagitRefresh
	endif

	if ( a:0 >= 3 )
		if (b:state.get_files_nb(cur_section) > 0)
			" if, in this order, current file, next file, previous file exists in
			" current section, move cursor to it
			let cur_file = 1
			for fname in [cur_filename, next_filename, prev_filename]
				try
					let file = b:state.get_file(cur_section, fname)
					if ( cur_file )
						let hunk_id = max([0, min([len(file.get_hunks())-1, cur_hunk_id])])
						let cur_file = 0
					else
						let hunk_id = 0
					endif

					if ( file.is_visible() )
						call cursor(file.get_hunks()[hunk_id].line_pos, 0)
						if ( g:magit_auto_foldopen )
							foldopen
						endif
					else
						call cursor(file.line_pos, 0)
					endif
					break
				catch 'file_doesnt_exists'
				endtry
			endfor
		else
			" if current section is empty, move cursor to top to other section
			if (cur_section == 'staged')
				let cur_section = 'unstaged'
			elseif (cur_section == 'unstaged')
				let cur_section = 'staged'
			endif
			let section_line=search(g:magit_sections[cur_section], "bnw")
			call cursor(section_line, 0)
		endif
		silent execute "normal! zt"
	endif

	if exists(':AirlineRefresh')
		execute "AirlineRefresh"
	endif

endfunction

" magit#toggle_help: toggle inline help showing in magit buffer
function! magit#toggle_help()
	let g:magit_show_help = ( g:magit_show_help == 0 ) ? 1 : 0
	call magit#update_buffer()
endfunction

" magit#show_magit: prepare and show magit buffer
" it also set local mappings to magit buffer
" param[in] display:
"     'v': vertical split
"     'h': horizontal split
"     'c': current buffer (should be used when opening vim in vimagit mode
function! magit#show_magit(display, ...)
	if ( &filetype == 'netrw' )
		let cur_file = ""
		let cur_file_path = b:netrw_curdir
	else
		let cur_file = expand("%:p")
		let cur_file_path = isdirectory(cur_file) ? cur_file : fnamemodify(cur_file, ":h")
	endif

	let git_dir=''
	let try_paths = [ cur_file_path, getcwd() ]
	for path in try_paths
		let git_dir=magit#git#is_work_tree(path)
		if ( git_dir != '' )
			break
		endif
	endfor

	if ( git_dir == '' )
		echohl ErrorMsg
		echom "magit can not find any git repository"
		echohl None
		echom "make sure that current opened file or vim current directory points to a git repository"
		echom "search paths:"
		for path in try_paths
			echom path
		endfor
		return
	endif

	let buffer_name=fnameescape('magit://' . git_dir)

	let magit_win = bufwinid(buffer_name)

	if ( magit_win != -1 )
		call win_gotoid(magit_win)
	elseif ( a:display == 'v' )
		silent execute "vnew " . buffer_name
		" next is a workaround for vader, revert as soon as vader bug is fixed
		" https://github.com/junegunn/vader.vim/issues/135
		silent execute "buffer " . buffer_name
		let b:magit_only = 0
	elseif ( a:display == 'h' )
		silent execute "new " . buffer_name
		let b:magit_only = 0
	elseif ( a:display == 'c' )
		if ( !bufexists(buffer_name) )
			if ( bufname("%") == "" )
				silent keepalt enew
			else
				silent enew
			endif
			silent execute "file " . buffer_name
		else
			silent execute "buffer " . buffer_name
		endif
		let b:magit_only = 1
	else
		throw 'parameter_error'
	endif

	call magit#git#set_top_dir(git_dir)

	let b:magit_default_show_all_files = g:magit_default_show_all_files
	let b:magit_default_fold_level = g:magit_default_fold_level
	let b:magit_warning_max_lines_answered = 0

	if ( a:0 > 0 )
		let b:magit_default_show_all_files = a:1
	endif
	if ( a:0 > 1 )
		let b:magit_default_fold_level = a:2
	endif

	setlocal buftype=nofile
	setlocal bufhidden=hide
	setlocal noswapfile
	setlocal foldmethod=syntax
	setlocal foldnestmax=20
	setlocal nobuflisted
	setlocal nomodeline
	let &l:foldlevel = b:magit_default_fold_level
	setlocal filetype=magit
	if ( g:magit_scrolloff != -1 )
		if ( has("patch-8.1.0864") )
			let &l:scrolloff = g:magit_scrolloff
		else
			let s:default_scrolloff=&scrolloff
			" ugly hack, scrolloff is a global only option before patch 8.1.0864
			execute "autocmd BufLeave " . buffer_name . " :set scrolloff=" . s:default_scrolloff
			execute "autocmd BufEnter " . buffer_name . " :set scrolloff=" . g:magit_scrolloff
			let &scrolloff = g:magit_scrolloff
		endif
	endif

	augroup vimagit_buffer
	autocmd!
	" catch write command
	execute "autocmd BufWriteCmd " . buffer_name . " :call magit#commit_command('CC')"

	" let magit buffer in read mode when cursor is not in file, to avoid
	" unfortunate commit with a :wall command out of magit buffer if a commit
	" message is ongoing
	execute "autocmd BufEnter " . buffer_name . "
	      \ :if ( exists('b:magit_current_commit_mode') &&
	      \ b:magit_current_commit_mode != '' ) |
	      \   call s:set_mode_write() |
	      \ endif"
	execute "autocmd BufLeave " . buffer_name . "
	      \ :if ( exists('b:magit_current_commit_mode') &&
	      \ b:magit_current_commit_mode != '' ) |
	      \   call s:set_mode_read() |
	      \ endif"
	augroup END

	" s:magit_commit_mode: global variable which states in which commit mode we are
	" values are:
	"       '': not in commit mode
	"       'CC': normal commit mode, next commit command will create a new commit
	"       'CA': amend commit mode, next commit command will ament current commit
	"       'CF': fixup commit mode, it should not be a global state mode
	let b:magit_current_commit_mode=''
	let b:magit_commit_newly_open=0

	let b:magit_diff_context=3

	let b:magit_just_commited = 0

	if ( magit#git#check_repo() != 0 )
		echohl ErrorMsg
		echom "git repository seems to be corrupted"
		echohl None
		echom "To be safe, vimagit ends now"
		echom "Check your repository health with git fsck"
		echom "If the result shows no problem, open an issue"
		return
	endif

	let b:state = deepcopy(g:magit#state#state)
	call magit#utils#setbufnr(bufnr(buffer_name))
	call magit#sign#init()

	call magit#mapping#set_default()

	if exists('#User#VimagitBufferInit')
		doautocmd User VimagitBufferInit
	endif

	call magit#update_buffer()

	function! s:jump_first_file()
		let unstaged_files = b:state.get_files_ordered('unstaged')
		if ( !empty(unstaged_files) )
			call cursor(unstaged_files[0].line_pos, 0)
		else
			let staged_files = b:state.get_files_ordered('staged')
			if ( !empty(staged_files) )
				call cursor(staged_files[0].line_pos, 0)
			endif
		endif
	endfunction
	" move cursor to (in priority order if not found):
	"  - current file unstaged
	"  - current file staged
	"  - first unstaged file
	"  - first stage file
	let cur_filename = matchlist(cur_file, git_dir . '\(.*\)')
	if ( !empty(cur_filename) )
		let cur_file = cur_filename[1]
		try
			let file = b:state.get_file('unstaged', cur_file, 0)
			call cursor(file.line_pos, 0)
		catch 'file_doesnt_exists'
			try
				let file = b:state.get_file('staged', cur_file, 0)
				call cursor(file.line_pos, 0)
			catch 'file_doesnt_exists'
				call s:jump_first_file()
			endtry
		endtry
	else
		call s:jump_first_file()
	endif

endfunction

function! magit#close_magit()
	if ( b:magit_only == 0 )
		close
	else
		try
			edit #
		catch /^Vim\%((\a\+)\)\=:E\%(194\|499\)/
			try
				close
			catch /^Vim\%((\a\+)\)\=:E444/
				quit
			endtry
		endtry
	endif
endfunction

function! s:mg_stage_closed_file(discard)
	if ( getline(".") =~ g:magit_file_re )
		let list = matchlist(getline("."), g:magit_file_re)
		let filename = list[2]
		let section=magit#helper#get_section()
		
		let file = b:state.get_file(section, filename)
		if ( file.is_visible() == 0 ||
			\ file.is_dir() == 1 )
			if ( a:discard == 0 )
				if ( section == 'unstaged' )
					call magit#git#git_add(magit#utils#add_quotes(filename))
				elseif ( section == 'staged' )
					call magit#git#git_reset(magit#utils#add_quotes(filename))
				else
					echoerr "Must be in \"" .
								\ g:magit_sections.staged . "\" or \"" .
								\ g:magit_sections.unstaged . "\" section"
				endif
			else
				if ( section == 'unstaged' )
					if ( file.status == '?' || file.must_be_added() )
						if ( g:magit_discard_untracked_do_delete == 1 )
							if ( delete(filename, "rf") != 0 )
								echoerr "Can not delete \"" . filename . "\""
								return
							endif
						else
							echohl WarningMsg
							echomsg "By default, vimagit won't discard "
								\ "untracked file (which means delete this file)"
							echomsg "You can force this behaviour, "
								\ "setting g:magit_discard_untracked_do_delete=1"
							echohl None
							return
						endif
					else
						call magit#git#git_checkout(magit#utils#add_quotes(filename))
					endif
				else
					echohl WarningMsg
					echomsg "Can not discard file in \"" .
								\ g:magit_sections.staged . "\" section, "
								\ "unstage file first."
					echohl None
					return
				endif
			endif

			call magit#update_buffer(filename, section, 0)

			return
		endif
	endif
	throw "out_of_block"
endfunction

" magit#stage_block: this function (un)stage a block, according to parameter
" INFO: in unstaged section, it stages the hunk, and in staged section, it
" unstages the hunk
" param[in] block_type: can be 'file' or 'hunk'
" param[in] discard: boolean, if true, discard instead of (un)stage
" return: no
function! magit#stage_block(selection, discard) abort
	let section=magit#helper#get_section()
	let filename=magit#helper#get_filename()

	let file = b:state.get_file(section, filename, 0)
	let header = file.get_header()

	" find current hunk position in file matching against current selection
	" header
	try
		let hunk_id = match(map(deepcopy(file.get_hunks()), 'v:val.header'), "\\V" . a:selection[0])
	catch /^Vim\%((\a\+)\)\=:E874/
		echoerr "Escape issue with '" . a:selection[0] ."'"
		return
	endtry


	if ( a:discard == 0 )
		if ( section == 'unstaged' )
			if ( file.must_be_added() )
				call magit#git#git_add(magit#utils#add_quotes(filename))
			else
				call magit#git#git_apply(header, a:selection)
			endif
		elseif ( section == 'staged' )
			if ( file.must_be_added() )
				call magit#git#git_reset(magit#utils#add_quotes(filename))
			else
				call magit#git#git_unapply(header, a:selection, 'staged')
			endif
		else
			echoerr "Must be in \"" .
						\ g:magit_sections.staged . "\" or \"" .
						\ g:magit_sections.unstaged . "\" section"
		endif
	else
		if ( section == 'unstaged' )
			if ( file.status == '?' || file.must_be_added() )
				if ( g:magit_discard_untracked_do_delete == 1 )
					if ( delete(filename, "rf") != 0 )
						echoerr "Can not delete \"" . filename . "\""
						return
					endif
				else
					echohl WarningMsg
					echomsg "By default, vimagit won't discard "
								\ "untracked file (which means delete this file)"
					echomsg "You can force this behaviour, "
								\ "setting g:magit_discard_untracked_do_delete=1"
					echohl None
					return
				endif
			else
				call magit#git#git_unapply(header, a:selection, 'unstaged')
			endif
		else
			echoerr "Must be in \"" .
						\ g:magit_sections.unstaged . "\" section"
		endif
	endif

	call magit#update_buffer(filename, section, hunk_id)

endfunction

" magit#stage_file: this function (un)stage a whole file, from the current
" cursor position
" INFO: in unstaged section, it stages the file, and in staged section, it
" unstages the file
" return: no
function! magit#stage_file()
	try
		call <SID>mg_stage_closed_file(0)
		return
	catch 'out_of_block'
		try
			let [start, end] = <SID>mg_select_file_block()
		catch /^out_of_block$/
			echohl ErrorMsg
			echomsg "Error while staging."
			echohl None
			echomsg "Your cursor must be:"
			echomsg " - on a file header line"
			echomsg " - or on a hunk header line"
			echomsg " - or within a hunk"
			echomsg "If you repect one of previous points, please open a new issue:"
			echomsg "https://github.com/jreybert/vimagit/issues/new"
			return
		endtry
		let selection = getline(start, end)
	endtry
	return magit#stage_block(selection, 0)
endfunction
"
" magit#stage_hunk: this function (un)stage/discard a hunk, from the current
" cursor position
" INFO: in unstaged section, it stages the hunk, and in staged section, it
" unstages the hunk
" param[in] discard:
"     - when set to 0, (un)stage
"     - when set to 1, discard
" return: no
function! magit#stage_hunk(discard)
	try
		call <SID>mg_stage_closed_file(a:discard)
		return
	catch 'out_of_block'
		try
			let [start,end] = <SID>mg_select_hunk_block()
		catch 'out_of_block'
			try
				let [start,end] = <SID>mg_select_file_block()
			catch /^out_of_block$/
				echohl ErrorMsg
				echomsg "Error while staging."
				echohl None
				echomsg "Your cursor must be:"
				echomsg " - on a file header line"
				echomsg " - or on a hunk header line"
				echomsg " - or within a hunk"
				echomsg "If you repect one of previous points, please open a new issue:"
				echomsg "https://github.com/jreybert/vimagit/issues/new"
				return
			endtry
		endtry
		let marked_lines = magit#sign#find_stage_signs(start, end)
		if ( empty(marked_lines) )
			let selection = getline(start, end)
		else
			let selection = <SID>mg_create_diff_from_select(
						\ map(keys(marked_lines), 'str2nr(v:val)'))
			call magit#sign#remove_signs(marked_lines)
		endif
	endtry
	return magit#stage_block(selection, a:discard)
endfunction

" magit#stage_vselect: this function (un)stage text being sectected in Visual
" mode
" remarks: it works with full lines, and can not span over multiple chunks
" INFO: in unstaged section, it stages the file, and in staged section, it
" unstages the file
" return: no
function! magit#stage_vselect() range
	" func-range a:firstline a:lastline seems to work at least from vim 7.2
	let lines = []
	let curline = a:firstline
	while ( curline <= a:lastline )
		call add(lines, curline)
		let curline += 1
	endwhile
	try
		let selection = <SID>mg_create_diff_from_select(lines)
	catch /^out_of_block$/
		echohl ErrorMsg
		echomsg "Error while staging a visual selection."
		echohl None
		echomsg "Visual selection staging has currently some limitations:"
		echomsg " - selection must be limited within a single hunk"
		echomsg " - only work for staging, not for unstaging"
		echomsg "If you repect current limitations, please open a new issue:"
		echomsg "https://github.com/jreybert/vimagit/issues/new"
		return
	endtry
	return magit#stage_block(selection, 0)
endfunction

" magit#mark_vselect: wrapper function to mark selected lines (see
" mg_mark_lines_in_hunk)
function! magit#mark_vselect() range
	return <SID>mg_mark_lines_in_hunk(a:firstline, a:lastline)
endfunction

" magit#ignore_file: this function add the file under cursor to .gitignore
" FIXME: git diff adds some strange characters to end of line
function! magit#ignore_file() abort
	let ignore_file=magit#helper#get_filename()
	call magit#utils#append_file(magit#git#top_dir() . ".gitignore",
			\ [ ignore_file ] )
	call magit#update_buffer()
endfunction

" set magit buffer in write mode
function! s:set_mode_write()
	setlocal buftype=acwrite
endfunction

" set magit buffer in read only mode
function! s:set_mode_read()
	setlocal buftype=nofile
endfunction

" magit#commit_command: entry function for commit mode
" INFO: it has a different effect if current section is commit section or not
" param[in] mode: commit mode
"   'CF': do not set global b:magit_current_commit_mode, directly call magit#git_commit
"   'CA'/'CF': if in commit section mode, call magit#git_commit, else just set
"   global state variable b:magit_current_commit_mode,
function! magit#commit_command(mode)
	if ( a:mode == 'CF' )
		call <SID>mg_git_commit(a:mode)
	else
		let section=magit#helper#get_section()
		if ( section == 'commit' &&
\			!(b:magit_current_commit_mode == 'CC' && a:mode == 'CA' ) )
			if ( b:magit_current_commit_mode == '' )
				echoerr "Error, commit section should not be enabled"
				return
			endif
			" when we do commit, it is prefered ot commit the way we prepared it
			" (.i.e normal or amend), whatever we commit with CC or CA.
			call <SID>mg_git_commit(b:magit_current_commit_mode)
			if exists('#User#VimagitLeaveCommit')
				doautocmd User VimagitLeaveCommit
			endif
		else
			let b:magit_current_commit_mode=a:mode
			let b:magit_commit_newly_open=1
			call s:set_mode_write()
			setlocal nomodified
		endif
	endif
	call magit#update_buffer()
endfunction

" magit#close_commit: cancel for commit mode
" close commit section if opened
function! magit#close_commit()
  if ( b:magit_current_commit_mode == '' )
    return
  endif

  let git_dir=magit#git#git_dir()
  let commit_editmsg=git_dir . 'COMMIT_EDITMSG'
  if ( filereadable(commit_editmsg) )
    let commit_msg=s:mg_get_commit_msg()
    call writefile(commit_msg, commit_editmsg)
  endif

  let b:magit_current_commit_mode=''
  let b:magit_current_commit_msg=[]
  if exists('#User#VimagitLeaveCommit')
	  doautocmd User VimagitLeaveCommit
  endif
  call magit#update_buffer()
endfunction

" magit#jump_hunk: function to jump among hunks
" it closes the current fold (if any), jump to next hunk and unfold it
" param[in] dir: can be 'N' (for next) or 'P' (for previous)
function! magit#jump_hunk(dir)
	let back = ( a:dir == 'P' ) ? 'b' : ''
	let line = search('\%(^@@ \|' . g:magit_file_re . '\)', back . 'wn')
	if ( line != 0 )
		if ( foldlevel(line('.')) == 2 )
			try
				foldclose
			catch /^Vim\%((\a\+)\)\=:E490/
			endtry
		endif
		call cursor(line, 0)

		if ( foldlevel(line('.')) == 0 )
			return
		endif
		" if current line if an header file of an open file, go next
		if ( foldlevel(line('.')) == 1 )
			let line = search('\%(^@@ \|' . g:magit_file_re . '\)', back . 'wn')
			call cursor(line, 0)
		endif
		while ( foldclosed(line) != -1 )
			try
				foldopen
			catch /^Vim\%((\a\+)\)\=:E490/
				break
			endtry
		endwhile
		silent execute "normal! zt"
	endif
endfunction

" magit#get_staged_files: function returning an array with staged files names
" return: an array with staged files names
function! magit#get_staged_files()
	return keys(b:state.dict.staged)
endfunction

" magit#get_staged_files: function returning an array with unstaged files
" names
" return: an array with unstaged files names
function! magit#get_unstaged_files()
	return keys(b:state.dict.unstaged)
endfunction

" magit#jump_to: function to move cursor to the file location of the current
" hunk
" if this file is already displayed in a window, jump to the window, if not,
" jump to last window and open buffer, at the beginning of the hunk
function! magit#jump_to()
	let section=magit#helper#get_section()
	let filename=fnameescape(magit#git#top_dir() . magit#helper#get_filename())
	let header_line_nb=magit#helper#get_hunkheader_line_nb()

	let line_in_file=substitute(getline(header_line_nb),
				\ '^@@ -\d\+,\d\+ +\(\d\+\),\d\+ @@.*$', '\1', "")

	" header_line_nb+2: +2 because we skip the header and the fist line
	let hunk_extract=getline(header_line_nb+2, line('.'))
	let line_in_hunk = len(filter(hunk_extract, 'v:val =~ "^[ +]"'))
	let line_in_file += line_in_hunk

	if ( winnr('$') == 1 )
		" if single window in current tab, create a new window to the right
		rightbelow vnew
	elseif ( bufwinid(filename) != -1 )
		" window in current tab with 'filename' buffer visible
		call win_gotoid(bufwinid(filename))
	elseif ( win_getid(winnr('#')) )
		" last access window
		call win_gotoid(win_getid(winnr('#')))
	elseif ( win_getid(winnr('1l')) != win_getid() )
		" right window
		call win_gotoid(win_getid(winnr('1l')))
	elseif ( win_getid(winnr('1h')) != win_getid() )
		" left window
		call win_gotoid(win_getid(winnr('1h')))
	else
		throw "unexpected error: no viable window"
	endif

	try
		if ( bufexists(filename) )
			execute "buffer " . "+" . line_in_file . " " filename
		else
			execute "edit " . "+" . line_in_file . " " filename
		endif
	catch
		if ( v:exception == 'Vim:Interrupt' && buf_win == 0)
			close
		elseif ( v:exception != 'Vim(edit):E325: ATTENTION' )
			throw "vimagit: " . v:exception
		endif
	endtry
endfunction

function! magit#update_diff(way)
	if ( a:way == "+" )
		let b:magit_diff_context+=1
	elseif ( a:way == "0" )
		let b:magit_diff_context=3
	elseif ( b:magit_diff_context > 1 )
		let b:magit_diff_context-=1
	endif
  call magit#update_buffer()
endfunction

function! magit#show_version()
	return g:vimagit_version[0] . "." .
				\ g:vimagit_version[1] . "." .
				\ g:vimagit_version[2]
endfunction

function! magit#get_current_mode()
	if ( b:magit_current_commit_mode == '' )
		return "STAGING"
	elseif ( b:magit_current_commit_mode == 'CC' )
		return "COMMIT"
	elseif ( b:magit_current_commit_mode == 'CA' )
		return "AMEND"
	endif
endfunction

command! Magit call magit#show_magit('v')
command! MagitOnly call magit#show_magit('c')

" }}}
