diff --git a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java index 3b9ab0728637..1fa5ce12b7f9 100644 --- a/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/find/FindManagerTest.java @@ -423,6 +423,22 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { assertEquals(text, getEditor().getDocument().getText()); } + public void testReplaceWithLocalLowerCase() { + doTestRegexpReplace("SOMETHING", "ome", "\\l$0", "SoMETHING"); + } + + public void testReplaceWithLocalUpperCase() { + doTestRegexpReplace("something", "ome", "\\u$0", "sOmething"); + } + + public void testReplaceWithRegionLowerCase() { + doTestRegexpReplace("SOMETHING", "ome", "\\L$0\\E", "SomeTHING"); + } + + public void testReplaceWithRegionUpperCase() { + doTestRegexpReplace("something", "ome", "\\U$0\\E", "sOMEthing"); + } + public void testReplaceRegexpWithNewLine() { FindModel findModel = new FindModel(); findModel.setStringToFind("xxx"); @@ -750,4 +766,15 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { DumbServiceImpl.getInstance(getProject()).setDumb(false); } } + + private void doTestRegexpReplace(String initialText, String searchString, String replaceString, String expectedResult) { + configureByText(FileTypes.PLAIN_TEXT, initialText); + FindModel model = new FindModel(); + model.setRegularExpressions(true); + model.setStringToFind(searchString); + model.setStringToReplace(replaceString); + model.setPromptOnReplace(false); + assertTrue(FindUtil.replace(myProject, myEditor, 0, model)); + assertEquals(expectedResult, myEditor.getDocument().getText()); + } } diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java index dc14d5031d14..de950796c0cf 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -756,13 +756,10 @@ public class FindManagerImpl extends FindManager { } private static String getStringToReplaceByRegexp(@NotNull final FindModel model, Matcher matcher) throws MalformedReplacementStringException{ - StringBuffer replaced = new StringBuffer(); if (matcher == null) return null; try { - String toReplace = StringUtil.unescapeStringCharacters(model.getStringToReplace()); - matcher.appendReplacement(replaced, toReplace); - - return replaced.substring(matcher.start()); + String toReplace = model.getStringToReplace(); + return new RegExReplacementBuilder(matcher).createReplacement(toReplace); } catch (Exception e) { throw createMalformedReplacementException(model, e); diff --git a/platform/lang-impl/src/com/intellij/find/impl/RegExReplacementBuilder.java b/platform/lang-impl/src/com/intellij/find/impl/RegExReplacementBuilder.java new file mode 100644 index 000000000000..f951738798d6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/find/impl/RegExReplacementBuilder.java @@ -0,0 +1,276 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.find.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; + +/** + * Generates a replacement string for search/replace operation using regular expressions. + *
+ * The logic is based on java.util.regex.Matcher.appendReplacement method, special characters (\n, \r, \t, \f, \b, \xNNNN) + * and case conversion characters (\l, \u, \L, \U, \E) are additionally supported. + *
+ * Instances of this class are not safe for use by multiple concurrent threads, just as {@link Matcher} instances are.
+ */
+public class RegExReplacementBuilder {
+ private static final Logger LOGGER = Logger.getInstance(RegExReplacementBuilder.class);
+
+ private static Method ourNamedGroupValueRetriever;
+
+ static {
+ if (SystemInfo.isJavaVersionAtLeast("1.7")) {
+ try {
+ Method method = Matcher.class.getMethod("group", String.class);
+ Class> returnType = method.getReturnType();
+ if (String.class.equals(returnType)) {
+ ourNamedGroupValueRetriever = method;
+ }
+ else {
+ throw new RuntimeException("Unexpected method return value: " + returnType);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.warn("Error accessing method java.util.regex.Matcher.group(java.lang.String)", e);
+ }
+ }
+ }
+
+ @NotNull private final Matcher myMatcher;
+
+ private String myTemplate;
+ private int myCursor;
+ private StringBuilder myReplacement;
+ private List
+ * Matcher used to create this instance of RegExReplacementBuilder is supposed to be in a state
+ * created by a successful {@link Matcher#find() find()} or {@link Matcher#find(int) find(int)} invocation.
+ */
+ public String createReplacement(String template) {
+ myTemplate = template;
+ resetState();
+ while (myCursor < myTemplate.length()) {
+ char nextChar = myTemplate.charAt(myCursor++);
+ if (nextChar == '\\') {
+ processEscapedChar();
+ } else if (nextChar == '$') {
+ processGroupValue();
+ } else {
+ myReplacement.append(nextChar);
+ }
+ }
+ return generateResult();
+ }
+
+ private void resetState() {
+ myCursor = 0;
+ myReplacement = new StringBuilder();
+ myConversionRegions = new ArrayList