Files
Ingo Karkat 3452dc3977 Test failure in PatternsOnText/t7580-SubstituteMultiple-magic.vim alerted me to the fact that \V[1] needs to be escaped as \[1]
Also process the collections through new ingo#regexp#magic#ConvertMagicnessOfCollection().
2017-05-26 17:54:27 +02:00

161 lines
6.2 KiB
VimL

" ingo/regexp/magic.vim: Functions around handling magicness in regular expressions.
"
" DEPENDENCIES:
" - ingo/collections.vim autoload script for ingo#regexp#magic#Normalize()
" - ingo/collections/fromsplit.vim autoload script
" - ingo/regexp/collection.vim autoload script
"
" Copyright: (C) 2011-2017 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
"
" REVISION DATE REMARKS
" 1.029.004 24-Jan-2017 Test failure in
" PatternsOnText/t7580-SubstituteMultiple-magic.vim
" alerted me to the fact that \V[1] needs to be
" escaped as \[1]. Also process the collections
" through new
" ingo#regexp#magic#ConvertMagicnessOfCollection().
" 1.029.003 23-Jan-2017 BUG: ingo#regexp#magic#Normalize() also
" processes the contents of collections [...];
" especially the escaping of "]" wreaks havoc on
" the pattern. Rename s:ConvertMagicness() into
" ingo#regexp#magic#ConvertMagicnessOfElement()
" and introduce intermediate
" s:ConvertMagicnessOfFragment() that first
" separates collections from other elements and
" only invokes the former on those other elements.
" 1.009.002 14-Jun-2013 Minor: Make substitute() robust against
" 'ignorecase'.
" 1.006.001 24-May-2013 file creation from ingosearch.vim.
let s:save_cpo = &cpo
set cpo&vim
function! ingo#regexp#magic#GetNormalizeMagicnessAtom( pattern )
"******************************************************************************
"* PURPOSE:
" Return normalizing \m (or \M) if a:pattern contains atom(s) that change the
" default magicness. This makes it possible to append another pattern without
" having a:pattern affect it.
"
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:pattern Regular expression to observe.
"* RETURN VALUES:
" Normalizing atom or empty string.
"******************************************************************************
let l:normalizingAtom = (&magic ? 'm' : 'M')
let l:magicChangeAtoms = substitute('vmMV', '\C'.l:normalizingAtom, '', '')
return (a:pattern =~# '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\[' . l:magicChangeAtoms . ']' ? '\' . l:normalizingAtom : '')
endfunction
let s:magicAtomsExpr = '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\[vmMV]'
function! ingo#regexp#magic#HasMagicAtoms( pattern )
return a:pattern =~# s:magicAtomsExpr
endfunction
let s:specialSearchCharacterExpressions = {
\ 'v': '\W',
\ 'm': '[\\^$.*[~]',
\ 'M': '[\\^$]',
\ 'V': '\\',
\}
function! s:ConvertMagicnessOfFragment( fragment, sourceSpecialCharacterExpr, targetSpecialCharacterExpr )
let l:elements = ingo#collections#fromsplit#MapItemsAndSeparators(a:fragment, '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!\\\?' . ingo#regexp#collection#Expr(),
\ printf('ingo#regexp#magic#ConvertMagicnessOfElement(v:val, %s, %s)', string(a:sourceSpecialCharacterExpr), string(a:targetSpecialCharacterExpr)),
\ printf('ingo#regexp#magic#ConvertMagicnessOfCollection(v:val, %s, %s)', string(a:sourceSpecialCharacterExpr), string(a:targetSpecialCharacterExpr))
\)
return join(l:elements, '')
endfunction
function! ingo#regexp#magic#ConvertMagicnessOfCollection( collection, sourceSpecialCharacterExpr, targetSpecialCharacterExpr )
let l:isEscaped = (a:collection =~# '^\\\[')
if l:isEscaped && '[' =~# a:sourceSpecialCharacterExpr && '[' !~# a:targetSpecialCharacterExpr
return a:collection[1:]
elseif ! l:isEscaped && '[' !~# a:sourceSpecialCharacterExpr && '[' =~# a:targetSpecialCharacterExpr
return '\' . a:collection
else
return a:collection
endif
endfunction
function! ingo#regexp#magic#ConvertMagicnessOfElement( element, sourceSpecialCharacterExpr, targetSpecialCharacterExpr )
let l:isEscaped = 0
let l:chars = split(a:element, '\zs')
for l:index in range(len(l:chars))
let l:char = l:chars[l:index]
if (l:char =~# a:sourceSpecialCharacterExpr) + (l:char =~# a:targetSpecialCharacterExpr) == 1
" The current character belongs to different classes in source and target.
if l:isEscaped
let l:chars[l:index - 1] = ''
else
let l:chars[l:index] = '\' . l:char
endif
endif
if l:char ==# '\'
let l:isEscaped = ! l:isEscaped
else
let l:isEscaped = 0
endif
endfor
return join(l:chars, '')
endfunction
function! ingo#regexp#magic#Normalize( pattern )
"******************************************************************************
"* PURPOSE:
" Remove any \v, /m, \M, \V atoms from a:pattern that change the magicness,
" and re-write the pattern (by selective escaping and unescaping) into an
" equivalent pattern that is based on the current 'magic' setting.
"
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" None.
"* INPUTS:
" a:pattern Regular expression that may contain atoms that affect the
" magicness.
"* RETURN VALUES:
" Equivalent pattern that has any atoms affecting the magicness removed and is
" based on the current 'magic' setting.
"******************************************************************************
let l:currentMagicMode = (&magic ? 'm' : 'M')
let l:defaultMagicMode = l:currentMagicMode
let l:patternFragments = ingo#collections#SplitKeepSeparators(a:pattern, s:magicAtomsExpr, 1)
" Because we asked to keep any empty fragments, we can easily test whether
" there's any work to do.
if len(l:patternFragments) == 1
return a:pattern
endif
"****D echomsg string(l:patternFragments)
for l:fragmentIndex in range(len(l:patternFragments))
let l:fragment = l:patternFragments[l:fragmentIndex]
if l:fragment =~# s:magicAtomsExpr
let l:currentMagicMode = l:fragment[1]
let l:patternFragments[l:fragmentIndex] = ''
continue
endif
if l:currentMagicMode ==# l:defaultMagicMode
" No need for conversion.
continue
endif
let l:patternFragments[l:fragmentIndex] = s:ConvertMagicnessOfFragment(
\ l:fragment,
\ s:specialSearchCharacterExpressions[l:currentMagicMode],
\ s:specialSearchCharacterExpressions[l:defaultMagicMode]
\)
endfor
"****D echomsg string(l:patternFragments)
return join(l:patternFragments, '')
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :