I have a vim script to aid in Python code refactoring; depending on the file, it inserts multiple equal import
lines, e.g. many occurrences of from Products.CMFCore.utils import getToolByName
if many lines are changed to use getToolByName
.
To clean up, I have several lines like the following near the end of my script:
normal gg
/^(from AccessControl import Unauthorized>)?/
.+1,$s,^from AccessControl import Unauthorizedn,,e
normal gg
/^(from Products.CMFCore.utils import getToolByName>)?/
.+1,$s,^from Products.CMFCore.utils import getToolByNamen,,e
The idea is:
- For each possibly inserted import line,
- go to the top of the file,
- seek the first line which contains this import (well, starts with it, and perhaps contains more),
- and delete all further lines (
.+1,$
) which match exactly the inserted import.
To avoid errors (which would hurt a lot, especially when using the script via :argdo
), I made the search optional (?
), since the e
flag works for the :s
command only. If that search doesn’t find a true match, the following :.+1,$ s
command shouldn’t remove anything either, since it is more rescrictive.
(The remaining inserted line needs to be moved manually)
However, because of that ?
, the substitution does not only remove the following occurences (.+1
) but all – including the first one I’d like to keep.
How would I fix this?
A sed
-based solution would help me as well.
Versions:
Same problem with vim v7.4 (CentOS Linux) and gvim 8.1 on Windows.
Thank you!
Edit:
Now that I have a workaround, I’m still interested in a "proper" solution which doesn’t need temporary changes, and thus makes vim
consider the file unchanged if everything was fine already.
2
Answers
This will probably not be the best solution, but here is the workaround I found:
Above the first
normal gg
line, I added code to add two lines to the end of the file:Then I changed my
/.../
lines to unconditionally but alternatively find those additional lines, e.g.:Finally I remove those lines:
As you’ve noticed, the problem happens because your search for
from AccessControl import Unauthorized
is using a?
, which actually makes it match every line and not the first line with the actual import.You could work around this issue by using the pattern that matches the exact line you would like to match, but using
:silent!
to prevent any errors from breaking your sequence.This would work:
But a much better solution is to use a range that finds the pattern and starts on the next line at the same time. You can also use the
:d
command to delete a line, and:g//d
to delete all lines (in the range) matching a specific regular expression. Furthermore, if you use the same pattern twice, then using simply//
on your:g
command will match the same pattern again.Putting it all together:
So this starts on line 0 (before the beginning of the file, so it also matches on the very first line of the file), then finds the first occurrence of the pattern, and then starting on the line that follows, looks for all other matches of that same pattern all the way to the end of the file, and deletes the lines that match.
Same as earlier, if you don’t want to see an error if there’s no match of the pattern (or if there’s a single match, in which case the
:g//
will fail), you can use:silent!
to suppress it again.