" ingo/cmdargs/substitute.vim: Functions for parsing of :substitute arguments.
"
" DEPENDENCIES:
"   - ingo/list.vim autoload script
"
" Copyright: (C) 2012-2017 Ingo Karkat
"   The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer:	Ingo Karkat <ingo@karkat.de>
"
" REVISION	DATE		REMARKS
"   1.021.011	20-Jun-2014	FIX: ingo#cmdargs#substitute#Parse() branch for
"				special case of {flags} without /pat/string/
"				must only be entered when a:arguments is not
"				empty.
"   1.014.010	15-Oct-2013	Factor out s:EnsureList() to ingo/list.vim.
"   1.011.009	24-Jul-2013	FIX: Use the rules for the /pattern/ separator
"				as stated in :help E146.
"   1.009.008	14-Jun-2013	Minor: Make matchlist() robust against
"				'ignorecase'.
"   1.007.007	01-Jun-2013	Move functions from ingo/cmdargs.vim to
"				ingo/cmdargs/pattern.vim and
"				ingo/cmdargs/substitute.vim.
"   1.006.006	29-May-2013	Again change
"				ingo#cmdargs#ParseSubstituteArgument() interface
"				to parse the :substitute [flags] [count] by
"				default.
"   1.006.005	28-May-2013	BUG: ingo#cmdargs#ParseSubstituteArgument()
"				mistakenly returns a:defaultFlags when full
"				/pat/repl/ or a literal pat is passed. Only
"				return a:defaultFlags when the passed
"				a:arguments is really empty.
"				CHG: Redesign
"				ingo#cmdargs#ParseSubstituteArgument() interface
"				to the existing use cases. a:defaultReplacement
"				should only be used when a:arguments is really
"				empty, too. Introduce an optional options
"				Dictionary and preset replacement / flags
"				defaults of "~" and "&" resp. for when
"				a:arguments is really empty, which makes sense
"				for use with :substitute. Allow submatches for
"				a:flagsExpr via a:options.flagsMatchCount, to
"				avoid further parsing in the client.
"				ENH: Also parse lone {flags} (if a:flagsExpr is
"				given) by default, and allow to turn this off
"				via a:options.isAllowLoneFlags.
"				ENH: Allow to pass a:options.emptyPattern, too.
"   1.001.004	21-Feb-2013	Move to ingo-library.
"	003	29-Jan-2013	Add ingocmdargs#ParseSubstituteArgument() for
"				use in PatternsOnText/Except.vim and
"				ExtractMatchesToReg.vim.
"				Change ingocmdargs#UnescapePatternArgument() to
"				take the result of
"				ingocmdargs#ParsePatternArgument() instead of
"				invoking that function itself. And make it
"				handle an empty separator.
"	002	21-Jan-2013	Add ingocmdargs#ParsePatternArgument() and
"				ingocmdargs#UnescapePatternArgument() from
"				PatternsOnText.vim.
"	001	25-Nov-2012	file creation from CaptureClipboard.vim.

function! s:ApplyEmptyFlags( emptyFlags, parsedFlags)
    return (empty(filter(copy(a:parsedFlags), '! empty(v:val)')) ? a:emptyFlags : a:parsedFlags)
endfunction
function! ingo#cmdargs#substitute#Parse( arguments, ... )
"******************************************************************************
"* PURPOSE:
"   Parse the arguments of a custom command that works like :substitute.
"* ASSUMPTIONS / PRECONDITIONS:
"   None.
"* EFFECTS / POSTCONDITIONS:
"   None.
"* INPUTS:
"   a:arguments The command's raw arguments; usually <q-args>.
"   a:options.flagsExpr             Pattern that captures any optional part
"				    after the replacement (usually some
"				    substitution flags). By default, captures
"				    the known :substitute |:s_flags| and
"				    optional [count]. Pass an empty string to
"				    disallow any flags.
"   a:options.additionalFlags       Flags that will be recognized in addition to
"				    the default |:s_flags|; default none. Modify
"				    this instead of passing a:options.flagsExpr
"				    if you want to recognize additional flags.
"   a:options.flagsMatchCount       Optional number of submatches captured by
"				    a:options.flagsExpr. Defaults to 2 with the
"				    default a:options.flagsExpr, to 1 with a
"				    non-standard non-empty a:options.flagsExpr,
"				    and 0 if a:options.flagsExpr is empty.
"   a:options.defaultReplacement    Replacement to use when the replacement part
"				    is omitted. Empty by default.
"   a:options.emptyPattern          Pattern to use when no arguments at all are
"				    given. Defaults to "", which automatically
"				    uses the last search pattern in a
"				    :substitute. You need to escape this
"				    yourself (to be able to pass in @/, which
"				    already is escaped).
"   a:options.emptyReplacement      Replacement to use when no arguments at all
"				    are given. Defaults to "~" to use the
"				    previous replacement in a :substitute.
"   a:options.emptyFlags            Flags to use when a:options.flagsExpr is not
"				    empty, but no arguments at all are given.
"				    Defaults to ["&", ""] to use the previous
"				    flags of a :substitute. Provide a List if
"				    a:options.flagsMatchCount is larger than 1.
"   a:options.isAllowLoneFlags      Allow to omit /pat/repl/, and parse a
"				    stand-alone a:options.flagsExpr (assuming
"				    one is passed). On by default.
"* RETURN VALUES:
"   A list of [separator, pattern, replacement, flags, count] (default)
"   A list of [separator, pattern, replacement] when a:options.flagsExpr is
"   empty or a:options.flagsMatchCount is 0.
"   A list of [separator, pattern, replacement, submatch1, ...];
"   elements added depending on a:options.flagsMatchCount.
"   flags and count are meant to be directly concatenated; count therefore keeps
"   leading whitespace, but be aware that this is optional with :substitute,
"   too!
"   The replacement part is always escaped for use inside separator, also when
"   the default is taken.
"******************************************************************************
    let l:options = (a:0 ? a:1 : {})
    let l:additionalFlags = get(l:options, 'additionalFlags', '')
    let l:flagsExpr = get(l:options, 'flagsExpr', '\(&\?[cegiInp#lr' . l:additionalFlags . ']*\)\(\s*\d*\)')
    let l:isParseFlags = (! empty(l:flagsExpr))
    let l:flagsMatchCount = get(l:options, 'flagsMatchCount', (has_key(l:options, 'flagsExpr') ? (l:isParseFlags ? 1 : 0) : 2))
    let l:defaultFlags = (l:isParseFlags ? repeat([''], l:flagsMatchCount) : [])
    let l:defaultReplacement = get(l:options, 'defaultReplacement', '')
    let l:emptyPattern = get(l:options, 'emptyPattern', '')
    let l:emptyReplacement = get(l:options, 'emptyReplacement', '~')
    let l:emptyFlags = get(l:options, 'emptyFlags', ['&'] + repeat([''], l:flagsMatchCount - 1))
    let l:isAllowLoneFlags = get(l:options, 'isAllowLoneFlags', 1)

    let l:matches = matchlist(a:arguments, '\C^\([[:alnum:]\\"|]\@![\x00-\xFF]\)\(.\{-}\)\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\1\(.\{-}\)\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\1' . l:flagsExpr . '$')
    if ! empty(l:matches)
	" Full /pat/repl/[flags].
	return l:matches[1:3] + (l:isParseFlags ? l:matches[4:(4 + l:flagsMatchCount - 1)] : [])
    endif

    let l:matches = matchlist(a:arguments, '\C^\([[:alnum:]\\"|]\@![\x00-\xFF]\)\(.\{-}\)\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\1\(.\{-}\)$')
    if ! empty(l:matches)
	" Partial /pat/[repl].
	return l:matches[1:2] + [(empty(l:matches[3]) ? escape(l:defaultReplacement, l:matches[1]) : l:matches[3])] + l:defaultFlags
    endif

    let l:matches = matchlist(a:arguments, '\C^\([[:alnum:]\\"|]\@![\x00-\xFF]\)\(.\{-}\)$')
    if ! empty(l:matches)
	" Minimal /[pat].
	return l:matches[1:2] + [escape(l:defaultReplacement, l:matches[1])] + l:defaultFlags
    endif

    if ! empty(a:arguments)
	if l:isParseFlags && l:isAllowLoneFlags
	    let l:matches = matchlist(a:arguments, '\C^' . l:flagsExpr . '$')
	    if ! empty(l:matches)
		" Special case of {flags} without /pat/string/.
		return ['/', l:emptyPattern, escape(l:emptyReplacement, '/')] + s:ApplyEmptyFlags(ingo#list#Make(l:emptyFlags), l:matches[1:(l:flagsMatchCount)])
	    endif
	endif

	" Literal pat.
	if ! empty(l:defaultReplacement)
	    " Clients cannot concatentate the results without a separator, so
	    " use one.
	    return ['/', escape(a:arguments, '/'), escape(l:defaultReplacement, '/')] + l:defaultFlags
	else
	    return ['', a:arguments, l:defaultReplacement] + l:defaultFlags
	endif
    else
	" Nothing.
	return ['/', l:emptyPattern, escape(l:emptyReplacement, '/')] + (l:isParseFlags ? ingo#list#Make(l:emptyFlags) : [])
    endif
endfunction

" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
