From 8aa66c1911f9185f1709f5b0d28a1102f91ed3a2 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 18 May 2012 14:59:56 +0400 Subject: [PATCH 01/18] non-negative range asserted --- .../codeInsight/editorActions/moveUpDown/LineRange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/lang-api/src/com/intellij/codeInsight/editorActions/moveUpDown/LineRange.java b/platform/lang-api/src/com/intellij/codeInsight/editorActions/moveUpDown/LineRange.java index b9fba8d2bf57..cce3cd8c39e3 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/editorActions/moveUpDown/LineRange.java +++ b/platform/lang-api/src/com/intellij/codeInsight/editorActions/moveUpDown/LineRange.java @@ -33,7 +33,7 @@ public class LineRange { public LineRange(final int startLine, final int endLine) { this.startLine = startLine; this.endLine = endLine; - LOG.assertTrue(startLine > 0, "Negative start line"); + LOG.assertTrue(startLine >= 0, "Negative start line"); if (startLine > endLine) { LOG.error("start > end: start=" + startLine+"; end="+endLine); } From 56311ae33e2cf3dc0d63b6e6bdde1414cd9ef491 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Fri, 18 May 2012 13:00:19 +0400 Subject: [PATCH 02/18] inline to anonymous: wrap code with code block when needed (IDEA-86007) --- ...InlineToAnonymousConstructorProcessor.java | 22 +++++++++++++--- .../inlineToAnonymousClass/Braces.java | 26 +++++++++++++++++++ .../inlineToAnonymousClass/Braces.java.after | 22 ++++++++++++++++ .../inline/InlineToAnonymousClassTest.java | 4 +++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java create mode 100644 java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java.after diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java index 31fbe26be597..b762395a7a2a 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineToAnonymousConstructorProcessor.java @@ -57,11 +57,11 @@ class InlineToAnonymousConstructorProcessor { psiElement().withText(PsiKeyword.THIS))); private final PsiClass myClass; - private final PsiNewExpression myNewExpression; + private PsiNewExpression myNewExpression; private final PsiType mySuperType; private final Map myFieldInitializers = new HashMap(); private final Map myLocalsForParameters = new HashMap(); - private final PsiStatement myNewStatement; + private PsiStatement myNewStatement; private final PsiElementFactory myElementFactory; private PsiMethod myConstructor; private PsiExpressionList myConstructorArguments; @@ -263,7 +263,23 @@ class InlineToAnonymousConstructorProcessor { final PsiDeclarationStatement declaration = myElementFactory.createVariableDeclarationStatement(localName, type, initializer); PsiVariable variable = (PsiVariable)declaration.getDeclaredElements()[0]; PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true); - myNewStatement.getParent().addBefore(declaration, myNewStatement); + final PsiElement parent = myNewStatement.getParent(); + if (parent instanceof PsiCodeBlock) { + variable = (PsiVariable)((PsiDeclarationStatement)parent.addBefore(declaration, myNewStatement)).getDeclaredElements()[0]; + } + else { + final int offsetInStatement = myNewExpression.getTextRange().getStartOffset() - myNewStatement.getTextRange().getStartOffset(); + final PsiBlockStatement blockStatement = (PsiBlockStatement)myElementFactory.createStatementFromText("{}", null); + PsiCodeBlock block = blockStatement.getCodeBlock(); + block.add(declaration); + block.add(myNewStatement); + block = ((PsiBlockStatement)myNewStatement.replace(blockStatement)).getCodeBlock(); + + variable = (PsiVariable)((PsiDeclarationStatement)block.getStatements()[0]).getDeclaredElements()[0]; + myNewStatement = block.getStatements()[1]; + myNewExpression = PsiTreeUtil.getParentOfType(myNewStatement.findElementAt(offsetInStatement), PsiNewExpression.class); + } + return variable; } catch (IncorrectOperationException e) { diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java new file mode 100644 index 000000000000..fc3eddbb1b9c --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java @@ -0,0 +1,26 @@ +public class Demo { + + static class MyParent { + private final String value; + + MyParent(String value) { + this.value = value; + } + } + + static class MyChild extends MyParent { + MyChild(String value) { + super(value); + } + } + + public static void main(String[] args) { + + String value = "something"; + final MyParent p; + if (true) + p = new MyChild(value); + else + p = new MyParent("value"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java.after b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java.after new file mode 100644 index 000000000000..8a2d39edbd07 --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineToAnonymousClass/Braces.java.after @@ -0,0 +1,22 @@ +public class Demo { + + static class MyParent { + private final String value; + + MyParent(String value) { + this.value = value; + } + } + + public static void main(String[] args) { + + String value = "something"; + final MyParent p; + if (true) { + final String value1 = value; + p = new MyParent(value1); + } + else + p = new MyParent("value"); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineToAnonymousClassTest.java b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineToAnonymousClassTest.java index f9edb4b24490..469acd2a150f 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineToAnonymousClassTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineToAnonymousClassTest.java @@ -226,6 +226,10 @@ public class InlineToAnonymousClassTest extends LightRefactoringTestCase { doTest(false, true); } + public void testBraces() throws Exception { + doTest(false, false); + } + public void testNoInlineAbstract() throws Exception { doTestNoInline("Abstract classes cannot be inlined"); } From 5ff4cccc451109004a5b700dcd1257e53b8152ca Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 18 May 2012 16:03:49 +0400 Subject: [PATCH 03/18] Shuffle Names action --- plugins/devkit/resources/META-INF/plugin.xml | 5 + .../src/actions/ShuffleNamesAction.java | 130 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 plugins/devkit/src/actions/ShuffleNamesAction.java diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index f7bd8c9d1061..1a57d95de8a0 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -120,6 +120,11 @@ + + + + diff --git a/plugins/devkit/src/actions/ShuffleNamesAction.java b/plugins/devkit/src/actions/ShuffleNamesAction.java new file mode 100644 index 000000000000..5cf7dde89b25 --- /dev/null +++ b/plugins/devkit/src/actions/ShuffleNamesAction.java @@ -0,0 +1,130 @@ +/* + * 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 org.jetbrains.idea.devkit.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessorEx; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiRecursiveElementWalkingVisitor; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import gnu.trove.THashMap; + +import java.util.*; + +/** + * @author gregsh + */ +public class ShuffleNamesAction extends AnAction { + @Override + public void update(AnActionEvent e) { + Editor editor = PlatformDataKeys.EDITOR.getData(e.getDataContext()); + PsiFile file = LangDataKeys.PSI_FILE.getData(e.getDataContext()); + e.getPresentation().setEnabled(editor != null && file != null); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Editor editor = PlatformDataKeys.EDITOR.getData(e.getDataContext()); + PsiFile file = LangDataKeys.PSI_FILE.getData(e.getDataContext()); + if (editor == null || file == null) return; + final Project project = file.getProject(); + CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessorEx.getInstance(); + Object commandToken = commandProcessor.startCommand(project, e.getPresentation().getText(), e.getPresentation().getText(), UndoConfirmationPolicy.DEFAULT); + AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(getClass()); + try { + shuffleIds(file, editor); + } + finally { + token.finish(); + commandProcessor.finishCommand(project, commandToken, null); + } + } + + private static boolean shuffleIds(PsiFile file, Editor editor) { + final Map map = new THashMap(); + final StringBuilder sb = new StringBuilder(); + final StringBuilder quote = new StringBuilder(); + final ArrayList split = new ArrayList(100); + file.acceptChildren(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (element instanceof LeafPsiElement) { + String type = ((LeafPsiElement)element).getElementType().toString(); + String text = element.getText(); + if (text.isEmpty()) return; + + for (int i=0, len=text.length(); i 0; + boolean isNumber = false; + if (isQuoted || type.equals("ID") || type.contains("IDENT") && !"ts".equals(text) || + (isNumber = text.matches("[0-9]+"))) { + String replacement = map.get(text); + if (replacement == null) { + split.addAll(Arrays.asList((isQuoted? text.substring(quote.length(), text.length()-quote.length()).replace("''", "") : text).split(""))); + if (!isNumber) { + for (ListIterator it = split.listIterator(); it.hasNext(); ) { + String s = it.next(); + if (s.isEmpty()) { + it.remove(); + continue; + } + int c = s.charAt(0); + int cap = c & 32; + c &= ~cap; + c = (char) ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 7) % 26 + 'A') : c) | cap; + it.set(String.valueOf((char)c)); + } + } + Collections.shuffle(split); + if (isNumber && "0".equals(split.get(0))) { + split.set(0, "1"); + } + replacement = StringUtil.join(split, ""); + if (isQuoted) { + replacement = quote + replacement + quote.reverse(); + } + map.put(text, replacement); + } + text = replacement; + } + sb.append(text); + quote.setLength(0); + split.clear(); + } + super.visitElement(element); + } + }); + editor.getDocument().setText(sb.toString()); + return true; + } +} From 306b9ada21abba52c07a429bbb39795529ccab8f Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 18 May 2012 16:07:44 +0400 Subject: [PATCH 04/18] use separate modificationCount for PSI and Document (fixes psifile-dependent CachedValues) --- .../src/com/intellij/psi/impl/source/PsiFileImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index 2c05de57807b..5ba93fd8b982 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -73,6 +73,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF private IElementType myElementType; protected IElementType myContentElementType; + private long myModificationStamp; protected PsiFile myOriginalFile = null; private final FileViewProvider myViewProvider; @@ -354,7 +355,9 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF myStub = null; } - public void clearCaches() {} + public void clearCaches() { + myModificationStamp ++; + } @Override public String getText() { @@ -386,7 +389,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF @Override public long getModificationStamp() { - return getViewProvider().getModificationStamp(); + return myModificationStamp; } @Override From 7dd0bb241cc591ed8745ff3f7903e34d48b8275f Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Thu, 17 May 2012 19:02:38 +0400 Subject: [PATCH 05/18] cleanup --- .../TextEditorHighlightingPass.java | 2 ++ .../daemon/impl/LocalInspectionsPass.java | 3 ++- .../intellij/xml/actions/XmlSplitTagAction.java | 5 +++++ .../XmlSuppressableInspectionTool.java | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPass.java b/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPass.java index d40d70cb1723..fcda8e15f6b2 100644 --- a/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPass.java +++ b/platform/lang-impl/src/com/intellij/codeHighlighting/TextEditorHighlightingPass.java @@ -55,6 +55,7 @@ public abstract class TextEditorHighlightingPass implements HighlightingPass { this(project, document, true); } + @Override public final void collectInformation(ProgressIndicator progress) { if (!isValid()) return; //Document has changed. myDumb = DumbService.getInstance(myProject).isDumb(); @@ -88,6 +89,7 @@ public abstract class TextEditorHighlightingPass implements HighlightingPass { return true; } + @Override public final void applyInformationToEditor() { if (!isValid()) return; // Document has changed. if (DumbService.getInstance(myProject).isDumb() && !(this instanceof DumbAware)) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java index f17a5504853a..ed110d1bd749 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java @@ -709,7 +709,8 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass return new ArrayList(result); } - List getInspectionTools(InspectionProfileWrapper profile) { + @NotNull + List getInspectionTools(@NotNull InspectionProfileWrapper profile) { final List tools = profile.getHighlightingLocalInspectionTools(myFile); for (Iterator iterator = tools.iterator(); iterator.hasNext(); ) { LocalInspectionToolWrapper tool = iterator.next(); diff --git a/xml/impl/src/com/intellij/xml/actions/XmlSplitTagAction.java b/xml/impl/src/com/intellij/xml/actions/XmlSplitTagAction.java index 8291ac3d4c9a..77ec93a89a50 100644 --- a/xml/impl/src/com/intellij/xml/actions/XmlSplitTagAction.java +++ b/xml/impl/src/com/intellij/xml/actions/XmlSplitTagAction.java @@ -36,16 +36,19 @@ import org.jetbrains.annotations.NotNull; */ public class XmlSplitTagAction implements IntentionAction { + @Override @NotNull public String getText() { return XmlBundle.message("xml.split.tag.intention.action"); } + @Override @NotNull public String getFamilyName() { return XmlBundle.message("xml.split.tag.intention.action"); } + @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { if (file instanceof XmlFile) { if (editor != null) { @@ -75,6 +78,7 @@ public class XmlSplitTagAction implements IntentionAction { return "html".equals(name) || "body".equals(name) || "title".equals(name); } + @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { if (!CodeInsightUtilBase.prepareFileForWrite(file)) return; @@ -165,6 +169,7 @@ public class XmlSplitTagAction implements IntentionAction { return sb.toString(); } + @Override public boolean startInWriteAction() { return true; } diff --git a/xml/openapi/src/com/intellij/codeInspection/XmlSuppressableInspectionTool.java b/xml/openapi/src/com/intellij/codeInspection/XmlSuppressableInspectionTool.java index 26191bac3220..89a020872157 100644 --- a/xml/openapi/src/com/intellij/codeInspection/XmlSuppressableInspectionTool.java +++ b/xml/openapi/src/com/intellij/codeInspection/XmlSuppressableInspectionTool.java @@ -29,10 +29,12 @@ import org.jetbrains.annotations.NotNull; public abstract class XmlSuppressableInspectionTool extends LocalInspectionTool implements CustomSuppressableInspectionTool { @NonNls static final String ALL = "ALL"; + @Override public SuppressIntentionAction[] getSuppressActions(final PsiElement element) { return new SuppressIntentionAction[]{new SuppressTag(), new SuppressForFile(getID()), new SuppressAllForFile()}; } + @Override public boolean isSuppressedFor(final PsiElement element) { return XmlSuppressionProvider.isSuppressed(element, getID()); } @@ -45,27 +47,30 @@ public abstract class XmlSuppressableInspectionTool extends LocalInspectionTool } public static class SuppressTagStatic extends SuppressIntentionAction { - - private String id; + private final String id; public SuppressTagStatic(String id) { this.id = id; } + @Override @NotNull public String getText() { return InspectionsBundle.message("xml.suppressable.for.tag.title"); } + @Override @NotNull public String getFamilyName() { return getText(); } + @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) { return PsiTreeUtil.getParentOfType(element, XmlTag.class) != null; } + @Override public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException { XmlSuppressionProvider.getProvider(element.getContainingFile()).suppressForTag(element, id); } @@ -78,20 +83,24 @@ public abstract class XmlSuppressableInspectionTool extends LocalInspectionTool myInspectionId = inspectionId; } + @Override @NotNull public String getText() { return InspectionsBundle.message("xml.suppressable.for.file.title"); } + @Override @NotNull public String getFamilyName() { return getText(); } + @Override public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException { XmlSuppressionProvider.getProvider(element.getContainingFile()).suppressForFile(element, myInspectionId); } + @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) { return element.isValid() && element.getContainingFile() instanceof XmlFile; } @@ -103,6 +112,7 @@ public abstract class XmlSuppressableInspectionTool extends LocalInspectionTool super(ALL); } + @Override @NotNull public String getText() { return InspectionsBundle.message("xml.suppressable.all.for.file.title"); From a6657f836d2de9f0751d990f133a50f69e7bbc59 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 18 May 2012 13:09:28 +0400 Subject: [PATCH 06/18] rewire logic for ignoring changes in whitespace and comment into the ChangeLocalityDetector. --- .../impl/JavaChangeLocalityDetector.java | 11 ++++-- .../daemon/ChangeLocalityDetector.java | 10 +++++- .../impl/DefaultChangeLocalityDetector.java | 35 ++++++++++++++++++ .../daemon/impl/PsiChangeHandler.java | 24 ++++++------- .../src/META-INF/XmlPlugin.xml | 4 +-- .../xslt/impl/XsltChangeLocalityDetector.java | 3 +- resources/src/META-INF/IdeaPlugin.xml | 1 + .../xml/XmlChangeLocalityDetector.java | 36 +++++++++++++++++++ .../DefaultXmlSuppressionProvider.java | 9 ++++- 9 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultChangeLocalityDetector.java create mode 100644 xml/impl/src/com/intellij/xml/XmlChangeLocalityDetector.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaChangeLocalityDetector.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaChangeLocalityDetector.java index 6a8f8244faca..1f06435e398a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaChangeLocalityDetector.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/JavaChangeLocalityDetector.java @@ -21,15 +21,20 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeInsight.daemon.ChangeLocalityDetector; import com.intellij.psi.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class JavaChangeLocalityDetector implements ChangeLocalityDetector { @Override @Nullable - public PsiElement getChangeHighlightingDirtyScopeFor(final PsiElement element) { + public PsiElement getChangeHighlightingDirtyScopeFor(@NotNull final PsiElement element) { + // optimization PsiElement parent = element.getParent(); - if (element instanceof PsiCodeBlock && parent instanceof PsiMethod && !((PsiMethod)parent).isConstructor() && - parent.getParent() instanceof PsiClass && !(parent.getParent() instanceof PsiAnonymousClass)) { + if (element instanceof PsiCodeBlock + && parent instanceof PsiMethod + && !((PsiMethod)parent).isConstructor() + && parent.getParent() instanceof PsiClass + && !(parent.getParent() instanceof PsiAnonymousClass)) { // for changes inside method, rehighlight codeblock only // do not use this optimization for constructors and class initializers - to update non-initialized fields return parent; diff --git a/platform/lang-api/src/com/intellij/codeInsight/daemon/ChangeLocalityDetector.java b/platform/lang-api/src/com/intellij/codeInsight/daemon/ChangeLocalityDetector.java index b80a4b6fc9be..fedbd19f2f48 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/daemon/ChangeLocalityDetector.java +++ b/platform/lang-api/src/com/intellij/codeInsight/daemon/ChangeLocalityDetector.java @@ -20,9 +20,17 @@ package com.intellij.codeInsight.daemon; import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public interface ChangeLocalityDetector { + /** + * @param changedElement + * @return the psi element (ancestor of the changedElement) which should be re-highlighted, or null if unsure. + * e.g. in Java we re-highlight enclosing code block only when element inside has changed. + * Note: do not traverse PSI tree upwards here, + * since this ChangeLocalityDetector will be called for the changed element and all its parents anyway. + */ @Nullable - PsiElement getChangeHighlightingDirtyScopeFor(PsiElement changedElement); + PsiElement getChangeHighlightingDirtyScopeFor(@NotNull PsiElement changedElement); } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultChangeLocalityDetector.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultChangeLocalityDetector.java new file mode 100644 index 000000000000..a822811aa845 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DefaultChangeLocalityDetector.java @@ -0,0 +1,35 @@ +/* + * 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.codeInsight.daemon.impl; + +import com.intellij.codeInsight.daemon.ChangeLocalityDetector; +import com.intellij.codeInspection.SuppressionUtil; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import org.jetbrains.annotations.NotNull; + +public class DefaultChangeLocalityDetector implements ChangeLocalityDetector { + @Override + public PsiElement getChangeHighlightingDirtyScopeFor(@NotNull PsiElement changedElement) { + if (changedElement instanceof PsiWhiteSpace || + changedElement instanceof PsiComment + && !changedElement.getText().contains(SuppressionUtil.SUPPRESS_INSPECTIONS_TAG_NAME)) { + return changedElement; + } + return null; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PsiChangeHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PsiChangeHandler.java index f148691cd732..956f39823ba5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PsiChangeHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PsiChangeHandler.java @@ -17,7 +17,6 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeInsight.daemon.ChangeLocalityDetector; -import com.intellij.codeInspection.SuppressionUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; @@ -214,18 +213,9 @@ public class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable return; } - // optimization - if (whitespaceOptimizationAllowed && UpdateHighlightersUtil.isWhitespaceOptimizationAllowed(document)) { - if (child instanceof PsiWhiteSpace || - child instanceof PsiComment && !child.getText().contains(SuppressionUtil.SUPPRESS_INSPECTIONS_TAG_NAME)) { - myFileStatusMap.markFileScopeDirty(document, child.getTextRange(), fileLength); - return; - } - } - - PsiElement element = child; + PsiElement element = whitespaceOptimizationAllowed && UpdateHighlightersUtil.isWhitespaceOptimizationAllowed(document) ? child : child.getParent(); while (true) { - if (element instanceof PsiFile || element instanceof PsiDirectory) { + if (element == null || element instanceof PsiFile || element instanceof PsiDirectory) { myFileStatusMap.markAllFilesDirty(); return; } @@ -242,10 +232,18 @@ public class PsiChangeHandler extends PsiTreeChangeAdapter implements Disposable @Nullable private static PsiElement getChangeHighlightingScope(PsiElement element) { + DefaultChangeLocalityDetector defaultDetector = null; for (ChangeLocalityDetector detector : Extensions.getExtensions(EP_NAME)) { + if (detector instanceof DefaultChangeLocalityDetector) { + // run default detector last + assert defaultDetector == null : defaultDetector; + defaultDetector = (DefaultChangeLocalityDetector)detector; + continue; + } final PsiElement scope = detector.getChangeHighlightingDirtyScopeFor(element); if (scope != null) return scope; } - return null; + assert defaultDetector != null : "com.intellij.codeInsight.daemon.impl.DefaultChangeLocalityDetector is unregistered"; + return defaultDetector.getChangeHighlightingDirtyScopeFor(element); } } diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index 6465f1f168f9..834e6e03ed0c 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -404,7 +404,7 @@ - + + diff --git a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/XsltChangeLocalityDetector.java b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/XsltChangeLocalityDetector.java index c72f6ac58eb8..5b7dedcecae3 100644 --- a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/XsltChangeLocalityDetector.java +++ b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/impl/XsltChangeLocalityDetector.java @@ -22,6 +22,7 @@ import com.intellij.psi.xml.XmlElementType; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; import org.intellij.lang.xpath.xslt.XsltSupport; +import org.jetbrains.annotations.NotNull; /* * Created by IntelliJ IDEA. @@ -30,7 +31,7 @@ import org.intellij.lang.xpath.xslt.XsltSupport; */ public class XsltChangeLocalityDetector implements ChangeLocalityDetector { @Override - public PsiElement getChangeHighlightingDirtyScopeFor(PsiElement changedElement) { + public PsiElement getChangeHighlightingDirtyScopeFor(@NotNull PsiElement changedElement) { try { if (changedElement instanceof XmlToken && changedElement.getNode().getElementType() == XmlElementType.XML_ATTRIBUTE_VALUE_TOKEN) { final PsiElement grandParent = changedElement.getParent().getParent(); diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index cb3175edc39c..82372a8498d0 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -969,6 +969,7 @@ + diff --git a/xml/impl/src/com/intellij/xml/XmlChangeLocalityDetector.java b/xml/impl/src/com/intellij/xml/XmlChangeLocalityDetector.java new file mode 100644 index 000000000000..fb65aa55965d --- /dev/null +++ b/xml/impl/src/com/intellij/xml/XmlChangeLocalityDetector.java @@ -0,0 +1,36 @@ +/* + * 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.xml; + +import com.intellij.codeInsight.daemon.ChangeLocalityDetector; +import com.intellij.codeInspection.DefaultXmlSuppressionProvider; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +public class XmlChangeLocalityDetector implements ChangeLocalityDetector { + @Override + public PsiElement getChangeHighlightingDirtyScopeFor(@NotNull PsiElement changedElement) { + // rehighlight everything when inspection suppress comment changed + if (changedElement.getLanguage() instanceof XMLLanguage + && changedElement instanceof PsiComment + && changedElement.getText().contains(DefaultXmlSuppressionProvider.SUPPRESS_MARK)) { + return changedElement.getContainingFile(); + } + return null; + } +} diff --git a/xml/openapi/src/com/intellij/codeInspection/DefaultXmlSuppressionProvider.java b/xml/openapi/src/com/intellij/codeInspection/DefaultXmlSuppressionProvider.java index f1ce3969dfc2..6aa1a22ad909 100644 --- a/xml/openapi/src/com/intellij/codeInspection/DefaultXmlSuppressionProvider.java +++ b/xml/openapi/src/com/intellij/codeInspection/DefaultXmlSuppressionProvider.java @@ -37,16 +37,20 @@ import org.jetbrains.annotations.Nullable; */ public class DefaultXmlSuppressionProvider extends XmlSuppressionProvider { + public static final String SUPPRESS_MARK = "suppress"; + @Override public boolean isProviderAvailable(PsiFile file) { return true; } + @Override public boolean isSuppressedFor(PsiElement element, String inspectionId) { final XmlTag tag = element instanceof XmlFile ? ((XmlFile)element).getRootTag() : PsiTreeUtil.getContextOfType(element, XmlTag.class, false); return tag != null && findSuppression(tag, inspectionId, element) != null; } + @Override public void suppressForFile(PsiElement element, String inspectionId) { final PsiFile file = element.getContainingFile(); final XmlDocument document = ((XmlFile)file).getDocument(); @@ -55,6 +59,7 @@ public class DefaultXmlSuppressionProvider extends XmlSuppressionProvider { suppress(file, findFileSuppression(anchor, null, element), inspectionId, anchor.getTextRange().getStartOffset()); } + @Override public void suppressForTag(PsiElement element, String inspectionId) { final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); assert tag != null; @@ -140,7 +145,9 @@ public class DefaultXmlSuppressionProvider extends XmlSuppressionProvider { @NonNls protected String getPrefix() { - return "