Inspections

This commit is contained in:
Alexander Lobas
2012-05-29 18:32:01 +04:00
parent 515443ae41
commit 1b278c1b68
10 changed files with 368 additions and 34 deletions
@@ -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);
}
}
}
@@ -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<AndroidLintInspectionBase, HighlightDisplayLevel> 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<QuickFix> 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<XmlTag, XmlAttribute> 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<XmlTag, XmlAttribute> 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<XmlTag, XmlAttribute>(tag, attribute);
}
}
@@ -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;
}
@@ -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)};
}
}
@@ -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);
@@ -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<AndroidLintInspectionBase, HighlightDisplayLevel> getHighlighLevelAndInspection(@NotNull Issue issue,
public static Pair<AndroidLintInspectionBase, HighlightDisplayLevel> getHighlighLevelAndInspection(@NotNull Issue issue,
@NotNull PsiElement context) {
final String inspectionShortName = AndroidLintInspectionBase.getInspectionShortNameByIssue(issue);
if (inspectionShortName == null) {
@@ -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<ErrorInfo> getErrorInfos() {
List<RadComponent> selection = myDesigner.getSurfaceArea().getSelection();
if (selection.size() == 1) {
return ErrorInfo.get(selection.get(0));
}
return Collections.emptyList();
}
@Override
@@ -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<ErrorInfo> 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<ErrorInfo> 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<ErrorInfo> {
public FirstStep(List<ErrorInfo> 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<QuickFix> 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<QuickFix> {
public SecondStep(List<QuickFix> 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<Exception>() {
@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");
@@ -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<QuickFix> myQuickFixes = new ArrayList<QuickFix>();
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<QuickFix> getQuickFixes() {
return myQuickFixes;
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// Utils
//
//////////////////////////////////////////////////////////////////////////////////////////
public static boolean haveFixes(List<ErrorInfo> errorInfos) {
for (ErrorInfo errorInfo : errorInfos) {
if (!errorInfo.getQuickFixes().isEmpty()) {
return true;
}
}
return false;
}
public static List<ErrorInfo> get(RadComponent component) {
List<ErrorInfo> errorInfos = component.getClientProperty(KEY);
return errorInfos == null ? Collections.<ErrorInfo>emptyList() : errorInfos;
}
public static void add(RadComponent component, ErrorInfo errorInfo) {
List<ErrorInfo> errorInfos = component.getClientProperty(KEY);
if (errorInfos == null) {
errorInfos = new ArrayList<ErrorInfo>();
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);
}
}
@@ -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;
}