From ba799ce032cd0b56b674449c9bec2a30758b87c5 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Fri, 14 Apr 2017 12:07:21 +0300 Subject: [PATCH] properties: fix update of structure view & resource bundle editor (IDEA-170695) --- .../PropertiesPsiTreeChangePreprocessor.java | 82 +++++++++++++++++ plugins/properties/src/META-INF/plugin.xml | 2 + .../PropertiesModificationTest.java | 90 +++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesPsiTreeChangePreprocessor.java create mode 100644 plugins/properties/testSrc/com/intellij/lang/properties/PropertiesModificationTest.java diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesPsiTreeChangePreprocessor.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesPsiTreeChangePreprocessor.java new file mode 100644 index 000000000000..8f886f9ac715 --- /dev/null +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesPsiTreeChangePreprocessor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2017 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.lang.properties.psi.impl; + +import com.intellij.lang.properties.parsing.PropertiesTokenTypes; +import com.intellij.lang.properties.psi.PropertiesFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.PsiTreeChangeEventImpl; +import com.intellij.psi.impl.PsiTreeChangePreprocessorBase; +import com.intellij.psi.tree.TokenSet; +import com.intellij.psi.util.PsiUtilCore; +import org.jetbrains.annotations.NotNull; + +public class PropertiesPsiTreeChangePreprocessor extends PsiTreeChangePreprocessorBase { + private static final TokenSet CODE_BLOCK_ELEMENTS = TokenSet.create(PropertiesTokenTypes.VALUE_CHARACTERS, + PropertiesTokenTypes.END_OF_LINE_COMMENT, + PropertiesTokenTypes.WHITE_SPACE, + PropertiesTokenTypes.KEY_VALUE_SEPARATOR); + + public PropertiesPsiTreeChangePreprocessor(@NotNull PsiManager psiManager) { + super(psiManager); + } + + @Override + protected void onTreeChanged(@NotNull PsiTreeChangeEventImpl event) { + if (event.isGenericChange()) return; + switch (event.getCode()) { + case BEFORE_PROPERTY_CHANGE: + case BEFORE_CHILD_REMOVAL: + case BEFORE_CHILD_ADDITION: + case BEFORE_CHILD_MOVEMENT: + case BEFORE_CHILDREN_CHANGE: + case BEFORE_CHILD_REPLACEMENT: + return; + case CHILD_ADDED: + if (isCodeBlock(event.getChild())) return; + break; + case CHILD_REMOVED: + if (isCodeBlock(event.getChild())) return; + break; + case CHILD_REPLACED: + if (isCodeBlock(event.getOldChild()) || isCodeBlock(event.getNewChild())) return; + break; + case CHILD_MOVED: + if (isCodeBlock(event.getChild())) return; + break; + case CHILDREN_CHANGED: + if (isCodeBlock(event.getChild())) return; + case PROPERTY_CHANGED: + break; + } + doIncOutOfCodeBlockCounter(); + } + + @Override + protected boolean acceptsEvent(@NotNull PsiTreeChangeEventImpl event) { + return event.getFile() instanceof PropertiesFile; + } + + @Override + protected boolean isOutOfCodeBlock(@NotNull PsiElement element) { + throw new IllegalStateException(); + } + + private static boolean isCodeBlock(@NotNull PsiElement element) { + return CODE_BLOCK_ELEMENTS.contains(PsiUtilCore.getElementType(element)); + } +} diff --git a/plugins/properties/src/META-INF/plugin.xml b/plugins/properties/src/META-INF/plugin.xml index fc22b76a0c68..6bc313ede341 100644 --- a/plugins/properties/src/META-INF/plugin.xml +++ b/plugins/properties/src/META-INF/plugin.xml @@ -123,6 +123,8 @@ + + diff --git a/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesModificationTest.java b/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesModificationTest.java new file mode 100644 index 000000000000..8e4198e9b73d --- /dev/null +++ b/plugins/properties/testSrc/com/intellij/lang/properties/PropertiesModificationTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2017 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.lang.properties; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import org.jetbrains.annotations.NotNull; + +public class PropertiesModificationTest extends LightCodeInsightFixtureTestCase { + + public void testValueEditing() { + doTestEditing(" # comment", false); + } + + public void testKeyEditing() { + doTestEditing("key = value", true); + } + + public void testCommentEditing() { + doTestEditing("key = value", false); + } + + public void testKeyReplacement() { + doTestReplacement("key = value", true); + } + + public void testValueReplacement() { + doTestReplacement("key = value", false); + } + + public void testCommentReplacement() { + doTestReplacement("key = value \n# comment \n key2 = value2", false); + } + + public void testKeyDeletion() { + doTestDeletion("key = value", true); + } + + public void testPropertiesDeletion() { + doTestDeletion("key1 = value1 \n" + + "key2 = value2 \n" + + "key3 = value3 \n" + + "key4 = value4 \n" + + "key5 = value5 \n", true); + } + + private void doTestEditing(@NotNull String text, boolean isOutOfBlockModificationExpected) { + doTest(text, () -> myFixture.type("xxx"), isOutOfBlockModificationExpected); + } + + private void doTestReplacement(@NotNull String text, boolean isOutOfBlockModificationExpected) { + doTest(text, () -> WriteCommandAction.runWriteCommandAction(getProject(), () -> { + SelectionModel sel = getEditor().getSelectionModel(); + getEditor().getDocument().replaceString(sel.getSelectionStart(), sel.getSelectionEnd(), "xxx"); + }), isOutOfBlockModificationExpected); + } + + private void doTestDeletion(@NotNull String text, boolean isOutOfBlockModificationExpected) { + doTest(text, () -> WriteCommandAction.runWriteCommandAction(getProject(), () -> { + SelectionModel sel = getEditor().getSelectionModel(); + getEditor().getDocument().deleteString(sel.getSelectionStart(), sel.getSelectionEnd()); + }), isOutOfBlockModificationExpected); + } + + private void doTest(@NotNull String text, Runnable modificationAction, boolean isOutOfBlockModificationExpected) { + myFixture.configureByText("test.properties", text); + PsiModificationTracker tracker = myFixture.getPsiManager().getModificationTracker(); + long oldMod = tracker.getOutOfCodeBlockModificationCount(); + modificationAction.run(); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + long newMod = tracker.getOutOfCodeBlockModificationCount(); + assertTrue(isOutOfBlockModificationExpected ^ oldMod == newMod); + } +}