package org.elixir_lang.navigation; import com.intellij.navigation.ChooseByNameContributor; import com.intellij.navigation.NavigationItem; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.apache.commons.lang.math.IntRange; import org.elixir_lang.errorreport.Logger; import org.elixir_lang.psi.AtUnqualifiedNoParenthesesCall; import org.elixir_lang.psi.NamedElement; import org.elixir_lang.psi.call.Call; import org.elixir_lang.psi.stub.index.AllName; import org.elixir_lang.structure_view.element.*; import org.elixir_lang.structure_view.element.modular.Implementation; import org.elixir_lang.structure_view.element.modular.Modular; import org.elixir_lang.structure_view.element.modular.Module; import org.elixir_lang.structure_view.element.modular.Protocol; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @see <a href="https://github.com/ignatov/intellij-erlang/blob/2f59e59a31ecbb2fbdf9b7a3547fb4f206b0807e/src/org/intellij/erlang/go/ErlangSymbolContributor.java">org.intellij.erlang.go.ErlangSymbolContributor</a> */ public class GotoSymbolContributor implements ChooseByNameContributor { /* * Static Methods */ @NotNull private static GlobalSearchScope globalSearchScope(Project project, boolean includeNonProjectItems) { GlobalSearchScope scope; if (includeNonProjectItems) { scope = GlobalSearchScope.allScope(project); } else { scope = GlobalSearchScope.projectScope(project); } return scope; } /* * Instance Methods */ /** * Returns the list of navigation items matching the specified name. * * @param name the name selected from the list. * @param pattern the original pattern entered in the dialog * @param project the project in which the navigation is performed. * @param includeNonProjectItems if true, the navigation items for non-project items (for example, * library classes) should be included in the returned array. * @return the array of navigation items. */ @NotNull @Override public NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) { GlobalSearchScope scope = globalSearchScope(project, includeNonProjectItems); Collection<NamedElement> result = StubIndex.getElements(AllName.KEY, name, project, scope, NamedElement.class); List<NavigationItem> items = ContainerUtil.newArrayListWithCapacity(result.size()); EnclosingModularByCall enclosingModularByCall = new EnclosingModularByCall(); Map<CallDefinition.Tuple, CallDefinition> callDefinitionByTuple = new HashMap<CallDefinition.Tuple, CallDefinition>(); for (final NamedElement element : result) { // Use navigation element so that source element is used for compiled elements PsiElement sourceElement = element.getNavigationElement(); if (sourceElement instanceof Call) { getItemsByNameFromCall( name, items, enclosingModularByCall, callDefinitionByTuple, (Call) sourceElement ); } } return items.toArray(new NavigationItem[items.size()]); } private void getItemsByNameFromCall(@NotNull String name, @NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Map<CallDefinition.Tuple, CallDefinition> callDefinitionByTuple, @NotNull Call call) { if (CallDefinitionClause.is(call)) { getItemsFromCallDefinitionClause(items, enclosingModularByCall, callDefinitionByTuple, call); } else if (CallDefinitionHead.is(call)) { getItemsFromCallDefinitionHead(items, enclosingModularByCall, callDefinitionByTuple, call); } else if (CallDefinitionSpecification.is(call)) { getItemsFromCallDefinitionSpecification(items, enclosingModularByCall, call); } else if (Callback.is(call)) { getItemsFromCallback(items, enclosingModularByCall, call); } else if (Implementation.is(call)) { getItemsFromImplementation(name, items, enclosingModularByCall, call); } else if (Module.is(call)) { getItemsFromModule(items, enclosingModularByCall, call); } else if (Protocol.is(call)) { getItemsFromProtocol(items, enclosingModularByCall, call); } } private void getItemsFromCallback(@NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Call call) { Modular modular = enclosingModularByCall.putNew(call); if (modular != null) { Callback callback = new Callback(modular, call); items.add(callback); } else { error("Cannot find enclosing Modular for Callback", call); } } private void getItemsFromCallDefinitionClause( @NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Map<CallDefinition.Tuple, CallDefinition> callDefinitionByTuple, @NotNull Call call ) { Pair<String, IntRange> nameArityRange = CallDefinitionClause.nameArityRange(call); if (nameArityRange != null) { String callName = nameArityRange.first; IntRange arityRange = nameArityRange.second; Timed.Time time = CallDefinitionClause.time(call); Modular modular = enclosingModularByCall.putNew(call); if (modular == null) { // don't throw an error if really EEX, but has wrong extension if (!call.getText().contains("<%=")) { error("Cannot find enclosing Modular", call); } } else { for (int arity = arityRange.getMinimumInteger(); arity <= arityRange.getMaximumInteger(); arity++) { CallDefinition.Tuple tuple = new CallDefinition.Tuple(modular, time, callName, arity); CallDefinition callDefinition = callDefinitionByTuple.get(tuple); if (callDefinition == null) { callDefinition = new CallDefinition(tuple.modular, tuple.time, tuple.name, tuple.arity); items.add(callDefinition); callDefinitionByTuple.put(tuple, callDefinition); } CallDefinitionClause callDefinitionClause = callDefinition.clause(call); items.add(callDefinitionClause); } } } } private void getItemsFromCallDefinitionHead( @NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Map<CallDefinition.Tuple, CallDefinition> callDefinitionByTuple, @NotNull Call call ) { Call delegationCall = CallDefinitionHead.enclosingDelegationCall(call); if (delegationCall != null) { Modular modular = enclosingModularByCall.putNew(delegationCall); if (modular != null) { String callDefinitionName = call.functionName(); if (callDefinitionName != null) { int callDefinitionArity = call.resolvedFinalArity(); CallDefinition.Tuple tuple = new CallDefinition.Tuple( modular, // Delegation can't delegate macros Timed.Time.RUN, callDefinitionName, callDefinitionArity ); CallDefinition callDefinition = callDefinitionByTuple.get(tuple); if (callDefinition == null) { callDefinition = new CallDefinition(tuple.modular, tuple.time, tuple.name, tuple.arity); items.add(callDefinition); callDefinitionByTuple.put(tuple, callDefinition); } // Delegation is always public as import should be used for private Visible.Visibility visibility = Visible.Visibility.PUBLIC; //noinspection ConstantConditions CallDefinitionHead callDefinitionHead = new CallDefinitionHead(callDefinition, visibility, call); items.add(callDefinitionHead); } else { error("Call for CallDefinitionHead does not have function name", call); } } else { error("Cannot find enclosing Modular for Delegation call", delegationCall); } } else { error("Cannot find enclosing delegation call for CallDefinitionHead", call); } } private void getItemsFromCallDefinitionSpecification(@NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Call call) { Modular modular = enclosingModularByCall.putNew(call); if (modular != null) { // pseudo-named-arguments boolean callback = false; Timed.Time time = Timed.Time.RUN; //noinspection ConstantConditions CallDefinitionSpecification callDefinitionSpecification = new CallDefinitionSpecification( modular, (AtUnqualifiedNoParenthesesCall) call, callback, time ); items.add(callDefinitionSpecification); } else { error("Cannot find enclosing Modular for CallDefinitionSpecification", call); } } private void getItemsFromImplementation(@NotNull String name, @NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Call call) { Modular modular = enclosingModularByCall.putNew(call); Collection<String> forNameCollection = Implementation.forNameCollection(modular, call); if (forNameCollection != null) { for (String forName : forNameCollection) { Implementation forNameOverriddenImplementation = new Implementation(modular, call, forName); String implementationName = forNameOverriddenImplementation.getName(); if (implementationName != null && implementationName.contains(name)) { items.add(forNameOverriddenImplementation); } } } if (forNameCollection == null || forNameCollection.size() < 2) { Implementation implementation = new Implementation(modular, call); items.add(implementation); } } private void getItemsFromModule(@NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Call call) { Modular modular = enclosingModularByCall.putNew(call); Module module = new Module(modular, call); items.add(module); } private void getItemsFromProtocol(@NotNull List<NavigationItem> items, @NotNull EnclosingModularByCall enclosingModularByCall, @NotNull Call call) { Modular modular = enclosingModularByCall.putNew(call); Protocol protocol = new Protocol(modular, call); items.add(protocol); } /** * Returns the list of names for the specified project to which it is possible to navigate * by name. * * @param project the project in which the navigation is performed. * @param includeNonProjectItems if true, the names of non-project items (for example, * library classes) should be included in the returned array. * @return the array of names. */ @NotNull @Override public String[] getNames(Project project, boolean includeNonProjectItems) { return ArrayUtil.toStringArray(StubIndex.getInstance().getAllKeys(AllName.KEY, project)); } /* * Private Instance Methods */ private void error(@NotNull String userMessage, @NotNull PsiElement element) { Logger.error(this.getClass(), userMessage, element); } }