blob: b7e1dfd2be805295e546a8227035d617d5a11f86 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.tools.idea.gradle.invoker.messages;
import com.android.tools.idea.gradle.invoker.console.view.GradleConsoleToolWindowFactory;
import com.google.common.base.Joiner;
import com.intellij.icons.AllIcons;
import com.intellij.ide.errorTreeView.*;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.ui.MultilineTreeCellRenderer;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.util.containers.Convertor;
import icons.AndroidIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.Locale;
import static com.google.common.base.Strings.nullToEmpty;
import static com.intellij.openapi.util.text.StringUtil.capitalize;
import static com.intellij.util.ui.UIUtil.getParentOfType;
import static com.intellij.util.ui.tree.TreeUtil.selectFirstNode;
/**
* Tree view displayed in the "Messages" window. The difference between this one and the original one is that this one displays messages as
* they appear in the console. The original implementation sorts the messages by type.
*/
public class GradleBuildTreeViewPanel extends NewErrorTreeViewPanel {
private final GradleBuildTreeStructure myTreeStructure;
private final ErrorViewTreeBuilder myBuilder;
private final GradleBuildTreeViewConfiguration myConfiguration;
private volatile boolean myDisposed;
public GradleBuildTreeViewPanel(@NotNull Project project) {
super(project, null);
myConfiguration = GradleBuildTreeViewConfiguration.getInstance(project);
myTreeStructure = new GradleBuildTreeStructure(myProject, myConfiguration);
DefaultTreeModel model = (DefaultTreeModel)myTree.getModel();
myBuilder = new ErrorViewTreeBuilder(myTree, model, myTreeStructure);
super.dispose();
// We need to remove the JTree from the JScrollPane to register a new cell renderer. The reason is that the superclass calls
// MultilineTreeCellRenderer#installRenderer which installs a new cell renderer, puts the JTree in a JScrollPane and sets the
// cell renderer over and over when resetting the caches. A simple call to JTree#setCellRenderer does not work because of this.
JScrollPane scrollPane = getParentOfType(JScrollPane.class, myTree);
assert scrollPane != null;
myTree.getParent().remove(myTree);
Container parent = scrollPane.getParent();
assert parent instanceof JPanel;
parent.remove(scrollPane);
scrollPane = MultilineTreeCellRenderer.installRenderer(myTree, new MessageTreeRenderer());
parent.add(scrollPane, BorderLayout.CENTER);
new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
@Override
public String convert(TreePath treePath) {
Object last = treePath.getLastPathComponent();
if (last instanceof DefaultMutableTreeNode) {
Object data = ((DefaultMutableTreeNode)last).getUserObject();
if (data instanceof ErrorTreeNodeDescriptor) {
ErrorTreeElement e = ((ErrorTreeNodeDescriptor)data).getElement();
String[] text = e.getText();
if (text != null) {
return Joiner.on(' ').join(text);
}
}
}
return "";
}
});
}
@Nullable
private GradleBuildTreeViewConfiguration getConfiguration() {
return myConfiguration;
}
@Override
protected void fillRightToolbarGroup(DefaultActionGroup group) {
super.fillRightToolbarGroup(group);
group.add(new DumbAwareAction("Show Console Output", null, AndroidIcons.GradleConsole) {
@Override
public void actionPerformed(AnActionEvent e) {
ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(GradleConsoleToolWindowFactory.ID);
if (window != null) {
window.activate(null, false);
}
}
@Override
public void update(AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(true);
}
});
DefaultActionGroup filterGroup = new DefaultActionGroup("GradleBuildMessagesFilter", true) {
@Override
public void update(AnActionEvent e) {
Presentation presentation = e.getPresentation();
presentation.setDescription("Filter messages to display");
presentation.setIcon(AllIcons.General.Filter);
}
@Override
public boolean isDumbAware() {
return true;
}
};
// We could have iterated through ErrorTreeElementKind.values() and have less code, but we want to keep this order:
filterGroup.add(new FilterMessagesByKindAction(ErrorTreeElementKind.ERROR));
filterGroup.add(new FilterMessagesByKindAction(ErrorTreeElementKind.WARNING));
filterGroup.add(new FilterMessagesByKindAction(ErrorTreeElementKind.INFO));
filterGroup.add(new FilterMessagesByKindAction(ErrorTreeElementKind.NOTE));
filterGroup.add(new FilterMessagesByKindAction(ErrorTreeElementKind.GENERIC));
group.add(filterGroup);
}
@Override
protected boolean canHideWarnings() {
return false;
}
@Override
public void dispose() {
myDisposed = true;
myTreeStructure.clear();
Disposer.dispose(myBuilder);
}
@Override
public void updateTree() {
if (!myDisposed) {
myBuilder.updateTree();
}
}
@Override
public void reload() {
myBuilder.updateTree();
}
@Override
public void addMessage(int type,
@NotNull String[] text,
@Nullable VirtualFile underFileGroup,
@Nullable VirtualFile file,
int line,
int column,
@Nullable Object data) {
if (myDisposed) {
return;
}
ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type);
myTreeStructure.addMessage(kind, text, underFileGroup, file, line, column, data);
myBuilder.updateTree();
}
@Override
public void addMessage(int type,
@NotNull String[] text,
@Nullable String groupName,
@NotNull Navigatable navigatable,
@Nullable String exportTextPrefix,
@Nullable String rendererTextPrefix,
@Nullable Object data) {
if (myDisposed) {
return;
}
VirtualFile file = data instanceof VirtualFile ? (VirtualFile)data : null;
if (file == null && navigatable instanceof OpenFileDescriptor) {
file = ((OpenFileDescriptor)navigatable).getFile();
}
ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(type);
myTreeStructure.addNavigatableMessage(groupName, navigatable, kind, text, data, nullToEmpty(exportTextPrefix),
nullToEmpty(rendererTextPrefix), file);
myBuilder.updateTree();
}
@Override
public GradleBuildTreeStructure getErrorViewStructure() {
return myTreeStructure;
}
@Override
public void selectFirstMessage() {
ErrorTreeElement firstError = myTreeStructure.getFirstMessage(ErrorTreeElementKind.ERROR);
if (firstError != null) {
selectElement(firstError, new Runnable() {
@Override
public void run() {
if (shouldShowFirstErrorInEditor()) {
navigateToSource(false);
}
}
});
}
else {
selectFirstNode(myTree);
}
}
@Override
protected boolean shouldShowFirstErrorInEditor() {
return true;
}
private void selectElement(@NotNull ErrorTreeElement element, @Nullable Runnable onDone) {
myBuilder.select(element, onDone);
}
private void navigateToSource(boolean focusEditor) {
NavigatableMessageElement element = getSelectedMessageElement();
if (element == null) {
return;
}
Navigatable navigatable = element.getNavigatable();
if (navigatable.canNavigate()) {
navigatable.navigate(focusEditor);
}
}
@Nullable
private NavigatableMessageElement getSelectedMessageElement() {
ErrorTreeElement selectedElement = getSelectedErrorTreeElement();
return selectedElement instanceof NavigatableMessageElement ? (NavigatableMessageElement)selectedElement : null;
}
private class FilterMessagesByKindAction extends ToggleAction {
@NotNull private final ErrorTreeElementKind myElementKind;
FilterMessagesByKindAction(@NotNull ErrorTreeElementKind elementKind) {
super(capitalize(elementKind.toString().toLowerCase(Locale.getDefault())));
myElementKind = elementKind;
}
@Override
public boolean isSelected(AnActionEvent e) {
GradleBuildTreeViewConfiguration configuration = getConfiguration();
if (configuration == null) {
return false;
}
return configuration.canShow(myElementKind);
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
myConfiguration.update(myElementKind, state);
myBuilder.updateTree();
}
}
}