mirror of
https://github.com/inkarkat/vim-ingo-library.git
synced 2026-05-29 11:18:51 +02:00
204439f249
Fix by moving to new ingo#change#Set(), which is more elegant.
183 lines
7.4 KiB
VimL
183 lines
7.4 KiB
VimL
" ingo/text/replace.vim: Functions to replace a pattern with text.
|
|
"
|
|
" DEPENDENCIES:
|
|
" - ingo/msg.vim autoload script
|
|
"
|
|
" Copyright: (C) 2012-2019 Ingo Karkat
|
|
" The VIM LICENSE applies to this script; see ':help copyright'.
|
|
"
|
|
" Maintainer: Ingo Karkat <ingo@karkat.de>
|
|
|
|
function! s:ReplaceRange( source, startIdx, endIdx, string )
|
|
return strpart(a:source, 0, a:startIdx) . a:string . strpart(a:source, a:endIdx + 1)
|
|
endfunction
|
|
|
|
function! ingo#text#replace#Between( startPos, endPos, Text )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Replace the text between a:startPos and a:endPos from the current buffer
|
|
" with a:Text.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" Modifies current buffer.
|
|
" Sets the change marks '[,'] to the modified area.
|
|
"* INPUTS:
|
|
" a:startPos [line, col]
|
|
" a:endPos [line, col]
|
|
" a:Text Replacement text, or Funcref that gets passed the text to
|
|
" replace, and returns the replacement text.
|
|
"* RETURN VALUES:
|
|
" List of [originalText, replacementText, didReplacement].
|
|
"******************************************************************************
|
|
if a:startPos[0] != a:endPos[0]
|
|
throw 'Multi-line replacement not implemented yet'
|
|
endif
|
|
|
|
let l:line = getline(a:startPos[0])
|
|
let l:currentText = strpart(l:line, a:startPos[1] - 1, (a:endPos[1] - a:startPos[1] + 1))
|
|
if type(a:Text) == type(function('tr'))
|
|
let l:text = call(a:Text, [l:currentText])
|
|
else
|
|
let l:text = a:Text
|
|
endif
|
|
|
|
" Because of setline(), we can only (easily) handle text replacement in a
|
|
" single line, so replace with the first (non-empty) line only should the
|
|
" replacement text consist of multiple lines.
|
|
let l:text = split(l:text, "\n", 1)[0]
|
|
|
|
if l:currentText !=# l:text
|
|
call setline(a:startPos[0], s:ReplaceRange(l:line, a:startPos[1] - 1, a:endPos[1] - 1, l:text))
|
|
call ingo#change#Set(a:startPos, ingo#pos#Make4(a:startPos[0], a:startPos[1] + len(l:text) - len(matchstr(l:text, '.$'))))
|
|
return [l:currentText, l:text, 1]
|
|
else
|
|
" The range already contains the new text in the correct format, no
|
|
" replacement was done.
|
|
return [l:currentText, l:text, 0]
|
|
endif
|
|
endfunction
|
|
function! ingo#text#replace#Area( area, Text )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Replace the text in the area of [startPos, endPos] from the current buffer
|
|
" with a:Text.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" Modifies current buffer.
|
|
" Sets the change marks '[,'] to the modified area.
|
|
"* INPUTS:
|
|
" a:area [[startLnum, startCol], [endLnum, endCol]]; col is the 1-based
|
|
" byte-index.
|
|
" a:Text Replacement text, or Funcref that gets passed the text to
|
|
" replace, and returns the replacement text.
|
|
"* RETURN VALUES:
|
|
" List of [originalText, replacementText, didReplacement].
|
|
"******************************************************************************
|
|
return call('ingo#text#replace#Between', a:area + [a:Text])
|
|
endfunction
|
|
function! s:ReplaceTextInRange( startIdx, endIdx, Text, where )
|
|
let [l:originalText, l:replacementText, l:didReplacement] = ingo#text#replace#Between([line('.'), a:startIdx + 1], [line('.'), a:endIdx + 1], a:Text)
|
|
if l:didReplacement
|
|
call cursor(line('.'), a:startIdx + 1)
|
|
return {'startIdx': a:startIdx, 'endIdx': a:endIdx, 'original': l:originalText, 'replacement': l:replacementText, 'where': a:where}
|
|
else
|
|
return []
|
|
endif
|
|
endfunction
|
|
|
|
function! ingo#text#replace#PatternWithText( pattern, Text, ... )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Replace occurrences of a:pattern in the current line with a:text.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" Current buffer is modifiable.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" Changes the current line.
|
|
"* INPUTS:
|
|
" a:pattern Regular expression that defines the text to replace.
|
|
" a:Text Replacement text, or Funcref that gets passed the text to
|
|
" replace, and returns the replacement text.
|
|
" a:strategy Array of locations where in the current line a:pattern will
|
|
" match. Possible values: 'current', 'next', 'last'. The default
|
|
" is ['current', 'next'], to have the same behavior as the
|
|
" built-in "*" command.
|
|
"* RETURN VALUES:
|
|
" Object with replacement information: {'startIdx', 'endIdx', 'original',
|
|
" 'replacement', 'where'}, or empty Dictionary if no replacement was done.
|
|
"******************************************************************************
|
|
let l:strategy = (a:0 ? copy(a:1) : ['current', 'next'])
|
|
|
|
" Substitute any of the text patterns with the current text in the current
|
|
" text format.
|
|
let l:line = getline('.')
|
|
|
|
while ! empty(l:strategy)
|
|
let l:location = remove(l:strategy, 0)
|
|
if l:location ==# 'current'
|
|
" If the cursor is positioned on a text, update that one.
|
|
let l:cursorIdx = col('.') - 1
|
|
let l:startIdx = 0
|
|
let l:count = 0
|
|
while l:startIdx != -1
|
|
let l:count += 1
|
|
let l:startIdx = match(l:line, a:pattern, 0, l:count)
|
|
let l:endIdx = matchend(l:line, a:pattern, 0, l:count) - 1
|
|
if l:startIdx <= l:cursorIdx && l:cursorIdx <= l:endIdx
|
|
"****D echomsg '**** cursor match from ' . l:startIdx . ' to ' . l:endIdx
|
|
let l:result = s:ReplaceTextInRange(l:startIdx, l:endIdx, a:Text, '%s at cursor position')
|
|
if ! empty(l:result) | return l:result | endif
|
|
endif
|
|
endwhile
|
|
let l:maxCount = l:count
|
|
elseif l:location ==# 'next'
|
|
" Update the next text (that is not already the current text and
|
|
" format) found in the line.
|
|
let l:cursorIdx = col('.') - 1
|
|
let l:startIdx = 0
|
|
let l:count = 0
|
|
while l:startIdx != -1
|
|
let l:count += 1
|
|
let l:startIdx = match(l:line, a:pattern, l:cursorIdx, l:count)
|
|
let l:endIdx = matchend(l:line, a:pattern, l:cursorIdx, l:count) - 1
|
|
"****D echomsg '**** next match from ' . l:startIdx . ' to ' . l:endIdx
|
|
if l:startIdx != -1
|
|
let l:result = s:ReplaceTextInRange(l:startIdx, l:endIdx, a:Text, 'next %s in line')
|
|
if ! empty(l:result) | return l:result | endif
|
|
endif
|
|
endwhile
|
|
elseif l:location ==# 'last'
|
|
" Update the last text (that is not already the current text and
|
|
" format) found in the line. This will update non-current texts from last to
|
|
" first on subsequent invocations until all occurrences are current.
|
|
let l:count = (exists('l:maxCount') ? l:maxCount - 1 : len(l:line)) " XXX: This is ineffective but easier than first counting the matches.
|
|
while l:count > 0
|
|
let l:startIdx = match(l:line, a:pattern, 0, l:count)
|
|
let l:endIdx = matchend(l:line, a:pattern, 0, l:count) - 1
|
|
"****D echomsg '**** last match from ' . l:startIdx . ' to ' . l:endIdx . ' at count ' . l:count
|
|
if l:startIdx != -1
|
|
let l:result = s:ReplaceTextInRange(l:startIdx, l:endIdx, a:Text, 'last %s in line')
|
|
if ! empty(l:result) | return l:result | endif
|
|
endif
|
|
let l:count -= 1
|
|
endwhile
|
|
else
|
|
throw 'ASSERT: Unknown strategy location: ' . l:location
|
|
endif
|
|
endwhile
|
|
|
|
return {}
|
|
endfunction
|
|
function! ingo#text#replace#PatternWithTextAndMessage( what, pattern, text, ... )
|
|
let l:replacement = call('ingo#text#replace#PatternWithText', [a:pattern, a:text] + a:000)
|
|
if empty(l:replacement)
|
|
call ingo#msg#WarningMsg(printf('No %s was found in this line', a:what))
|
|
else
|
|
echo 'Updated' printf(l:replacement.where, a:what)
|
|
endif
|
|
return l:replacement
|
|
endfunction
|
|
|
|
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
|