/* * Copyright 2003-2016 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 jetbrains.mps.ide.typesystem.trace; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.ui.JBColor; import jetbrains.mps.ide.ui.tree.MPSTree; import jetbrains.mps.ide.ui.tree.MPSTreeNode; import jetbrains.mps.ide.ui.tree.TextTreeNode; import jetbrains.mps.ide.util.ColorAndGraphicsUtil; import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations; import jetbrains.mps.newTypesystem.TypesUtil; import jetbrains.mps.newTypesystem.operation.AbstractOperation; import jetbrains.mps.newTypesystem.operation.AddErrorOperation; import jetbrains.mps.newTypesystem.operation.ApplyRuleOperation; import jetbrains.mps.newTypesystem.operation.AssignTypeOperation; import jetbrains.mps.newTypesystem.operation.ExpandTypeOperation; import jetbrains.mps.newTypesystem.operation.TraceWarningOperation; import jetbrains.mps.newTypesystem.operation.block.AbstractBlockOperation; import jetbrains.mps.newTypesystem.operation.block.AddDependencyOperation; import jetbrains.mps.newTypesystem.operation.block.RemoveDependencyOperation; import jetbrains.mps.newTypesystem.operation.equation.AddEquationOperation; import jetbrains.mps.newTypesystem.state.State; import jetbrains.mps.newTypesystem.state.blocks.Block; import jetbrains.mps.nodeEditor.DefaultEditorMessage; import jetbrains.mps.nodeEditor.EditorComponent; import jetbrains.mps.nodeEditor.NodeHighlightManager; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.message.EditorMessageOwner; import jetbrains.mps.openapi.editor.message.SimpleEditorMessage; import jetbrains.mps.openapi.editor.style.StyleRegistry; import jetbrains.mps.project.Project; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.util.Computable; import jetbrains.mps.util.Pair; import jetbrains.mps.workbench.MPSDataKeys; import jetbrains.mps.workbench.action.ActionUtils; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.model.SNode; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.Color; import java.awt.Graphics; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; public class TypeSystemTraceTree extends MPSTree implements DataProvider { private final Project myProject; private final TypecheckingContextTracker myContextTracker; private final SNode mySelectedNode; private Set<SNode> myNodes; private TypeSystemTracePanel myParent; private EditorComponent myEditorComponent; private List<TypeSystemTraceTreeNode> myErrorNodes = new LinkedList<TypeSystemTraceTreeNode>(); private TypeSystemTraceTree.DetailsTree myDetailsTree; private final NodeHighlightManager myHighlightManager; private EditorMessageOwner myMessageOwner; public TypeSystemTraceTree(Project mpsProject, SNode node, TypeSystemTracePanel parent, EditorComponent editorComponent) { myProject = mpsProject; myContextTracker = new TypecheckingContextTracker(node.getContainingRoot()); myParent = parent; myEditorComponent = editorComponent; mySelectedNode = node; initNodes(node); setGenerationMode(TraceSettings.isGenerationMode()); this.myHighlightManager = editorComponent.getHighlightManager(); this.myMessageOwner = new EditorMessageOwner() { }; EditorCell nodeCell = myEditorComponent.findNodeCell(mySelectedNode); if (nodeCell != null) { myHighlightManager.mark(new TypeSystemTraceTree.SelectedNodeEditorMessage(nodeCell, "")); } this.rebuildNow(); expandAll(); this.myDetailsTree = new TypeSystemTraceTree.DetailsTree(null); addTreeSelectionListener(new TypeSystemTraceTree.ShowDetailsUpdater()); getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION); } private void initNodes(SNode node) { myNodes = new HashSet<SNode>(); myNodes.addAll(SNodeOperations.getNodeDescendants(node, null, false, new SConcept[]{})); myNodes.add(node); } /*package*/ MPSTree getDetailsTree() { return myDetailsTree; } public void rebuildTrace() { myContextTracker.checkRoot(true); this.rebuildNow(); this.expandAll(); } public void setGenerationMode(boolean generationMode) { myContextTracker.setGenerationMode(generationMode, mySelectedNode); } public State getState() { return myContextTracker.getStateCopy(); } @Override protected MPSTreeNode rebuild() { setRootVisible(false); setGenerationMode(TraceSettings.isGenerationMode()); if (TraceSettings.isTraceForSelectedNode() && mySelectedNode != null) { getSliceVars(myContextTracker.getOperation()); } MPSTreeNode result = create(myContextTracker.getOperation(), true); if (result == null) { result = new TextTreeNode("Empty type system trace"); } // <node> return result; } public TypeSystemTraceTreeNode create(AbstractOperation operation, boolean withChildren) { if (!(filterNodeType(operation))) { return null; } final boolean showNode = showNodeRecursively(operation); List<TypeSystemTraceTreeNode> children = new ArrayList<TypeSystemTraceTreeNode>(); if (withChildren) { for (AbstractOperation consequence : operation.getConsequences()) { TypeSystemTraceTreeNode node = create(consequence, false); if (node != null) { children.add(node); } } } else if (!(showNode)) { if (!(operation.getConsequences().iterator().hasNext())) { return null; } } final boolean hasAnError = hasAnErrorAsConsequence(operation); TypeSystemTraceTreeNode result = new TypeSystemTraceTreeNode(operation, myContextTracker.getCurrentState(), myEditorComponent) { @Override public void doUpdatePresentation() { super.doUpdatePresentation(); if (!(showNode)) { setColor(JBColor.GRAY); } else if (hasAnError) { setColor(JBColor.RED); } } }; for (TypeSystemTraceTreeNode node : children) { result.add(node); } if (hasAnError) { myErrorNodes.add(result); } return result; } private boolean hasAnErrorAsConsequence(AbstractOperation operation) { if (operation instanceof AddErrorOperation || operation instanceof TraceWarningOperation) { return true; } for (AbstractOperation consequence : operation.getConsequences()) { if (hasAnErrorAsConsequence(consequence)) { return true; } } return false; } @Override public void dispose() { myHighlightManager.clearForOwner(myMessageOwner); myContextTracker.dispose(); super.dispose(); } private boolean showNode(AbstractOperation diff) { if (!(TraceSettings.isTraceForSelectedNode())) { return true; } if (mySelectedNode == null) { return true; } if (myNodes.contains(diff.getSource())) { return true; } if (diff instanceof AddEquationOperation) { AddEquationOperation eq = (AddEquationOperation) diff; if (myNodes.contains(eq.getChild()) || myNodes.contains(eq.getParent())) { return true; } } if (diff instanceof AbstractBlockOperation) { Block block = ((AbstractBlockOperation) diff).getBlock(); for (SNode node : block.getInputs()) { if (myNodes.contains(node)) { return true; } } } return false; } private boolean showNodeRecursively(AbstractOperation diff) { if (diff == null) { return false; } for (AbstractOperation csq : diff.getConsequences()) { if (showNodeRecursively(csq)) { return true; } } return showNode(diff); } private boolean filterNodeType(AbstractOperation operation) { if (!(TraceSettings.isShowTypesExpansion()) && operation instanceof ExpandTypeOperation) { return false; } if (!(TraceSettings.isShowApplyRuleOperations()) && operation instanceof ApplyRuleOperation) { return false; } if (!(TraceSettings.isShowBlockDependencies()) && (operation instanceof AddDependencyOperation || operation instanceof RemoveDependencyOperation)) { return false; } return true; } public void goToNextError() { int currentRow = -1; MPSTreeNode currentNode = this.getCurrentNode(); if (null != currentNode) { currentRow = this.getRowForPath(new TreePath(currentNode.getPath())); } MPSTreeNode errorNode = getNextErrorNode(currentRow); if (null != errorNode) { this.scrollPathToVisible(new TreePath(errorNode.getPath())); this.selectNode(errorNode); } } private MPSTreeNode getNextErrorNode(int row) { TreePath errorPath; int errorRow; for (TypeSystemTraceTreeNode errorNode : myErrorNodes) { errorPath = new TreePath(errorNode.getPath()); errorRow = this.getRowForPath(errorPath); if (errorRow > row) { return errorNode; } } if (!(myErrorNodes.isEmpty())) { return myErrorNodes.get(0); } return null; } private void getSliceVars(AbstractOperation diff) { if (diff == null) { return; } if (diff instanceof AddEquationOperation) { AddEquationOperation eq = (AddEquationOperation) diff; SNode child = eq.getChild(); SNode parent = eq.getParent(); if (myNodes.contains(child)) { myNodes.addAll(TypesUtil.getVariables(parent, myContextTracker.getStateCopy())); } if (myNodes.contains(parent)) { myNodes.addAll(TypesUtil.getVariables(child, myContextTracker.getStateCopy())); } } if (diff instanceof AssignTypeOperation) { AssignTypeOperation typeDifference = (AssignTypeOperation) diff; if (myNodes.contains(typeDifference.getNode()) && TypesUtil.isVariable(typeDifference.getType())) { myNodes.add(typeDifference.getType()); } } for (AbstractOperation childDiff : diff.getConsequences()) { getSliceVars(childDiff); } } @Override @Nullable public Object getData(@NonNls final String id) { MPSTreeNode currentNode = this.getCurrentNode(); if (currentNode instanceof TypeSystemTraceTreeNode) { return new ModelAccessHelper(myProject.getModelAccess()).runReadAction(new Computable<Object>() { @Override public Object compute() { return _getData(id); } }); } return null; } private Object _getData(String id) { AbstractOperation operation = getAssociatedOperation(this.getCurrentNode()); if (operation == null) { return null; } final Pair<String, String> rule = operation.getRule(); final SNode source = operation.getSource(); if (id.equals(MPSDataKeys.RULE_MODEL_AND_ID.getName())) { return rule; } if (source != null && source.getModel() != null) { if (id.equals(MPSDataKeys.SOURCE_NODE.getName())) { return source; } } return null; } @Override protected ActionGroup createPopupActionGroup(final MPSTreeNode treeNode) { return ActionUtils.groupFromActions(ActionManager.getInstance().getAction("jetbrains.mps.ide.actions.GoToNode_Action")); } private void showState(final TypeSystemTraceTreeNode newNode) { myProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { Object difference = newNode.getUserObject(); myParent.resetState(myContextTracker.resetCurrentState((AbstractOperation) difference)); final State newState = myContextTracker.updateCurrentState((AbstractOperation) difference); if (newState != null) { myParent.updateState(newState); } } }); } private void showState(final MPSTreeNode fromNode, final MPSTreeNode toNode) { myProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { Object fromDiff = fromNode.getUserObject(); Object toDiff = toNode.getUserObject(); myParent.resetState(myContextTracker.resetCurrentState((AbstractOperation) fromDiff)); myParent.updateState(myContextTracker.updateCurrentState((AbstractOperation) fromDiff, (AbstractOperation) toDiff)); } }); } private void showDetails(MPSTreeNode treeNode) { myDetailsTree.setOperation(getAssociatedOperation(treeNode)); } private void showDetails(Collection<? extends MPSTreeNode> treeNodes) { List<AbstractOperation> operations = new ArrayList<AbstractOperation>(); for (MPSTreeNode treeNode : treeNodes) { operations.add(getAssociatedOperation(treeNode)); } myDetailsTree.setOperations(operations); } private class ShowDetailsUpdater implements TreeSelectionListener { private ShowDetailsUpdater() { } @Override public void valueChanged(TreeSelectionEvent e) { TreePath[] selectionPaths = getSelectionPaths(); if (selectionPaths != null && selectionPaths.length >= 1) { if (selectionPaths.length == 1) { TreePath path = selectionPaths[0]; if (path == null) { return; } MPSTreeNode treeNode = (MPSTreeNode) path.getLastPathComponent(); showState((TypeSystemTraceTreeNode) treeNode); showDetails(treeNode); } else { TreePath fromPath = selectionPaths[0]; TreePath toPath = selectionPaths[selectionPaths.length - 1]; showState((MPSTreeNode) fromPath.getLastPathComponent(), (MPSTreeNode) toPath.getLastPathComponent()); List<MPSTreeNode> selectedNodes = new ArrayList<MPSTreeNode>(); for (TreePath tp : selectionPaths) { selectedNodes.add((MPSTreeNode) tp.getLastPathComponent()); } showDetails(selectedNodes); } } } } private class SelectedNodeEditorMessage extends DefaultEditorMessage { private EditorCell myCell; public SelectedNodeEditorMessage(EditorCell cell, String message) { super(cell.getSNode(), StyleRegistry.getInstance().getSimpleColor(new Color(192, 255, 255)), message, myMessageOwner); this.myCell = cell; } @Override public EditorCell getCell(EditorComponent component) { return myCell; } @Override public boolean acceptCell(EditorCell cell, EditorComponent component) { return myCell == cell; } @Override public boolean sameAs(SimpleEditorMessage that) { return super.sameAs(that) && this.equals(that); } @Override protected void paintWithColor(Graphics graphics, EditorCell cell, Color color) { int x = cell.getX() + cell.getLeftInset(); int y = cell.getY() + cell.getTopInset(); int width = cell.getWidth() - cell.getLeftInset() - cell.getRightInset() - 1; int height = cell.getHeight() - cell.getTopInset() - cell.getBottomInset() - 1; graphics.setColor(color); ColorAndGraphicsUtil.fillStripes(graphics, x, y, width, height); } @Override public boolean isBackground() { return true; } @Override public boolean equals(Object that) { if (that == null) { return false; } if (this == that) { return true; } if (that.getClass() != TypeSystemTraceTree.SelectedNodeEditorMessage.class) { return false; } return this.myCell.equals(((TypeSystemTraceTree.SelectedNodeEditorMessage) that).myCell); } @Override public int hashCode() { return myCell.hashCode() * 37; } } public class DetailsTree extends MPSTree implements DataProvider { private List<AbstractOperation> myOperations; public DetailsTree(AbstractOperation operation) { this.myOperations = Collections.singletonList(operation); rebuildNow(); expandAll(); addTreeSelectionListener(new TypeSystemTraceTree.DetailsTree.ShowStateUpdater()); } public void setOperation(AbstractOperation operation) { this.myOperations = Collections.singletonList(operation); rebuildNow(); expandAll(); } public void setOperations(Collection<? extends AbstractOperation> operation) { this.myOperations = new ArrayList<AbstractOperation>(operation); rebuildNow(); expandAll(); } public Collection<MPSTreeNode> create(Collection<? extends AbstractOperation> operations, boolean showParent) { if (operations == null || operations.size() == 0 || operations.iterator().next() == null) { return null; } List<MPSTreeNode> result = new ArrayList<MPSTreeNode>(); for (AbstractOperation operation : operations) { final boolean showNode = showNode(operation); List<MPSTreeNode> children = new ArrayList<MPSTreeNode>(); for (AbstractOperation consequence : operation.getConsequences()) { Collection<MPSTreeNode> nodes = create(Collections.singletonList(consequence), true); if (nodes != null) { children.addAll(nodes); } } if (!(filterNodeType(operation))) { continue; } if (showParent) { TypeSystemTraceTreeNode treeNode = new TypeSystemTraceTreeNode(operation, myContextTracker.getCurrentState(), myEditorComponent) { @Override public void doUpdatePresentation() { super.doUpdatePresentation(); if (!(showNode)) { setColor(JBColor.BLACK); } } }; for (MPSTreeNode node : children) { treeNode.add(node); } result.add(treeNode); } else { result.addAll(children); } // <node> } return result; } @Override @Nullable public Object getData(@NonNls final String id) { MPSTreeNode currentNode = this.getCurrentNode(); if (currentNode instanceof TypeSystemTraceTreeNode) { return new ModelAccessHelper(myProject.getModelAccess()).runReadAction(new Computable<Object>() { @Override public Object compute() { return _getData(id); } }); } return null; } private Object _getData(String id) { AbstractOperation operation = getAssociatedOperation(this.getCurrentNode()); if (operation == null) { return null; } final Pair<String, String> rule = operation.getRule(); final SNode source = operation.getSource(); if (id.equals(MPSDataKeys.RULE_MODEL_AND_ID.getName())) { return rule; } if (source != null && source.getModel() != null) { if (id.equals(MPSDataKeys.SOURCE_NODE.getName())) { return source; } } return null; } @Override protected ActionGroup createPopupActionGroup(MPSTreeNode node) { return ActionUtils.groupFromActions(ActionManager.getInstance().getAction("jetbrains.mps.ide.actions.GoToNode_Action"), ActionManager.getInstance().getAction("jetbrains.mps.ide.actions.GoToRule_Action")); } @Override protected MPSTreeNode rebuild() { setRootVisible(false); MPSTreeNode result; Collection<MPSTreeNode> nodes = create(myOperations, false); if (nodes == null) { result = new TextTreeNode("Empty type system trace"); setRootVisible(true); } else { result = new TextTreeNode("Details"); for (MPSTreeNode node : nodes) { result.add(node); } } return result; } private class ShowStateUpdater implements TreeSelectionListener { private ShowStateUpdater() { } @Override public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getNewLeadSelectionPath(); if (path == null) { return; } Object treeNode = path.getLastPathComponent(); if (treeNode instanceof TypeSystemTraceTreeNode) { showState((TypeSystemTraceTreeNode) treeNode); } } } } private static AbstractOperation getAssociatedOperation(MPSTreeNode checkedDotOperand) { if (null != checkedDotOperand) { return (AbstractOperation) checkedDotOperand.getUserObject(); } return null; } }