diff --git a/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFoldingTest.groovy b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFoldingTest.groovy
new file mode 100644
index 000000000000..9166eaf5d4ae
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/psi/codeStyle/arrangement/JavaRearrangerFoldingTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2012 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.psi.codeStyle.arrangement
+
+/**
+ * @author Denis Zhdanov
+ * @since 11/20/12 3:34 PM
+ */
+class JavaRearrangerFoldingTest extends AbstractJavaRearrangerTest {
+
+ void "test dummy"() {
+ }
+
+ // This should be uncommented as soon as cdr pushed fixes for range markers processing.
+// void "test that doc comment folding is preserved"() {
+// commonSettings.BLANK_LINES_AROUND_METHOD = 1
+// doTest(
+// initial: '''\
+//import java.util.List;
+//import java.util.Set;
+//
+///**
+// * Class comment
+// */
+//class Test {
+//
+// /**
+// * Another method comment
+// */
+// public void test(Set s) {}
+//}''',
+//
+// rules: [rule(PUBLIC), rule(PRIVATE)],
+//
+// expected: '''\
+//import java.util.List;
+//import java.util.Set;
+//
+///**
+// * Class comment
+// */
+//class Test {
+//
+// /**
+// * Method comment
+// */
+// private void test(List l) {}
+//}'''
+// )
+// }
+}
diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java
index 1906a47aa724..062ddec25790 100644
--- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java
+++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java
@@ -91,7 +91,7 @@ public class RearrangeCodeAction extends AnAction {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
- engine.arrange(file, ranges);
+ engine.arrange(editor, file, ranges);
}
}, getTemplatePresentation().getText(), null);
}
diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementCallback.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementCallback.java
new file mode 100644
index 000000000000..3c512509f5af
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementCallback.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2012 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.psi.codeStyle.arrangement.engine;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author Denis Zhdanov
+ * @since 11/21/12 12:54 PM
+ */
+public interface ArrangementCallback {
+
+ /**
+ * Callback to be notified when arrangement has been performed.
+ *
+ * @param moveInfos information about the changes performed during arrangement
+ */
+ void afterArrangement(@NotNull List moveInfos);
+}
diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java
index 4abd8d4f6b08..644971e992b4 100644
--- a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java
+++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java
@@ -17,6 +17,7 @@ package com.intellij.psi.codeStyle.arrangement.engine;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
@@ -34,6 +35,7 @@ import com.intellij.psi.codeStyle.arrangement.match.ArrangementMatchRule;
import com.intellij.psi.codeStyle.arrangement.order.ArrangementEntryOrderType;
import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware;
import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.Stack;
import com.intellij.util.text.CharArrayUtil;
@@ -55,6 +57,16 @@ import java.util.*;
*/
public class ArrangementEngine {
+ public void arrange(@NotNull final Editor editor, @NotNull PsiFile file, Collection ranges) {
+ arrange(file, ranges, null);
+ // This should be uncommented as soon as cdr pushed fixes for range markers processing.
+ //arrange(file, ranges, new RestoreFoldArrangementCallback(editor));
+ }
+
+ public void arrange(@NotNull PsiFile file, @NotNull Collection ranges) {
+ arrange(file, ranges, null);
+ }
+
/**
* Arranges given PSI root contents that belong to the given ranges.
*
@@ -62,7 +74,7 @@ public class ArrangementEngine {
* @param ranges target ranges to use within the given root
*/
@SuppressWarnings("MethodMayBeStatic")
- public void arrange(@NotNull PsiFile file, @NotNull Collection ranges) {
+ public void arrange(@NotNull PsiFile file, @NotNull Collection ranges, @Nullable final ArrangementCallback callback) {
final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) {
return;
@@ -99,14 +111,17 @@ public class ArrangementEngine {
@Override
public void run() {
if (documentEx != null) {
- documentEx.setInBulkUpdate(true);
+ //documentEx.setInBulkUpdate(true);
}
try {
doArrange(context);
+ if (callback != null) {
+ callback.afterArrangement(context.moveInfos);
+ }
}
finally {
if (documentEx != null) {
- documentEx.setInBulkUpdate(false);
+ //documentEx.setInBulkUpdate(false);
}
}
}
@@ -327,6 +342,8 @@ public class ArrangementEngine {
private static class Context {
+ @NotNull public final List moveInfos = ContainerUtilRt.newArrayList();
+
@NotNull public final Rearranger rearranger;
@NotNull public final Collection> wrappers;
@NotNull public final Document document;
@@ -348,6 +365,10 @@ public class ArrangementEngine {
this.changer = changer;
}
+ public void addMoveInfo(int oldStart, int oldEnd, int newStart) {
+ moveInfos.add(new ArrangementMoveInfo(oldStart, oldEnd, newStart));
+ }
+
public static Context from(@NotNull Rearranger rearranger,
@NotNull Document document,
@NotNull PsiElement root,
@@ -464,6 +485,9 @@ public class ArrangementEngine {
String newEntryText = myParentText.substring(newWrapper.getStartOffset() - myParentShift, newWrapper.getEndOffset() - myParentShift);
int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore;
if (lineFeedsDiff == 0 || desiredBlankLinesNumber < 0) {
+ context.addMoveInfo(newWrapper.getStartOffset() - myParentShift,
+ newWrapper.getEndOffset() - myParentShift,
+ oldWrapper.getStartOffset());
context.document.replaceString(oldWrapper.getStartOffset(), oldWrapper.getEndOffset(), newEntryText);
return;
}
@@ -547,6 +571,7 @@ public class ArrangementEngine {
insertionOffset -= newWrapper.getEndOffset() - newWrapper.getStartOffset();
}
if (newWrapper.getStartOffset() != oldWrapper.getStartOffset() || !newWrapper.equals(oldWrapper)) {
+ context.addMoveInfo(newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
myDocument.moveText(newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
for (int i = myWrappers.size() - 1; i >= 0; i--) {
ArrangementEntryWrapper w = myWrappers.get(i);
diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementMoveInfo.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementMoveInfo.java
new file mode 100644
index 000000000000..86e87209b69c
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementMoveInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2012 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.psi.codeStyle.arrangement.engine;
+
+/**
+ * @author Denis Zhdanov
+ * @since 11/21/12 12:51 PM
+ */
+public class ArrangementMoveInfo {
+
+ private final int myOldStart;
+ private final int myOldEnd;
+ private final int myNewStart;
+
+ public ArrangementMoveInfo(int oldStart, int oldEnd, int newStart) {
+ myOldStart = oldStart;
+ myOldEnd = oldEnd;
+ myNewStart = newStart;
+ }
+
+ public int getOldStart() {
+ return myOldStart;
+ }
+
+ public int getOldEnd() {
+ return myOldEnd;
+ }
+
+ public int getNewStart() {
+ return myNewStart;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("range [%d; %d) to offset %d", myOldStart, myOldEnd, myNewStart);
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/RestoreFoldArrangementCallback.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/RestoreFoldArrangementCallback.java
new file mode 100644
index 000000000000..8f93afe4cc50
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/RestoreFoldArrangementCallback.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2012 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.psi.codeStyle.arrangement.engine;
+
+import com.intellij.codeInsight.folding.CodeFoldingManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.FoldRegion;
+import com.intellij.openapi.fileEditor.impl.text.CodeFoldingState;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.ContainerUtilRt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @author Denis Zhdanov
+ * @since 11/21/12 12:57 PM
+ */
+public class RestoreFoldArrangementCallback implements ArrangementCallback {
+
+ @NotNull private final Editor myEditor;
+ @Nullable private final CodeFoldingState myCodeFoldingState;
+
+ public RestoreFoldArrangementCallback(@NotNull Editor editor) {
+ myEditor = editor;
+
+ Project project = editor.getProject();
+ if (project == null) {
+ myCodeFoldingState = null;
+ }
+ else {
+ final CodeFoldingManager foldingManager = CodeFoldingManager.getInstance(editor.getProject());
+ myCodeFoldingState = foldingManager.saveFoldingState(editor);
+ }
+ }
+
+ @Override
+ public void afterArrangement(@NotNull final List moveInfos) {
+ // Restore state for the PSI elements not affected by arrangement.
+ Project project = myEditor.getProject();
+ if (myCodeFoldingState != null && project != null) {
+ final CodeFoldingManager foldingManager = CodeFoldingManager.getInstance(project);
+ foldingManager.updateFoldRegions(myEditor);
+ myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
+ @Override
+ public void run() {
+ foldingManager.restoreFoldingState(myEditor, myCodeFoldingState);
+ }
+ });
+ }
+ }
+}
diff --git a/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy b/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy
index d9df73b46e20..ffe119f8bbec 100644
--- a/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy
+++ b/platform/testFramework/testSrc/com/intellij/psi/codeStyle/arrangement/AbstractRearrangerTest.groovy
@@ -32,12 +32,17 @@ import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition
import com.intellij.psi.codeStyle.arrangement.order.ArrangementEntryOrderType
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import org.jetbrains.annotations.NotNull
+import org.jetbrains.annotations.Nullable
+import org.junit.Assert
+
/**
* @author Denis Zhdanov
* @since 7/20/12 2:54 PM
*/
abstract class AbstractRearrangerTest extends LightPlatformCodeInsightFixtureTestCase {
+ static final def RICH_TEXT_HANDLERS = [ new RangeHandler(), new FoldingHandler() ]
+
FileType fileType
Language language;
@@ -92,38 +97,125 @@ abstract class AbstractRearrangerTest extends LightPlatformCodeInsightFixtureTes
}
protected void doTest(@NotNull args) {
- def (String textToUse, List rangesToUse) = parseRanges(args.initial)
- if (rangesToUse && args.ranges) {
+ Info info = parse(args.initial)
+ if (info.ranges && args.ranges) {
junit.framework.Assert.fail(
- "Duplicate ranges info detected: explicitly given: $args.ranges, derived from markup: $rangesToUse. Text:\n$args.initial"
+ "Duplicate ranges info detected: explicitly given: $args.ranges, derived from markup: ${info.ranges}. Text:\n$args.initial"
)
}
- if (!rangesToUse) {
- rangesToUse = args.ranges ?: [TextRange.from(0, args.initial.length())]
+ if (!info.ranges) {
+ info.ranges = args.ranges ?: [TextRange.from(0, args.initial.length())]
+ }
+
+ myFixture.configureByText(fileType, info.text)
+
+ def foldingModel = myFixture.editor.foldingModel
+
+ info.foldings.each { FoldingInfo foldingInfo ->
+ foldingModel.runBatchFoldingOperation {
+ def region = foldingModel.addFoldRegion(foldingInfo.start, foldingInfo.end, foldingInfo.placeholder)
+ region.expanded = false
+ }
}
- myFixture.configureByText(fileType, textToUse)
def settings = CodeStyleSettingsManager.getInstance(myFixture.project).currentSettings.getCommonSettings(language)
settings.arrangementSettings = new StdArrangementSettings(args.groups ?: [], args.rules ?: [])
ArrangementEngine engine = ServiceManager.getService(myFixture.project, ArrangementEngine)
- engine.arrange(myFixture.file, rangesToUse);
- junit.framework.Assert.assertEquals(args.expected, myFixture.editor.document.text);
+ engine.arrange(myFixture.editor, myFixture.file, info.ranges);
+
+ // Check expectation.
+ info = parse(args.expected)
+ Assert.assertEquals(info.text, myFixture.editor.document.text)
+ info.foldings.each {
+ def foldRegion = foldingModel.getCollapsedRegionAtOffset(it.start)
+ assertNotNull("Expected to find fold region at offset ${it.start}", foldRegion)
+ assertEquals(it.end, foldRegion.endOffset)
+ }
}
@NotNull
- private static def parseRanges(@NotNull String text) {
- def clearText = new StringBuilder(text)
- def ranges = []
- int shift = 0
- int shiftIncrease = ''.length() * 2 + 1
- def match = text =~ '(?is).*?'
- match.each {
- ranges << TextRange.create(match.start() - shift, match.end() - shift - shiftIncrease)
- clearText.delete(match.end() - ''.length() - shift, match.end() - shift)
- clearText.delete(match.start() - shift, match.start() + ''.length() - shift)
- shift += shiftIncrease
+ private static def parse(@NotNull String text) {
+ def handlers = [:]
+ RICH_TEXT_HANDLERS.each { handlers["<${it.marker}"] = it }
+ def result = new Info()
+ def buffer = new StringBuilder(text)
+ int offset = 0
+ int richTextMarkStart = -1
+ RichTextHandler handler = null
+ while (offset < buffer.length()) {
+ handlers.each { String key, RichTextHandler value ->
+ int i = buffer.indexOf(key, offset)
+ if (i >= 0 && (handler == null || i < richTextMarkStart)) {
+ richTextMarkStart = i
+ handler = value
+ }
+ }
+
+ if (handler) {
+ int openingTagEndOffset = buffer.indexOf('>', richTextMarkStart)
+ int openTagLength = openingTagEndOffset - richTextMarkStart + 1
+ def attributes = parseAttributes(buffer.substring(1 + richTextMarkStart + handler.marker.length(), openingTagEndOffset))
+
+ def closingTag = "${handler.marker}>"
+ int closingTagStart = buffer.indexOf(closingTag)
+ assert closingTagStart > 0
+ int closingTagLength = 3 + handler.marker.length() //
+ handler.handle(result, attributes, richTextMarkStart, closingTagStart - openTagLength)
+ buffer.delete(closingTagStart, closingTagStart + closingTagLength)
+ buffer.delete(richTextMarkStart, openingTagEndOffset + 1)
+ offset = closingTagStart - openTagLength
+ richTextMarkStart = -1
+ handler = null
+ }
+ else {
+ break
+ }
+ }
+ result.text = buffer.toString()
+ result
+ }
+
+ @NotNull
+ private static Map parseAttributes(@NotNull String text) {
+ def result = [:]
+ (text =~ /([^\s]+)=([^\s]+)/).each {
+ result[it[1]] = it[2]
+ }
+ result
+ }
+
+ private static class Info {
+ String text
+ @Nullable List ranges = []
+ List foldings = []
+ }
+
+ private static class FoldingInfo {
+ def placeholder
+ def start
+ def end
+ }
+
+ private interface RichTextHandler {
+ String getMarker()
+ void handle(@NotNull Info info, @NotNull Map attributes, int start, int end)
+ }
+
+ private static class RangeHandler implements RichTextHandler {
+ @Override String getMarker() { "range" }
+
+ @Override
+ void handle(Info info, Map attributes, int start, int end) {
+ info.ranges << TextRange.create(start, end)
+ }
+ }
+
+ private static class FoldingHandler implements RichTextHandler {
+ @Override String getMarker() { "fold" }
+
+ @Override
+ void handle(Info info, Map attributes, int start, int end) {
+ info.foldings << new FoldingInfo(placeholder: attributes.text ?: '...', start: start, end: end)
}
-
- [clearText.toString(), ranges]
}
}