blob: 90b956e321e98f6443444acc2ed70fca3a7bb412 [file] [log] [blame]
/*
* 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;
}
}
}
}
}