From 1b278c1b68a3009086caf5cb72cbbb1a548cd30b Mon Sep 17 00:00:00 2001 From: Alexander Lobas Date: Tue, 29 May 2012 18:32:01 +0400 Subject: [PATCH] Inspections --- .../AndroidDesignerEditorPanel.java | 7 +- .../designer/inspection/ErrorAnalyzer.java | 162 ++++++++++++++++-- .../lint/AndroidLintInspectionBase.java | 7 +- .../AndroidLintInspectionToolProvider.java | 2 +- .../inspections/lint/AndroidLintQuickFix.java | 2 +- .../inspections/lint/AndroidLintUtil.java | 4 +- .../designSurface/QuickFixManager.java | 9 +- .../inspection/AbstractQuickFixManager.java | 109 ++++++++++-- .../designer/inspection/ErrorInfo.java | 79 ++++++++- ...rrorAnalyzerManager.java => QuickFix.java} | 21 ++- 10 files changed, 368 insertions(+), 34 deletions(-) rename plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/{ErrorAnalyzerManager.java => QuickFix.java} (65%) diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java index ef0fbde13b96..8ced2c5c0e5f 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java @@ -40,6 +40,7 @@ import com.intellij.designer.palette.Item; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.module.Module; +import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.text.StringUtil; @@ -163,6 +164,8 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel { removeNativeRoot(); myRootComponent = newRootComponent; + loadInspections(new EmptyProgressIndicator()); + updateInspections(); myLayeredPane.add(rootPanel, LAYER_COMPONENT); myParseTime = false; @@ -514,6 +517,8 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel { @Override public void loadInspections(ProgressIndicator progress) { - ErrorAnalyzer.load(myXmlFile, myRootComponent, progress); + if (myRootComponent != null) { + ErrorAnalyzer.load(myXmlFile, myRootComponent, progress); + } } } \ No newline at end of file diff --git a/plugins/android-designer/src/com/intellij/android/designer/inspection/ErrorAnalyzer.java b/plugins/android-designer/src/com/intellij/android/designer/inspection/ErrorAnalyzer.java index 5c29c2a1c2e5..94d6a491e10d 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/inspection/ErrorAnalyzer.java +++ b/plugins/android-designer/src/com/intellij/android/designer/inspection/ErrorAnalyzer.java @@ -15,29 +15,169 @@ */ package com.intellij.android.designer.inspection; +import com.android.tools.lint.detector.api.Issue; +import com.intellij.android.designer.model.RadViewComponent; +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.SuppressIntentionAction; +import com.intellij.designer.inspection.ErrorInfo; +import com.intellij.designer.inspection.QuickFix; import com.intellij.designer.model.RadComponent; +import com.intellij.designer.model.RadComponentVisitor; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; -import org.jetbrains.android.inspections.lint.AndroidLintExternalAnnotator; -import org.jetbrains.android.inspections.lint.ProblemData; -import org.jetbrains.android.inspections.lint.State; +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.android.inspections.lint.*; + +import java.util.List; /** * @author Alexander Lobas */ -public class ErrorAnalyzer { +public final class ErrorAnalyzer { public static void load(XmlFile xmlFile, RadComponent rootComponent, ProgressIndicator progress) { + ErrorInfo.clear(rootComponent); + AndroidLintExternalAnnotator annotator = new AndroidLintExternalAnnotator(); State state = annotator.collectionInformation(xmlFile); - if (state == null) { - System.out.println("==== No inspections(" + rootComponent + ") ===="); - } - else { + if (state != null) { state = annotator.doAnnotate(state); - System.out.println("==== Problems(" + rootComponent + ") ===="); - for (ProblemData problem : state.getProblems()) { - System.out.println(problem.getIssue() + " | " + problem.getMessage() + " | " + problem.getTextRange()); + for (ProblemData problemData : state.getProblems()) { + Issue issue = problemData.getIssue(); + String message = problemData.getMessage(); + + TextRange range = problemData.getTextRange(); + if (range.getStartOffset() == range.getEndOffset()) { + continue; + } + + Pair pair = AndroidLintUtil.getHighlighLevelAndInspection(issue, xmlFile); + if (pair == null) { + continue; + } + + AndroidLintInspectionBase inspection = pair.getFirst(); + + if (inspection != null) { + HighlightDisplayKey key = HighlightDisplayKey.find(inspection.getShortName()); + + if (key != null) { + PsiElement startElement = xmlFile.findElementAt(range.getStartOffset()); + PsiElement endElement = xmlFile.findElementAt(range.getEndOffset() - 1); + + if (startElement != null && endElement != null && !inspection.isSuppressedFor(startElement)) { + RadComponent component = findComponent(rootComponent, startElement); + ErrorInfo errorInfo = new ErrorInfo(message, null, pair.getSecond()); + ErrorInfo.add(component, errorInfo); + + List designerFixes = errorInfo.getQuickFixes(); + + for (AndroidLintQuickFix fix : inspection.public_getQuickFixes(message)) { + if (fix.isApplicable(startElement, endElement, false)) { + designerFixes.add(new QuickFix(fix.getName(), null) { + @Override + public void run() throws Exception { + System.out.println("00000000"); + // TODO: Auto-generated method stub + } + }); + } + } + + for (IntentionAction intention : inspection.getIntentions(startElement, endElement)) { + designerFixes.add(new QuickFix(intention.getText(), null) { + @Override + public void run() throws Exception { + System.out.println("1111111"); + // TODO: Auto-generated method stub + } + }); + } + + designerFixes.add(new QuickFix("Disable inspection", null) { + @Override + public void run() throws Exception { + System.out.println("22222"); + // TODO: Auto-generated method stub + } + }); + designerFixes.add(new QuickFix("Edit '" + inspection.getDisplayName() + "' inspection settings", null) { + @Override + public void run() throws Exception { + System.out.println("3333333"); + // TODO: Auto-generated method stub + } + }); + + SuppressIntentionAction[] suppressActions = inspection.getSuppressActions(startElement); + if (suppressActions != null) { + for (SuppressIntentionAction action : suppressActions) { + if (action.isAvailable(xmlFile.getProject(), null, startElement)) { + designerFixes.add(new QuickFix("Suppress: " + action.getText(), action.getIcon(0)) { + @Override + public void run() throws Exception { + System.out.println("4444444"); + // TODO: Auto-generated method stub + } + }); + } + } + } + } + } + } } } } + + private static RadComponent findComponent(RadComponent rootComponent, PsiElement element) { + Pair tagInfo = extractTag(element); + if (tagInfo.first == null) { + return rootComponent; + } + + final XmlTag tag = tagInfo.first; + final RadComponent[] result = new RadComponent[]{rootComponent}; + + rootComponent.accept(new RadComponentVisitor() { + @Override + public boolean visit(RadComponent component) { + if (tag == ((RadViewComponent)component).getTag()) { + result[0] = component; + return false; + } + return true; + } + + @Override + public void endVisit(RadComponent component) { + } + }, true); + + return result[0]; + } + + private static Pair extractTag(PsiElement element) { + XmlTag tag = null; + XmlAttribute attribute = null; + + while (element != null) { + if (element instanceof XmlAttribute) { + attribute = (XmlAttribute)element; + } + if (element instanceof XmlTag) { + tag = (XmlTag)element; + break; + } + + element = element.getParent(); + } + + return new Pair(tag, attribute); + } } \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java index f20b1acbdacf..4beaccefca87 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionBase.java @@ -70,7 +70,12 @@ public abstract class AndroidLintInspectionBase extends GlobalInspectionTool imp } @NotNull - protected IntentionAction[] getIntentions(@NotNull PsiElement startElement, @NotNull PsiElement endElement) { + public AndroidLintQuickFix[] public_getQuickFixes(@NotNull String message) { + return getQuickFixes(message); + } + + @NotNull + public IntentionAction[] getIntentions(@NotNull PsiElement startElement, @NotNull PsiElement endElement) { return IntentionAction.EMPTY_ARRAY; } diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java index a4fdf4c94596..855510204e91 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintInspectionToolProvider.java @@ -405,7 +405,7 @@ public class AndroidLintInspectionToolProvider implements InspectionToolProvider @NotNull @Override - protected IntentionAction[] getIntentions(@NotNull final PsiElement startElement, @NotNull PsiElement endElement) { + public IntentionAction[] getIntentions(@NotNull final PsiElement startElement, @NotNull PsiElement endElement) { return new IntentionAction[]{new AndroidAddStringResourceQuickFix(startElement)}; } } diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java index f029cb923057..a5e9396fee0f 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintQuickFix.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable; /** * @author Eugene.Kudelevsky */ -interface AndroidLintQuickFix { +public interface AndroidLintQuickFix { AndroidLintQuickFix[] EMPTY_ARRAY = new AndroidLintQuickFix[0]; void apply(@NotNull PsiElement startElement, @NotNull PsiElement endElement, @Nullable Editor editor); diff --git a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java index b305cf662e11..c7ce779882d2 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java +++ b/plugins/android/src/org/jetbrains/android/inspections/lint/AndroidLintUtil.java @@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable; /** * @author Eugene.Kudelevsky */ -class AndroidLintUtil { +public class AndroidLintUtil { @NonNls static final String ATTR_VALUE_VERTICAL = "vertical"; @NonNls static final String ATTR_VALUE_WRAP_CONTENT = "wrap_content"; @NonNls static final String ATTR_LAYOUT_HEIGHT = "layout_height"; @@ -25,7 +25,7 @@ class AndroidLintUtil { } @Nullable - static Pair getHighlighLevelAndInspection(@NotNull Issue issue, + public static Pair getHighlighLevelAndInspection(@NotNull Issue issue, @NotNull PsiElement context) { final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(issue); if (inspectionShortName == null) { diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/QuickFixManager.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/QuickFixManager.java index 1ffc96c79dc6..13391d476af9 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/QuickFixManager.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/designSurface/QuickFixManager.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.util.Collections; import java.util.List; /** @@ -41,8 +42,12 @@ public class QuickFixManager extends AbstractQuickFixManager implements Componen @NotNull @Override - protected ErrorInfo[] getErrorInfos() { - return new ErrorInfo[0]; // TODO: Auto-generated method stub + protected List getErrorInfos() { + List selection = myDesigner.getSurfaceArea().getSelection(); + if (selection.size() == 1) { + return ErrorInfo.get(selection.get(0)); + } + return Collections.emptyList(); } @Override diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/AbstractQuickFixManager.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/AbstractQuickFixManager.java index 7e51e86ff5c1..607e7373d749 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/AbstractQuickFixManager.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/AbstractQuickFixManager.java @@ -19,6 +19,10 @@ import com.intellij.designer.DesignerBundle; import com.intellij.designer.designSurface.DesignerEditorPanel; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.wm.impl.VisibilityWatcher; import com.intellij.ui.HintHint; @@ -26,6 +30,7 @@ import com.intellij.ui.LightweightHint; import com.intellij.ui.RowIcon; import com.intellij.util.Alarm; import com.intellij.util.IJSwingUtilities; +import com.intellij.util.ThrowableRunnable; import com.intellij.util.ui.EmptyIcon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,6 +44,7 @@ import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.List; /** * @author Alexander Lobas @@ -131,20 +137,14 @@ public abstract class AbstractQuickFixManager { updateHintVisibility(); } else { - ErrorInfo[] errorInfos = getErrorInfos(); Rectangle bounds = getErrorBounds(); - if (!haveFixes(errorInfos) || bounds == null || !bounds.equals(myLastHintBounds)) { + if (!ErrorInfo.haveFixes(getErrorInfos()) || bounds == null || !bounds.equals(myLastHintBounds)) { hideHint(); updateHintVisibility(); } } } - private static boolean haveFixes(ErrorInfo[] errorInfos) { - // XXX - return true; - } - private void showHint() { if (!myComponent.isShowing() || !IJSwingUtilities.hasFocus(myComponent)) { hideHint(); @@ -155,8 +155,7 @@ public abstract class AbstractQuickFixManager { hideHint(); // 2. Found error (if any) - ErrorInfo[] errorInfos = getErrorInfos(); - if (!haveFixes(errorInfos)) { + if (!ErrorInfo.haveFixes(getErrorInfos())) { hideHint(); return; } @@ -174,10 +173,20 @@ public abstract class AbstractQuickFixManager { } private void showPopup() { - // TODO: Auto-generated method stub + if (myHint == null || !myHint.isVisible()) { + return; + } + + List errorInfos = getErrorInfos(); + if (!ErrorInfo.haveFixes(errorInfos)) { + return; + } + + ListPopup popup = JBPopupFactory.getInstance().createListPopup(new FirstStep(errorInfos)); + popup.showUnderneathOf(myHint.getComponent()); } - protected void hideHint() { + public final void hideHint() { myAlarm.cancelAllRequests(); if (myHint != null && myHint.isVisible()) { myHint.hide(); @@ -186,7 +195,7 @@ public abstract class AbstractQuickFixManager { } } - protected void updateHintVisibility() { + protected final void updateHintVisibility() { myAlarm.cancelAllRequests(); myAlarm.addRequest(myShowHintRequest, 500); } @@ -215,7 +224,7 @@ public abstract class AbstractQuickFixManager { * @return error info for the current {@link #myComponent} state. */ @NotNull - protected abstract ErrorInfo[] getErrorInfos(); + protected abstract List getErrorInfos(); /** * @return rectangle (in {@link #myComponent} coordinates) that represents @@ -226,11 +235,85 @@ public abstract class AbstractQuickFixManager { @Nullable protected abstract Rectangle getErrorBounds(); + ////////////////////////////////////////////////////////////////////////////////////////// + // + // + // + ////////////////////////////////////////////////////////////////////////////////////////// + + private class FirstStep extends BaseListPopupStep { + public FirstStep(List errorInfos) { + super(null, errorInfos); + } + + @Override + public Icon getIconFor(ErrorInfo value) { + return INTENTION_ICON; + } + + @NotNull + @Override + public String getTextFor(ErrorInfo value) { + return value.getName(); + } + + @Override + public PopupStep onChosen(ErrorInfo value, boolean finalChoice) { + List quickFixes = value.getQuickFixes(); + if (finalChoice) { + return doFinalStep(getQuickFixRunnable(quickFixes.get(0))); + } + return new SecondStep(quickFixes); + } + + @Override + public boolean hasSubstep(ErrorInfo selectedValue) { + return true; + } + } + + private class SecondStep extends BaseListPopupStep { + public SecondStep(List fixList) { + super(null, fixList); + } + + @Override + public Icon getIconFor(QuickFix value) { + return value.getIcon(); + } + + @NotNull + @Override + public String getTextFor(QuickFix value) { + return value.getName(); + } + + @Override + public PopupStep onChosen(QuickFix value, boolean finalChoice) { + return doFinalStep(getQuickFixRunnable(value)); + } + } + + private Runnable getQuickFixRunnable(final QuickFix value) { + return new Runnable() { + @Override + public void run() { + myDesigner.getToolProvider().execute(new ThrowableRunnable() { + @Override + public void run() throws Exception { + value.run(); + } + }, "Run '" + value.getName() + "' QuickFix", true); + } + }; + } + private static final Border INACTIVE_BORDER = BorderFactory.createEmptyBorder(4, 4, 4, 4); private static final Border ACTIVE_BORDER = BorderFactory .createCompoundBorder(BorderFactory.createLineBorder(Color.orange, 2), BorderFactory.createEmptyBorder(2, 2, 2, 2)); + private static final Icon INTENTION_ICON = IconLoader.getIcon("/actions/realIntentionBulb.png"); private static final Icon ARROW_ICON = IconLoader.getIcon("/general/arrowDown.png"); private static final Icon INACTIVE_ARROW_ICON = new EmptyIcon(ARROW_ICON.getIconWidth(), ARROW_ICON.getIconHeight()); private static final Icon ICON = IconLoader.findIcon("/actions/intentionBulb.png"); diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorInfo.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorInfo.java index 43c3a4fa4a2e..9054a3f4d8fe 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorInfo.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorInfo.java @@ -15,8 +15,85 @@ */ package com.intellij.designer.inspection; +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.designer.model.RadComponent; +import com.intellij.designer.model.RadComponentVisitor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * @author Alexander Lobas */ -public class ErrorInfo { +public final class ErrorInfo { + private static final String KEY = "Inspection.Errors"; + + private final String myName; + private final String myPropertyName; + private final HighlightDisplayLevel myLevel; + private final List myQuickFixes = new ArrayList(); + + public ErrorInfo(@NotNull String name, @Nullable String propertyName, @NotNull HighlightDisplayLevel level) { + myName = name; + myPropertyName = propertyName; + myLevel = level; + } + + public String getName() { + return myName; + } + + public HighlightDisplayLevel getLevel() { + return myLevel; + } + + @Nullable + public String getPropertyName() { + return myPropertyName; + } + + public List getQuickFixes() { + return myQuickFixes; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // + // Utils + // + ////////////////////////////////////////////////////////////////////////////////////////// + + public static boolean haveFixes(List errorInfos) { + for (ErrorInfo errorInfo : errorInfos) { + if (!errorInfo.getQuickFixes().isEmpty()) { + return true; + } + } + return false; + } + + public static List get(RadComponent component) { + List errorInfos = component.getClientProperty(KEY); + return errorInfos == null ? Collections.emptyList() : errorInfos; + } + + public static void add(RadComponent component, ErrorInfo errorInfo) { + List errorInfos = component.getClientProperty(KEY); + if (errorInfos == null) { + errorInfos = new ArrayList(); + component.setClientProperty(KEY, errorInfos); + } + errorInfos.add(errorInfo); + } + + public static void clear(RadComponent component) { + component.accept(new RadComponentVisitor() { + @Override + public void endVisit(RadComponent component) { + component.extractClientProperty(KEY); + } + }, true); + } } \ No newline at end of file diff --git a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorAnalyzerManager.java b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/QuickFix.java similarity index 65% rename from plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorAnalyzerManager.java rename to plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/QuickFix.java index 1fe70c2fb881..90b83cbcd0e4 100644 --- a/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/ErrorAnalyzerManager.java +++ b/plugins/ui-designer/ui-designer-new/src/com/intellij/designer/inspection/QuickFix.java @@ -15,8 +15,27 @@ */ package com.intellij.designer.inspection; +import javax.swing.*; + /** * @author Alexander Lobas */ -public final class ErrorAnalyzerManager { +public abstract class QuickFix { + private final String myName; + private final Icon myIcon; + + public QuickFix(String name, Icon icon) { + myName = name; + myIcon = icon; + } + + public final String getName() { + return myName; + } + + public final Icon getIcon() { + return myIcon; + } + + public abstract void run() throws Exception; } \ No newline at end of file