" vim600:fdm=marker:fdc=3:cms=\ "\ %s:fml=2:tw=76:
"
" ----------------------------------------------------------------------------
" rcs.vim -- Automatically handle RCS controlled files.
"
" Author:      Christian J. Robinson <heptite@gmail.com>
" URL:         http://christianrobinson.name/vim/
" Last Change: February 23, 2019
" Version:     0.16.0
"
" Copyright (C) 2002-2019 Christian J. Robinson <heptite@gmail.com>
" Distributed under the terms of the Vim license.  See ":help license".
"
" Install Details: -----------------------------------------------------------
"
" 1) Make the following directories:
"    Unix:
"      ~/.vim/plugin
"      ~/.vim/doc
"    Windows:
"      %USERPROFILE%\vimfiles\plugin
"      %USERPROFILE%\vimfiles\doc
"
" 2) Make sure rcs, co, ci, and rlog are in the system PATH somewhere. It is
"    possible to use the RCS package from Cygwin, as long as the Cygwin bin
"    directory is in the Windows system path.
"
" 3) Place this script in the plugin directory, then start Vim. The
"    documentation should automatically be created. Then you can do:
"      :help rcs.txt
"
" ----------------------------------------------------------------------------
"
" TODO:  Allow diffing between two arbitrary revisions, both with :RCSdiff
"        and from the log display.
"
" $Id: rcs.vim,v 1.52 2019/02/24 02:02:05 Heptite Exp $
"
" ChangeLog: {{{1
"
" $Log: rcs.vim,v $
" Revision 1.52  2019/02/24 02:02:05  Heptite
" - Always load the menu
" - Add support for windows
"
" Revision 1.51  2010/07/31 20:25:30  infynity
" Allow suppression of the non-unix error
"
" Revision 1.50  2010/05/16 22:05:12  infynity
" Update URL
"
" Revision 1.49  2010/05/15 20:19:04  infynity
" Make :RCSco check for invalid arguments
"
" Revision 1.48  2010/04/27 19:49:54  infynity
" Wasn't actually running the command to force a check out in the event that
"  the user answered "Yes" to the locked by another user prompt.
"
" Revision 1.47  2010/04/27 03:37:35  infynity
" - Redirect rcsdiff output for checking for unlocked changes to /dev/null
"   (Jon Peatfield)
" - Add a check for a lock by another user in the s:CheckOut() routine
"
" Revision 1.46  2010/04/24 21:18:49  infynity
" Prompt the user on checkout if there were "unlocked" changes, suggested
"  by Jon Peatfield
"
" Revision 1.45  2010/04/24 20:17:25  infynity
" Update version and date
"
" Revision 1.44  2010/04/24 20:14:21  infynity
" Detect RCS's ,v files in the file's cwd as well (Jon Peatfield)
"
" Revision 1.43  2009/06/23 14:05:51  infynity
" Update email address
"
" Revision 1.42  2008/08/09 23:44:23  infynity
" *** empty log message ***
"
" Revision 1.41  2008/08/09 23:38:46  infynity
" Try yet another method of creating/updating the help file
"
" Revision 1.40  2008/08/04 07:36:16  infynity
" - Prompt the user when the file is unwritten and :RCSci is used (Sven Bischof)
" - Sometimes the help file wasn't being generated--try a different method
"   (Sven Bischof)
"
" Revision 1.39  2008/07/11 16:23:15  infynity
" Fix error when g:rcs_plugin_* isn't set (Sven Bischof)
"
" Revision 1.38  2008/06/15 05:46:46  infynity
" Try to safely escape shell commands
"
" Revision 1.37  2008/06/02 07:11:09  infynity
" *** empty log message ***
"
" Revision 1.36  2008/06/01 08:33:45  infynity
" Check all windows in all tabs for the 'diff' option
"
" Revision 1.35  2008/06/01 03:30:08  infynity
" Wasn't checking for the 'diff' option properly.
"
" Revision 1.34  2008/05/31 02:49:48  infynity
" - Allow diffing of two consecutive revisions from the log view
" - Allow folding of the log view
" - Tweaks
"
" Revision 1.33  2008/05/28 20:36:14  infynity
" *** empty log message ***
"
" Revision 1.32  2008/05/27 13:03:33  infynity
" Make entering the log message a little easier
"
" Revision 1.31  2008/05/04 11:10:09  infynity
" Use matchadd() instead of :2match -- less likelyhood of a "collision" with
"   another plugin
"
" Revision 1.30  2008/04/29 20:36:49  infynity
" :RCSUpdateHelp {arg} wasn't working
"
" Revision 1.29  2008/04/26 19:47:08  infynity
" Added the :RCSUpdateHelp [directory] command
"
" Revision 1.28  2008/04/17 06:11:32  infynity
" Delay the helpfile auto-update until Vim has initialized so it won't stop gvim
"  from starting from a non-terminal
"
" Revision 1.27  2008/04/16 03:54:31  infynity
" Enhanced the log display and editing feature
" Auto-install a help file if possible
" Refactoring
"
" Revision 1.26  2008/04/15 04:58:53  infynity
" *** empty log message ***
"
" Revision 1.25  2008/04/15 04:55:38  infynity
" Allow editing of individual revision log messages from the log display
"
" Revision 1.24  2008/04/08 16:28:31  infynity
" *** empty log message ***
"
" Revision 1.23  2008/04/08 16:19:43  infynity
" Internal documentation added
" Code clean up
"
" Revision 1.22  2007/08/08 04:16:45  infynity
" FIleChangedRO autocmd opens folds -- possibly undesirable
"
" Revision 1.21  2006/08/23 04:05:17  infynity
" Perserve cursor position when reloading the buffer after calling co/ci.
"
" Revision 1.20  2006/08/14 07:02:17  infynity
" Changed for Vim7 compatibility.
"
" Revision 1.19  2004/04/17 01:52:18  infynity
" Added copyright information.
"
" Revision 1.18  2004/03/22 12:43:24  infynity
" Fixed detection of existence of diff window for RCS_Diff().
"
" Revision 1.17  2004/01/26 22:31:28  infynity
" Restore 'foldcolumn' when the diff window is closed.
"
" Revision 1.16  2003/12/28 15:05:12  infynity
" Functionalize more stuff.
" Diffing now handled better, including restoring options when the diff window
"  is closed
"
" Revision 1.15  2003/12/20 06:11:26  infynity
" Use "setlocal" rather than "set" in some places.
" Close all folds in both windows when doing a diff view
"
" Revision 1.14  2003/10/09 21:36:05  infynity
" Properly escape $ characters.
"
" Revision 1.13  2003/05/23 21:26:48  infynity
" Change RCSco command call of RCS_CheckOut to use <f-args> rather than <args>.
"
" Revision 1.12  2003/04/21 22:21:12  infynity
" *** empty log message ***
"
" Revision 1.11  2003/04/14 00:24:57  infynity
" *** empty log message ***
"
" Revision 1.10  2003/04/13 03:08:30  infynity
" Syntax highlight the contents of the RCS log window.
"
" Revision 1.9  2003/04/13 01:13:50  infynity
" *** empty log message ***
"
" Revision 1.8  2003/04/12 09:35:56  infynity
" *** empty log message ***
"
" Revision 1.7  2003/04/12 09:34:43  infynity
" Commands for everything added for console vim.
" Show Log command displays in a vim window.
" Tweaks.
"
" Revision 1.6  2003/04/09 20:58:14  infynity
" *** empty log message ***
"
" Revision 1.5  2003/02/05 09:17:07  infynity
" Deleted old commented code.
"
" Revision 1.4  2002/12/07 00:29:48  infynity
" Set 'cpoptions' to make sure the file sources properly, then restore it.
"
" Revision 1.3  2002/09/06 07:26:26  infynity
" Moved RCS.Diff menu item contents to RCSdiff command.
" Use script-local functions &c.
" Other clean-ups.
"
" Revision 1.2  2002/06/29 14:30:00  infynity
" *** empty log message ***
"
" Revision 1.1  2002/06/29 14:10:03  infynity
" Initial revision
"
" }}}1

if v:version < 800
	echohl ErrorMsg
	echomsg 'Vim 8.0 or greater is needed to run ' . expand('<sfile>:p')
	echohl None
	finish
endif

if exists("g:loaded_rcs_plugin")
	finish
endif
let g:loaded_rcs_plugin = 1

let s:savecpo = &cpoptions
set cpoptions&vim

" Find RCS executables: {{{1
let s:ext = ''
if has('unix')
	let s:pathsep = ':'
elseif has('windows')
	let s:pathsep = ';'
	let s:ext = '.exe'
else
	let s:pathsep = ':'
	if ! (exists('g:rcs_plugin_ignore_unrecognized_os') && g:rcs_plugin_ignore_unrecognized_os != 0)
		echohl ErrorMsg
		echomsg expand('<sfile>:p') . ' will probably not work correctly on this OS'
		echohl None
	endif
endif

let s:cmds = {}
for s:cmd in ['rcs', 'co', 'ci', 'rlog', 'rcsdiff']
	for s:found in globpath(substitute($PATH, s:pathsep, ',', 'g'), s:cmd . s:ext, v:false, v:true)
		if executable(s:found)
			let s:cmds[s:cmd] = s:found
			break
		endif
	endfor
endfor
unlet s:cmd s:found

" TODO: Test for all RCS commands instead of just "rcs"?
if ! exists("s:cmds['rcs']")
	echohl ErrorMsg
	echomsg expand('<sfile>:p') . ' could not find RCS command, script loading aborted'
	echohl None

	let &cpoptions = s:savecpo
	unlet s:savecpo
	finish
endif

" Modern RCS has scripts for co, ci, and rlog, so emulate those scripts here
" if they aren't found above:
if ! exists("s:cmds['co']")
	let s:cmds['co'] = s:cmds['rcs'] . ' co'
endif
if ! exists("s:cmds['ci']")
	let s:cmds['ci'] = s:cmds['rcs'] . ' ci'
endif
if ! exists("s:cmds['rlog']")
	let s:cmds['rlog'] = s:cmds['rcs'] . ' rlog'
endif
if ! exists("s:cmds['rcsdiff']")
	let s:cmds['rcsdiff'] = s:cmds['rcs'] . ' rcsdiff'
endif

"echomsg s:cmds

" }}}1

" Auto-update the help file if necessary and possible:  {{{1
let s:self    = expand('<sfile>')
let s:selfdoc = expand('<sfile>:p:h:h') . '/doc/' . expand('<sfile>:p:t:r') . '.txt'
if getftime(s:self) > getftime(s:selfdoc)
	" Do this /after/ Vim has initialized, because tempname() breaks
	" otherwise:
	autocmd VimEnter * RCSUpdateHelp
endif  " }}}1

" Menus: {{{1
if ! exists('g:rcs_plugin_toplevel_menu')
	let g:rcs_plugin_toplevel_menu = ''
endif
if ! exists('g:rcs_plugin_menu_priority')
	let g:rcs_plugin_menu_priority = ''
endif

let s:m = g:rcs_plugin_toplevel_menu
let s:p = g:rcs_plugin_menu_priority

if s:m != '' && s:m[-1:] != '.'
	let s:m = s:m . '.'
endif

if s:p[-1:] != '.'
	let s:p = s:p . '.'
endif

exe 'amenu <silent> ' . s:p . '30 ' . s:m .
	\ '&RCS.&Diff<Tab>:RCSdiff ' .
	\ ':RCSdiff<CR>'
exe 'amenu <silent> ' . s:p . '40 ' . s:m .
	\ '&RCS.Show\ &&\ Edit\ &Log<Tab>:RCSlog ' .
	\ ':RCSlog<CR>'
exe 'amenu <silent> ' . s:p . '60 ' . s:m .
	\ '&RCS.Check\ Out\ &Readonly<Tab>:RCSco\ ro ' .
	\ ':RCSco ro<CR>'
exe 'amenu <silent> ' . s:p . '60 ' . s:m .
	\ '&RCS.Check\ Out\ &Writable<Tab>:RCSco\ w ' .
	\ ':RCSco w<CR>'
exe 'amenu <silent> ' . s:p . '70 ' . s:m .
	\ '&RCS.Check\ &In<Tab>:RCSci ' .
	\ ':RCSci<CR>'

unlet s:m s:p


" Autocommands: {{{1
augroup RCS_plugin
	au!
	autocmd FileChangedRO * nested call s:FileChangedRO()
	autocmd BufUnload * nested call s:BufUnload()
augroup END

" Commands: {{{1
command!          RCSdiff call s:Diff(expand("%:p"))
command!          RCSlog  call s:ViewLog(expand("%:p"))
command! -nargs=1 RCSco   call s:CheckOut(expand("%:p"), <f-args>)
command! -nargs=? RCSci   call s:CheckIn(expand("%:p"))

command! -nargs=? RCSUpdateHelp call s:UpdateHelp(
			\ s:self,
			\ (<q-args> != '' ? fnamemodify(<q-args> . '/' . fnamemodify(s:self, ':p:t:r') . '.txt', '') : s:selfdoc)
		\)

" Functions: {{{1

function! s:FileChangedRO()  " {{{2
	if (filereadable(expand('<afile>:p:h') . '/RCS/' . expand("<afile>:t") . ',v')
				\ || filereadable(expand('<afile>') . ',v'))
				\ && (confirm("This is a read-only RCS controlled file, check out?", "&Yes\n&No", 1, "Q") == 1)
		call s:CheckOut(expand('<afile>:p'), 1)
		silent! foldopen!
	endif
endfunction

function! s:BufUnload()  " {{{2
	if getbufvar(expand('<afile>:p'), 'RCS_CheckedOut') != ''
				\ && (getbufvar(expand('<afile>:p'), 'RCS_CheckedOut') == expand('<afile>:p'))
				\ && (confirm(expand('<afile>:t') . " is an RCS controlled file checked out by Vim.\nCheck back in?", "&Yes\n&No", 1, "Q") == 1)
		call s:CheckIn(expand('<afile>:p'), 0)
	endif
endfunction

function! s:Diff(file)  " {{{2
	if len(s:WinLocalVars('&diff')) > 0
		echohl ErrorMsg
		echomsg "It appears Vim is already running a diff, close those buffers first."
		echohl None
		return 0
	endif

	let rcs_diff_name = "[Previous version of " . fnamemodify(a:file, ':t') . "]"

	if bufnr('^\V' . rcs_diff_name) != -1
		echohl ErrorMsg
		echo "Already viewing differences for the current file."
		echohl None
		return
	endif

	let rcs_diff_name = escape(rcs_diff_name, ' \')
	let rcs_diff_file = tempname()
	let curbuf        = bufnr(a:file)
	let filetype      = getbufvar(a:file, '&filetype')
	let syntax        = getbufvar(a:file, '&syntax')
	let wrap          = getbufvar(a:file, '&wrap')
	let foldcolumn    = getbufvar(a:file, '&foldcolumn')
	let foldmethod    = getbufvar(a:file, '&foldmethod')
	let scrollopt     = getbufvar(a:file, '&scrollopt')
	let scrollbind    = getbufvar(a:file, '&scrollbind')

	silent call system(s:cmds['co'] . ' -p ' . s:ShellEscape(a:file) . ' > ' . s:ShellEscape(rcs_diff_file) . ' 2> /dev/null')
	exe 'silent vertical rightbelow diffsplit ' . rcs_diff_file
	exe 'silent file ' . rcs_diff_name
	exe 'silent bwipe! ' . rcs_diff_file
	call delete(rcs_diff_file)
	exe 'setlocal filetype=' . filetype . ' syntax=' . syntax
	setlocal buftype=nofile noswapfile foldmethod=diff readonly nomodifiable
	setlocal bufhidden=wipe

	exe 'autocmd! BufDelete <buffer> ' .
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&diff", "0") | '
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&wrap", "' . wrap . '") | '
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&scrollbind", "' . scrollbind . '") | '
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&scrollopt", "' . scrollopt . '") | '
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&foldcolumn", "' . foldcolumn . '") | '
		\ 'call setwinvar(bufwinnr(' . curbuf . '), "&foldmethod", "' . foldmethod . '") | '
		\ 'redraw!'

	normal zX
	wincmd p
	normal zX
endfunction

function! s:CheckForLock(file) " {{{2
	let rlog_out = split(system(s:cmds['rlog'] . ' -L -h ' . a:file), '\n')
	if len(rlog_out) == 0
		return ''
	endif

	let locker = ''
	let index = 0
	while index < len(rlog_out)
		if rlog_out[index] =~? '\slocked by: \S\+;'
			let locker = substitute(rlog_out[index], '.*\slocked by: \(\S\+\);.*', '\1', '')
			break
		endif
		let index = index + 1
	endwhile

	return locker
endfunction

function! s:CheckOut(file, mode)  " {{{2
	let mode = ''

	if a:mode == 1 || a:mode ==? 'w'
		let mode = '-l '
	elseif a:mode != '0' && a:mode !=? 'ro' && a:mode !=? 'r'
		echohl ErrorMsg
		echo 'Unknown argument: ' . a:mode . '  Valid arguments are "r"/"ro" or "w".'
		echohl None
		return
	endif

	let locker = s:CheckForLock(a:file)

	if locker != '' && locker != $LOGNAME . 'a'
		if confirm(a:file . " appears to have been locked by username '" . locker . "'.\nForce a check out anyway (this could cause loss of data)?", "&Yes\n&No", 2, 'W') == 2
			return
		else
			let mode = '-f ' . mode
			let RCS_Out = system(s:cmds['co'] . ' ' . mode . s:ShellEscape(a:file))
		endif
	elseif filewritable(a:file)
		if confirm(a:file . " is writable (locked).\nForce a check out of previous version (your changes will be lost)?", "&Yes\n&No", 2, 'W') == 1
			let mode = '-f ' . mode
			let RCS_Out = system(s:cmds['co'] . ' ' . mode . s:ShellEscape(a:file))
		elseif a:mode == 1 || a:mode == 'w' && confirm('Tell Vim this is a controlled RCS file anyway?', "&Yes\n&No", 1, 'Q') == 1
			let b:RCS_CheckedOut = a:file
			return
		else
			return
		endif
	else
		silent! exe '!' . s:cmds[rcsdiff] . ' ' . s:ShellEscape(a:file) . ' >/dev/null 2>&1'
		if v:shell_error > 0 && confirm(a:file . " appears to have been modified without being checked out writable (locked) first.\nCheck out anyway (changes, if any, will be lost)?", "&Yes\n&No", 2, 'W') == 2
			return

		else
			let RCS_Out = system(s:cmds['co'] . ' ' . mode . s:ShellEscape(a:file))
		endif
	endif

	if v:shell_error
		echoerr "Nonzero exit status from 'co " . mode . "...':"
		echohl ErrorMsg | :echo RCS_Out | :echohl None
		let v:errmsg = RCS_Out
		"echoerr RCS_Out

		return 1
	endif

	if a:mode == 1 || a:mode == 'w'
		let b:RCS_CheckedOut = a:file
	elseif exists('b:RCS_CheckedOut')
		let b:RCS_CheckedOut = ''
	endif

	let eventignore_save = &eventignore
	let &eventignore = 'BufUnload,FileChangedRO'
	let l = line(".")
	let c = col(".")
	execute "silent e!"
	call cursor(l, c)
	let &eventignore = eventignore_save
endfunction

function! s:CheckIn(file, ...)  " {{{2
	if (getbufvar(a:file, '&modified') == 1)
				\ && (confirm(fnamemodify(a:file, ':t') . " has unwritten changes, check in anyway?", "&Yes\n&No", 2, "Q") != 1)
		return
	endif

	call setbufvar(a:file, 'RCS_CheckedOut', '')

	let message=printf('%-70s', 'Enter log message for "' . fnamemodify(a:file, ':t') . '" (. to end):')

	if strlen(message) <= 70
		if &columns >= 80
			echo message . "<-70 80->|\n"
		else
			echo message . "<-70\n"
		endif
	endif

	let rlog = "" | let fullrlog = ""

	while rlog != "."
		let fullrlog = fullrlog . "\n" . rlog
		let rlog = input("> ")
		echo "  " . rlog . "\n"
	endwhile

	if fullrlog =~ '^[[:return:][:space:]]*$'
		let fullrlog = '*** empty log message ***'
	endif

	let fullrlog = s:ShellEscape(fullrlog)
	let fullrlog = substitute(fullrlog, '\\'."\n", "\n", 'g')

	let RCS_Out = system(s:cmds['ci'] . ' -m' . fullrlog  . " " . s:ShellEscape(a:file))
	if v:shell_error
		echoerr "Nonzero exit status from 'ci -m ...':"
		echohl ErrorMsg | :echo RCS_Out | :echohl None
		let v:errmsg = RCS_Out
		"echoerr RCS_Out
	endif

	let RCS_Out = system(s:cmds['co'] . ' -u ' . s:ShellEscape(a:file))
	if v:shell_error
		echoerr "Nonzero exit status from 'co -u ...':"
		echohl ErrorMsg | :echo RCS_Out | :echohl None
		let v:errmsg = RCS_Out
		"echoerr RCS_Out
	endif

	if a:0 >= 1 && a:1 == 0
		return
	endif

	let eventignore_save = &eventignore
	let &eventignore = 'BufUnload,FileChangedRO'
	let l = line(".")
	let c = col(".")
	execute "silent e!"
	call cursor(l, c)
	let &eventignore = eventignore_save
endfunction

function! s:ViewLog(file)  " {{{2
	let file_escaped=escape(fnamemodify(a:file, ':t'), ' \')

	exe 'silent topleft new [RCS\ log\ for\ ' . file_escaped . ']'
	let b:rcs_filename = a:file

	call s:ViewLog2(a:file)

	syntax case match
	syntax match rcslogDelim  '^-\{4,}$'
	syntax match rcslogDelim  '^=\{4,}+$'
	syntax match rcslogValues '^revision [0-9.]\+$' contains=rcslogNumber
	syntax match rcslogFile   '^\(RCS file\|Working file\): .\+' contains=rcslogString
	syntax match rcslogValues '^\(head\|branch\|locks\|access list\|symbolic names\|keyword substitution\|total revisions\|description\|locked by\|date\):\( [^;]\+\)\=' contains=rcslogString
	syntax match rcslogValues '\(author\|state\): [^;]\+;'me=e-1 contains=rcslogString
	syntax match rcslogValues '\(lines\|selected revisions\): [ 0-9+-]\+$' contains=rcslogNumber
	syntax match rcslogString ': [^;]\+'ms=s+2 contained contains=rcslogNumber
	syntax match rcslogNumber '[+-]\=[0-9.]\+' contained
	highlight default link rcslogKeys    Comment
	highlight default link rcslogDelim   PreProc
	highlight default link rcslogValues  Identifier
	highlight default link rcslogFile    Type
	highlight default link rcslogString  String
	highlight default link rcslogNumber  Number
	highlight default link rcslogCurrent NonText

	setlocal buftype=nofile noswapfile readonly nomodifiable bufhidden=wipe
	setlocal foldexpr=RCSFoldLog() foldmethod=expr

	normal zR

	nnoremap <buffer> q <C-w>c
	nnoremap <buffer> <space> <C-f>
	nnoremap <buffer> b <C-b>
	nnoremap <silent> <buffer> J :if search('^-\+\nrevision \d\+\.\d\+', 'W')<bar>exe 'normal j'<bar>endif<CR>
	nnoremap <silent> <buffer> K :call search('^revision \d\+\.\d\+', 'Wb')<CR>
				\:call search('^-\+\nrevision \d\+\.\d\+', 'Wb')<CR>j
	nnoremap <silent> <buffer> <cr> :call <SID>EditLogItem()<CR>
	nnoremap <silent> <buffer> d :call <SID>LogDiff()<CR>
	nnoremap <silent> <buffer> <c-l> <c-l>:call <SID>ViewLog2(b:rcs_filename)<CR>

	autocmd CursorMoved <buffer> call s:LogHighlight()
endfunction

function! s:ViewLog2(file)  " {{{2
	setlocal noreadonly modifiable
	let where = s:ByteOffset()
	silent! 1,$delete
	exe 'silent 0r !rlog ' . s:ShellEscape(a:file)
	let keys = [
			\ '+++ Keys:                                                            +++',
			\ '+++  <space>     -  Page down                                        +++',
			\ '+++  b           -  Page up                                          +++',
			\ '+++  <control-l> -  Refresh the screen and reload the log            +++',
			\ '+++  J           -  Jump to next log section                         +++',
			\ '+++  K           -  Jump to previous log section                     +++',
			\ "+++  <enter>     -  Edit the current revision entry's message        +++",
			\ "+++  d           -  Diff the current revision with the previous one  +++",
			\ '+++  q           -  Close this log view                              +++',
		\ ]
	call append(0, keys)
	setlocal readonly nomodifiable
	1 " Go to the first line in the file.
	silent! execute 'goto ' . where

	exe 'syntax match rcslogKeys   =^\%<' . (len(keys) + 1) . 'l+++ .\+ +++$='
endfunction

function! RCSFoldLog()  " {{{2
	if getline(v:lnum) =~ '^+++ .\+ +++$'
		return 1
	endif
	if getline(v:lnum) == '' && getline(v:lnum - 1) =~ '^+++ .\+ +++$'
		return 0
	endif
	if getline(v:lnum) =~ '^-\+$'
		return '>1'
	endif

	return '='
endfunction

function! s:LogHighlight()  " {{{2
	let curline = line('.')
	let idarr = s:GetLogId(curline)

	if idarr[0] != -1
		if exists('b:rcsmatchid')
			silent! call matchdelete(b:rcsmatchid)
			call matchadd('rcslogCurrent', '^\%' . idarr[1] . 'l\_.\+\%' . idarr[2] . 'l-\+', 20, b:rcsmatchid)
		else
			let b:rcsmatchid = matchadd('rcslogCurrent', '^\%' . idarr[1] . 'l\_.\+\%' . idarr[2] . 'l-\+', 20)
		endif
	else
		silent! call matchdelete(b:rcsmatchid)
	endif

	" A faster/easier way?:
	"  /^-\+\(\n\(-\+\)\@!.\+\)*\%#.*\(\n\(-\+\)\@!.\+\)*
endfunction

function! s:GetLogId(line)  " {{{2
	let offset = s:ByteOffset()
	call cursor(a:line, 0)

	let back    = search('^-\+\nrevision \d\+\.\d\+', 'bWn')
	let forward = search('^-\+$', 'Wn')

	exe 'go ' . offset

	if back > 0 && a:line >= back && a:line <= forward && getline('.') !~ '^-\+$'
		let line = getline(back + 1)
		let id   = substitute(line, 'revision \(\d\+\.\d\+\).*', '\1', '')

		return [id, back, forward]
	else
		return [-1, -1, -1]
	endif
endfunction

function! s:LogDiff()  " {{{2
	if ! exists('b:rcs_filename')
		echohl ErrorMsg
		echomsg "Can't determine the filename associated with the current log"
		echohl None
		return 0
	endif

	if len(s:WinLocalVars('&diff')) > 0
		echohl ErrorMsg
		echomsg "It appears Vim is already running a diff, close those buffers first."
		echohl None
		return 0
	endif

	let rcs_filename = b:rcs_filename

	let curline = line('.')
	let idarr1 = s:GetLogId(curline)

	if idarr1[0] != -1
		let idarr2 = s:GetLogId(idarr1[2] + 1)
	endif

	if idarr1[0] == -1 || idarr2[0] == -1
		echohl ErrorMsg
		echomsg "Can't determine the revision IDs to diff"
		echohl None
		return 0
	endif

	let file_escaped=escape(fnamemodify(rcs_filename, ':t'), ' \')

	exe 'silent topleft new [' . file_escaped . ', revision ' . idarr2[0] . ']'
	silent exe 'read !co -p -r' . idarr2[0] . ' ' . s:ShellEscape(rcs_filename) . ' 2>/dev/null'
	diffthis
	setlocal buftype=nofile noswapfile readonly nomodifiable bufhidden=wipe

	exe 'silent vertical rightbelow new [' . file_escaped . ', revision ' . idarr1[0] . ']'
	silent exe 'read !co -p -r' . idarr1[0] . ' ' . s:ShellEscape(rcs_filename) . ' 2>/dev/null'
	diffthis
	setlocal buftype=nofile noswapfile readonly nomodifiable bufhidden=wipe

	wincmd p
	wincmd _
	1
endfunction

function! s:EditLogItem()  " {{{2
	if ! exists('b:rcs_filename')
		echohl ErrorMsg
		echomsg "Can't determine the filename associated with the current log"
		echohl None
		return 0
	endif

	let rcs_filename = b:rcs_filename

	let curline = line('.')
	let idarr = s:GetLogId(curline)

	if idarr[0] != -1
		let fname =  '[Log entry for ' . fnamemodify(rcs_filename, ':p:t') . ' revision ' . idarr[0] . ']'

		if bufloaded(fname)
			echohl ErrorMsg
			echo "A buffer for that log message already exists"
			echohl None
			return 0
		endif

		execute 'new ' . escape(fname, ' \')
		setlocal buftype=acwrite bufhidden=wipe
		let b:rcs_id       = idarr[0]
		let b:rcs_filename = rcs_filename
		silent! execute 'read !' . s:cmds['rlog'] . ' -r' . idarr[0] . ' ' . s:ShellEscape(rcs_filename)
		silent! 1,/^revision .\+\ndate: \d\{4\}\/\d\d\/\d\d \d\d:\d\d:\d\d.*/+1 delete
		silent! $delete
		call append(0, ["+++ Change the log message below this line and write+quit +++", ''])
		setlocal nomodified

		syntax match rcslogKeys =^\%<2l+++ .\+ +++$=
		highlight default link rcslogKeys Todo

		autocmd BufWriteCmd <buffer> call s:SaveLogItem()
	else
		echohl ErrorMsg
		echom "The cursor isn't within a log section"
		echohl None
		return 0
	endif
endfunction

function! s:SaveLogItem()  " {{{2
	if ! exists('b:rcs_id') || ! exists('b:rcs_filename')
		return 0
	endif

	let lnum     = 1
	while match(getline(lnum), '^+++ .\+ +++$') >= 0
		let lnum = lnum + 1
	endwhile

	if match(getline(lnum), '^$') >= 0
		let lnum = lnum + 1
	endif

	let fullrlog = join(getline(lnum, '$'), "\n")

	if fullrlog =~ '^[[:return:][:space:]]*$'
		let fullrlog = '*** empty log message ***'
	endif

	let fullrlog = s:ShellEscape(fullrlog)
	let fullrlog = substitute(fullrlog, '\\'."\n", "\n", 'g')

	let RCS_Out = system(s:cmds['rcs'] . " -m" . b:rcs_id  . ":" . fullrlog . " " . s:ShellEscape(b:rcs_filename))
	if v:shell_error
		echoerr "Nonzero exit status from 'ci -m ...':"
		echohl ErrorMsg | :echo RCS_Out | :echohl None
		let v:errmsg = RCS_Out
	endif

	setlocal nomodified
endfunction

function! s:ByteOffset()  " {{{2
	let offset = line2byte(line(".")) + col(".") - 1
	return (offset < 1 ? 1 : offset)
endfunction

function! s:WinLocalVars(var)  " {{{2
	let vals = []
	for i in range(1, tabpagenr('$'))
		for j in range(1, tabpagewinnr(i, '$'))
			let tmp = gettabwinvar(i, j, a:var)
			if tmp != 0 && tmp != ''
				call add(vals, [i, j, tmp])
			endif
		endfor
	endfor
	return vals
endfunction

function! s:UpdateHelp(self, doc)  " {{{2
	let docdir = fnamemodify(a:doc, ':p:h')

	if ! isdirectory(docdir)
		call mkdir(docdir, 'p')
	endif

	if filewritable(docdir) != 2
		echohl ErrorMsg
		echomsg "Can't write to directory \"" . docdir . "\"." . 
					\"  Please make sure it exists as a directory and is writable."
		echohl None
		return 0
	endif

	echomsg "Updating help file for " . fnamemodify(a:self, ':p:t')

	let lines = readfile(a:self)

	if len(lines) <= 0
		echohl ErrorMsg
		echomsg "Unable to scan \"" . a:self . "\" for help file."
		echohl None
		return 0
	endif

	for i in range(len(lines))
		if lines[i] =~ '^" Last Change:'
			let lastchange = substitute(lines[i], '" ', '\t\t', '')
		elseif lines[i] =~ '^finish " -- Help file follows: {\{3}$'
			let starthelp = i + 2
			break
		endif
	endfor

	call insert(lines, lastchange, starthelp + 1)
	call writefile(lines[starthelp :], a:doc)

	silent execute 'helptags ' . docdir

	return 1
endfunction

function! s:ShellEscape(str) " {{{2
	if exists('*shellescape')
		return shellescape(a:str)
	else
		if has('unix')
			return "'" . substitute(a:str, "'", "'\\\\''", 'g') . "'"
		else
			" Don't know how to properly escape for 'doze, so don't bother:
		return a:str
		endif
	endif
endfunction
" }}}1

let &cpoptions = s:savecpo
unlet s:savecpo

finish " -- Help file follows: {{{

*rcs.txt*	Assist with editing RCS controlled files.
		Author: Christian J. Robinson

					*rcs.vim* *rcs*

This is a set of autocommands, commands, and a menu to help you handle RCS
controlled files.  It requires Vim 7.0 or later to run.

------------------------------------------------------------------------------

1. Introduction				|rcs-intro|
2. Commands				|rcs-commands|
3. Configuration Variables		|rcs-configuration|

==============================================================================
1. Introduction					*rcs-intro*

If you try to modify a readonly file that has a RCS/<file>,v counterpart you
will be asked if you want to check the file out for modification, and when
you unload the buffer you'll be prompted if you want to check the file back
in, and allowed to enter a log message.

The commands have corresponding menu items, which should be fairly
self-explanatory.

==============================================================================
2. Commands					*rcs-commands*

						*:RCSdiff*
:RCSdiff
	View the differences between the working file and the last revision,
	using vimdiff.

						*:RCSlog*
:RCSlog
	Show the entire log file--syntax highlighted--in a split window.
	Individual log entries can be edited from this display.  And you can
	show the differences between two consecutive versions.

						*:RCSco*
:RCSco ro
	Check out the current file readonly (unlocked).

:RCSco w
	Check out the current file writable (locked).
	
	With either use of the ":RCSco" command, Vim will ask if you want to
	discard changes if you already have a locked/modifiable file, or if
	you have an unlocked file that appears to have been modified, or if
	it looks like a user other than you has locked the file.  In the
	last two cases it is advised that you say no and resolve the
	differences outside of Vim.

						*:RCSci*
:RCSci
	Check the current (changed) file in, you will be prompted for a log
	message, and the file will automatically be checked back out
	readonly.

						*:RCSUpdateHelp*
:RCSUpdateHelp [directory]
	Update the help file for this script.  If you specify a directory
	the help file will be written there rather than the doc directory
	relative to where this script is installed--useful if that directory
	is the wrong one.


==============================================================================
3. Configuration Variables			*rcs-configuration*

*g:rcs_plugin_toplevel_menu*
The name of the menu to place the RCS menu into, if you don't want it at the
top level.

*g:rcs_plugin_menu_priority*
The menu priority to use for the RCS menu.

*g:rcs_plugin_ignore_unrecognized_os*
Do not issue a warning when starting in an unrecognized operating system.

Examples: >
 :let g:rcs_plugin_toplevel_menu = '&Misc'
 :let g:rcs_plugin_menu_priority = '130.10'
 :let g:rcs_plugin_menu_force = 1
 :let g:rcs_plugin_ignore_unrecognized_os = 1
<