OSDN Git Service

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