mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-95410 Arrangement: Preserve fold regions on arrangement
This commit is contained in:
+74
@@ -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 <fold>java.util.List;
|
||||
//import java.util.Set;</fold>
|
||||
//
|
||||
//<fold text="/**...*/">/**
|
||||
// * Class comment
|
||||
// */</fold>
|
||||
//class Test {
|
||||
//
|
||||
// <fold text="/**...*/>/**
|
||||
// * Method comment
|
||||
// */</fold>
|
||||
// private void test(List<String> l) {}
|
||||
//
|
||||
// <fold text="/**...*/>/**
|
||||
// * Another method comment
|
||||
// */</fold>
|
||||
// public void test(Set<String> s) {}
|
||||
//}''',
|
||||
//
|
||||
// rules: [rule(PUBLIC), rule(PRIVATE)],
|
||||
//
|
||||
// expected: '''\
|
||||
//import <fold>java.util.List;
|
||||
//import java.util.Set;</fold>
|
||||
//
|
||||
//<fold text="/**...*/">/**
|
||||
// * Class comment
|
||||
// */</fold>
|
||||
//class Test {
|
||||
//
|
||||
// <fold text="/**...*/>/**
|
||||
// * Another method comment
|
||||
// */</fold>
|
||||
// public void test(Set<String> s) {}
|
||||
//
|
||||
// <fold text="/**...*/>/**
|
||||
// * Method comment
|
||||
// */</fold>
|
||||
// private void test(List<String> l) {}
|
||||
//}'''
|
||||
// )
|
||||
// }
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+34
@@ -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<ArrangementMoveInfo> moveInfos);
|
||||
}
|
||||
+28
-3
@@ -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<TextRange> 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<TextRange> 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<TextRange> ranges) {
|
||||
public void arrange(@NotNull PsiFile file, @NotNull Collection<TextRange> 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<E extends ArrangementEntry> {
|
||||
|
||||
@NotNull public final List<ArrangementMoveInfo> moveInfos = ContainerUtilRt.newArrayList();
|
||||
|
||||
@NotNull public final Rearranger<E> rearranger;
|
||||
@NotNull public final Collection<ArrangementEntryWrapper<E>> 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 <T extends ArrangementEntry> Context<T> from(@NotNull Rearranger<T> 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<E> w = myWrappers.get(i);
|
||||
|
||||
+50
@@ -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);
|
||||
}
|
||||
}
|
||||
+70
@@ -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<ArrangementMoveInfo> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
-21
@@ -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<TextRange> 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 = '<range>'.length() * 2 + 1
|
||||
def match = text =~ '(?is)<range>.*?</range>'
|
||||
match.each {
|
||||
ranges << TextRange.create(match.start() - shift, match.end() - shift - shiftIncrease)
|
||||
clearText.delete(match.end() - '</range>'.length() - shift, match.end() - shift)
|
||||
clearText.delete(match.start() - shift, match.start() + '<range>'.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() // </marker>
|
||||
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<String, String> parseAttributes(@NotNull String text) {
|
||||
def result = [:]
|
||||
(text =~ /([^\s]+)=([^\s]+)/).each {
|
||||
result[it[1]] = it[2]
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
private static class Info {
|
||||
String text
|
||||
@Nullable List<TextRange> ranges = []
|
||||
List<FoldingInfo> foldings = []
|
||||
}
|
||||
|
||||
private static class FoldingInfo {
|
||||
def placeholder
|
||||
def start
|
||||
def end
|
||||
}
|
||||
|
||||
private interface RichTextHandler {
|
||||
String getMarker()
|
||||
void handle(@NotNull Info info, @NotNull Map<String, String> attributes, int start, int end)
|
||||
}
|
||||
|
||||
private static class RangeHandler implements RichTextHandler {
|
||||
@Override String getMarker() { "range" }
|
||||
|
||||
@Override
|
||||
void handle(Info info, Map<String, String> 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<String, String> attributes, int start, int end) {
|
||||
info.foldings << new FoldingInfo(placeholder: attributes.text ?: '...', start: start, end: end)
|
||||
}
|
||||
|
||||
[clearText.toString(), ranges]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user