mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
DiffTree reworked: smarter PSI trees merge to allow SmartPsiPointers work in XML
This commit is contained in:
@@ -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.
|
||||
@@ -17,6 +17,7 @@ package com.intellij.psi.impl.smartPointers;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.CodeInsightTestCase;
|
||||
import com.intellij.ide.highlighter.HtmlFileType;
|
||||
import com.intellij.ide.highlighter.JavaFileType;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -37,6 +38,7 @@ import com.intellij.psi.stubs.StubTree;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilBase;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.PlatformTestCase;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
@@ -469,4 +471,64 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase {
|
||||
assertEquals(range1.shiftRight(1), pointer1.getRange());
|
||||
assertEquals(range2.shiftRight(1), pointer2.getRange());
|
||||
}
|
||||
|
||||
public void testInXml() {
|
||||
final PsiFile file = configureByText(HtmlFileType.INSTANCE,
|
||||
"<!doctype html>\n" +
|
||||
"<html>\n" +
|
||||
" <fieldset></fieldset>\n" +
|
||||
" <select></select>\n" +
|
||||
"\n" +
|
||||
" <caret>\n" +
|
||||
"</html>"
|
||||
);
|
||||
|
||||
final XmlTag fieldSet = PsiTreeUtil.getParentOfType(file.findElementAt(file.getText().indexOf("fieldset")), XmlTag.class);
|
||||
assertNotNull(fieldSet);
|
||||
assertEquals("fieldset", fieldSet.getName());
|
||||
|
||||
final XmlTag select = PsiTreeUtil.getParentOfType(file.findElementAt(file.getText().indexOf("select")), XmlTag.class);
|
||||
assertNotNull(select);
|
||||
assertEquals("select", select.getName());
|
||||
|
||||
final SmartPsiElementPointer<XmlTag> fieldSetPointer = SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(
|
||||
fieldSet);
|
||||
final SmartPsiElementPointer<XmlTag> selectPointer = SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(select);
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getEditor().getDocument().insertString(getEditor().getCaretModel().getOffset(), "<a></a>");
|
||||
}
|
||||
});
|
||||
|
||||
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
||||
|
||||
final XmlTag newFieldSet = fieldSetPointer.getElement();
|
||||
assertNotNull(newFieldSet);
|
||||
assertEquals("fieldset", newFieldSet.getName());
|
||||
|
||||
final XmlTag newSelect = selectPointer.getElement();
|
||||
assertNotNull(newSelect);
|
||||
assertEquals("select", newSelect.getName());
|
||||
}
|
||||
public void testInsertImport() {
|
||||
final PsiFile file = configureByText(JavaFileType.INSTANCE,
|
||||
"class S {\n" +
|
||||
"}");
|
||||
|
||||
PsiClass aClass = ((PsiJavaFile)file).getClasses()[0];
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getEditor().getDocument().insertString(0, "import java.util.Map;\n");
|
||||
}
|
||||
});
|
||||
|
||||
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
|
||||
|
||||
PsiClass aClass2 = ((PsiJavaFile)file).getClasses()[0];
|
||||
assertSame(aClass, aClass2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.intellij.util.diff.DiffTreeChangeBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -173,6 +174,8 @@ public class DiffLog implements DiffTreeChangeBuilder<ASTNode,ASTNode> {
|
||||
|
||||
private InsertEntry(@NotNull ASTNode oldParent, @NotNull ASTNode newNode, int pos) {
|
||||
assert oldParent instanceof CompositeElement : oldParent;
|
||||
assert pos>=0 : pos;
|
||||
assert pos<=oldParent.getChildren(null).length : pos + " "+ Arrays.toString(oldParent.getChildren(null));
|
||||
myOldParent = oldParent;
|
||||
myNewNode = newNode;
|
||||
myPos = pos;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2009 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.
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package com.intellij.refactoring.classMembers;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
|
||||
/**
|
||||
@@ -25,7 +27,7 @@ import com.intellij.psi.util.PsiUtilCore;
|
||||
*/
|
||||
public abstract class MemberInfoBase<T extends PsiElement> {
|
||||
protected static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.extractSuperclass.MemberInfo");
|
||||
protected T myMember;
|
||||
private SmartPsiElementPointer<T> myMember;
|
||||
protected boolean isStatic;
|
||||
protected String displayName;
|
||||
private boolean isChecked = false;
|
||||
@@ -36,7 +38,7 @@ public abstract class MemberInfoBase<T extends PsiElement> {
|
||||
private boolean toAbstract = false;
|
||||
|
||||
public MemberInfoBase(T member) {
|
||||
myMember = member;
|
||||
updateMember(member);
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
@@ -67,16 +69,16 @@ public abstract class MemberInfoBase<T extends PsiElement> {
|
||||
}
|
||||
|
||||
public T getMember() {
|
||||
PsiUtilCore.ensureValid(myMember);
|
||||
return myMember;
|
||||
T element = myMember.getElement();
|
||||
PsiUtilCore.ensureValid(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method solely to update element from smart pointer and the likes
|
||||
* @param element
|
||||
*/
|
||||
public void updateMember(T element) {
|
||||
myMember = element;
|
||||
myMember = SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
|
||||
}
|
||||
|
||||
public boolean isToAbstract() {
|
||||
@@ -87,7 +89,7 @@ public abstract class MemberInfoBase<T extends PsiElement> {
|
||||
this.toAbstract = toAbstract;
|
||||
}
|
||||
|
||||
public static interface Filter<T extends PsiElement> {
|
||||
public interface Filter<T extends PsiElement> {
|
||||
boolean includeMember(T member);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2013 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.
|
||||
@@ -368,14 +368,16 @@ public abstract class InplaceRefactoring {
|
||||
|
||||
final int offset = myEditor.getCaretModel().getOffset();
|
||||
|
||||
Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
|
||||
TextRange range = myScope.getTextRange();
|
||||
assert range != null;
|
||||
RangeMarker rangeMarker = topLevelEditor.getDocument().createRangeMarker(range);
|
||||
|
||||
Template template = builder.buildInlineTemplate();
|
||||
template.setToShortenLongNames(false);
|
||||
template.setToReformat(false);
|
||||
TextRange range = myScope.getTextRange();
|
||||
assert range != null;
|
||||
myHighlighters = new ArrayList<RangeHighlighter>();
|
||||
Editor topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(myEditor);
|
||||
topLevelEditor.getCaretModel().moveToOffset(range.getStartOffset());
|
||||
topLevelEditor.getCaretModel().moveToOffset(rangeMarker.getStartOffset());
|
||||
|
||||
TemplateManager.getInstance(myProject).startTemplate(topLevelEditor, template, templateListener);
|
||||
restoreOldCaretPositionAndSelection(offset);
|
||||
|
||||
@@ -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.
|
||||
@@ -41,7 +41,6 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
|
||||
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler;
|
||||
import com.intellij.testFramework.TestDataFile;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
@@ -195,12 +194,12 @@ public class CodeInsightTestUtil {
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void doInlineRename(VariableInplaceRenameHandler handler, final String newName, Editor editor, PsiElement elementAtCaret) {
|
||||
public static void doInlineRename(VariableInplaceRenameHandler handler, final String newName, @NotNull Editor editor, PsiElement elementAtCaret) {
|
||||
Project project = editor.getProject();
|
||||
TemplateManagerImpl templateManager = (TemplateManagerImpl)TemplateManager.getInstance(project);
|
||||
try {
|
||||
templateManager.setTemplateTesting(true);
|
||||
InplaceRefactoring renamer = handler.doRename(elementAtCaret, editor, null);
|
||||
handler.doRename(elementAtCaret, editor, DataManager.getInstance().getDataContext(editor.getComponent()));
|
||||
if (editor instanceof EditorWindow) {
|
||||
editor = ((EditorWindow)editor).getDelegate();
|
||||
}
|
||||
|
||||
@@ -31,25 +31,23 @@ public class DiffTree<OT, NT> {
|
||||
private final FlyweightCapableTreeStructure<OT> myOldTree;
|
||||
private final FlyweightCapableTreeStructure<NT> myNewTree;
|
||||
private final ShallowNodeComparator<OT, NT> myComparator;
|
||||
private final DiffTreeChangeBuilder<OT, NT> myConsumer;
|
||||
private final List<Ref<OT[]>> myOldChildrenLists = new ArrayList<Ref<OT[]>>();
|
||||
private final List<Ref<NT[]>> myNewChildrenLists = new ArrayList<Ref<NT[]>>();
|
||||
|
||||
private DiffTree(@NotNull FlyweightCapableTreeStructure<OT> oldTree,
|
||||
@NotNull FlyweightCapableTreeStructure<NT> newTree,
|
||||
@NotNull ShallowNodeComparator<OT, NT> comparator,
|
||||
@NotNull DiffTreeChangeBuilder<OT, NT> consumer) {
|
||||
@NotNull ShallowNodeComparator<OT, NT> comparator) {
|
||||
myOldTree = oldTree;
|
||||
myNewTree = newTree;
|
||||
myComparator = comparator;
|
||||
myConsumer = consumer;
|
||||
}
|
||||
|
||||
public static <OT, NT> void diff(@NotNull FlyweightCapableTreeStructure<OT> oldTree,
|
||||
@NotNull FlyweightCapableTreeStructure<NT> newTree,
|
||||
@NotNull ShallowNodeComparator<OT, NT> comparator,
|
||||
@NotNull DiffTreeChangeBuilder<OT, NT> consumer) {
|
||||
new DiffTree<OT, NT>(oldTree, newTree, comparator, consumer).build(oldTree.getRoot(), newTree.getRoot(), 0);
|
||||
final DiffTree<OT, NT> tree = new DiffTree<OT, NT>(oldTree, newTree, comparator);
|
||||
tree.build(oldTree.getRoot(), newTree.getRoot(), 0, Integer.MAX_VALUE, consumer);
|
||||
}
|
||||
|
||||
private enum CompareResult {
|
||||
@@ -59,7 +57,32 @@ public class DiffTree<OT, NT> {
|
||||
NOT_EQUAL, // 100% different
|
||||
}
|
||||
|
||||
private void build(@NotNull OT oldN, @NotNull NT newN, int level) {
|
||||
@NotNull
|
||||
private static <OT, NT> DiffTreeChangeBuilder<OT, NT> emptyConsumer() {
|
||||
//noinspection unchecked
|
||||
return EMPTY_CONSUMER;
|
||||
}
|
||||
private static final DiffTreeChangeBuilder EMPTY_CONSUMER = new DiffTreeChangeBuilder() {
|
||||
@Override
|
||||
public void nodeReplaced(@NotNull Object oldChild, @NotNull Object newChild) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDeleted(@NotNull Object oldParent, @NotNull Object oldNode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeInserted(@NotNull Object oldParent, @NotNull Object newNode, int pos) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@NotNull
|
||||
private CompareResult build(@NotNull OT oldN, @NotNull NT newN, int level, int maxLevel, @NotNull DiffTreeChangeBuilder<OT, NT> consumer) {
|
||||
if (level == maxLevel) return CompareResult.NOT_EQUAL; // too deep, abort
|
||||
|
||||
OT oldNode = myOldTree.prepareForGetChildren(oldN);
|
||||
NT newNode = myNewTree.prepareForGetChildren(newN);
|
||||
|
||||
@@ -76,102 +99,180 @@ public class DiffTree<OT, NT> {
|
||||
int newChildrenSize = myNewTree.getChildren(newNode, newChildrenR);
|
||||
final NT[] newChildren = newChildrenR.get();
|
||||
|
||||
CompareResult result;
|
||||
if (Math.abs(oldChildrenSize - newChildrenSize) > CHANGE_PARENT_VERSUS_CHILDREN_THRESHOLD) {
|
||||
myConsumer.nodeReplaced(oldNode, newNode);
|
||||
consumer.nodeReplaced(oldNode, newNode);
|
||||
result = CompareResult.NOT_EQUAL;
|
||||
}
|
||||
else if (oldChildrenSize == 0 && newChildrenSize == 0) {
|
||||
if (!myComparator.hashCodesEqual(oldNode, newNode) || !myComparator.typesEqual(oldNode, newNode)) {
|
||||
myConsumer.nodeReplaced(oldNode, newNode);
|
||||
consumer.nodeReplaced(oldNode, newNode);
|
||||
result = CompareResult.NOT_EQUAL;
|
||||
}
|
||||
else {
|
||||
result = CompareResult.EQUAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int oldSize = oldChildrenSize;
|
||||
int newSize = newChildrenSize;
|
||||
final ShallowNodeComparator<OT, NT> comparator = myComparator;
|
||||
while (oldSize > 0 && newSize > 0) {
|
||||
OT oldChild1 = oldChildren[oldSize -1];
|
||||
NT newChild1 = newChildren[newSize -1];
|
||||
|
||||
CompareResult c11 = looksEqual(comparator, oldChild1, newChild1);
|
||||
int minSize = Math.min(oldChildrenSize, newChildrenSize);
|
||||
int newMaxLevel = Math.min(maxLevel, level+4); // try not to descend recursively too deep
|
||||
int suffixLength = match(oldChildren, oldChildrenSize - 1, newChildren, newChildrenSize - 1, level, -1, minSize, newMaxLevel);
|
||||
int prefixLength = oldChildrenSize == 1 && newChildrenSize == 1 ? 0 : match(oldChildren, 0, newChildren, 0, level, 1, minSize-suffixLength, newMaxLevel);
|
||||
|
||||
if (c11 != CompareResult.EQUAL && c11 != CompareResult.DRILL_DOWN_NEEDED) {
|
||||
break;
|
||||
}
|
||||
if (c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
build(oldChild1, newChild1, level + 1);
|
||||
}
|
||||
oldSize--;
|
||||
newSize--;
|
||||
if (oldChildrenSize == newChildrenSize && suffixLength + prefixLength == oldChildrenSize) {
|
||||
result = CompareResult.EQUAL;
|
||||
}
|
||||
else if (consumer == emptyConsumer()){
|
||||
result = CompareResult.NOT_EQUAL;
|
||||
}
|
||||
else {
|
||||
int oldIndex = oldChildrenSize - suffixLength - 1;
|
||||
int newIndex = newChildrenSize - suffixLength - 1;
|
||||
while (oldIndex >= prefixLength || newIndex >= prefixLength) {
|
||||
OT oldChild1 = oldIndex >= prefixLength ? oldChildren[oldIndex] : null;
|
||||
OT oldChild2 = oldIndex >= prefixLength + 1 ? oldChildren[oldIndex - 1] : null;
|
||||
OT oldChild3 = oldIndex >= prefixLength + 2 ? oldChildren[oldIndex - 2] : null;
|
||||
NT newChild1 = newIndex >= prefixLength ? newChildren[newIndex] : null;
|
||||
NT newChild2 = newIndex >= prefixLength + 1 ? newChildren[newIndex - 1] : null;
|
||||
NT newChild3 = newIndex >= prefixLength + 2 ? newChildren[newIndex - 2] : null;
|
||||
|
||||
int oldIndex = 0;
|
||||
int newIndex = 0;
|
||||
while (oldIndex < oldSize || newIndex < newSize) {
|
||||
OT oldChild1 = oldIndex < oldSize ? oldChildren[oldIndex] : null;
|
||||
OT oldChild2 = oldIndex < oldSize -1 ? oldChildren[oldIndex+1] : null;
|
||||
NT newChild1 = newIndex < newSize ? newChildren[newIndex] : null;
|
||||
NT newChild2 = newIndex < newSize -1 ? newChildren[newIndex+1] : null;
|
||||
|
||||
CompareResult c11 = looksEqual(comparator, oldChild1, newChild1);
|
||||
if (c11 == CompareResult.EQUAL || c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
if (c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
build(oldChild1, newChild1, level +1);
|
||||
}
|
||||
oldIndex++;
|
||||
newIndex++;
|
||||
continue;
|
||||
}
|
||||
if (c11 == CompareResult.TYPE_ONLY) {
|
||||
CompareResult c21 = looksEqual(comparator, oldChild2, newChild1);
|
||||
if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
myConsumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex++;
|
||||
CompareResult c11 = looksEqual(comparator, oldChild1, newChild1);
|
||||
if (c11 == CompareResult.EQUAL || c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
if (c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
build(oldChild1, newChild1, level + 1, maxLevel, consumer);
|
||||
}
|
||||
oldIndex--;
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
if (c11 == CompareResult.TYPE_ONLY) {
|
||||
CompareResult c21 = looksEqual(comparator, oldChild2, newChild1);
|
||||
if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
consumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex--;
|
||||
continue;
|
||||
}
|
||||
CompareResult c12 = looksEqual(comparator, oldChild1, newChild2);
|
||||
if (c12 == CompareResult.EQUAL || c12 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
consumer.nodeInserted(oldNode, newChild1, oldIndex + 1);
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
consumer.nodeReplaced(oldChild1, newChild1);
|
||||
oldIndex--;
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
CompareResult c12 = looksEqual(comparator, oldChild1, newChild2);
|
||||
if (c12 == CompareResult.EQUAL || c12 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
myConsumer.nodeInserted(oldNode, newChild1, newIndex);
|
||||
newIndex++;
|
||||
consumer.nodeInserted(oldNode, newChild1, oldIndex + 1);
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
myConsumer.nodeReplaced(oldChild1, newChild1);
|
||||
oldIndex++;
|
||||
newIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
CompareResult c12 = looksEqual(comparator, oldChild1, newChild2);
|
||||
if (c12 == CompareResult.EQUAL || c12 == CompareResult.DRILL_DOWN_NEEDED || c12 == CompareResult.TYPE_ONLY) {
|
||||
myConsumer.nodeInserted(oldNode, newChild1, newIndex);
|
||||
newIndex++;
|
||||
continue;
|
||||
}
|
||||
CompareResult c21 = looksEqual(comparator, oldChild2, newChild1);
|
||||
if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED || c21 == CompareResult.TYPE_ONLY) {
|
||||
consumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
CompareResult c21 = looksEqual(comparator, oldChild2, newChild1);
|
||||
if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED || c21 == CompareResult.TYPE_ONLY) {
|
||||
myConsumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex++;
|
||||
continue;
|
||||
}
|
||||
if (c12 == CompareResult.TYPE_ONLY) {
|
||||
consumer.nodeInserted(oldNode, newChild1, oldIndex + 1);
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldChild1 == null) {
|
||||
myConsumer.nodeInserted(oldNode, newChild1, newIndex);
|
||||
newIndex++;
|
||||
continue;
|
||||
}
|
||||
if (newChild1 == null) {
|
||||
myConsumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex++;
|
||||
continue;
|
||||
}
|
||||
if (oldChild1 == null) {
|
||||
//consumer.nodeInserted(oldNode, newChild1, newIndex);
|
||||
consumer.nodeInserted(oldNode, newChild1, oldIndex+1);
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
if (newChild1 == null) {
|
||||
consumer.nodeDeleted(oldNode, oldChild1);
|
||||
oldIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
myConsumer.nodeReplaced(oldChild1, newChild1);
|
||||
oldIndex++;
|
||||
newIndex++;
|
||||
// check that maybe two children are inserted/deleted
|
||||
// (which frequently is a case when e.g. a PsiMethod inserted, the trailing PsiWhiteSpace is appended too)
|
||||
if (oldChild3 != null || newChild3 != null) {
|
||||
CompareResult c13 = looksEqual(comparator, oldChild1, newChild3);
|
||||
if (c13 == CompareResult.EQUAL || c13 == CompareResult.DRILL_DOWN_NEEDED || c13 == CompareResult.TYPE_ONLY) {
|
||||
consumer.nodeInserted(oldNode, newChild1, oldIndex + 1);
|
||||
consumer.nodeInserted(oldNode, newChild2, oldIndex+1);
|
||||
newIndex--;
|
||||
newIndex--;
|
||||
continue;
|
||||
}
|
||||
CompareResult c31 = looksEqual(comparator, oldChild3, newChild1);
|
||||
if (c31 == CompareResult.EQUAL || c31 == CompareResult.DRILL_DOWN_NEEDED || c31 == CompareResult.TYPE_ONLY) {
|
||||
consumer.nodeDeleted(oldNode, oldChild1);
|
||||
consumer.nodeDeleted(oldNode, oldChild2);
|
||||
oldIndex--;
|
||||
oldIndex--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// last resort: maybe the first elements are more similar?
|
||||
OT oldFirstChild = oldIndex >= prefixLength ? oldChildren[prefixLength] : null;
|
||||
NT newFirstChild = newIndex >= prefixLength ? newChildren[prefixLength] : null;
|
||||
CompareResult c = oldFirstChild == null || newFirstChild == null ? CompareResult.NOT_EQUAL : looksEqual(comparator, oldFirstChild, newFirstChild);
|
||||
if (c == CompareResult.EQUAL || c == CompareResult.TYPE_ONLY || c == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
if (c == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
build(oldFirstChild, newFirstChild, level + 1, maxLevel, consumer);
|
||||
}
|
||||
else {
|
||||
consumer.nodeReplaced(oldFirstChild, newFirstChild);
|
||||
}
|
||||
prefixLength++;
|
||||
continue;
|
||||
}
|
||||
|
||||
consumer.nodeReplaced(oldChild1, newChild1);
|
||||
oldIndex--;
|
||||
newIndex--;
|
||||
}
|
||||
result = CompareResult.NOT_EQUAL;
|
||||
}
|
||||
}
|
||||
myOldTree.disposeChildren(oldChildren, oldChildrenSize);
|
||||
myNewTree.disposeChildren(newChildren, newChildrenSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
// tries to match as many nodes as possible from the beginning (if step=1) of from the end (if step =-1)
|
||||
// returns number of nodes matched
|
||||
private int match(OT[] oldChildren,
|
||||
int oldIndex,
|
||||
NT[] newChildren,
|
||||
int newIndex,
|
||||
int level,
|
||||
int step, // 1 if we go from the start to the end; -1 if we go from the end to the start
|
||||
int maxLength,
|
||||
int maxLevel) {
|
||||
int delta = 0;
|
||||
while (delta != maxLength*step) {
|
||||
OT oldChild = oldChildren[oldIndex + delta];
|
||||
NT newChild = newChildren[newIndex + delta];
|
||||
|
||||
CompareResult c11 = looksEqual(myComparator, oldChild, newChild);
|
||||
|
||||
if (c11 == CompareResult.DRILL_DOWN_NEEDED) {
|
||||
c11 = build(oldChild, newChild, level + 1, maxLevel, DiffTree.<OT, NT>emptyConsumer());
|
||||
}
|
||||
assert c11 != CompareResult.DRILL_DOWN_NEEDED;
|
||||
if (c11 != CompareResult.EQUAL) {
|
||||
break;
|
||||
}
|
||||
delta += step;
|
||||
}
|
||||
return delta*step;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -32,13 +32,14 @@ public class DiffTreeTest extends TestCase {
|
||||
private static class Node {
|
||||
@NotNull
|
||||
private final Node[] myChildren;
|
||||
int myId;
|
||||
private final int myId;
|
||||
|
||||
public Node(final int id, @NotNull Node... children) {
|
||||
myChildren = children;
|
||||
myId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myId + myChildren.length; // This is intentionally bad hashcode
|
||||
}
|
||||
@@ -52,6 +53,7 @@ public class DiffTreeTest extends TestCase {
|
||||
return myId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(myId);
|
||||
}
|
||||
@@ -60,7 +62,7 @@ public class DiffTreeTest extends TestCase {
|
||||
private static class TreeStructure implements FlyweightCapableTreeStructure<Node> {
|
||||
private final Node myRoot;
|
||||
|
||||
public TreeStructure(final Node root) {
|
||||
private TreeStructure(final Node root) {
|
||||
myRoot = root;
|
||||
}
|
||||
|
||||
@@ -110,7 +112,7 @@ public class DiffTreeTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiffBuilder implements DiffTreeChangeBuilder<Node, Node> {
|
||||
private static class DiffBuilder implements DiffTreeChangeBuilder<Node, Node> {
|
||||
private final List<String> myResults = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
@@ -202,7 +204,7 @@ public class DiffTreeTest extends TestCase {
|
||||
Node r1 = new Node(0, new Node(1, new Node(21), new Node(22)));
|
||||
Node r2 = new Node(0, new Node(1, new Node(21), new Node(22), new Node(23), new Node(24)));
|
||||
|
||||
performTest(r1, r2, "INSERTED to 1: 23 at 2", "INSERTED to 1: 24 at 3");
|
||||
performTest(r1, r2, "INSERTED to 1: 24 at 2", "INSERTED to 1: 23 at 2");
|
||||
}
|
||||
|
||||
public void testSubtreeAppears() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user