]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Version 0.1
authorAndy Wokula <anwoku@yahoo.de>
Wed, 19 Dec 2012 00:00:00 +0000 (00:00 +0000)
committerAble Scraper <scraper@vim-scripts.org>
Sun, 24 Mar 2013 03:50:30 +0000 (22:50 -0500)
Uploaded again, this time not gzipped (vimball anyway keeps an unpacked version on disk)

README [new file with mode: 0644]
autoload/explainpat.vim [new file with mode: 0644]
autoload/nwo/magic.vim [new file with mode: 0644]
doc/explainpat.txt [new file with mode: 0644]
plugin/explainpat.vim [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..65cba88
--- /dev/null
+++ b/README
@@ -0,0 +1,48 @@
+This is a mirror of http://www.vim.org/scripts/script.php?script_id=4364
+
+:ExplainPattern {pattern}               or
+:ExplainPattern {register}
+
+        parse the given Vim {pattern} and print a line of help
+        (with color) for each found pattern item.  Nested
+        items get extra indent.
+
+        A single-char argument is used as {register} argument:
+                /      explain the last search pattern
+                *      explain pattern from the clipboard
+                a      explain pattern from register a
+
+
+Example:  :ExplainPattern *
+
+Register: *
+Pattern: \C^\%(\\\%(@<.\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|@[>=!]\|_[[^$.]\=\|.\)\|.\)
+  \C         match case while matching the pattern
+  ^          match at start of line (or string)
+  \%(        start of non-capturing group
+  |   \\         literal string (1 atom(s))
+  |   \%(        start of non-capturing group
+  |   |   @<         literal string (2 atom(s))
+  |   |   .          match any character
+  |   | \|         OR branch
+  |   |   %          literal string (1 atom(s))
+  |   |   [dxouU[(^$V#<>] collection
+  |   |   \=         (multi) zero or one of the preceding atom
+  |   | \|         OR branch
+  |   |   z          literal string (1 atom(s))
+  |   |   [1-9se(]   collection
+  |   | \|         OR branch
+  |   |   @          literal string (1 atom(s))
+  |   |   [>=!]      collection
+  |   | \|         OR branch
+  |   |   _          literal string (1 atom(s))
+  |   |   [[^$.]     collection
+  |   |   \=         (multi) zero or one of the preceding atom
+  |   | \|         OR branch
+  |   |   .          match any character
+  |   \)         end of group
+  | \|         OR branch
+  |   .          match any character
+  \)         end of group
+
+2013 Jan 17
diff --git a/autoload/explainpat.vim b/autoload/explainpat.vim
new file mode 100644 (file)
index 0000000..181099c
--- /dev/null
@@ -0,0 +1,470 @@
+" File:         explainpat.vim
+" Created:      2011 Nov 02
+" Last Change:  2012 Dec 19
+" Rev Days:     3
+" Author:      Andy Wokula <anwoku@yahoo.de>
+" License:     Vim License, see :h license
+" Version:     0.1
+
+" Implements :ExplainPattern [pattern]
+
+" TODO {{{
+" - more testing, completeness check
+" - detailed collections
+" - \z
+" ? literal string: also print the unescaped magic items
+" ? literal string: show leading/trailing spaces
+" + start of "first" capturing group, start of 2nd ...
+" + `\|' should get less indent than the branches, do we need to create an
+"   AST?       ! no, keep it straight forward
+" + \%[...]
+" + \{, \{-
+"}}}
+
+" Init Folklore {{{
+let s:cpo_save = &cpo
+set cpo&vim
+let g:explainpat#loaded = 1
+"}}}
+
+func! explainpat#ExplainPattern(cmd_arg) "{{{
+    if a:cmd_arg == ""
+       " let pattern_str = nwo#vis#Get()
+       echo "(usage) :ExplainPattern [{register} | {pattern}]"
+       return
+    elseif strlen(a:cmd_arg) == 1 && a:cmd_arg =~ '["@0-9a-z\-:.*+/]'
+       echo 'Register:' a:cmd_arg
+       let pattern_str = getreg(a:cmd_arg)
+    else
+       let pattern_str = a:cmd_arg
+    endif
+
+    echo printf('Pattern: %s', pattern_str)
+    let magicpat = nwo#magic#MakeMagic(pattern_str)
+    if magicpat !=# pattern_str
+       echo printf('Magic Pattern: %s', magicpat)
+    endif
+
+    " hmm, we need state for \%[ ... ]
+    let s:in_opt_atoms = 0
+    let s:capture_group_nr = 0
+
+    let hulit = s:NewHelpPrinter()
+    call hulit.AddIndent('  ')
+    let bull = s:NewTokenBiter(magicpat, '')
+    while !bull.AtEnd()
+       let item = bull.Bite(s:magic_item_pattern)
+       if item != ''
+           let Doc = get(s:doc, item, '')
+           if empty(Doc)
+               call hulit.AddLiteral(item)
+           elseif type(Doc) == s:STRING
+               call hulit.Print(item, Doc)
+           elseif type(Doc) == s:FUNCREF
+               call call(Doc, [bull, hulit, item])
+           endif
+       else
+           echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest())
+           break
+       endif
+       unlet Doc
+    endwhile
+    call hulit.FlushLiterals()
+endfunc "}}}
+
+" s: types {{{
+let s:STRING = type("")
+let s:DICT   = type({})
+let s:FUNCREF = type(function("tr"))
+" }}}
+
+let s:magic_item_pattern = '\C^\%(\\\%(@<.\|%\%([dxouU[(^$V#]\|[<>]\=\%(''.\)\=\)\|z[1-9se(]\|{\|@[>=!]\|_[[^$.]\=\|.\)\|.\)'
+
+let s:doc = {} " {{{
+" this is all the help data ...
+"   strings, funcrefs and intermixed s:DocFoo() functions
+" strongly depends on s:magic_item_pattern
+
+func! s:DocOrBranch(bull, hulit, item) "{{{
+    call a:hulit.RemIndent()
+    call a:hulit.Print(a:item, "OR branch")
+    call a:hulit.AddIndent('  ')
+endfunc "}}}
+
+let s:doc['\|'] = function("s:DocOrBranch")
+let s:doc['\&'] = "AND branch"
+
+let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth')
+
+func! s:DocGroupStart(bull, hulit, item) "{{{
+    if a:item == '\%('
+       call a:hulit.Print(a:item, "start of non-capturing group")
+    else " a:item == '\('
+       let s:capture_group_nr += 1
+       call a:hulit.Print(a:item, printf("start of %s capturing group", get(s:ord, s:capture_group_nr, '(invalid)')))
+    endif
+    call a:hulit.AddIndent('| ', '  ')
+endfunc "}}}
+func! s:DocGroupEnd(bull, hulit, item) "{{{
+    call a:hulit.RemIndent(2)
+    call a:hulit.Print(a:item, "end of group")
+endfunc "}}}
+
+let s:doc['\('] = function("s:DocGroupStart")
+let s:doc['\%('] = function("s:DocGroupStart")
+let s:doc['\)'] =  function("s:DocGroupEnd")
+
+let s:doc['\z('] = "only in syntax scripts"
+let s:doc['*'] = "(multi) zero or more of the preceding atom"
+let s:doc['\+'] = "(multi) one or more of the preceding atom"
+let s:doc['\='] = "(multi) zero or one of the preceding atom"
+let s:doc['\?'] = "(multi) zero or one of the preceding atom"
+" let s:doc['\{'] = "(multi) N to M, greedy"
+" let s:doc['\{-'] = "(multi) N to M, non-greedy"
+
+func! s:DocBraceMulti(bull, hulit, item) "{{{
+    let rest = a:bull.Bite('^-\=\d*\%(,\d*\)\=}')
+    if rest != ""
+       if rest == '-}'
+           call a:hulit.Print(a:item. rest, "non-greedy version of `*'")
+       elseif rest =~ '^-'
+           call a:hulit.Print(a:item. rest, "(multi) N to M, non-greedy")
+       else
+           call a:hulit.Print(a:item. rest, "(multi) N to M, greedy")
+       endif
+    else
+       echoerr printf('ExplainPattern: cannot parse %s', a:item. a:bull.Rest())
+    endif
+endfunc "}}}
+
+let s:doc['\{'] = function("s:DocBraceMulti")
+
+let s:doc['\@>'] = "(multi) match preceding atom like a full pattern"
+let s:doc['\@='] = "(assertion) require match for preceding atom"
+let s:doc['\@!'] = "(assertion) forbid match for preceding atom"
+let s:doc['\@<='] = "(assertion) require match for preceding atom to the left"
+let s:doc['\@<!'] = "(assertion) forbid match for preceding atom to the left"
+let s:doc['^'] = "(assertion) require match at start of line"
+let s:doc['\_^'] = "(assertion) like `^', allowed anywhere in the pattern"
+let s:doc['$'] = "(assertion) require match at end of line"
+let s:doc['\_$'] = "(assertion) like `$', allowed anywhere in the pattern"
+let s:doc['.'] = "match any character"
+let s:doc['\_.'] = "match any character or newline"
+
+func! s:DocUnderscore(bull, hulit, item) "{{{
+    let cclass = a:bull.Bite('^\a')
+    if cclass != ''
+       let cclass_doc = get(s:doc, '\'. cclass, '(invalid character class)')
+       call a:hulit.Print(a:item. cclass, printf('%s or end-of-line', cclass_doc))
+    else
+       echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.'))
+    endif
+endfunc "}}}
+
+let s:doc['\_'] = function("s:DocUnderscore")
+let s:doc['\<'] = "(assertion) require match at begin of word, :h word"
+let s:doc['\>'] = "(assertion) require match at end of word, :h word"
+let s:doc['\zs'] = "set begin of match here"
+let s:doc['\ze'] = "set end of match here"
+let s:doc['\%^'] = "(assertion) match at begin of buffer"
+let s:doc['\%$'] = "(assertion) match at end of buffer"
+let s:doc['\%V'] = "(assertion) match within the Visual area"
+let s:doc['\%#'] = "(assertion) match with cursor position"
+
+" \%'m   \%<'m   \%>'m
+" \%23l  \%<23l  \%>23l
+" \%23c  \%<23c  \%>23c
+" \%23v  \%<23v  \%>23v
+" backslash percent at/before/after
+func! s:DocBspercAt(bull, hulit, item) "{{{
+    let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)')
+    if rest[0] == "'"
+       call a:hulit.Print("(assertion) match with position of mark ". rest[1])
+    else
+       let number = rest[:-2]
+       let type = rest[-1:]
+       if type == "l"
+           call a:hulit.Print("match in line ". number)
+       elseif type == "c"
+           call a:hulit.Print("match in column ". number)
+       elseif type == "v"
+           call a:hulit.Print("match in virtual column ". number)
+       endif
+    endif
+endfunc "}}}
+func! s:DocBspercBefore(bull, hulit, item) "{{{
+    let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)')
+    if rest[0] == "'"
+       call a:hulit.Print("(assertion) match before position of mark ". rest[1])
+    else
+       let number = rest[:-2]
+       let type = rest[-1:]
+       if type == "l"
+           call a:hulit.Print("match above line ". number)
+       elseif type == "c"
+           call a:hulit.Print("match before column ". number)
+       elseif type == "v"
+           call a:hulit.Print("match before virtual column ". number)
+       endif
+    endif
+endfunc "}}}
+func! s:DocBspercAfter(bull, hulit, item) "{{{
+    let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)')
+    if rest[0] == "'"
+       call a:hulit.Print("(assertion) match after position of mark ". rest[1])
+    else
+       let number = rest[:-2]
+       let type = rest[-1:]
+       if type == "l"
+           call a:hulit.Print("match below line ". number)
+       elseif type == "c"
+           call a:hulit.Print("match after column ". number)
+       elseif type == "v"
+           call a:hulit.Print("match after virtual column ". number)
+       endif
+    endif
+endfunc "}}}
+
+let s:doc['\%'] = function("s:DocBspercAt")
+let s:doc['\%<'] = function("s:DocBspercBefore")
+let s:doc['\%>'] = function("s:DocBspercAfter")
+
+let s:doc['\i'] = "identifier character (see 'isident' option)"
+let s:doc['\I'] = "like \"\\i\", but excluding digits"
+let s:doc['\k'] = "keyword character (see 'iskeyword' option)"
+let s:doc['\K'] = "like \"\\k\", but excluding digits"
+let s:doc['\f'] = "file name character (see 'isfname' option)"
+let s:doc['\F'] = "like \"\\f\", but excluding digits"
+let s:doc['\p'] = "printable character (see 'isprint' option)"
+let s:doc['\P'] = "like \"\\p\", but excluding digits"
+let s:doc['\s'] = "whitespace character: <Space> and <Tab>"
+let s:doc['\S'] = "non-whitespace character; opposite of \\s"
+let s:doc['\d'] = "digit: [0-9]"
+let s:doc['\D'] = "non-digit: [^0-9]"
+let s:doc['\x'] = "hex digit: [0-9A-Fa-f]"
+let s:doc['\X'] = "non-hex digit: [^0-9A-Fa-f]"
+let s:doc['\o'] = "octal digit: [0-7]"
+let s:doc['\O'] = "non-octal digit: [^0-7]"
+let s:doc['\w'] = "word character: [0-9A-Za-z_]"
+let s:doc['\W'] = "non-word character: [^0-9A-Za-z_]"
+let s:doc['\h'] = "head of word character: [A-Za-z_]"
+let s:doc['\H'] = "non-head of word character: [^A-Za-z_]"
+let s:doc['\a'] = "alphabetic character: [A-Za-z]"
+let s:doc['\A'] = "non-alphabetic character: [^A-Za-z]"
+let s:doc['\l'] = "lowercase character: [a-z]"
+let s:doc['\L'] = "non-lowercase character: [^a-z]"
+let s:doc['\u'] = "uppercase character: [A-Z]"
+let s:doc['\U'] = "non-uppercase character: [^A-Z]"
+
+let s:doc['\e'] = "match <Esc>"
+let s:doc['\t'] = "match <Tab>"
+let s:doc['\r'] = "match <CR>"
+let s:doc['\b'] = "match CTRL-H"
+let s:doc['\n'] = "match a newline"
+let s:doc['~'] = "match the last given substitute string"
+let s:doc['\1'] = "match first captured string"
+let s:doc['\2'] = "match second captured string"
+let s:doc['\3'] = "match third captured string"
+let s:doc['\4'] = "match fourth captured string "
+let s:doc['\5'] = "match fifth captured string"
+let s:doc['\6'] = "match sixth captured string"
+let s:doc['\7'] = "match seventh captured string"
+let s:doc['\8'] = "match eighth captured string"
+let s:doc['\9'] = "match ninth captured string"
+
+" \z1
+" \z2
+" \z9
+
+" from MakeMagic()
+" skip the rest of a collection
+let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]'
+
+func! s:DocCollection(bull, hulit, item) "{{{
+    let collstr = a:bull.Bite(s:coll_skip_pat)
+    call a:hulit.Print(a:item. collstr, 'collection'. (a:item=='\_[' ? ' with end-of-line added' : ''))
+endfunc "}}}
+
+let s:doc['['] = function("s:DocCollection")
+let s:doc['\_['] = function("s:DocCollection")
+
+func! s:DocOptAtoms(bull, hulit, item) "{{{
+    if a:item == '\%['
+       call a:hulit.Print(a:item, "start a sequence of optionally matched atoms")
+       let s:in_opt_atoms = 1
+       call a:hulit.AddIndent('. ')
+    else " a:item == ']'
+       if s:in_opt_atoms
+           call a:hulit.RemIndent()
+           call a:hulit.Print(a:item, "end of optionally matched atoms")
+           let s:in_opt_atoms = 0
+       else
+           call a:hulit.AddLiteral(a:item)
+       endif
+    endif
+endfunc "}}}
+
+" let s:doc['\%['] = "start a sequence of optionally matched atoms"
+let s:doc['\%['] = function("s:DocOptAtoms")
+let s:doc[']'] = function("s:DocOptAtoms")
+
+let s:doc['\c'] = "ignore case while matching the pattern"
+let s:doc['\C'] = "match case while matching the pattern"
+let s:doc['\Z'] = "ignore composing characters in the pattern"
+
+" \%d 123
+" \%x 2a
+" \%o 0377
+" \%u 20AC
+" \%U 1234abcd
+
+func! s:DocBspercDecimal(bull, hulit, item) "{{{
+    let number = a:bull.Bite('^\d\{,3}')
+    call a:hulit.Print(a:item. number, "match character specified by decimal number ". number)
+endfunc "}}}
+func! s:DocBspercHexTwo(bull, hulit, item) "{{{
+    let number = a:bull.Bite('^\x\{,2}')
+    call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
+endfunc "}}}
+func! s:DocBspercOctal(bull, hulit, item) "{{{
+    let number = a:bull.Bite('^\o\{,4}')
+    call a:hulit.Print(a:item. number, "match character specified with octal number 0". substitute(number, '^0*', '', ''))
+endfunc "}}}
+func! s:DocBspercHexFour(bull, hulit, item) "{{{
+    let number = a:bull.Bite('^\x\{,4}')
+    call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
+endfunc "}}}
+func! s:DocBspercHexEight(bull, hulit, item) "{{{
+    let number = a:bull.Bite('^\x\{,8}')
+    call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
+endfunc "}}}
+
+let s:doc['\%d'] = function("s:DocBspercDecimal") " 123
+let s:doc['\%x'] = function("s:DocBspercHexTwo") " 2a
+let s:doc['\%o'] = function("s:DocBspercOctal") " 0377
+let s:doc['\%u'] = function("s:DocBspercHexFour") " 20AC
+let s:doc['\%U'] = function("s:DocBspercHexEight") " 1234abcd
+
+" \m
+" \M
+" \v
+" \V
+"}}}
+
+func! s:NewHelpPrinter() "{{{
+    let obj = {}
+    let obj.literals = ''
+    let obj.indents = []
+    let obj.len = 0        " can be negative (!)
+
+    func! obj.Print(str, ...) "{{{
+       call self.FlushLiterals()
+       let indstr = join(self.indents, '')
+       echohl Comment
+       echo indstr
+       echohl None
+       if a:0 == 0
+           echon a:str
+       else
+           " echo indstr. printf("`%s'   %s", a:str, a:1)
+           echohl PreProc
+           echon printf("%-10s", a:str)
+           echohl None
+           echohl Comment
+           echon printf(" %s", a:1)
+           echohl None
+       endif
+    endfunc "}}}
+
+    func! obj.AddLiteral(item) "{{{
+       let self.literals .= a:item
+    endfunc "}}}
+
+    func! obj.FlushLiterals() "{{{
+       if self.literals == ''
+           return
+       endif
+       let indstr = join(self.indents, '')
+       echohl Comment
+       echo indstr
+       echohl None
+       if self.literals =~ '^\s\|\s$'
+           echon printf("%-10s", '"'. self.literals. '"')
+       else
+           echon printf("%-10s", self.literals)
+       endif
+       echohl Comment
+       echon " literal string"
+       if exists("*strchars")
+           if self.literals =~ '\\'
+               let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
+           endif
+           let so = self.literals =~ '[^ ]' ? '' : ', spaces only'
+           echon " (". strchars(self.literals). " atom(s)". so.")"
+       endif
+       echohl None
+       let self.literals = ''
+    endfunc  "}}}
+
+    func! obj.AddIndent(...) "{{{
+       call self.FlushLiterals()
+       if self.len >= 0
+           call extend(self.indents, copy(a:000))
+       elseif self.len + a:0 >= 1
+           call extend(self.indents, a:000[-(self.len+a:0):])
+       endif
+       let self.len += a:0
+    endfunc "}}}
+
+    func! obj.RemIndent(...) "{{{
+       call self.FlushLiterals()
+       if a:0 == 0
+           if self.len >= 1
+               call remove(self.indents, -1)
+           endif
+           let self.len -= 1
+       else
+           if self.len > a:1
+               call remove(self.indents, -a:1, -1)
+           elseif self.len >= 1
+               call remove(self.indents, 0, -1)
+           endif
+           let self.len -= a:1
+       endif
+    endfunc "}}}
+
+    return obj
+endfunc "}}}
+
+func! s:NewTokenBiter(str, ...) "{{{
+    " {str}    string to eat pieces from
+    " {a:1}    pattern to skip separators
+    let whitepat = a:0>=1 ? a:1 : '^\s*'
+    let obj = {'str': a:str, 'whitepat': whitepat}
+
+    " consume piece from start of input matching {pat}
+    func! obj.Bite(pat) "{{{
+       " {pat}     should start with '^'
+       let skip = matchend(self.str, self.whitepat)
+       let bite = matchstr(self.str, a:pat, skip)
+       let self.str = strpart(self.str, matchend(self.str, self.whitepat, skip + strlen(bite)))
+       return bite
+    endfunc "}}}
+
+    " get the unparsed rest of input
+    func! obj.Rest() "{{{
+       return self.str
+    endfunc "}}}
+
+    " check if end of input reached
+    func! obj.AtEnd() "{{{
+       return self.str == ""
+    endfunc "}}}
+
+    return obj
+endfunc "}}}
+
+" Modeline: {{{1
+let &cpo = s:cpo_save
+unlet s:cpo_save
+" vim:ts=8:fdm=marker:
diff --git a/autoload/nwo/magic.vim b/autoload/nwo/magic.vim
new file mode 100644 (file)
index 0000000..3342102
--- /dev/null
@@ -0,0 +1,168 @@
+" File:         makemagic.vim
+" Created:      2011 Apr 18
+" Last Change:  2012 Dec 19
+" Rev Days:     4
+" Author:      Andy Wokula <anwoku@yahoo.de>
+" License:     Vim License, see :h license
+" Version:     0.1
+
+"" Comments {{{
+
+" nwo#magic#MakeMagic({pat})
+"
+"   remove embedded switches (\v, \m, \M and \V) from pattern {pat} by
+"   converting {pat} into a purely magic pattern.  Return the converted
+"   pattern.
+"
+
+" TODO
+" - recognize [#-\\]], with spaces: [ #-\ \] ]
+"   (collection ends at second ']')
+
+" 2011 Nov 01  copied from asneeded\makemagic.vim
+"              now asneeded\nwo\makemagic.vim (comments there!)
+"}}}
+
+" Init Folklore {{{
+let s:cpo_save = &cpo
+set cpo&vim
+let g:nwo#magic#loaded = 1
+"}}}
+
+func! nwo#magic#MakeMagic(pat, ...) "{{{
+    " {pat}    (string)
+    " {a:1}    (boolean) initial magic mode (default follows the 'magic' option)
+
+    if a:0>=1 ? a:1 : &magic
+       let magic_mode = 'm'
+       let bracket_is_magic = 1
+    else
+       let magic_mode = 'M'
+       let bracket_is_magic = 0
+    endif
+    let result_pat = ''
+    let endpos = strlen(a:pat)
+
+    let spos = 0
+    while spos >= 0 && spos < endpos
+       let mc1 = a:pat[spos]
+       let mc2 = a:pat[spos+1]
+
+       let collection = 0
+       if mc1 == '\'
+           if mc2 == '[' && !bracket_is_magic
+               let collection = 1
+               let spos += 1
+           elseif mc2 =~ '[vmMV]'
+               let magic_mode = mc2
+               let bracket_is_magic = mc2 =~# '[vm]'
+               let spos += 2
+           elseif mc2 == '_'
+               let mc3 = a:pat[spos+2]
+               if mc3 == '['
+                   let collection = 1
+               endif
+           endif
+       elseif mc1 == '[' && bracket_is_magic
+           let collection = 1
+       endif
+
+       if collection
+           let nextpos = matchend(a:pat, s:collection_skip_pat, spos)
+           if nextpos >= 0
+               let magpart = strpart(a:pat, spos, nextpos-spos)
+           else
+               let magpart = strpart(a:pat, spos)
+           endif
+       else
+           let nextpos = match(a:pat, s:switchpat[magic_mode], spos)
+           if nextpos >= 0
+               if nextpos == spos
+                   continue " optional
+               endif
+               let part = strpart(a:pat, spos, nextpos-spos)
+           else
+               let part = strpart(a:pat, spos)
+           endif
+           if magic_mode ==# 'v'
+               let magpart = substitute(part, s:vmagic_items_pat, '\=s:ToggleVmagicBslash(submatch(0))', 'g')
+           elseif magic_mode ==# 'm'
+               let magpart = part
+           elseif magic_mode ==# 'M'
+               let s:rem_bslash_before = '.*[~'
+               " the first two branches are only to eat the matches:
+               let magpart = substitute(part, '\\%\[\|\\_\\\=.\|\\.\|[.*[~]', '\=s:ToggleBslash(submatch(0))', 'g')
+           elseif magic_mode ==# 'V'
+               let s:rem_bslash_before = '^$.*[~'
+               let magpart = substitute(part, '\\%\[\|\\_\\\=.\|\\.\|[\^$.*[~]', '\=s:ToggleBslash(submatch(0))', 'g')
+           endif
+       endif
+
+       let result_pat .= magpart
+       let spos = nextpos
+    endwhile
+
+    return result_pat
+endfunc "}}}
+
+" s:variables {{{
+
+" pattern to match very magic items:
+let s:vmagic_items_pat = '\\.\|%\%([#$(UV[\^cdlouvx]\|''.\|[<>]\%(''.\|[clv]\)\)\|[&()+<=>?|]\|@\%([!=>]\|<[!=]\)\|{'
+
+" not escaped - require an even number of '\' (zero or more) to the left:
+let s:not_escaped  = '\%(\%(^\|[^\\]\)\%(\\\\\)*\)\@<='
+
+" prohibit an unescaped match for '%' before what follows (used when trying
+" to find '[', but not '%[', :h /\%[ )
+let s:not_vmagic_opt_atoms = '\%(\%(^\|[^\\]\)\%(\\\\\)*%\)\@<!'
+
+" not opt atoms - (used when trying to find '[', but not '\%[')
+let s:not_opt_atoms = '\%(\%(^\|[^\\]\)\%(\\\\\)*\\%\)\@<!'
+
+" match a switch (\V,\M,\m,\v) or the start of a collection:
+let s:switchpat = {
+    \ "v": s:not_escaped.'\%('.s:not_vmagic_opt_atoms.'\[\|\\[vmMV]\)',
+    \ "m": s:not_escaped.'\%('.s:not_opt_atoms . '\[\|\\[vmMV]\)',
+    \ "M": s:not_escaped.'\%(\\_\=\[\|\\[vmMV]\)',
+    \ "V": s:not_escaped.'\%(\\_\=\[\|\\[vmMV]\)'}
+
+" skip over a collection (starting at '[' (same for all magic modes) or
+" starting at '\_[' (same for all modes))
+let s:collection_skip_pat = '^\%(\\_\)\=\[\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]'
+
+" }}}
+
+" for magic modes 'V' and 'M'
+func! s:ToggleBslash(patitem) "{{{
+    " {patitem}            magic char or '\'.char
+    if a:patitem =~ '^.$'
+       return '\'.a:patitem
+    else
+       let mchar = matchstr(a:patitem, '^\\\zs.')
+       if stridx(s:rem_bslash_before, mchar) >= 0
+           return mchar
+       else
+           return a:patitem
+       endif
+    endif
+endfunc "}}}
+
+func! s:ToggleVmagicBslash(patitem) "{{{
+    " {patitem}            magic char or '\'.char
+    if a:patitem =~ '^\\'
+       let mchar = a:patitem[1]
+       if mchar =~ '[\^$.*[\]~\\[:alnum:]_]'
+           return a:patitem
+       else
+           return mchar
+       endif
+    else
+       return '\'.a:patitem
+    endif
+endfunc "}}}
+
+" Modeline: {{{1
+let &cpo = s:cpo_save
+unlet s:cpo_save
+" vim:ts=8:fdm=marker:
diff --git a/doc/explainpat.txt b/doc/explainpat.txt
new file mode 100644 (file)
index 0000000..e38bc62
--- /dev/null
@@ -0,0 +1,36 @@
+*explainpat.txt*    Give detailed help on a regexp pattern.
+
+                   For Vim version 7.0.  Last change: 2012 Dec 19
+                   By Andy Wokula <anwoku@yahoo.de>
+
+                                               *explainpat* *explainpat.vim*
+When you want to inspect a given Vim regexp pattern, this script might save
+you lots of help lookups.  And it will make the structure of a regexp visible.
+And it helps spotting mistakes.
+
+If you find that it explains something wrong, drop me an email.
+
+==============================================================================
+                                               *:ExplainPattern*
+:ExplainPattern [{pattern} | {register}]
+                       parse the given Vim {pattern} and print a line of help
+                       (with color) for each found pattern item.  Nested
+                       items get extra indent.
+
+                       A single-char argument is used as {register} argument:
+                               /       explain the last search pattern
+                               *       explain pattern from the clipboard
+                               a       explain pattern from register a
+
+Notes:
+The pattern is first converted into a purely magic pattern using
+|nwo#magic#MakeMagic()|.  This means that embedded |\V| |\M| |\m| |\V|
+specifiers are effectively removed from the explanation.
+
+:ExplainPattern also accepts some invalid patterns: >
+       :ExplainPattern \)
+       Pattern: \)
+       \)         end of group
+
+==============================================================================
+vim:tw=78:fo=tcq2:sts=0:ts=8:sw=8:fdm=marker:fmr=^_\^,^\^:ft=help:
diff --git a/plugin/explainpat.vim b/plugin/explainpat.vim
new file mode 100644 (file)
index 0000000..aa3e86f
--- /dev/null
@@ -0,0 +1,34 @@
+" File:         explainpat.vim
+" Created:      2011 Nov 02
+" Last Change:  2012 Dec 19
+" Rev Days:     3
+" Author:      Andy Wokula <anwoku@yahoo.de>
+" License:     Vim License, see :h license
+" Version:     0.1
+
+" :ExplainPattern [pattern]
+"
+"   parse the given Vim [pattern] (default: text in the Visual area) and
+"   print a line of help (with color!) for each found pattern item.  Nested
+"   items get extra indent.
+"
+"   A single-char [pattern] argument is used as register argument:
+"      /       explain the last search pattern
+"      *       explain pattern from the clipboard
+"      a       explain pattern from register a
+"
+
+if exists("loaded_explainpat")
+    finish
+endif
+let loaded_explainpat = 1
+
+if v:version < 700
+    echomsg "explainpat: you need at least Vim 7.0"
+    finish
+endif
+
+com! -nargs=*  ExplainPattern  call explainpat#ExplainPattern(<q-args>)
+
+" Modeline: {{{1
+" vim:ts=8:fdm=marker: