package org.elixir_lang.structure_view.node_provider; import com.intellij.icons.AllIcons; import com.intellij.ide.util.ActionShortcutProvider; import com.intellij.ide.util.FileStructureNodeProvider; import com.intellij.ide.util.treeView.smartTree.ActionPresentation; import com.intellij.ide.util.treeView.smartTree.ActionPresentationData; import com.intellij.ide.util.treeView.smartTree.TreeElement; import com.intellij.openapi.actionSystem.Shortcut; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.util.IncorrectOperationException; import org.apache.commons.lang.math.IntRange; import org.elixir_lang.psi.ElixirAccessExpression; import org.elixir_lang.psi.QualifiableAlias; import org.elixir_lang.psi.call.Call; import org.elixir_lang.psi.impl.ElixirPsiImplUtil; import org.elixir_lang.structure_view.element.*; import org.elixir_lang.structure_view.element.modular.Module; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.util.*; import static com.intellij.openapi.util.Pair.pair; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.stripAccessExpression; import static org.elixir_lang.structure_view.element.modular.Module.addClausesToCallDefinition; public class Used implements FileStructureNodeProvider<TreeElement>, ActionShortcutProvider { /* * CONSTANTS */ @NonNls public static final String ID = "SHOW_USED"; public static final String USING = "__using__"; /* * Static Methods */ public static Collection<TreeElement> filterOverridden(@NotNull Collection<TreeElement> nodesFromChildren, @NotNull Collection<TreeElement> children) { Map<Pair<String, Integer>, CallDefinition> childFunctionByNameArity = functionByNameArity(children); Collection<TreeElement> filtered = new ArrayList<TreeElement>(nodesFromChildren.size()); for (TreeElement nodeFromChildren : nodesFromChildren) { if (nodeFromChildren instanceof CallDefinition) { CallDefinition callDefinition = (CallDefinition) nodeFromChildren; // only functions work with defoverridable if (callDefinition.time() == Timed.Time.RUN) { Pair<String, Integer> nameArity = pair(callDefinition.name(), callDefinition.arity()); if (childFunctionByNameArity.containsKey(nameArity)) { continue; } } } filtered.add(nodeFromChildren); } return filtered; } public static Map<Pair<String, Integer>, CallDefinition> functionByNameArity(@NotNull Collection<TreeElement> children) { Map<Pair<String, Integer>, CallDefinition> functionByNameArity = new HashMap<Pair<String, Integer>, CallDefinition>(children.size()); for (TreeElement child : children) { if (child instanceof CallDefinition) { CallDefinition callDefinition = (CallDefinition) child; if (callDefinition.time() == Timed.Time.RUN) { Pair<String, Integer> nameArity = pair(callDefinition.name(), callDefinition.arity()); functionByNameArity.put(nameArity, callDefinition); } } } return functionByNameArity; } public static Collection<TreeElement> provideNodesFromChild(@NotNull TreeElement child) { Collection<TreeElement> nodes = null; if (child instanceof Use) { Use use = (Use) child; PsiElement[] finalArguments = ElixirPsiImplUtil.finalArguments(use.call()); assert finalArguments != null; if (finalArguments.length > 0) { PsiElement firstFinalArgument = finalArguments[0]; if (firstFinalArgument instanceof ElixirAccessExpression) { PsiElement accessExpressionChild = stripAccessExpression(firstFinalArgument); if (accessExpressionChild instanceof QualifiableAlias) { PsiReference reference = accessExpressionChild.getReference(); if (reference != null) { PsiElement ancestor = reference.resolve(); while (ancestor != null && !(ancestor instanceof PsiFile)) { if (ancestor instanceof Call) { Call call = (Call) ancestor; if (Module.is(call)) { Module module = new Module(call); Call[] childCalls = ElixirPsiImplUtil.macroChildCalls(call); if (childCalls != null) { Map<Pair<String, Integer>, CallDefinition> macroByNameArity = new HashMap<Pair<String, Integer>, CallDefinition>(childCalls.length); for (Call childCall : childCalls) { /* portion of {@link org.elixir_lang.structure_view.element.enclosingModular.Module#childCallTreeElements} dealing with macros, restricted to __using__/1 */ if (CallDefinitionClause.isMacro(childCall)) { Pair<String, IntRange> nameArityRange = CallDefinitionClause.nameArityRange(childCall); if (nameArityRange != null) { String name = nameArityRange.first; IntRange arityRange = nameArityRange.second; if (name.equals(USING) && arityRange.containsInteger(1)) { addClausesToCallDefinition( childCall, name, arityRange, macroByNameArity, module, Timed.Time.COMPILE, new Inserter<CallDefinition>() { @Override public void insert(CallDefinition element) { } } ); } } } } if (macroByNameArity.size() > 0) { PsiElement[] usingArguments; CallDefinition macro; CallDefinitionClause matchingClause = null; if (finalArguments.length > 1) { usingArguments = Arrays.copyOfRange(finalArguments, 1, finalArguments.length); Pair<String, Integer> nameArity = pair(USING, usingArguments.length); macro = macroByNameArity.get(nameArity); if (macro != null) { matchingClause = macro.matchingClause(usingArguments); } } else { /* `use <ALIAS>` will calls `__using__/1` even though there is no additional argument, but it obviously can't select a clause. */ Pair<String, Integer> nameArity = pair(USING, 1); macro = macroByNameArity.get(nameArity); List<CallDefinitionClause> macroClauseList = macro.clauseList(); if (macroClauseList.size() == 1) { matchingClause = macroClauseList.get(0); } else { // TODO match default argument clause/head to non-default argument clause that would be executed. } } if (matchingClause != null) { TreeElement[] callDefinitionClauseChildren = matchingClause.getChildren(); int length = callDefinitionClauseChildren.length; if (length > 0) { TreeElement lastCallDefinitionClauseChild = callDefinitionClauseChildren[length - 1]; if (lastCallDefinitionClauseChild instanceof Quote) { Quote quote = (Quote) lastCallDefinitionClauseChild; Quote injectedQuote = quote.used(use); TreeElement[] injectedQuoteChildren = injectedQuote.getChildren(); nodes = new ArrayList<TreeElement>(injectedQuoteChildren.length); for (TreeElement injectedQuoteChild : injectedQuoteChildren) { if (!(injectedQuoteChild instanceof Overridable)) { nodes.add(injectedQuoteChild); } } break; } } } } } } else { break; } } ancestor = ancestor.getParent(); } } } } } } if (nodes == null) { nodes = Collections.emptyList(); } return nodes; } public static Collection<TreeElement> provideNodesFromChildren(@NotNull Collection<TreeElement> children) { Collection<TreeElement> nodes = new ArrayList<TreeElement>(); for (TreeElement child : children) { Collection<TreeElement> nodesFromChild = provideNodesFromChild(child); nodes.addAll(nodesFromChild); } return nodes; } /* * Instance Methods */ @NotNull @Override public String getActionIdForShortcut() { return "FileStructurePopup"; } @NotNull @Override public String getCheckBoxText() { return "Show Used"; } /** * Returns a unique identifier for the action. * * @return the action identifier. */ @NotNull @Override public String getName() { return ID; } /** * Returns the presentation for the action. * * @return the action presentation. * @see ActionPresentationData#ActionPresentationData(String, String, Icon) */ @NotNull @Override public ActionPresentation getPresentation() { return new ActionPresentationData("Show Used", null, AllIcons.Hierarchy.Supertypes); } @NotNull @Override public Shortcut[] getShortcut() { throw new IncorrectOperationException("see getActionIdForShortcut()"); } @NotNull @Override public Collection<TreeElement> provideNodes(@NotNull TreeElement node) { Collection<TreeElement> nodes = new ArrayList<TreeElement>(); if (node instanceof Module) { Module module = (Module) node; TreeElement[] children = module.getChildren(); Collection<TreeElement> childCollection = Arrays.asList(children); nodes = provideNodesFromChildren(childCollection); nodes = filterOverridden(nodes, childCollection); } return nodes; } }