blob: 3a03688d200d56190b1a62b4bd923e019ba8367c [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.intellij.lang.ant.config.execution;
import com.intellij.ide.CopyProvider;
import com.intellij.ide.DataManager;
import com.intellij.ide.OccurenceNavigator;
import com.intellij.ide.OccurenceNavigatorSupport;
import com.intellij.lang.ant.AntBundle;
import com.intellij.lang.ant.config.AntBuildFile;
import com.intellij.lang.ant.config.AntBuildModelBase;
import com.intellij.lang.ant.config.AntBuildTargetBase;
import com.intellij.lang.ant.config.AntConfigurationBase;
import com.intellij.lang.ant.config.impl.BuildTask;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.AutoScrollToSourceHandler;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.OpenSourceUtil;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.StringTokenizer;
public final class TreeView implements AntOutputView, OccurenceNavigator {
private Tree myTree;
private DefaultTreeModel myTreeModel;
private TreePath myParentPath = null;
private final ArrayList<MessageNode> myMessageItems = new ArrayList<MessageNode>();
private final JPanel myPanel;
private boolean myActionsEnabled = true;
private String myCurrentTaskName;
private final Project myProject;
private final AntBuildFile myBuildFile;
private DefaultMutableTreeNode myStatusNode;
private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
private OccurenceNavigatorSupport myOccurenceNavigatorSupport;
@NonNls public static final String ROOT_TREE_USER_OBJECT = "root";
@NonNls public static final String JUNIT_TASK_NAME = "junit";
public TreeView(final Project project, final AntBuildFile buildFile) {
myProject = project;
myBuildFile = buildFile;
myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
protected boolean isAutoScrollMode() {
return AntConfigurationBase.getInstance(myProject).isAutoScrollToSource();
}
protected void setAutoScrollMode(boolean state) {
AntConfigurationBase.getInstance(myProject).setAutoScrollToSource(state);
}
};
myPanel = createPanel();
}
public JComponent getComponent() {
return myPanel;
}
private JPanel createPanel() {
createModel();
myTree = new MyTree();
myTree.setLineStyleAngled();
myTree.setRootVisible(false);
myTree.setShowsRootHandles(true);
myTree.updateUI();
myTree.setLargeModel(true);
myTree.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myTree), false);
}
}
});
myTree.addMouseListener(new PopupHandler() {
public void invokePopup(Component comp, int x, int y) {
popupInvoked(comp, x, y);
}
});
EditSourceOnDoubleClickHandler.install(myTree);
myAutoScrollToSourceHandler.install(myTree);
myOccurenceNavigatorSupport = new OccurenceNavigatorSupport(myTree) {
protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
if (!(node instanceof MessageNode)) {
return null;
}
MessageNode messageNode = (MessageNode)node;
AntBuildMessageView.MessageType type = messageNode.getType();
if (type != AntBuildMessageView.MessageType.MESSAGE && type != AntBuildMessageView.MessageType.ERROR) {
return null;
}
if (!isValid(messageNode.getFile())) {
return null;
}
return new OpenFileDescriptor(myProject, messageNode.getFile(), messageNode.getOffset());
}
@Nullable
public String getNextOccurenceActionName() {
return AntBundle.message("ant.execution.next.error.warning.action.name");
}
@Nullable
public String getPreviousOccurenceActionName() {
return AntBundle.message("ant.execution.previous.error.warning.action.name");
}
};
JPanel panel = new JPanel(new BorderLayout());
JScrollPane scrollPane = MessageTreeRenderer.install(myTree);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private void createModel() {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(ROOT_TREE_USER_OBJECT);
myTreeModel = new DefaultTreeModel(rootNode);
myParentPath = new TreePath(rootNode);
}
public void setActionsEnabled(boolean actionsEnabled) {
myActionsEnabled = actionsEnabled;
if (actionsEnabled) {
myTreeModel.reload();
}
}
public Object addMessage(AntMessage message) {
MessageNode messageNode = createMessageNode(message);
MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent();
myTreeModel.insertNodeInto(messageNode, parentNode, parentNode.getChildCount());
myMessageItems.add(messageNode);
handleExpansion();
return messageNode;
}
public void addMessages(AntMessage[] messages) {
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)myParentPath.getLastPathComponent();
int[] indices = new int[messages.length];
for (int i = 0; i < messages.length; i++) {
AntMessage message = messages[i];
MessageNode messageNode = createMessageNode(message);
indices[i] = parentNode.getChildCount();
parentNode.insert(messageNode, indices[i]);
myMessageItems.add(messageNode);
}
myTreeModel.nodesWereInserted(parentNode, indices);
handleExpansion();
}
private MessageNode createMessageNode(AntMessage message) {
String text = message.getText();
boolean allowToShowPosition = true;
if (JUNIT_TASK_NAME.equals(myCurrentTaskName)) {
HyperlinkUtil.PlaceInfo info = HyperlinkUtil.parseJUnitMessage(myProject, text);
if (info != null) {
message = new AntMessage(message.getType(), message.getPriority(), text, info.getFile(), 1, 1);
allowToShowPosition = false;
}
}
return new MessageNode(message, myProject, allowToShowPosition);
}
private void handleExpansion() {
if (myActionsEnabled && !myTree.hasBeenExpanded(myParentPath)) {
myTree.expandPath(myParentPath);
}
}
void scrollToLastMessage() {
if (myTree == null) return;
int count = myTree.getRowCount();
if (count > 0) {
int row = count - 1;
TreeUtil.selectPath(myTree, myTree.getPathForRow(row));
}
}
public void addJavacMessage(AntMessage message, String url) {
final StringBuilder builder = StringBuilderSpinAllocator.alloc();
try {
final VirtualFile file = message.getFile();
if (message.getLine() > 0) {
if (file != null) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
String presentableUrl = file.getPresentableUrl();
builder.append(presentableUrl);
builder.append(' ');
}
});
}
else if (url != null) {
builder.append(url);
builder.append(' ');
}
builder.append('(');
builder.append(message.getLine());
builder.append(':');
builder.append(message.getColumn());
builder.append(") ");
}
addJavacMessageImpl(new AntMessage(message.getType(), message.getPriority(), builder.toString() + message.getText(),
message.getFile(), message.getLine(), message.getColumn()));
}
finally {
StringBuilderSpinAllocator.dispose(builder);
}
}
private void addJavacMessageImpl(AntMessage message) {
MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent();
MessageNode messageNode = new MessageNode(message, myProject, false);
myTreeModel.insertNodeInto(messageNode, parentNode, parentNode.getChildCount());
myMessageItems.add(messageNode);
handleExpansion();
}
public void addException(AntMessage exception, boolean showFullTrace) {
MessageNode exceptionRootNode = null;
StringTokenizer tokenizer = new StringTokenizer(exception.getText(), "\r\n");
while (tokenizer.hasMoreElements()) {
String line = (String)tokenizer.nextElement();
if (exceptionRootNode == null) {
AntMessage newMessage = new AntMessage(exception.getType(), exception.getPriority(), line, exception.getFile(), exception.getLine(),
exception.getColumn());
exceptionRootNode = new MessageNode(newMessage, myProject, true);
myMessageItems.add(exceptionRootNode);
}
else if (showFullTrace) {
if (StringUtil.startsWithChar(line, '\t')) {
line = line.substring(1);
}
HyperlinkUtil.PlaceInfo info = HyperlinkUtil.parseStackLine(myProject, '\t' + line);
VirtualFile file = info != null ? info.getFile() : null;
int lineNumber = info != null ? info.getLine() : 0;
int column = info != null ? info.getColumn() : 1;
AntMessage newMessage = new AntMessage(exception.getType(), exception.getPriority(), line, file, lineNumber, column);
MessageNode child = new MessageNode(newMessage, myProject, false);
exceptionRootNode.add(child);
myMessageItems.add(child);
}
}
if (exceptionRootNode == null) return;
MutableTreeNode parentNode = (MutableTreeNode)myParentPath.getLastPathComponent();
myTreeModel.insertNodeInto(exceptionRootNode, parentNode, parentNode.getChildCount());
handleExpansion();
}
public void collapseAll() {
TreeUtil.collapseAll(myTree, 2);
}
public void expandAll() {
TreePath[] selectionPaths = myTree.getSelectionPaths();
TreePath leadSelectionPath = myTree.getLeadSelectionPath();
int row = 0;
while (row < myTree.getRowCount()) {
myTree.expandRow(row);
row++;
}
if (selectionPaths != null) {
// restore selection
myTree.setSelectionPaths(selectionPaths);
}
if (leadSelectionPath != null) {
// scroll to lead selection path
myTree.scrollPathToVisible(leadSelectionPath);
}
}
public void clearAllMessages() {
for (MessageNode messageItem : myMessageItems) {
messageItem.clearRangeMarker();
}
myMessageItems.clear();
myStatusNode = null;
createModel();
myTree.setModel(myTreeModel);
}
public void startBuild(AntMessage message) {
}
public void buildFailed(AntMessage message) {
addMessage(message);
}
public void startTarget(AntMessage message) {
collapseTargets();
MessageNode targetNode = (MessageNode)addMessage(message);
myParentPath = myParentPath.pathByAddingChild(targetNode);
}
private void collapseTargets() {
DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)root.getChildAt(i);
myTree.collapsePath(new TreePath(node.getPath()));
}
}
public void startTask(AntMessage message) {
myCurrentTaskName = message.getText();
MessageNode taskNode = (MessageNode)addMessage(message);
myParentPath = myParentPath.pathByAddingChild(taskNode);
}
private void popupInvoked(Component component, int x, int y) {
final TreePath path = myTree.getLeadSelectionPath();
if (path == null) return;
if (!(path.getLastPathComponent()instanceof MessageNode)) return;
if (getData(CommonDataKeys.NAVIGATABLE_ARRAY.getName()) == null) return;
DefaultActionGroup group = new DefaultActionGroup();
group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.ANT_MESSAGES_POPUP, group);
menu.getComponent().show(component, x, y);
}
@Nullable
private MessageNode getSelectedItem() {
TreePath path = myTree.getSelectionPath();
if (path == null) return null;
if (!(path.getLastPathComponent()instanceof MessageNode)) return null;
return (MessageNode)path.getLastPathComponent();
}
@Nullable
public Object getData(String dataId) {
if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
MessageNode item = getSelectedItem();
if (item == null) return null;
if (isValid(item.getFile())) {
return new OpenFileDescriptor(myProject, item.getFile(), item.getOffset());
}
if (item.getType() == AntBuildMessageView.MessageType.TARGET) {
final OpenFileDescriptor descriptor = getDescriptorForTargetNode(item);
if (descriptor != null && isValid(descriptor.getFile())) {
return descriptor;
}
}
if (item.getType() == AntBuildMessageView.MessageType.TASK) {
final OpenFileDescriptor descriptor = getDescriptorForTaskNode(item);
if (descriptor != null && isValid(descriptor.getFile())) {
return descriptor;
}
}
}
return null;
}
@Nullable
private OpenFileDescriptor getDescriptorForTargetNode(MessageNode node) {
final String targetName = node.getText()[0];
final AntBuildTargetBase target = (AntBuildTargetBase)myBuildFile.getModel().findTarget(targetName);
return (target == null) ? null : target.getOpenFileDescriptor();
}
private
@Nullable
OpenFileDescriptor getDescriptorForTaskNode(MessageNode node) {
final String[] text = node.getText();
if (text == null || text.length == 0) return null;
final String taskName = text[0];
final TreeNode parentNode = node.getParent();
if (!(parentNode instanceof MessageNode)) return null;
final MessageNode messageNode = (MessageNode)parentNode;
if (messageNode.getType() != AntBuildMessageView.MessageType.TARGET) return null;
final BuildTask task = ((AntBuildModelBase)myBuildFile.getModel()).findTask(messageNode.getText()[0], taskName);
return (task == null) ? null : task.getOpenFileDescriptor();
}
private static boolean isValid(final VirtualFile file) {
return file != null && ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
public Boolean compute() {
return file.isValid();
}
}).booleanValue();
}
public void finishBuild(String messageText) {
collapseTargets();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot();
myStatusNode = new DefaultMutableTreeNode(messageText);
myTreeModel.insertNodeInto(myStatusNode, root, root.getChildCount());
}
public void scrollToStatus() {
if (myStatusNode != null) {
TreeUtil.selectPath(myTree, new TreePath(myStatusNode.getPath()));
}
}
public void finishTarget() {
final TreePath parentPath = myParentPath.getParentPath();
if (parentPath != null) {
myParentPath = parentPath;
}
}
public void finishTask() {
myCurrentTaskName = null;
final TreePath parentPath = myParentPath.getParentPath();
if (parentPath != null) {
myParentPath = parentPath;
}
}
@Nullable
private static TreePath getFirstErrorPath(TreePath treePath) {
TreeNode treeNode = (TreeNode)treePath.getLastPathComponent();
if (treeNode instanceof MessageNode) {
AntBuildMessageView.MessageType type = ((MessageNode)treeNode).getType();
if (type == AntBuildMessageView.MessageType.ERROR) {
return treePath;
}
}
if (treeNode.getChildCount() == 0) {
return null;
}
for (int i = 0; i < treeNode.getChildCount(); i++) {
TreeNode childNode = treeNode.getChildAt(i);
TreePath childPath = treePath.pathByAddingChild(childNode);
TreePath usagePath = getFirstErrorPath(childPath);
if (usagePath != null) {
return usagePath;
}
}
return null;
}
public void scrollToFirstError() {
TreePath path = getFirstErrorPath(new TreePath(myTreeModel.getRoot()));
if (path != null) {
TreeUtil.selectPath(myTree, path);
}
}
public static final class TreeSelection {
public String mySelectedTarget;
public String mySelectedTask;
public boolean isEmpty() {
return mySelectedTarget == null && mySelectedTask == null;
}
}
public TreeSelection getSelection() {
TreeSelection selection = new TreeSelection();
TreePath path = myTree.getSelectionPath();
if (path == null) return selection;
Object[] paths = path.getPath();
for (Object o : paths) {
if (o instanceof MessageNode) {
MessageNode messageNode = (MessageNode)o;
AntBuildMessageView.MessageType type = messageNode.getType();
if (type == AntBuildMessageView.MessageType.TARGET) {
selection.mySelectedTarget = messageNode.getText()[0];
}
else if (type == AntBuildMessageView.MessageType.TASK) {
selection.mySelectedTask = messageNode.getText()[0];
}
}
}
return selection;
}
public boolean restoreSelection(TreeSelection treeSelection) {
if (treeSelection.isEmpty()) return false;
DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTreeModel.getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
TreeNode node = root.getChildAt(i);
if (node instanceof MessageNode) {
MessageNode messageNode = (MessageNode)node;
String[] text = messageNode.getText();
if (text.length == 0) continue;
if (Comparing.equal(treeSelection.mySelectedTarget, text[0])) {
TreePath pathToSelect = new TreePath(messageNode.getPath());
for (Enumeration enumeration = messageNode.children(); enumeration.hasMoreElements();) {
Object o = enumeration.nextElement();
if (o instanceof MessageNode) {
messageNode = (MessageNode)o;
if (Comparing.equal(treeSelection.mySelectedTask, text[0])) {
pathToSelect = new TreePath(messageNode.getPath());
break;
}
}
}
TreeUtil.selectPath(myTree, pathToSelect);
myTree.expandPath(pathToSelect);
return true;
}
}
}
return false;
}
ToggleAction createToggleAutoscrollAction() {
return myAutoScrollToSourceHandler.createToggleAction();
}
public String getNextOccurenceActionName() {
return myOccurenceNavigatorSupport.getNextOccurenceActionName();
}
public String getPreviousOccurenceActionName() {
return myOccurenceNavigatorSupport.getPreviousOccurenceActionName();
}
public OccurenceNavigator.OccurenceInfo goNextOccurence() {
return myOccurenceNavigatorSupport.goNextOccurence();
}
public OccurenceNavigator.OccurenceInfo goPreviousOccurence() {
return myOccurenceNavigatorSupport.goPreviousOccurence();
}
public boolean hasNextOccurence() {
return myOccurenceNavigatorSupport.hasNextOccurence();
}
public boolean hasPreviousOccurence() {
return myOccurenceNavigatorSupport.hasPreviousOccurence();
}
private class MyTree extends Tree implements DataProvider {
public MyTree() {
super(myTreeModel);
}
public void setRowHeight(int i) {
super.setRowHeight(0);
// this is needed in order to make UI calculate the height for each particular row
}
public void updateUI() {
super.updateUI();
TreeUtil.installActions(this);
}
public Object getData(String dataId) {
if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
return new CopyProvider() {
public boolean isCopyEnabled(@NotNull DataContext dataContext) {
return getSelectionPath() != null;
}
public boolean isCopyVisible(@NotNull DataContext dataContext) {
return true;
}
public void performCopy(@NotNull DataContext dataContext) {
TreePath selection = getSelectionPath();
Object value = selection.getLastPathComponent();
String text;
if (value instanceof MessageNode) {
MessageNode messageNode = ((MessageNode)value);
String[] lines = messageNode.getText();
text = "";
for (String line : lines) {
text += line + "\n";
}
}
else {
text = value.toString();
}
CopyPasteManager.getInstance().setContents(new StringSelection(text));
}
};
}
return null;
}
}
}