diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java new file mode 100644 index 000000000..90b956e32 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java @@ -0,0 +1,657 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.android.ide.eclipse.adt.internal.lint; + +import static com.android.SdkConstants.DOT_JAVA; +import static com.android.SdkConstants.DOT_XML; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.preferences.LintPreferencePage; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.tools.lint.detector.api.LintUtils; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.IJobChangeListener; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.preference.IPreferenceNode; +import org.eclipse.jface.preference.PreferenceDialog; +import org.eclipse.jface.preference.PreferenceManager; +import org.eclipse.jface.preference.PreferenceNode; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.part.ViewPart; +import org.eclipse.ui.texteditor.IDocumentProvider; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Eclipse View which shows lint warnings for the current project + */ +public class LintViewPart extends ViewPart implements SelectionListener, IJobChangeListener { + /** The view id for this view part */ + public static final String ID = "com.android.ide.eclipse.adt.internal.lint.LintViewPart"; //$NON-NLS-1$ + private static final String QUICKFIX_DISABLED_ICON = "quickfix-disabled"; //$NON-NLS-1$ + private static final String QUICKFIX_ICON = "quickfix"; //$NON-NLS-1$ + private static final String REFRESH_ICON = "refresh"; //$NON-NLS-1$ + private static final String EXPAND_DISABLED_ICON = "expandall-disabled"; //$NON-NLS-1$ + private static final String EXPAND_ICON = "expandall"; //$NON-NLS-1$ + private static final String COLUMNS_ICON = "columns"; //$NON-NLS-1$ + private static final String OPTIONS_ICON = "options"; //$NON-NLS-1$ + private static final String IGNORE_THIS_ICON = "ignore-this"; //$NON-NLS-1$ + private static final String IGNORE_THIS_DISABLED_ICON = "ignore-this-disabled"; //$NON-NLS-1$ + private static final String IGNORE_FILE_ICON = "ignore-file"; //$NON-NLS-1$ + private static final String IGNORE_FILE_DISABLED_ICON = "ignore-file-disabled"; //$NON-NLS-1$ + private static final String IGNORE_PRJ_ICON = "ignore-project"; //$NON-NLS-1$ + private static final String IGNORE_PRJ_DISABLED_ICON = "ignore-project-disabled"; //$NON-NLS-1$ + private static final String IGNORE_ALL_ICON = "ignore-all"; //$NON-NLS-1$ + private static final String IGNORE_ALL_DISABLED_ICON = "ignore-all-disabled"; //$NON-NLS-1$ + private IMemento mMemento; + private LintList mLintView; + private Text mDetailsText; + private Label mErrorLabel; + private SashForm mSashForm; + private Action mFixAction; + private Action mRemoveAction; + private Action mIgnoreAction; + private Action mAlwaysIgnoreAction; + private Action mIgnoreFileAction; + private Action mIgnoreProjectAction; + private Action mRemoveAllAction; + private Action mRefreshAction; + private Action mExpandAll; + private Action mCollapseAll; + private Action mConfigureColumns; + private Action mOptions; + + /** + * Initial projects to show: this field is only briefly not null during the + * construction initiated by {@link #show(List)} + */ + private static List<? extends IResource> sInitialResources; + + /** + * Constructs a new {@link LintViewPart} + */ + public LintViewPart() { + } + + @Override + public void init(IViewSite site, IMemento memento) throws PartInitException { + super.init(site, memento); + mMemento = memento; + } + + @Override + public void saveState(IMemento memento) { + super.saveState(memento); + + mLintView.saveState(memento); + } + + @Override + public void dispose() { + if (mLintView != null) { + mLintView.dispose(); + mLintView = null; + } + super.dispose(); + } + + @Override + public void createPartControl(Composite parent) { + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.verticalSpacing = 0; + gridLayout.marginWidth = 0; + gridLayout.marginHeight = 0; + parent.setLayout(gridLayout); + + mErrorLabel = new Label(parent, SWT.NONE); + mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + + mSashForm = new SashForm(parent, SWT.NONE); + mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + mLintView = new LintList(getSite(), mSashForm, mMemento, false /*singleFile*/); + + mDetailsText = new Text(mSashForm, + SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL | SWT.MULTI); + Display display = parent.getDisplay(); + mDetailsText.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + mDetailsText.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + + mLintView.addSelectionListener(this); + mSashForm.setWeights(new int[] {8, 2}); + + createActions(); + initializeToolBar(); + + // If there are currently running jobs, listen for them such that we can update the + // button state + refreshStopIcon(); + + if (sInitialResources != null) { + mLintView.setResources(sInitialResources); + sInitialResources = null; + } else { + // No supplied context: show lint warnings for all projects + IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null); + if (androidProjects.length > 0) { + List<IResource> projects = new ArrayList<IResource>(); + for (IJavaProject project : androidProjects) { + projects.add(project.getProject()); + } + mLintView.setResources(projects); + } + } + + updateIssueCount(); + } + + /** + * Create the actions. + */ + private void createActions() { + ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); + IconFactory iconFactory = IconFactory.getInstance(); + mFixAction = new LintViewAction("Fix", ACTION_FIX, + iconFactory.getImageDescriptor(QUICKFIX_ICON), + iconFactory.getImageDescriptor(QUICKFIX_DISABLED_ICON)); + + mIgnoreAction = new LintViewAction("Suppress this error with an annotation/attribute", + ACTION_IGNORE_THIS, + iconFactory.getImageDescriptor(IGNORE_THIS_ICON), + iconFactory.getImageDescriptor(IGNORE_THIS_DISABLED_ICON)); + mIgnoreFileAction = new LintViewAction("Ignore in this file", ACTION_IGNORE_FILE, + iconFactory.getImageDescriptor(IGNORE_FILE_ICON), + iconFactory.getImageDescriptor(IGNORE_FILE_DISABLED_ICON)); + mIgnoreProjectAction = new LintViewAction("Ignore in this project", ACTION_IGNORE_TYPE, + iconFactory.getImageDescriptor(IGNORE_PRJ_ICON), + iconFactory.getImageDescriptor(IGNORE_PRJ_DISABLED_ICON)); + mAlwaysIgnoreAction = new LintViewAction("Always Ignore", ACTION_IGNORE_ALL, + iconFactory.getImageDescriptor(IGNORE_ALL_ICON), + iconFactory.getImageDescriptor(IGNORE_ALL_DISABLED_ICON)); + + mRemoveAction = new LintViewAction("Remove", ACTION_REMOVE, + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVE), + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVE_DISABLED)); + mRemoveAllAction = new LintViewAction("Remove All", ACTION_REMOVE_ALL, + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVEALL), + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVEALL_DISABLED)); + mRefreshAction = new LintViewAction("Refresh (& Save Files)", ACTION_REFRESH, + iconFactory.getImageDescriptor(REFRESH_ICON), null); + mRemoveAllAction.setEnabled(true); + mCollapseAll = new LintViewAction("Collapse All", ACTION_COLLAPSE, + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL), + sharedImages.getImageDescriptor(ISharedImages.IMG_ELCL_COLLAPSEALL_DISABLED)); + mCollapseAll.setEnabled(true); + mExpandAll = new LintViewAction("Expand All", ACTION_EXPAND, + iconFactory.getImageDescriptor(EXPAND_ICON), + iconFactory.getImageDescriptor(EXPAND_DISABLED_ICON)); + mExpandAll.setEnabled(true); + + mConfigureColumns = new LintViewAction("Configure Columns...", ACTION_COLUMNS, + iconFactory.getImageDescriptor(COLUMNS_ICON), + null); + + mOptions = new LintViewAction("Options...", ACTION_OPTIONS, + iconFactory.getImageDescriptor(OPTIONS_ICON), + null); + + enableActions(Collections.<IMarker>emptyList(), false /*updateWidgets*/); + } + + /** + * Initialize the toolbar. + */ + private void initializeToolBar() { + IToolBarManager toolbarManager = getViewSite().getActionBars().getToolBarManager(); + toolbarManager.add(mRefreshAction); + toolbarManager.add(mFixAction); + toolbarManager.add(mIgnoreAction); + toolbarManager.add(mIgnoreFileAction); + toolbarManager.add(mIgnoreProjectAction); + toolbarManager.add(mAlwaysIgnoreAction); + toolbarManager.add(new Separator()); + toolbarManager.add(mRemoveAction); + toolbarManager.add(mRemoveAllAction); + toolbarManager.add(new Separator()); + toolbarManager.add(mExpandAll); + toolbarManager.add(mCollapseAll); + toolbarManager.add(mConfigureColumns); + toolbarManager.add(mOptions); + } + + @Override + public void setFocus() { + mLintView.setFocus(); + } + + /** + * Sets the resource associated with the lint view + * + * @param resources the associated resources + */ + public void setResources(List<? extends IResource> resources) { + mLintView.setResources(resources); + + // Refresh the stop/refresh icon status + refreshStopIcon(); + } + + private void refreshStopIcon() { + Job[] currentJobs = LintJob.getCurrentJobs(); + if (currentJobs.length > 0) { + ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); + mRefreshAction.setImageDescriptor(sharedImages.getImageDescriptor( + ISharedImages.IMG_ELCL_STOP)); + for (Job job : currentJobs) { + job.addJobChangeListener(this); + } + } else { + mRefreshAction.setImageDescriptor( + IconFactory.getInstance().getImageDescriptor(REFRESH_ICON)); + + } + } + + // ---- Implements SelectionListener ---- + + @Override + public void widgetSelected(SelectionEvent e) { + List<IMarker> markers = mLintView.getSelectedMarkers(); + if (markers.size() != 1) { + mDetailsText.setText(""); //$NON-NLS-1$ + } else { + mDetailsText.setText(EclipseLintClient.describe(markers.get(0))); + } + + IStatusLineManager status = getViewSite().getActionBars().getStatusLineManager(); + status.setMessage(mDetailsText.getText()); + + updateIssueCount(); + + enableActions(markers, true /* updateWidgets */); + } + + private void enableActions(List<IMarker> markers, boolean updateWidgets) { + // Update enabled state of actions + boolean hasSelection = markers.size() > 0; + boolean canFix = hasSelection; + for (IMarker marker : markers) { + if (!LintFix.hasFix(EclipseLintClient.getId(marker))) { + canFix = false; + break; + } + + // Some fixes cannot be run in bulk + if (markers.size() > 1) { + List<LintFix> fixes = LintFix.getFixes(EclipseLintClient.getId(marker), marker); + if (fixes == null || !fixes.get(0).isBulkCapable()) { + canFix = false; + break; + } + } + } + + boolean haveFile = false; + boolean isJavaOrXml = true; + for (IMarker marker : markers) { + IResource resource = marker.getResource(); + if (resource instanceof IFile || resource instanceof IFolder) { + haveFile = true; + String name = resource.getName(); + if (!LintUtils.endsWith(name, DOT_XML) && !LintUtils.endsWith(name, DOT_JAVA)) { + isJavaOrXml = false; + } + break; + } + } + + mFixAction.setEnabled(canFix); + mIgnoreAction.setEnabled(hasSelection && haveFile && isJavaOrXml); + mIgnoreFileAction.setEnabled(hasSelection && haveFile); + mIgnoreProjectAction.setEnabled(hasSelection); + mAlwaysIgnoreAction.setEnabled(hasSelection); + mRemoveAction.setEnabled(hasSelection); + + if (updateWidgets) { + getViewSite().getActionBars().getToolBarManager().update(false); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + Object source = e.getSource(); + if (source == mLintView.getTreeViewer().getControl()) { + // Jump to editor + List<IMarker> selection = mLintView.getSelectedMarkers(); + if (selection.size() > 0) { + EclipseLintClient.showMarker(selection.get(0)); + } + } + } + + // --- Implements IJobChangeListener ---- + + @Override + public void done(IJobChangeEvent event) { + mRefreshAction.setImageDescriptor( + IconFactory.getInstance().getImageDescriptor(REFRESH_ICON)); + + if (!mLintView.isDisposed()) { + mLintView.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (!mLintView.isDisposed()) { + updateIssueCount(); + } + } + }); + } + } + + private void updateIssueCount() { + int errors = mLintView.getErrorCount(); + int warnings = mLintView.getWarningCount(); + mErrorLabel.setText(String.format("%1$d errors, %2$d warnings", errors, warnings)); + } + + @Override + public void aboutToRun(IJobChangeEvent event) { + } + + @Override + public void awake(IJobChangeEvent event) { + } + + @Override + public void running(IJobChangeEvent event) { + } + + @Override + public void scheduled(IJobChangeEvent event) { + } + + @Override + public void sleeping(IJobChangeEvent event) { + } + + // ---- Actions ---- + + private static final int ACTION_REFRESH = 1; + private static final int ACTION_FIX = 2; + private static final int ACTION_IGNORE_THIS = 3; + private static final int ACTION_IGNORE_FILE = 4; + private static final int ACTION_IGNORE_TYPE = 5; + private static final int ACTION_IGNORE_ALL = 6; + private static final int ACTION_REMOVE = 7; + private static final int ACTION_REMOVE_ALL = 8; + private static final int ACTION_COLLAPSE = 9; + private static final int ACTION_EXPAND = 10; + private static final int ACTION_COLUMNS = 11; + private static final int ACTION_OPTIONS = 12; + + private class LintViewAction extends Action { + + private final int mAction; + + private LintViewAction(String label, int action, + ImageDescriptor imageDesc, ImageDescriptor disabledImageDesc) { + super(label); + mAction = action; + setImageDescriptor(imageDesc); + if (disabledImageDesc != null) { + setDisabledImageDescriptor(disabledImageDesc); + } + } + + @Override + public void run() { + switch (mAction) { + case ACTION_REFRESH: { + IWorkbench workbench = PlatformUI.getWorkbench(); + if (workbench != null) { + workbench.saveAllEditors(false /*confirm*/); + } + + Job[] jobs = LintJob.getCurrentJobs(); + if (jobs.length > 0) { + EclipseLintRunner.cancelCurrentJobs(false); + } else { + List<? extends IResource> resources = mLintView.getResources(); + if (resources == null) { + return; + } + Job job = EclipseLintRunner.startLint(resources, null, null, + false /*fatalOnly*/, false /*show*/); + if (job != null && workbench != null) { + job.addJobChangeListener(LintViewPart.this); + ISharedImages sharedImages = workbench.getSharedImages(); + setImageDescriptor(sharedImages.getImageDescriptor( + ISharedImages.IMG_ELCL_STOP)); + } + } + break; + } + case ACTION_FIX: { + List<IMarker> markers = mLintView.getSelectedMarkers(); + for (IMarker marker : markers) { + List<LintFix> fixes = LintFix.getFixes(EclipseLintClient.getId(marker), + marker); + if (fixes == null) { + continue; + } + LintFix fix = fixes.get(0); + IResource resource = marker.getResource(); + if (fix.needsFocus() && resource instanceof IFile) { + IRegion region = null; + try { + int start = marker.getAttribute(IMarker.CHAR_START, -1); + int end = marker.getAttribute(IMarker.CHAR_END, -1); + if (start != -1) { + region = new Region(start, end - start); + } + AdtPlugin.openFile((IFile) resource, region); + } catch (PartInitException e) { + AdtPlugin.log(e, "Can't open file %1$s", resource); + } + } + IDocumentProvider provider = new TextFileDocumentProvider(); + try { + provider.connect(resource); + IDocument document = provider.getDocument(resource); + if (document != null) { + fix.apply(document); + if (!fix.needsFocus()) { + provider.saveDocument(new NullProgressMonitor(), resource, + document, true /*overwrite*/); + } + } + } catch (Exception e) { + AdtPlugin.log(e, "Did not find associated editor to apply fix: %1$s", + resource.getName()); + } finally { + provider.disconnect(resource); + } + } + break; + } + case ACTION_REMOVE: { + for (IMarker marker : mLintView.getSelectedMarkers()) { + try { + marker.delete(); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + } + break; + } + case ACTION_REMOVE_ALL: { + List<? extends IResource> resources = mLintView.getResources(); + if (resources != null) { + for (IResource resource : resources) { + EclipseLintClient.clearMarkers(resource); + } + } + break; + } + case ACTION_IGNORE_ALL: + assert false; + break; + case ACTION_IGNORE_TYPE: + case ACTION_IGNORE_FILE: { + boolean ignoreInFile = mAction == ACTION_IGNORE_FILE; + for (IMarker marker : mLintView.getSelectedMarkers()) { + String id = EclipseLintClient.getId(marker); + if (id != null) { + IResource resource = marker.getResource(); + LintFixGenerator.suppressDetector(id, true, + ignoreInFile ? resource : resource.getProject(), + ignoreInFile); + } + } + break; + } + case ACTION_IGNORE_THIS: { + for (IMarker marker : mLintView.getSelectedMarkers()) { + LintFixGenerator.addSuppressAnnotation(marker); + } + break; + } + case ACTION_COLLAPSE: { + mLintView.collapseAll(); + break; + } + case ACTION_EXPAND: { + mLintView.expandAll(); + break; + } + case ACTION_COLUMNS: { + mLintView.configureColumns(); + break; + } + case ACTION_OPTIONS: { + PreferenceManager manager = new PreferenceManager(); + + LintPreferencePage page = new LintPreferencePage(); + String title = "Default/Global Settings"; + page.setTitle(title); + IPreferenceNode node = new PreferenceNode(title, page); + manager.addToRoot(node); + + + List<? extends IResource> resources = mLintView.getResources(); + if (resources != null) { + Set<IProject> projects = new HashSet<IProject>(); + for (IResource resource : resources) { + projects.add(resource.getProject()); + } + if (projects.size() > 0) { + for (IProject project : projects) { + page = new LintPreferencePage(); + page.setTitle(String.format("Settings for %1$s", + project.getName())); + page.setElement(project); + node = new PreferenceNode(project.getName(), page); + manager.addToRoot(node); + } + } + } + + Shell shell = LintViewPart.this.getSite().getShell(); + PreferenceDialog dialog = new PreferenceDialog(shell, manager); + dialog.create(); + dialog.setSelectedNode(title); + dialog.open(); + break; + } + default: + assert false : mAction; + } + updateIssueCount(); + } + } + + /** + * Shows or reconfigures the LintView to show the lint warnings for the + * given project + * + * @param projects the projects to show lint warnings for + */ + public static void show(List<? extends IResource> projects) { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (window != null) { + IWorkbenchPage page = window.getActivePage(); + if (page != null) { + try { + // Pass initial project context via static field read by constructor + sInitialResources = projects; + IViewPart view = page.showView(LintViewPart.ID, null, + IWorkbenchPage.VIEW_ACTIVATE); + if (sInitialResources != null && view instanceof LintViewPart) { + // The view must be showing already since the constructor was not + // run, so reconfigure the view instead + LintViewPart lintView = (LintViewPart) view; + lintView.setResources(projects); + } + } catch (PartInitException e) { + AdtPlugin.log(e, "Cannot open Lint View"); + } finally { + sInitialResources = null; + } + } + } + } +} |