diff --git a/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java b/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java index ac55e6f1c85b..3df8d9f01953 100644 --- a/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java +++ b/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java @@ -15,11 +15,12 @@ */ package com.intellij.psi.impl; -import com.intellij.openapi.util.Conditions; import com.intellij.psi.*; import com.intellij.psi.impl.PsiTreeChangeEventImpl.PsiEventType; import com.intellij.psi.impl.source.jsp.jspXml.JspDirective; +import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; @@ -60,7 +61,7 @@ public class JavaCodeBlockModificationListener extends PsiTreeChangePreprocessor @Override protected boolean containsStructuralElements(@NotNull PsiElement element) { - return hasClassesInside(element); + return mayHaveJavaStructureInside(element); } @Override @@ -68,7 +69,7 @@ public class JavaCodeBlockModificationListener extends PsiTreeChangePreprocessor Set changedChildren = getChangedChildren(event); PsiModificationTrackerImpl tracker = (PsiModificationTrackerImpl)myPsiManager.getModificationTracker(); - if (!changedChildren.isEmpty() && changedChildren.stream().anyMatch(JavaCodeBlockModificationListener::hasClassesInside)) { + if (!changedChildren.isEmpty() && changedChildren.stream().anyMatch(JavaCodeBlockModificationListener::mayHaveJavaStructureInside)) { tracker.incCounter(); } @@ -91,12 +92,24 @@ public class JavaCodeBlockModificationListener extends PsiTreeChangePreprocessor if (code == PsiEventType.CHILD_ADDED || code == PsiEventType.CHILD_REMOVED || code == PsiEventType.CHILD_REPLACED) { return StreamEx.of(event.getOldChild(), event.getChild(), event.getNewChild()).nonNull().toSet(); } + if (code == PsiEventType.BEFORE_CHILD_REMOVAL || code == PsiEventType.BEFORE_CHILD_REPLACEMENT) { + return StreamEx.of(event.getOldChild(), event.getChild()).nonNull().toSet(); + } + if (code == PsiEventType.BEFORE_CHILDREN_CHANGE && !event.isGenericChange()) { + PsiElement parent = event.getParent(); + if (!(parent instanceof PsiFileSystemItem) && !TreeUtil.isCollapsedChameleon(parent.getNode())) { + return ContainerUtil.newHashSet(parent.getChildren()); + } + } return Collections.emptySet(); } - private static boolean hasClassesInside(@NotNull PsiElement element) { - return !SyntaxTraverser.psiTraverser(element).traverse() - .filter(Conditions.instanceOf(PsiClass.class, PsiLambdaExpression.class)).isEmpty(); + private static boolean mayHaveJavaStructureInside(@NotNull PsiElement root) { + return !SyntaxTraverser.psiTraverser(root) + .expand(e -> !TreeUtil.isCollapsedChameleon(e.getNode())) + .traverse() + .filter(e -> e instanceof PsiClass || e instanceof PsiLambdaExpression || TreeUtil.isCollapsedChameleon(e.getNode())) + .isEmpty(); } } diff --git a/java/java-tests/testSrc/com/intellij/java/psi/PsiModificationTrackerTest.java b/java/java-tests/testSrc/com/intellij/java/psi/PsiModificationTrackerTest.java index 871b671f46f5..2e6b5b314a7e 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/PsiModificationTrackerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/PsiModificationTrackerTest.java @@ -43,6 +43,7 @@ import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.containers.JBIterable; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -143,7 +144,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { private void doTest(@NonNls String text, Processor run) { PsiFile file = configureByText(JavaFileType.INSTANCE, text); - PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker modificationTracker = getTracker(); long count = modificationTracker.getModificationCount(); WriteCommandAction.runWriteCommandAction(getProject(), ()->{run.process(file);}); @@ -162,7 +163,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { setFileText(virtualFile, "text"); configureByFile(virtualFile); PsiFile file = getFile(); - PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker modificationTracker = getTracker(); long count = modificationTracker.getJavaStructureModificationCount(); ApplicationManager.getApplication().runWriteAction(() -> file.getContainingDirectory().delete()); @@ -228,7 +229,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotDisappearWithoutEvents() throws Exception { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long count0 = tracker.getJavaStructureModificationCount(); final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); @@ -253,7 +254,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { public void testClassShouldNotDisappearWithoutEvents_NoDocument() throws IOException { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); @@ -273,7 +274,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotAppearWithoutEvents_NoPsiDirectory() throws IOException { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long count0 = tracker.getJavaStructureModificationCount(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); @@ -290,7 +291,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotAppearWithoutEvents_NoPsiGrandParentDirectory() throws IOException { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long count0 = tracker.getJavaStructureModificationCount(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); @@ -306,7 +307,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotDisappearWithoutEvents_VirtualFileDeleted() throws IOException { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); @@ -324,7 +325,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotDisappearWithoutEvents_ParentVirtualDirectoryDeleted() throws Exception { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); final VirtualFile file = addFileToProject("foo/Foo.java", "package foo; class Foo {}").getVirtualFile(); @@ -343,7 +344,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotDisappearWithoutEvents_InCodeBlock() throws Exception { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); String barStr = "class Bar {}"; PsiFile file = addFileToProject("Foo.java", "class Foo {{" + barStr + "}}"); @@ -361,7 +362,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testClassShouldNotAppearWithoutEvents_InCodeBlock() throws Exception { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); String barStr = "class Bar {}"; PsiFile file = addFileToProject("Foo.java", "class Foo {{" + "}}"); @@ -378,7 +379,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testVirtualFileRename_WithPsi() throws IOException { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject()); GlobalSearchScope scope = GlobalSearchScope.allScope(getProject()); @@ -406,11 +407,11 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass("Foo", scope); assertNotNull(psiClass); - long count = PsiManager.getInstance(getProject()).getModificationTracker().getJavaStructureModificationCount(); + long count = getTracker().getJavaStructureModificationCount(); IdeaTestUtil.setModuleLanguageLevel(getModule(), LanguageLevel.JDK_1_3); - assertTrue(count != PsiManager.getInstance(getProject()).getModificationTracker().getJavaStructureModificationCount()); + assertTrue(count != getTracker().getJavaStructureModificationCount()); psiClass = JavaPsiFacade.getInstance(getProject()).findClass("Foo", scope); assertNotNull(psiClass); @@ -426,7 +427,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { } public void testRootsChangeIncreasesCounts() { - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long mc = tracker.getModificationCount(); long js = tracker.getJavaStructureModificationCount(); long ocb = tracker.getOutOfCodeBlockModificationCount(); @@ -442,7 +443,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { FixtureRuleKt.runInLoadComponentStateMode(myProject, () -> { ProjectKt.getStateStore(myProject).save(new SmartList<>()); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long mc = tracker.getModificationCount(); VirtualFile ws = myProject.getWorkspaceFile(); @@ -462,7 +463,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { public void testNoIncrementOnReadOnlyStatusChange() throws IOException { VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile(); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long mc = tracker.getModificationCount(); WriteAction.run(() -> file.setWritable(false)); @@ -478,7 +479,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { public void testJavaStructureModCountMustNotBeAdvancedOnJavadocChange() { configureByText(JavaFileType.INSTANCE, "/* abc */ class A{}"); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long javaCount = tracker.getJavaStructureModificationCount(); long codeBlockCount = tracker.getOutOfCodeBlockModificationCount(); @@ -491,7 +492,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { public void testJavaStructureModCountMustNotBeAdvancedOnAddingSpace() { configureByText(JavaFileType.INSTANCE, "class A{ }"); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long javaCount = tracker.getJavaStructureModificationCount(); long codeBlockCount = tracker.getOutOfCodeBlockModificationCount(); @@ -512,7 +513,7 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { PsiAnonymousClass anon = SyntaxTraverser.psiTraverser(file).filter(PsiAnonymousClass.class).first(); Arrays.stream(anon.getAllMethods()).forEach(PsiUtilCore::ensureValid); - PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker(); + PsiModificationTracker tracker = getTracker(); long javaCount = tracker.getJavaStructureModificationCount(); WriteCommandAction.runWriteCommandAction(getProject(), () -> { @@ -529,4 +530,41 @@ public class PsiModificationTrackerTest extends CodeInsightTestCase { assertFalse(javaCount == tracker.getJavaStructureModificationCount()); } + public void testDeleteLocalClass() { + PsiFile file = configureByText(JavaFileType.INSTANCE, "class A{ void bar() {\n" + + "abstract class Local { abstract void foo(); }\n" + + "int a = 1;" + + "while (true) {\n" + + "Local r = new Local() {\n" + + " public void foo() {}\n" + + "}" + + "};\n" + + "}}"); + + PsiAnonymousClass anon = SyntaxTraverser.psiTraverser(file).filter(PsiAnonymousClass.class).first(); + PsiMethod method = anon.getMethods()[0]; + + PsiUtilCore.ensureValid(method); + Arrays.stream(method.findSuperMethods()).forEach(PsiUtilCore::ensureValid); + + long javaCount = getTracker().getJavaStructureModificationCount(); + + WriteCommandAction.runWriteCommandAction(getProject(), () -> { + TextRange localRange = anon.getSuperClass().getTextRange(); + getEditor().getDocument().deleteString(localRange.getStartOffset(), localRange.getEndOffset()); + + PsiDocumentManager.getInstance(myProject).commitDocument(getEditor().getDocument()); + }); + + PsiUtilCore.ensureValid(method); + Arrays.stream(method.findSuperMethods()).forEach(PsiUtilCore::ensureValid); + + assertFalse(javaCount == getTracker().getJavaStructureModificationCount()); + } + + @NotNull + private PsiModificationTracker getTracker() { + return PsiManager.getInstance(getProject()).getModificationTracker(); + } + } diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiTreeChangePreprocessorBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiTreeChangePreprocessorBase.java index cff2142aa11d..200c6ee2b02a 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiTreeChangePreprocessorBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiTreeChangePreprocessorBase.java @@ -87,11 +87,13 @@ public abstract class PsiTreeChangePreprocessorBase implements PsiTreeChangePrep protected final boolean isOutOfCodeBlockChangeEvent(@NotNull PsiTreeChangeEventImpl event) { switch (event.getCode()) { case BEFORE_PROPERTY_CHANGE: - case BEFORE_CHILD_MOVEMENT: case BEFORE_CHILD_ADDITION: + case BEFORE_CHILD_MOVEMENT: + return false; + case BEFORE_CHILD_REMOVAL: case BEFORE_CHILD_REPLACEMENT: - return false; + return _containsStructuralElements(event.getChild()) || _containsStructuralElements(event.getOldChild()); case BEFORE_CHILDREN_CHANGE: case CHILDREN_CHANGED: