OSDN Git Service

162e089c49596fdfa0acb2b1a16080621a045230
[android-x86/sdk.git] / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / lint / LintFixGenerator.java
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.lint;
17
18 import com.android.ide.eclipse.adt.AdtConstants;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AdtUtils;
21 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
23 import com.android.tools.lint.detector.api.Issue;
24
25 import org.eclipse.core.resources.IFile;
26 import org.eclipse.core.resources.IMarker;
27 import org.eclipse.core.resources.IResource;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.jface.dialogs.MessageDialog;
30 import org.eclipse.jface.preference.IPreferenceStore;
31 import org.eclipse.jface.text.IDocument;
32 import org.eclipse.jface.text.contentassist.ICompletionProposal;
33 import org.eclipse.jface.text.contentassist.IContextInformation;
34 import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
35 import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
36 import org.eclipse.jface.text.source.Annotation;
37 import org.eclipse.jface.text.source.ISourceViewer;
38 import org.eclipse.swt.graphics.Image;
39 import org.eclipse.swt.graphics.Point;
40 import org.eclipse.ui.IEditorPart;
41 import org.eclipse.ui.IMarkerResolution;
42 import org.eclipse.ui.IMarkerResolution2;
43 import org.eclipse.ui.IMarkerResolutionGenerator2;
44 import org.eclipse.ui.ISharedImages;
45 import org.eclipse.ui.PlatformUI;
46
47 import java.util.ArrayList;
48 import java.util.List;
49
50 /**
51  * A quickfix and marker resolution for disabling lint checks, and any
52  * IDE specific implementations for fixing the warnings.
53  * <p>
54  * I would really like for this quickfix to show up as a light bulb on top of the error
55  * icon in the editor, and I've spent a whole day trying to make it work. I did not
56  * succeed, but here are the steps I tried in case I want to pick up the work again
57  * later:
58  * <ul>
59  * <li>
60  *     The WST has some support for quick fixes, and I came across some forum posts
61  *     referencing the ability to show light bulbs. However, it turns out that the
62  *     quickfix support for annotations in WST is hardcoded to source validation
63  *     errors *only*.
64  * <li>
65  *     I tried defining my own editor annotations, and customizing the icon directly
66  *     by either setting an icon or using the image provider. This works fine
67  *     if I make my marker be a new independent marker type. However, whenever I
68  *     switch the marker type back to extend the "Problem" type, then the icon reverts
69  *     back to the standard error icon and it ignores my custom settings.
70  *     And if I switch away from the Problems marker type, then the errors no longer
71  *     show up in the Problems view. (I also tried extending the JDT marker but that
72  *     still didn't work.)
73  * <li>
74  *     It looks like only JDT handles quickfix icons. It has a bunch of custom code
75  *     to handle this, along with its own Annotation subclass used by the editor.
76  *     I tried duplicating some of this by subclassing StructuredTextEditor, but
77  *     it was evident that I'd have to pull in a *huge* amount of duplicated code to
78  *     make this work, which seems risky given that all this is internal code that
79  *     can change from one Eclipse version to the next.
80  * </ul>
81  * It looks like our best bet would be to reconsider whether these should show up
82  * in the Problems view; perhaps we should use a custom view for these. That would also
83  * make marker management more obvious.
84  */
85 public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
86     /** Constructs a new {@link LintFixGenerator} */
87     public LintFixGenerator() {
88     }
89
90     // ---- Implements IMarkerResolutionGenerator2 ----
91
92     public boolean hasResolutions(IMarker marker) {
93         try {
94             assert marker.getType().equals(AdtConstants.MARKER_LINT);
95         } catch (CoreException e) {
96         }
97
98         return true;
99     }
100
101     public IMarkerResolution[] getResolutions(IMarker marker) {
102         String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
103                 ""); //$NON-NLS-1$
104         IResource resource = marker.getResource();
105         return new IMarkerResolution[] {
106                 new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)),
107                 new SuppressProposal(id, true /* all */),
108                 // Not yet implemented
109                 //new SuppressProposal(id, false),
110                 new ClearMarkersProposal(resource, false /* all */),
111                 new ClearMarkersProposal(resource, true /* all */),
112         };
113     }
114
115     // ---- Implements IQuickAssistProcessor ----
116
117     public String getErrorMessage() {
118         return "Disable Lint Error";
119     }
120
121     public boolean canFix(Annotation annotation) {
122         return true;
123     }
124
125     public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
126         return true;
127     }
128
129     public ICompletionProposal[] computeQuickAssistProposals(
130             IQuickAssistInvocationContext invocationContext) {
131         ISourceViewer sourceViewer = invocationContext.getSourceViewer();
132         AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer);
133         if (editor != null) {
134             IFile file = editor.getInputFile();
135             IDocument document = sourceViewer.getDocument();
136             List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_LINT,
137                     file, document, invocationContext.getOffset());
138             List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
139             if (markers.size() > 0) {
140                 for (IMarker marker : markers) {
141                     String id = marker.getAttribute(LintRunner.MARKER_CHECKID_PROPERTY,
142                             ""); //$NON-NLS-1$
143                     // TODO: Allow for more than one fix?
144                     ICompletionProposal fix = LintFix.getFix(id, marker);
145                     if (fix != null) {
146                         proposals.add(fix);
147                     }
148
149                     String message = marker.getAttribute(IMarker.MESSAGE, null);
150                     proposals.add(new MoreInfoProposal(id, message));
151
152                     proposals.add(new SuppressProposal(id, true /* all */));
153                     // Not yet implemented
154                     //proposals.add(new SuppressProposal(id, false));
155
156                     proposals.add(new ClearMarkersProposal(file, false /* all */));
157                     proposals.add(new ClearMarkersProposal(file, true /* all */));
158                 }
159             }
160             if (proposals.size() > 0) {
161                 return proposals.toArray(new ICompletionProposal[proposals.size()]);
162             }
163         }
164
165         return null;
166     }
167
168     /**
169      * Suppress the given detector, and rerun the checks on the file
170      *
171      * @param id the id of the detector to be suppressed
172      */
173     public static void suppressDetector(String id) {
174         // Excluded checks
175         IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
176         String value = store.getString(AdtPrefs.PREFS_DISABLED_ISSUES);
177         assert value == null || !value.contains(id);
178         if (value == null || value.length() == 0) {
179             value = id;
180         } else {
181             value = value + ',' + id;
182         }
183         store.setValue(AdtPrefs.PREFS_DISABLED_ISSUES, value);
184
185         // Rerun analysis on the current file to remove this and related markers.
186         // TODO: if mGlobal, rerun on whole project?
187         IEditorPart activeEditor = AdtUtils.getActiveEditor();
188         if (activeEditor instanceof AndroidXmlEditor) {
189             AndroidXmlEditor editor = (AndroidXmlEditor) activeEditor;
190             LintRunner.startLint(editor.getInputFile(), editor.getStructuredDocument());
191         }
192     }
193
194     private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 {
195         private final String mId;
196         private final boolean mGlobal;
197
198         public SuppressProposal(String check, boolean global) {
199             super();
200             mId = check;
201             mGlobal = global;
202         }
203
204         private void perform() {
205             suppressDetector(mId);
206         }
207
208         public String getDisplayString() {
209             return mGlobal ? "Disable Check" : "Disable Check in this file only";
210         }
211
212         // ---- Implements MarkerResolution2 ----
213
214         public String getLabel() {
215             return getDisplayString();
216         }
217
218         public void run(IMarker marker) {
219             perform();
220         }
221
222         public String getDescription() {
223             return getAdditionalProposalInfo();
224         }
225
226         // ---- Implements ICompletionProposal ----
227
228         public void apply(IDocument document) {
229             perform();
230         }
231
232         public Point getSelection(IDocument document) {
233             return null;
234         }
235
236         public String getAdditionalProposalInfo() {
237             StringBuilder sb = new StringBuilder(200);
238             if (mGlobal) {
239                 sb.append("Suppresses this type of lint warning in all files.");
240             } else {
241                 sb.append("Suppresses this type of lint warning in the current file only.");
242             }
243             sb.append("<br><br>"); //$NON-NLS-1$
244             sb.append("You can re-enable checks from the \"Android > Lint Error Checking\" preference page.");
245
246             return sb.toString();
247         }
248
249         public Image getImage() {
250             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
251             return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
252         }
253
254         public IContextInformation getContextInformation() {
255             return null;
256         }
257     }
258
259     private static class ClearMarkersProposal implements ICompletionProposal, IMarkerResolution2 {
260         private final boolean mGlobal;
261         private final IResource mResource;
262
263         public ClearMarkersProposal(IResource resource, boolean global) {
264             mResource = resource;
265             mGlobal = global;
266         }
267
268         private void perform() {
269             IResource resource = mGlobal ? mResource.getProject() : mResource;
270             LintEclipseContext.clearMarkers(resource);
271         }
272
273         public String getDisplayString() {
274             return mGlobal ? "Clear All Lint Markers" : "Clear Markers in This File Only";
275         }
276
277         // ---- Implements MarkerResolution2 ----
278
279         public String getLabel() {
280             return getDisplayString();
281         }
282
283         public void run(IMarker marker) {
284             perform();
285         }
286
287         public String getDescription() {
288             return getAdditionalProposalInfo();
289         }
290
291         // ---- Implements ICompletionProposal ----
292
293         public void apply(IDocument document) {
294             perform();
295         }
296
297         public Point getSelection(IDocument document) {
298             return null;
299         }
300
301         public String getAdditionalProposalInfo() {
302             StringBuilder sb = new StringBuilder(200);
303             if (mGlobal) {
304                 sb.append("Clears all lint warning markers from the project.");
305             } else {
306                 sb.append("Clears all lint warnings from this file.");
307             }
308             sb.append("<br><br>"); //$NON-NLS-1$
309             sb.append("This temporarily hides the problem, but does not suppress it. " +
310                     "Running Lint again can bring the error back.");
311             if (AdtPrefs.getPrefs().isLintOnSave()) {
312                 sb.append(' ');
313                 sb.append("This will happen the next time the file is saved since lint-on-save " +
314                         "is enabled. You can turn this off in the \"Lint Error Checking\" " +
315                         "preference page.");
316             }
317
318             return sb.toString();
319         }
320
321         public Image getImage() {
322             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
323             return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
324         }
325
326         public IContextInformation getContextInformation() {
327             return null;
328         }
329     }
330
331     private static class MoreInfoProposal implements ICompletionProposal, IMarkerResolution2 {
332         private final String mId;
333         private final String mMessage;
334
335         public MoreInfoProposal(String id, String message) {
336             mId = id;
337             mMessage = message;
338         }
339
340         private void perform() {
341             Issue issue = LintEclipseContext.getRegistry().getIssue(mId);
342             assert issue != null : mId;
343
344             StringBuilder sb = new StringBuilder(300);
345             sb.append(mMessage);
346             sb.append('\n').append('\n');
347             sb.append("Issue Explanation:");
348             sb.append('\n');
349             if (issue.getExplanation() != null) {
350                 sb.append('\n');
351                 sb.append(issue.getExplanation());
352             } else {
353                 sb.append(issue.getDescription());
354             }
355
356             if (issue.getMoreInfo() != null) {
357                 sb.append('\n').append('\n');
358                 sb.append("More Information: ");
359                 sb.append(issue.getMoreInfo());
360             }
361
362             MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(), "More Info",
363                     sb.toString());
364         }
365
366         public String getDisplayString() {
367             return "Explain Issue";
368         }
369
370         // ---- Implements MarkerResolution2 ----
371
372         public String getLabel() {
373             return getDisplayString();
374         }
375
376         public void run(IMarker marker) {
377             perform();
378         }
379
380         public String getDescription() {
381             return getAdditionalProposalInfo();
382         }
383
384         // ---- Implements ICompletionProposal ----
385
386         public void apply(IDocument document) {
387             perform();
388         }
389
390         public Point getSelection(IDocument document) {
391             return null;
392         }
393
394         public String getAdditionalProposalInfo() {
395             return "Provides more information about this issue";
396         }
397
398         public Image getImage() {
399             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
400             return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
401         }
402
403         public IContextInformation getContextInformation() {
404             return null;
405         }
406     }
407
408 }