more robust heuristic: compare text when deciding whether two nodes worth merging

This commit is contained in:
Alexey Kudravtsev
2015-04-20 13:24:41 +03:00
parent d58be68f2b
commit 6f521ff2d7
6 changed files with 58 additions and 50 deletions
@@ -19,6 +19,7 @@ import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.CodeInsightTestCase;
import com.intellij.ide.highlighter.HtmlFileType;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.lang.FileASTNode;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
@@ -514,26 +515,12 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase {
}
public void testInXml2() {
final PsiFile file = configureByText(HtmlFileType.INSTANCE,
"<!DOCTYPE html>\n" +
final PsiFile file = configureByText(XmlFileType.INSTANCE,
"<html>\n" +
"<head>\n" +
" <title></title>\n" +
"</head>\n" +
"<body>\n" +
"<div class=\"cls\">\n" +
" <ul class=\"dropdown-menu\">\n" +
" <li><a href=\"#\">Action</a></li>\n" +
" <li><a href=\"#\">Another action</a></li>\n" +
" <li><a href=\"#\">Something else here</a></li>\n" +
" <li class=\"divider\"></li>\n" +
" <li class=\"dropdown-header\">Nav header</li>\n" +
" <li><a href=\"#\">Separated link</a></li>\n" +
" <li><a href=\"#\">One more separated link</a></li>\n" +
" </ul>\n" +
"<caret>\n" +
"</div>\n" +
"</body>\n" +
"</html>"
);
@@ -542,17 +529,14 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase {
assertEquals("ul", ul.getName());
assertEquals("dropdown-menu", ul.getAttributeValue("class"));
final SmartPsiElementPointer<XmlTag> ulPointer = SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(
ul);
SmartPsiElementPointer<XmlTag> ulPointer = SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(ul);
WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() {
@Override
public void run() {
getEditor().getDocument().insertString(getEditor().getCaretModel().getOffset(), " <ul class=\"nav navbar-nav navbar-right\">\n" +
" <li><a href=\"../navbar/\">Default</a></li>\n" +
" <li class=\"active\"><a href=\"./\">Static top</a></li>\n" +
" <li><a href=\"../navbar-fixed-top/\">Fixed top</a></li>\n" +
" </ul>\n");
int offset = getEditor().getCaretModel().getOffset();
getEditor().getDocument().insertString(offset, " <ul class=\"nav navbar-nav navbar-right\">\n" +
" </ul>\n");
}
});
@@ -83,8 +83,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private CharTable myCharTable;
private final CharSequence myText;
private final char[] myTextArray;
private boolean myDebugMode = false;
private int myLexemeCount = 0;
private boolean myDebugMode;
private int myLexemeCount;
private boolean myTokenTypeChecked;
private ITokenTypeRemapper myRemapper;
private WhitespaceSkippedCallback myWhitespaceSkippedCallback;
@@ -93,7 +93,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private final MyTreeStructure myParentLightTree;
private final int myOffset;
private Map<Key, Object> myUserData = null;
private Map<Key, Object> myUserData;
private IElementType myCachedTokenType;
private final LimitedPool<StartMarker> START_MARKERS = new LimitedPool<StartMarker>(2000, new LimitedPool.ObjectFactory<StartMarker>() {
@@ -635,7 +635,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private static class DoneMarker extends ProductionMarker {
private StartMarker myStart;
private boolean myCollapse = false;
private boolean myCollapse;
public DoneMarker() {
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
@@ -916,7 +916,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
return myCurrentLexeme >= myLexemeCount;
}
@SuppressWarnings({"SuspiciousMethodCalls"})
@SuppressWarnings("SuspiciousMethodCalls")
private void rollbackTo(Marker marker) {
myCurrentLexeme = ((StartMarker)marker).myLexemeIndex;
myTokenTypeChecked = true;
@@ -948,7 +948,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
return false;
}
@SuppressWarnings({"SuspiciousMethodCalls"})
@SuppressWarnings("SuspiciousMethodCalls")
public void drop(Marker marker) {
final DoneMarker doneMarker = ((StartMarker)marker).myDoneMarker;
if (doneMarker != null) {
@@ -973,7 +973,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
myProduction.add(doneMarker);
}
@SuppressWarnings({"SuspiciousMethodCalls"})
@SuppressWarnings("SuspiciousMethodCalls")
public void errorBefore(Marker marker, String message, Marker before) {
doValidityChecks(marker, before);
@@ -1001,7 +1001,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
myProduction.add(doneMarker);
}
@SuppressWarnings({"SuspiciousMethodCalls"})
@SuppressWarnings("SuspiciousMethodCalls")
public void doneBefore(Marker marker, Marker before) {
doValidityChecks(marker, before);
@@ -1173,9 +1173,9 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
@NotNull
private DiffLog merge(@NotNull final ASTNode oldRoot, @NotNull StartMarker newRoot) {
DiffLog diffLog = new DiffLog();
final ConvertFromTokensToASTBuilder builder = new ConvertFromTokensToASTBuilder(newRoot, diffLog);
final MyTreeStructure treeStructure = new MyTreeStructure(newRoot, null);
final MyComparator comparator = new MyComparator(getUserDataUnprotected(CUSTOM_COMPARATOR), treeStructure);
DiffTreeChangeBuilder<ASTNode, LighterASTNode> builder = new ConvertFromTokensToASTBuilder(newRoot, diffLog);
MyTreeStructure treeStructure = new MyTreeStructure(newRoot, null);
ShallowNodeComparator<ASTNode, LighterASTNode> comparator = new MyComparator(getUserDataUnprotected(CUSTOM_COMPARATOR), treeStructure);
final ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
BlockSupportImpl.diffTrees(oldRoot, builder, comparator, treeStructure, indicator == null ? new EmptyProgressIndicator() : indicator);
@@ -1197,7 +1197,9 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
final Stack<StartMarker> nodes = ContainerUtil.newStack();
nodes.push(rootMarker);
@SuppressWarnings({"MultipleVariablesInDeclaration"}) int lastErrorIndex = -1, maxDepth = 0, curDepth = 0;
int lastErrorIndex = -1;
int maxDepth = 0;
int curDepth = 0;
for (int i = 1; i < myProduction.size(); i++) {
final ProductionMarker item = myProduction.get(i);
@@ -1692,7 +1694,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private void insertLeaf(@NotNull IElementType type, @NotNull PsiBuilderImpl builder, int startLexemeIndex, int endLexemeIndex) {
final int start = builder.myLexStarts[startLexemeIndex];
final int end = builder.myLexStarts[endLexemeIndex];
if (start > end || ((start == end) && !(type instanceof ILeafElementType))) return;
if (start > end || start == end && !(type instanceof ILeafElementType)) return;
final Token lexeme;
if (type instanceof ILightLazyParseableElementType) {
@@ -1712,6 +1714,12 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
ensureCapacity();
nodes[count++] = lexeme;
}
@NotNull
@Override
public CharSequence toString(@NotNull LighterASTNode node) {
return myRoot.myBuilder.myText.subSequence(node.getStartOffset(), node.getEndOffset());
}
}
private static class ASTConverter implements Convertor<Node, ASTNode> {
@@ -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.
@@ -77,4 +77,10 @@ public class ASTStructure implements FlyweightCapableTreeStructure<ASTNode> {
@Override
public void disposeChildren(final ASTNode[] nodes, final int count) {
}
@NotNull
@Override
public CharSequence toString(@NotNull ASTNode node) {
return node.getChars();
}
}
@@ -16,6 +16,7 @@
package com.intellij.util.diff;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
@@ -47,7 +48,7 @@ public class DiffTree<OT, NT> {
@NotNull ShallowNodeComparator<OT, NT> comparator,
@NotNull DiffTreeChangeBuilder<OT, NT> consumer) {
final DiffTree<OT, NT> tree = new DiffTree<OT, NT>(oldTree, newTree, comparator);
tree.build(oldTree.getRoot(), newTree.getRoot(), 0, Integer.MAX_VALUE, consumer);
tree.build(oldTree.getRoot(), newTree.getRoot(), 0, consumer);
}
private enum CompareResult {
@@ -80,9 +81,7 @@ public class DiffTree<OT, NT> {
};
@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
private CompareResult build(@NotNull OT oldN, @NotNull NT newN, int level, @NotNull DiffTreeChangeBuilder<OT, NT> consumer) {
OT oldNode = myOldTree.prepareForGetChildren(oldN);
NT newNode = myNewTree.prepareForGetChildren(newN);
@@ -117,14 +116,13 @@ public class DiffTree<OT, NT> {
final ShallowNodeComparator<OT, NT> comparator = myComparator;
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);
int suffixLength = match(oldChildren, oldChildrenSize - 1, newChildren, newChildrenSize - 1, level, -1, minSize);
int prefixLength = oldChildrenSize == 1 && newChildrenSize == 1 ? 0 : match(oldChildren, 0, newChildren, 0, level, 1, minSize-suffixLength);
if (oldChildrenSize == newChildrenSize && suffixLength + prefixLength == oldChildrenSize) {
result = CompareResult.EQUAL;
}
else if (consumer == emptyConsumer()){
else if (consumer == emptyConsumer()) {
result = CompareResult.NOT_EQUAL;
}
else {
@@ -141,7 +139,7 @@ public class DiffTree<OT, NT> {
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);
build(oldChild1, newChild1, level + 1, consumer);
}
oldIndex--;
newIndex--;
@@ -225,7 +223,7 @@ public class DiffTree<OT, NT> {
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);
build(oldFirstChild, newFirstChild, level + 1, consumer);
}
else {
consumer.nodeReplaced(oldFirstChild, newFirstChild);
@@ -254,8 +252,7 @@ public class DiffTree<OT, NT> {
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 maxLength) {
int delta = 0;
while (delta != maxLength*step) {
OT oldChild = oldChildren[oldIndex + delta];
@@ -264,7 +261,11 @@ public class DiffTree<OT, NT> {
CompareResult c11 = looksEqual(myComparator, oldChild, newChild);
if (c11 == CompareResult.DRILL_DOWN_NEEDED) {
c11 = build(oldChild, newChild, level + 1, maxLevel, DiffTree.<OT, NT>emptyConsumer());
CharSequence oldText = myOldTree.toString(oldChild);
CharSequence newText = myNewTree.toString(newChild);
c11 = StringUtil.equals(oldText, newText)
? build(oldChild, newChild, level + 1, DiffTree.<OT, NT>emptyConsumer())
: CompareResult.NOT_EQUAL;
}
assert c11 != CompareResult.DRILL_DOWN_NEEDED;
if (c11 != CompareResult.EQUAL) {
@@ -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.
@@ -36,4 +36,7 @@ public interface FlyweightCapableTreeStructure<T> {
int getChildren(@NotNull T parent, @NotNull Ref<T[]> into);
void disposeChildren(T[] nodes, int count);
@NotNull
CharSequence toString(@NotNull T node);
}
@@ -92,6 +92,12 @@ public class DiffTreeTest extends TestCase {
@Override
public void disposeChildren(final Node[] nodes, final int count) {
}
@NotNull
@Override
public CharSequence toString(@NotNull Node node) {
return node.toString();
}
}
private static class NodeComparator implements ShallowNodeComparator<Node, Node> {