From cfdcb2a2ff8ba90532a7a11ba195f90012c8be53 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 23 Sep 2017 22:05:18 -0500 Subject: [PATCH 1/5] Show Erlang SDK modules in Symbol search --- src/org/elixir_lang/sdk/Type.java | 25 ++++--- src/org/elixir_lang/sdk/elixir/Type.java | 2 +- .../AdditionalDataConfigurable.java | 74 ++++++++++++++++++- 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/org/elixir_lang/sdk/Type.java b/src/org/elixir_lang/sdk/Type.java index b4fb1ad8e..3b03bcd06 100644 --- a/src/org/elixir_lang/sdk/Type.java +++ b/src/org/elixir_lang/sdk/Type.java @@ -6,24 +6,31 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import java.nio.file.Path; +import java.util.function.Consumer; + import static org.elixir_lang.sdk.HomePath.eachEbinPath; public class Type { private Type() { } + public static void ebinPathChainVirtualFile(@NotNull Path ebinPath, Consumer virtualFileConsumer) { + VirtualFile virtualFile = LocalFileSystem + .getInstance() + .findFileByIoFile(ebinPath.toFile()); + + if (virtualFile != null) { + virtualFileConsumer.accept(virtualFile); + } + } + public static void addCodePaths(@NotNull SdkModificator sdkModificator) { eachEbinPath( sdkModificator.getHomePath(), - ebin -> { - VirtualFile virtualFile = LocalFileSystem - .getInstance() - .findFileByIoFile(ebin.toFile()); - - if (virtualFile != null) { - sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES); - } - } + ebin -> ebinPathChainVirtualFile( + ebin, virtualFile -> sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES) + ) ); } } diff --git a/src/org/elixir_lang/sdk/elixir/Type.java b/src/org/elixir_lang/sdk/elixir/Type.java index 8c6cdf2ee..c934658bd 100644 --- a/src/org/elixir_lang/sdk/elixir/Type.java +++ b/src/org/elixir_lang/sdk/elixir/Type.java @@ -589,7 +589,7 @@ public com.intellij.openapi.projectRoots.AdditionalDataConfigurable createAdditi @NotNull SdkModel sdkModel, @NotNull SdkModificator sdkModificator ) { - return new org.elixir_lang.sdk.erlang_dependent.AdditionalDataConfigurable(sdkModel); + return new org.elixir_lang.sdk.erlang_dependent.AdditionalDataConfigurable(sdkModel, sdkModificator); } public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) { diff --git a/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java b/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java index 73e47f285..093069afd 100644 --- a/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java +++ b/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java @@ -5,10 +5,14 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModel; import com.intellij.openapi.projectRoots.SdkModificator; +import com.intellij.openapi.projectRoots.SdkType; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; +import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ListCellRendererWrapper; +import com.intellij.util.ArrayUtil; import com.intellij.util.ui.JBUI; import org.elixir_lang.sdk.elixir.Type; import org.jetbrains.annotations.NotNull; @@ -16,7 +20,12 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ItemEvent; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import static org.elixir_lang.sdk.HomePath.eachEbinPath; +import static org.elixir_lang.sdk.Type.ebinPathChainVirtualFile; import static org.elixir_lang.sdk.erlang_dependent.Type.staticIsValidDependency; public class AdditionalDataConfigurable implements com.intellij.openapi.projectRoots.AdditionalDataConfigurable { @@ -25,11 +34,15 @@ public class AdditionalDataConfigurable implements com.intellij.openapi.projectR private final ComboBox internalErlangSdksComboBox = new ComboBox(internalErlangSdksComboBoxModel); private final SdkModel sdkModel; private final SdkModel.Listener sdkModelListener; + private final SdkModificator sdkModificator; private Sdk elixirSdk; private boolean modified; + private boolean freeze = false; - public AdditionalDataConfigurable(@NotNull SdkModel sdkModel) { + public AdditionalDataConfigurable(@NotNull SdkModel sdkModel, SdkModificator sdkModificator) { this.sdkModel = sdkModel; + this.sdkModificator = sdkModificator; + sdkModelListener = new SdkModel.Listener() { public void sdkAdded(Sdk sdk) { if (staticIsValidDependency(sdk)) { @@ -118,8 +131,37 @@ public void customize(JList list, Object value, int index, boolean selected, boo } ); internalErlangSdksComboBox.addItemListener(itemEvent -> { - if (itemEvent.getStateChange() == ItemEvent.SELECTED) { - modified = true; + if (!freeze) { + final int stateChange = itemEvent.getStateChange(); + + if (stateChange == ItemEvent.SELECTED) { + modified = true; + } + + final Sdk internalErlangSdk = (Sdk) itemEvent.getItem(); + final SdkType internalSdkType = (SdkType) internalErlangSdk.getSdkType(); + final SdkType elixirSdkType = (SdkType) elixirSdk.getSdkType(); + + for (OrderRootType type : OrderRootType.getAllTypes()) { + if (internalSdkType.isRootTypeApplicable(type) && elixirSdkType.isRootTypeApplicable(type)) { + final VirtualFile[] internalRoots = internalErlangSdk.getSdkModificator().getRoots(type); + final VirtualFile[] configuredRoots = sdkModificator.getRoots(type); + + for (VirtualFile internalRoot : internalRoots) { + + for (VirtualFile expandedInternalRoot : expandInternalRoot(internalRoot, type)) { + if (stateChange == ItemEvent.DESELECTED) { + // Remove roots copied from old Erlang SDK + sdkModificator.removeRoot(expandedInternalRoot, type); + } else if (ArrayUtil.find(configuredRoots, expandedInternalRoot) == -1) { + /* Copy roots from new Erlang SDK, so that completion works for Erlang SDK beams. + See #829. */ + sdkModificator.addRoot(expandedInternalRoot, type); + } + } + } + } + } } }); @@ -128,6 +170,30 @@ public void customize(JList list, Object value, int index, boolean selected, boo return wholePanel; } + @NotNull + private Iterable expandInternalRoot(@NotNull VirtualFile internalRoot, OrderRootType type) { + java.util.List expandedInternalRootList; + + if (type == OrderRootType.CLASSES) { + final String path = internalRoot.getPath(); + + /* Erlang SDK from intellij-erlang uses lib/erlang/lib as class path, but intellij-elixir needs the ebin + directories under lib/erlang/lib/APP-VERSION/ebin that works as a code path used by `-pa` argument to + `erl.exe` */ + if (path.endsWith("lib/erlang/lib")) { + expandedInternalRootList = new ArrayList<>(); + String parentPath = Paths.get(path).getParent().toString(); + eachEbinPath(parentPath, ebinPath -> ebinPathChainVirtualFile(ebinPath, expandedInternalRootList::add)); + } else { + expandedInternalRootList = Collections.singletonList(internalRoot); + } + } else { + expandedInternalRootList = Collections.singletonList(internalRoot); + } + + return expandedInternalRootList; + } + private void internalErlangSdkUpdate(final Sdk sdk) { final Sdk erlangSdk = ((SdkAdditionalData) sdk.getSdkAdditionalData()).getErlangSdk(); @@ -155,7 +221,9 @@ public void apply() throws ConfigurationException { } public void reset() { + freeze = true; updateJdkList(); + freeze = false; if (elixirSdk != null && elixirSdk.getSdkAdditionalData() instanceof SdkAdditionalData) { final SdkAdditionalData sdkAdditionalData = (SdkAdditionalData) elixirSdk.getSdkAdditionalData(); From 56a0577d5348280a3fd6c0cbb398b13376c96855 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 26 Sep 2017 20:42:10 -0500 Subject: [PATCH 2/5] Completion of atom module names --- gen/org/elixir_lang/psi/ElixirAlias.java | 4 + gen/org/elixir_lang/psi/ElixirAtom.java | 4 + .../psi/ElixirMatchedQualifiedAlias.java | 4 + .../psi/ElixirUnmatchedQualifiedAlias.java | 4 + .../elixir_lang/psi/impl/ElixirAliasImpl.java | 10 ++- .../elixir_lang/psi/impl/ElixirAtomImpl.java | 6 ++ .../impl/ElixirMatchedQualifiedAliasImpl.java | 10 ++- .../ElixirUnmatchedQualifiedAliasImpl.java | 10 ++- src/org/elixir_lang/Elixir.bnf | 5 +- .../psi/impl/ElixirPsiImplUtil.java | 13 +++ src/org/elixir_lang/psi/scope/Atom.java | 30 +++++++ .../elixir_lang/psi/scope/atom/Variants.java | 81 +++++++++++++++++++ src/org/elixir_lang/reference/Atom.java | 49 +++++++++++ 13 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 src/org/elixir_lang/psi/scope/Atom.java create mode 100644 src/org/elixir_lang/psi/scope/atom/Variants.java create mode 100644 src/org/elixir_lang/reference/Atom.java diff --git a/gen/org/elixir_lang/psi/ElixirAlias.java b/gen/org/elixir_lang/psi/ElixirAlias.java index fa9242f94..af245961f 100644 --- a/gen/org/elixir_lang/psi/ElixirAlias.java +++ b/gen/org/elixir_lang/psi/ElixirAlias.java @@ -3,6 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; @@ -23,6 +24,9 @@ public interface ElixirAlias extends NamedElement, QualifiableAlias, Quotable { @Nullable PsiReference getReference(); + @Nullable + PsiPolyVariantReference getReference(PsiElement maxScope); + boolean isModuleName(); boolean processDeclarations(PsiScopeProcessor processor, ResolveState state, PsiElement lastParent, PsiElement place); diff --git a/gen/org/elixir_lang/psi/ElixirAtom.java b/gen/org/elixir_lang/psi/ElixirAtom.java index 8db7baeec..5afcff421 100644 --- a/gen/org/elixir_lang/psi/ElixirAtom.java +++ b/gen/org/elixir_lang/psi/ElixirAtom.java @@ -3,6 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.psi.NavigatablePsiElement; +import com.intellij.psi.PsiReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,6 +15,9 @@ public interface ElixirAtom extends NavigatablePsiElement, Quotable { @Nullable ElixirStringLine getStringLine(); + @Nullable + PsiReference getReference(); + @NotNull OtpErlangObject quote(); diff --git a/gen/org/elixir_lang/psi/ElixirMatchedQualifiedAlias.java b/gen/org/elixir_lang/psi/ElixirMatchedQualifiedAlias.java index 3120a5578..12c3c86f7 100644 --- a/gen/org/elixir_lang/psi/ElixirMatchedQualifiedAlias.java +++ b/gen/org/elixir_lang/psi/ElixirMatchedQualifiedAlias.java @@ -3,6 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; @@ -32,6 +33,9 @@ public interface ElixirMatchedQualifiedAlias extends ElixirMatchedExpression, Na @Nullable PsiReference getReference(); + @Nullable + PsiPolyVariantReference getReference(PsiElement maxScope); + boolean isModuleName(); boolean processDeclarations(PsiScopeProcessor processor, ResolveState state, PsiElement lastParent, PsiElement place); diff --git a/gen/org/elixir_lang/psi/ElixirUnmatchedQualifiedAlias.java b/gen/org/elixir_lang/psi/ElixirUnmatchedQualifiedAlias.java index cd24f76d0..ab7d31e82 100644 --- a/gen/org/elixir_lang/psi/ElixirUnmatchedQualifiedAlias.java +++ b/gen/org/elixir_lang/psi/ElixirUnmatchedQualifiedAlias.java @@ -3,6 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; @@ -32,6 +33,9 @@ public interface ElixirUnmatchedQualifiedAlias extends ElixirUnmatchedExpression @Nullable PsiReference getReference(); + @Nullable + PsiPolyVariantReference getReference(PsiElement maxScope); + boolean isModuleName(); boolean processDeclarations(PsiScopeProcessor processor, ResolveState state, PsiElement lastParent, PsiElement place); diff --git a/gen/org/elixir_lang/psi/impl/ElixirAliasImpl.java b/gen/org/elixir_lang/psi/impl/ElixirAliasImpl.java index a29368bd4..56b9191b2 100644 --- a/gen/org/elixir_lang/psi/impl/ElixirAliasImpl.java +++ b/gen/org/elixir_lang/psi/impl/ElixirAliasImpl.java @@ -4,10 +4,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiReference; -import com.intellij.psi.ResolveState; +import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import org.elixir_lang.psi.ElixirAlias; import org.elixir_lang.psi.ElixirVisitor; @@ -49,6 +46,11 @@ public PsiReference getReference() { return ElixirPsiImplUtil.getReference(this); } + @Nullable + public PsiPolyVariantReference getReference(PsiElement maxScope) { + return ElixirPsiImplUtil.getReference(this, maxScope); + } + public boolean isModuleName() { return ElixirPsiImplUtil.isModuleName(this); } diff --git a/gen/org/elixir_lang/psi/impl/ElixirAtomImpl.java b/gen/org/elixir_lang/psi/impl/ElixirAtomImpl.java index 5cf3f94df..098d9ea97 100644 --- a/gen/org/elixir_lang/psi/impl/ElixirAtomImpl.java +++ b/gen/org/elixir_lang/psi/impl/ElixirAtomImpl.java @@ -5,6 +5,7 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import org.elixir_lang.psi.ElixirAtom; import org.elixir_lang.psi.ElixirCharListLine; @@ -40,6 +41,11 @@ public ElixirStringLine getStringLine() { return PsiTreeUtil.getChildOfType(this, ElixirStringLine.class); } + @Nullable + public PsiReference getReference() { + return ElixirPsiImplUtil.getReference(this); + } + @NotNull public OtpErlangObject quote() { return ElixirPsiImplUtil.quote(this); diff --git a/gen/org/elixir_lang/psi/impl/ElixirMatchedQualifiedAliasImpl.java b/gen/org/elixir_lang/psi/impl/ElixirMatchedQualifiedAliasImpl.java index 74683e074..ae4f11a8e 100644 --- a/gen/org/elixir_lang/psi/impl/ElixirMatchedQualifiedAliasImpl.java +++ b/gen/org/elixir_lang/psi/impl/ElixirMatchedQualifiedAliasImpl.java @@ -3,10 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiReference; -import com.intellij.psi.ResolveState; +import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import org.elixir_lang.psi.*; @@ -66,6 +63,11 @@ public PsiReference getReference() { return ElixirPsiImplUtil.getReference(this); } + @Nullable + public PsiPolyVariantReference getReference(PsiElement maxScope) { + return ElixirPsiImplUtil.getReference(this, maxScope); + } + public boolean isModuleName() { return ElixirPsiImplUtil.isModuleName(this); } diff --git a/gen/org/elixir_lang/psi/impl/ElixirUnmatchedQualifiedAliasImpl.java b/gen/org/elixir_lang/psi/impl/ElixirUnmatchedQualifiedAliasImpl.java index b7a89ffaa..b69646d9a 100644 --- a/gen/org/elixir_lang/psi/impl/ElixirUnmatchedQualifiedAliasImpl.java +++ b/gen/org/elixir_lang/psi/impl/ElixirUnmatchedQualifiedAliasImpl.java @@ -3,10 +3,7 @@ import com.ericsson.otp.erlang.OtpErlangObject; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiReference; -import com.intellij.psi.ResolveState; +import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import org.elixir_lang.psi.*; @@ -66,6 +63,11 @@ public PsiReference getReference() { return ElixirPsiImplUtil.getReference(this); } + @Nullable + public PsiPolyVariantReference getReference(PsiElement maxScope) { + return ElixirPsiImplUtil.getReference(this, maxScope); + } + public boolean isModuleName() { return ElixirPsiImplUtil.isModuleName(this); } diff --git a/src/org/elixir_lang/Elixir.bnf b/src/org/elixir_lang/Elixir.bnf index 7a3c7d36e..6b0a349d1 100644 --- a/src/org/elixir_lang/Elixir.bnf +++ b/src/org/elixir_lang/Elixir.bnf @@ -2772,7 +2772,10 @@ atom ::= COLON (ATOM_FRAGMENT | quote) "com.intellij.psi.NavigatablePsiElement" "org.elixir_lang.psi.Quotable" ] - methods = [quote] + methods = [ + getReference + quote + ] } private infixComma ::= COMMA EOL* diff --git a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java index 9d6db12d2..c8668ae0f 100644 --- a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java +++ b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java @@ -3230,6 +3230,19 @@ private static PsiPolyVariantReference computeReference(@NotNull QualifiableAlia return reference; } + @Nullable + public static PsiReference getReference(@NotNull ElixirAtom atom) { + return CachedValuesManager.getCachedValue( + atom, + () -> CachedValueProvider.Result.create(computeReference(atom), atom) + ); + } + + @NotNull + private static PsiReference computeReference(@NotNull ElixirAtom atom) { + return new org.elixir_lang.reference.Atom(atom); + } + @Nullable public static PsiReference getReference(@NotNull QualifiableAlias qualifiableAlias) { return getReference(qualifiableAlias, qualifiableAlias.getContainingFile()); diff --git a/src/org/elixir_lang/psi/scope/Atom.java b/src/org/elixir_lang/psi/scope/Atom.java new file mode 100644 index 000000000..8c7d2d5ea --- /dev/null +++ b/src/org/elixir_lang/psi/scope/Atom.java @@ -0,0 +1,30 @@ +package org.elixir_lang.psi.scope; + +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.ResolveState; +import com.intellij.psi.scope.PsiScopeProcessor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Atom implements PsiScopeProcessor { + /** + * @param element candidate element. + * @param state current state of resolver. + * @return false to stop processing. + */ + @Override + public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { + return false; + } + + @Nullable + @Override + public T getHint(@NotNull Key hintKey) { + return null; + } + + @Override + public void handleEvent(@NotNull Event event, @Nullable Object associated) { + } +} diff --git a/src/org/elixir_lang/psi/scope/atom/Variants.java b/src/org/elixir_lang/psi/scope/atom/Variants.java new file mode 100644 index 000000000..28652c7a3 --- /dev/null +++ b/src/org/elixir_lang/psi/scope/atom/Variants.java @@ -0,0 +1,81 @@ +package org.elixir_lang.psi.scope.atom; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.StubIndex; +import com.intellij.util.containers.ContainerUtil; +import org.elixir_lang.psi.NamedElement; +import org.elixir_lang.psi.scope.Atom; +import org.elixir_lang.psi.stub.index.AllName; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Variants extends Atom { + @NotNull + public static List lookupElementList(@NotNull PsiElement entrance) { + return new Variants().projectLookupElementStream(entrance); + } + + private List projectLookupElementStream(@NotNull PsiElement entrance) { + Project project = entrance.getProject(); + /* getAllKeys is not the actual keys in the actual project. They need to be checked. + See https://intellij-support.jetbrains.com/hc/en-us/community/posts/207930789-StubIndex-persisting-between-test-runs-leading-to-incorrect-completions */ + Collection indexedNameCollection = StubIndex.getInstance().getAllKeys(AllName.KEY, project); + GlobalSearchScope scope = GlobalSearchScope.allScope(project); + + Collection atomNameCollection = atomNameCollection(indexedNameCollection); + String prefix = prefix(entrance); + Collection prefixedNameCollection = prefixedNameCollection(atomNameCollection, prefix); + List lookupElementList = new ArrayList<>(); + + for (String atomName : prefixedNameCollection) { + Collection atomNamedElementCollection = StubIndex.getElements( + AllName.KEY, + atomName, + project, + scope, + NamedElement.class + ); + + for (NamedElement atomNamedElement : atomNamedElementCollection) { + PsiElement navigationElement = atomNamedElement.getNavigationElement(); + lookupElementList.add( + LookupElementBuilder.createWithSmartPointer(atomName, navigationElement) + ); + } + } + + return lookupElementList; + } + + @Contract(pure = true) + @NotNull + private Collection prefixedNameCollection(Collection atomNameCollection, String prefix) { + return ContainerUtil.filter(atomNameCollection, atomName -> atomName.startsWith(prefix)); + } + + @Contract(pure = true) + @NotNull + private static String prefix(PsiElement atom) { + return atom.getText().replace("IntellijIdeaRulezzz", ""); + } + + @Contract(pure = true) + @NotNull + private static Collection atomNameCollection(@NotNull Collection indexedNameCollection) { + return ContainerUtil.filter(indexedNameCollection, Variants::isAtomName); + } + + @Contract(value = "null -> false", pure = true) + private static boolean isAtomName(@Nullable String indexedName) { + return indexedName != null && indexedName.startsWith(":"); + } +} diff --git a/src/org/elixir_lang/reference/Atom.java b/src/org/elixir_lang/reference/Atom.java new file mode 100644 index 000000000..c59384b21 --- /dev/null +++ b/src/org/elixir_lang/reference/Atom.java @@ -0,0 +1,49 @@ +package org.elixir_lang.reference; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; +import com.intellij.psi.PsiReferenceBase; +import com.intellij.psi.ResolveResult; +import org.elixir_lang.psi.ElixirAtom; +import org.elixir_lang.psi.scope.atom.Variants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class Atom extends PsiReferenceBase implements PsiPolyVariantReference { + public Atom(ElixirAtom atom) { + super(atom, TextRange.create(0, atom.getTextLength())); + } + + @NotNull + @Override + public Object[] getVariants() { + List lookupElementList = Variants.lookupElementList(myElement); + + return lookupElementList.toArray(new Object[lookupElementList.size()]); + } + + /** + * Returns the results of resolving the reference. + * + * @param incompleteCode if true, the code in the context of which the reference is + * being resolved is considered incomplete, and the method may return additional + * invalid results. + * @return the array of results for resolving the reference. + */ + @NotNull + @Override + public ResolveResult[] multiResolve(boolean incompleteCode) { + return new ResolveResult[0]; + } + + @Nullable + @Override + public PsiElement resolve() { + ResolveResult[] resolveResults = multiResolve(false); + return resolveResults.length == 1 ? resolveResults[0].getElement() : null; + } +} From 0c060ba792828c8efbfba3a108439895f3adc132 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 30 Sep 2017 07:39:40 -0500 Subject: [PATCH 3/5] Resolve atom to indexed atoms Resolve atom to atom in index with same name, which is usually an Erlang SDK module. If the atom contains interpolation, treat that as `.*` in a regex and show all matches to result regex. --- src/org/elixir_lang/Reference.java | 93 +++++++++++ .../psi/impl/ElixirPsiImplUtil.java | 4 +- .../psi/scope/CallDefinitionClause.java | 4 +- .../psi/scope/module/Variants.java | 5 +- src/org/elixir_lang/reference/Atom.java | 5 +- src/org/elixir_lang/reference/Module.java | 86 +--------- .../elixir_lang/reference/resolver/Atom.java | 22 +++ .../reference/resolver/Module.java | 2 +- .../reference/resolver/atom/Resolvable.java | 149 ++++++++++++++++++ .../resolver/atom/resolvable/Exact.java | 38 +++++ .../resolver/atom/resolvable/Pattern.java | 46 ++++++ 11 files changed, 361 insertions(+), 93 deletions(-) create mode 100644 src/org/elixir_lang/Reference.java create mode 100644 src/org/elixir_lang/reference/resolver/Atom.java create mode 100644 src/org/elixir_lang/reference/resolver/atom/Resolvable.java create mode 100644 src/org/elixir_lang/reference/resolver/atom/resolvable/Exact.java create mode 100644 src/org/elixir_lang/reference/resolver/atom/resolvable/Pattern.java diff --git a/src/org/elixir_lang/Reference.java b/src/org/elixir_lang/Reference.java new file mode 100644 index 000000000..6bd8b2605 --- /dev/null +++ b/src/org/elixir_lang/Reference.java @@ -0,0 +1,93 @@ +package org.elixir_lang; + +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.StubIndex; +import com.intellij.util.Function; +import org.elixir_lang.psi.NamedElement; +import org.elixir_lang.psi.stub.index.AllName; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Stream; + +public class Reference { + @NotNull + public static Collection indexedNameCollection(@NotNull Project project) { + return StubIndex.getInstance().getAllKeys(AllName.KEY, project); + } + + @NotNull + public static Stream indexedNameStream(@NotNull Project project) { + return indexedNameCollection(project).stream(); + } + + /** + * Iterates over each navigation element for the PsiElements with {@code name} in {@code project}. + * + * @param project Whose index to search for {@code name} + * @param name Name to search for in {@code project} StubIndex + * @param function return {@code false} to stop iteration early + * @return {@code true} if all calls to {@code function} returned {@code true} + */ + public static boolean forEachNavigationElement(@NotNull Project project, + @NotNull String name, + @NotNull Function function) { + Collection namedElementCollection = namedElementCollection(project, name); + + return forEachNavigationElement(namedElementCollection, function); + } + + /** + * Iterates over each navigation element for the PsiElements in {@code psiElementCollection}. + * + * @param psiElementCollection Collection of PsiElements that aren't guaranteed to be navigation elements, such as + * the binary elements in {@code .beam} files. + * @param function Return {@code false} to stop processing and abandon enumeration early + * @return {@code true} if all calls to {@code function} returned {@code true} + */ + private static boolean forEachNavigationElement(@NotNull Collection psiElementCollection, + @NotNull Function function) { + boolean keepProcessing = true; + + for (PsiElement psiElement : psiElementCollection) { + /* The psiElement may be a ModuleImpl from a .beam. Using #getNaviationElement() ensures a source + (either true source or decompiled) is used. */ + keepProcessing = function.fun(psiElement.getNavigationElement()); + + if (!keepProcessing) { + break; + } + } + + return keepProcessing; + } + + public static Collection namedElementCollection(@NotNull Project project, @NotNull String name) { + return namedElementCollection(project, GlobalSearchScope.allScope(project), name); + } + + public static Collection namedElementCollection(@NotNull Project project, + @NotNull GlobalSearchScope scope, + @NotNull String name) { + Collection namedElementCollection; + + if (DumbService.isDumb(project)) { + namedElementCollection = Collections.emptyList(); + } else { + namedElementCollection = StubIndex.getElements( + AllName.KEY, + name, + project, + scope, + NamedElement.class + ); + } + + return namedElementCollection; + + } +} diff --git a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java index c8668ae0f..735e5423a 100644 --- a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java +++ b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java @@ -5650,7 +5650,7 @@ public static IElementType validElementType(@NotNull @SuppressWarnings("unused") * Private static methods */ - private static ASTNode[] childNodes(PsiElement parentElement) { + public static ASTNode[] childNodes(PsiElement parentElement) { ASTNode parentNode = parentElement.getNode(); return parentNode.getChildren(null); } @@ -5801,7 +5801,7 @@ private static List ensureCodePointList(@Nullable List codePoi } @NotNull - private static List addChildTextCodePoints(@Nullable List codePointList, @NotNull ASTNode child) { + public static List addChildTextCodePoints(@Nullable List codePointList, @NotNull ASTNode child) { return addStringCodePoints(codePointList, child.getText()); } diff --git a/src/org/elixir_lang/psi/scope/CallDefinitionClause.java b/src/org/elixir_lang/psi/scope/CallDefinitionClause.java index ad4186f9c..e5688887c 100644 --- a/src/org/elixir_lang/psi/scope/CallDefinitionClause.java +++ b/src/org/elixir_lang/psi/scope/CallDefinitionClause.java @@ -129,7 +129,7 @@ public Boolean fun(Call callDefinitionClause) { private boolean implicitImports(@NotNull PsiElement element, @NotNull ResolveState state) { Project project = element.getProject(); - boolean keepProcessing = org.elixir_lang.reference.Module.forEachNavigationElement( + boolean keepProcessing = org.elixir_lang.Reference.forEachNavigationElement( project, KERNEL, new Function() { @@ -159,7 +159,7 @@ public Boolean fun(Call callDefinitionClause) { // the implicit `import Kernel.SpecialForms` if (keepProcessing) { ResolveState modularCanonicalNameState = state.put(MODULAR_CANONICAL_NAME, KERNEL_SPECIAL_FORMS); - keepProcessing = org.elixir_lang.reference.Module.forEachNavigationElement( + keepProcessing = org.elixir_lang.Reference.forEachNavigationElement( project, KERNEL_SPECIAL_FORMS, new Function() { diff --git a/src/org/elixir_lang/psi/scope/module/Variants.java b/src/org/elixir_lang/psi/scope/module/Variants.java index f5d2fadfa..129016ff8 100644 --- a/src/org/elixir_lang/psi/scope/module/Variants.java +++ b/src/org/elixir_lang/psi/scope/module/Variants.java @@ -26,6 +26,7 @@ import static com.intellij.psi.util.PsiTreeUtil.treeWalkUp; import static org.elixir_lang.Module.concat; import static org.elixir_lang.Module.split; +import static org.elixir_lang.Reference.indexedNameCollection; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.ENTRANCE; public class Variants extends Module { @@ -230,9 +231,7 @@ protected boolean executeOnAliasedName(@NotNull PsiNamedElement match, @NotNull if (unaliasedName != null) { Project project = match.getProject(); - Collection indexedNameCollection = StubIndex - .getInstance() - .getAllKeys(AllName.KEY, project); + Collection indexedNameCollection = indexedNameCollection(project); List unaliasedNestedNames = ContainerUtil.findAll( indexedNameCollection, new org.elixir_lang.Module.IsNestedUnder(unaliasedName) diff --git a/src/org/elixir_lang/reference/Atom.java b/src/org/elixir_lang/reference/Atom.java index c59384b21..5c8075212 100644 --- a/src/org/elixir_lang/reference/Atom.java +++ b/src/org/elixir_lang/reference/Atom.java @@ -6,6 +6,7 @@ import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReferenceBase; import com.intellij.psi.ResolveResult; +import com.intellij.psi.impl.source.resolve.ResolveCache; import org.elixir_lang.psi.ElixirAtom; import org.elixir_lang.psi.scope.atom.Variants; import org.jetbrains.annotations.NotNull; @@ -37,7 +38,9 @@ public Object[] getVariants() { @NotNull @Override public ResolveResult[] multiResolve(boolean incompleteCode) { - return new ResolveResult[0]; + return ResolveCache + .getInstance(this.myElement.getProject()) + .resolveWithCaching(this, org.elixir_lang.reference.resolver.Atom.INSTANCE, false, incompleteCode); } @Nullable diff --git a/src/org/elixir_lang/reference/Module.java b/src/org/elixir_lang/reference/Module.java index e10c06a90..7c91ca4a6 100644 --- a/src/org/elixir_lang/reference/Module.java +++ b/src/org/elixir_lang/reference/Module.java @@ -1,44 +1,29 @@ package org.elixir_lang.reference; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReferenceBase; import com.intellij.psi.ResolveResult; import com.intellij.psi.impl.source.resolve.ResolveCache; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.stubs.StubIndex; -import com.intellij.util.Function; -import org.elixir_lang.psi.NamedElement; import org.elixir_lang.psi.QualifiableAlias; import org.elixir_lang.psi.scope.module.Variants; -import org.elixir_lang.psi.stub.index.AllName; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Collections; import java.util.List; public class Module extends PsiReferenceBase implements PsiPolyVariantReference { /* - * - * Static Methods - * - */ - - /* - * Public Static Methods + * Fields */ @NotNull public final PsiElement maxScope; /* - * Private Static Methods + * Constructors */ public Module(@NotNull QualifiableAlias qualifiableAlias, @NotNull PsiElement maxScope) { @@ -46,73 +31,6 @@ public Module(@NotNull QualifiableAlias qualifiableAlias, @NotNull PsiElement ma this.maxScope = maxScope; } - /** - * Iterates over each navigation element for the PsiElements with {@code name} in {@code project}. - * - * @param project Whose index to search for {@code name} - * @param name Name to search for in {@code project} StubIndex - * @param function return {@code false} to stop iteration early - * @return {@code true} if all calls to {@code function} returned {@code true} - */ - public static boolean forEachNavigationElement(@NotNull Project project, - @NotNull String name, - @NotNull Function function) { - Collection namedElementCollection = namedElementCollection(project, name); - - return forEachNavigationElement(namedElementCollection, function); - } - - /* - * Fields - */ - - /** - * Iterates over each navigation element for the PsiElements in {@code psiElementCollection}. - * - * @param psiElementCollection Collection of PsiElements that aren't guaranteed to be navigation elements, such as - * the binary elements in {@code .beam} files. - * @param function Return {@code false} to stop processing and abandon enumeration early - * @return {@code true} if all calls to {@code function} returned {@code true} - */ - private static boolean forEachNavigationElement(@NotNull Collection psiElementCollection, - @NotNull Function function) { - boolean keepProcessing = true; - - for (PsiElement psiElement : psiElementCollection) { - /* The psiElement may be a ModuleImpl from a .beam. Using #getNaviationElement() ensures a source - (either true source or decompiled) is used. */ - keepProcessing = function.fun(psiElement.getNavigationElement()); - - if (!keepProcessing) { - break; - } - } - - return keepProcessing; - } - - /* - * Constructors - */ - - private static Collection namedElementCollection(@NotNull Project project, @NotNull String name) { - Collection namedElementCollection; - - if (DumbService.isDumb(project)) { - namedElementCollection = Collections.emptyList(); - } else { - namedElementCollection = StubIndex.getElements( - AllName.KEY, - name, - project, - GlobalSearchScope.allScope(project), - NamedElement.class - ); - } - - return namedElementCollection; - } - /* * * Instance Methods diff --git a/src/org/elixir_lang/reference/resolver/Atom.java b/src/org/elixir_lang/reference/resolver/Atom.java new file mode 100644 index 000000000..b72f038e2 --- /dev/null +++ b/src/org/elixir_lang/reference/resolver/Atom.java @@ -0,0 +1,22 @@ +package org.elixir_lang.reference.resolver; + +import com.intellij.psi.ResolveResult; +import com.intellij.psi.impl.source.resolve.ResolveCache; +import org.elixir_lang.psi.ElixirAtom; +import org.elixir_lang.reference.resolver.atom.Resolvable; +import org.jetbrains.annotations.NotNull; + +import static org.elixir_lang.reference.resolver.atom.Resolvable.resolvable; + +public class Atom implements ResolveCache.PolyVariantResolver{ + public static final Atom INSTANCE = new Atom(); + + @NotNull + @Override + public ResolveResult[] resolve(@NotNull org.elixir_lang.reference.Atom atom, boolean incompleteCode) { + ElixirAtom element = atom.getElement(); + Resolvable resolvable = resolvable(element); + + return resolvable.resolve(element.getProject()); + } +} diff --git a/src/org/elixir_lang/reference/resolver/Module.java b/src/org/elixir_lang/reference/resolver/Module.java index 9b30bfb32..30416aab0 100644 --- a/src/org/elixir_lang/reference/resolver/Module.java +++ b/src/org/elixir_lang/reference/resolver/Module.java @@ -11,7 +11,7 @@ import java.util.ArrayList; import java.util.List; -import static org.elixir_lang.reference.Module.forEachNavigationElement; +import static org.elixir_lang.Reference.forEachNavigationElement; import static org.elixir_lang.reference.module.ResolvableName.resolvableName; public class Module implements ResolveCache.PolyVariantResolver { diff --git a/src/org/elixir_lang/reference/resolver/atom/Resolvable.java b/src/org/elixir_lang/reference/resolver/atom/Resolvable.java new file mode 100644 index 000000000..f32906eab --- /dev/null +++ b/src/org/elixir_lang/reference/resolver/atom/Resolvable.java @@ -0,0 +1,149 @@ +package org.elixir_lang.reference.resolver.atom; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.ResolveResult; +import com.intellij.psi.tree.IElementType; +import org.apache.commons.lang.NotImplementedException; +import org.elixir_lang.psi.*; +import org.elixir_lang.reference.resolver.atom.resolvable.Exact; +import org.elixir_lang.reference.resolver.atom.resolvable.Pattern; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedList; +import java.util.List; + +import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.addChildTextCodePoints; +import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.childNodes; + +/** + * How to resolve an {@link ElixirAtom}. + *

+ * If the {ElixirAtom} is a normal, unquoted atom, it can be resolved exactly, but if it's quoted and contains + * interpolation, then it cannot be resolved exactly. + */ +public abstract class Resolvable { + @NotNull + public static Resolvable resolvable(@NotNull ElixirAtom atom) { + ElixirCharListLine charListLine = atom.getCharListLine(); + Resolvable resolvable; + + if (charListLine != null) { + resolvable = resolvable(charListLine); + } else { + ElixirStringLine stringLine = atom.getStringLine(); + + if (stringLine != null) { + resolvable = resolvable(stringLine); + } else { + ASTNode atomNode = atom.getNode(); + ASTNode atomFragmentNode = atomNode.getLastChildNode(); + + assert atomFragmentNode.getElementType() == ElixirTypes.ATOM_FRAGMENT; + + resolvable = new Exact(":" + atomFragmentNode.getText()); + } + } + + return resolvable; + } + + @NotNull + private static Resolvable resolvable(@NotNull I parentBodied) { + Body body = parentBodied.getBody(); + + return resolvable(parentBodied, childNodes(body)); + } + + @NotNull + private static Resolvable resolvable(@NotNull Parent parent, @NotNull ASTNode[] children) { + Resolvable resolvable; + + if (children.length == 0) { + resolvable = new Exact(":\"\""); + } else { + List regexList = new LinkedList<>(); + List codePointList = null; + + for (ASTNode child : children) { + IElementType elementType = child.getElementType(); + + if (elementType == parent.getFragmentType()) { + codePointList = parent.addFragmentCodePoints(codePointList, child); + } else if (elementType == ElixirTypes.ESCAPED_CHARACTER) { + codePointList = parent.addEscapedCharacterCodePoints(codePointList, child); + } else if (elementType == ElixirTypes.ESCAPED_EOL) { + codePointList = parent.addEscapedEOL(codePointList, child); + } else if (elementType == ElixirTypes.HEXADECIMAL_ESCAPE_PREFIX) { + codePointList = addChildTextCodePoints(codePointList, child); + } else if (elementType == ElixirTypes.INTERPOLATION) { + if (codePointList != null) { + regexList.add(codePointListToString(codePointList)); + codePointList = null; + } + + regexList.add(interpolation()); + } else if (elementType == ElixirTypes.QUOTE_HEXADECIMAL_ESCAPE_SEQUENCE || + elementType == ElixirTypes.SIGIL_HEXADECIMAL_ESCAPE_SEQUENCE) { + codePointList = parent.addHexadecimalEscapeSequenceCodePoints(codePointList, child); + } else { + throw new NotImplementedException("Can't convert to Resolvable " + child); + } + } + + if (codePointList != null && regexList.isEmpty()) { + resolvable = resolvableLiteral(codePointList); + } else { + if (codePointList != null) { + regexList.add(codePointListToRegex(codePointList)); + } + + resolvable = new Pattern(join(regexList)); + } + } + + return resolvable; + } + + @NotNull + private static String join(List regexList) { + return String.join("", regexList); + } + + @Contract(pure = true) + @NotNull + private static String interpolation() { + return ".*"; + } + + @NotNull + private static String codePointListToRegex(@NotNull List codePointList) { + String string = codePointListToString(codePointList); + return java.util.regex.Pattern.quote(string); + } + + @NotNull + private static String codePointListToString(@NotNull List codePointList) { + StringBuilder stringAccumulator = new StringBuilder(); + + for (int codePoint : codePointList) { + stringAccumulator.appendCodePoint(codePoint); + } + + return stringAccumulator.toString(); + } + + @NotNull + private static Resolvable resolvableLiteral(List codePointList) { + StringBuilder stringAccumulator = new StringBuilder(); + + for (int codePoint : codePointList) { + stringAccumulator.appendCodePoint(codePoint); + } + + return new Exact(":" + stringAccumulator.toString()); + } + + public abstract ResolveResult[] resolve(@NotNull Project project); +} diff --git a/src/org/elixir_lang/reference/resolver/atom/resolvable/Exact.java b/src/org/elixir_lang/reference/resolver/atom/resolvable/Exact.java new file mode 100644 index 000000000..332775101 --- /dev/null +++ b/src/org/elixir_lang/reference/resolver/atom/resolvable/Exact.java @@ -0,0 +1,38 @@ +package org.elixir_lang.reference.resolver.atom.resolvable; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElementResolveResult; +import com.intellij.psi.ResolveResult; +import org.elixir_lang.reference.resolver.atom.Resolvable; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import static org.elixir_lang.Reference.forEachNavigationElement; + +public class Exact extends Resolvable { + @NotNull + private final String name; + + public Exact(@NotNull String name) { + this.name = name; + } + + @Override + public ResolveResult[] resolve(@NotNull Project project) { + List resolveResultList = new ArrayList<>(); + + forEachNavigationElement( + project, + name, + navigationElement -> { + resolveResultList.add(new PsiElementResolveResult(navigationElement)); + + return true; + } + ); + + return resolveResultList.toArray(new ResolveResult[resolveResultList.size()]); + } +} diff --git a/src/org/elixir_lang/reference/resolver/atom/resolvable/Pattern.java b/src/org/elixir_lang/reference/resolver/atom/resolvable/Pattern.java new file mode 100644 index 000000000..9c79c1479 --- /dev/null +++ b/src/org/elixir_lang/reference/resolver/atom/resolvable/Pattern.java @@ -0,0 +1,46 @@ +package org.elixir_lang.reference.resolver.atom.resolvable; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementResolveResult; +import com.intellij.psi.ResolveResult; +import com.intellij.psi.search.GlobalSearchScope; +import org.elixir_lang.reference.resolver.atom.Resolvable; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Predicate; + +import static org.elixir_lang.Reference.indexedNameStream; +import static org.elixir_lang.Reference.namedElementCollection; + +public class Pattern extends Resolvable { + @NotNull + private final Predicate predicate; + + public Pattern(@NotNull String regex) { + this(java.util.regex.Pattern.compile(":" + regex)); + } + + public Pattern(@NotNull java.util.regex.Pattern pattern) { + this(pattern.asPredicate()); + } + + public Pattern(@NotNull Predicate predicate) { + this.predicate = predicate; + } + + @Override + public ResolveResult[] resolve(@NotNull Project project) { + GlobalSearchScope scope = GlobalSearchScope.allScope(project); + return indexedNameStream(project) + .filter(predicate) + .flatMap(name -> + namedElementCollection(project, scope, name) + .stream() + .map(PsiElement::getNavigationElement) + .map(navigationElement -> + (ResolveResult) new PsiElementResolveResult(navigationElement, false) + ) + ).toArray(ResolveResult[]::new); + } +} From bf23a4803e8d06f039a46ec5fbd669db2f1b77b3 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 30 Sep 2017 13:47:00 -0500 Subject: [PATCH 4/5] Complete functions in modules with atom names after `.` --- .../provider/CallDefinitionClause.java | 7 +++--- src/org/elixir_lang/psi/Import.java | 2 +- .../psi/impl/ElixirPsiImplUtil.java | 25 +++++++++++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java b/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java index 48cc8d6f9..64cb94ca3 100644 --- a/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java +++ b/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java @@ -9,7 +9,6 @@ import com.intellij.util.ProcessingContext; import org.apache.commons.lang.math.IntRange; import org.elixir_lang.psi.call.Call; -import org.elixir_lang.psi.impl.ElixirPsiImplUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -17,6 +16,7 @@ import java.util.List; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.macroChildCalls; +import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.maybeModularNameToModular; import static org.elixir_lang.structure_view.element.CallDefinitionClause.nameArityRange; public class CallDefinitionClause extends CompletionProvider { @@ -79,10 +79,11 @@ protected void addCompletions(@NotNull CompletionParameters parameters, PsiElement grandParent = originalParent.getParent(); if (grandParent instanceof org.elixir_lang.psi.qualification.Qualified) { - org.elixir_lang.psi.qualification.Qualified qualifiedGrandParent = (org.elixir_lang.psi.qualification.Qualified) grandParent; + org.elixir_lang.psi.qualification.Qualified qualifiedGrandParent = + (org.elixir_lang.psi.qualification.Qualified) grandParent; PsiElement qualifier = qualifiedGrandParent.qualifier(); - Call modular = ElixirPsiImplUtil.maybeAliasToModular(qualifier, qualifier.getContainingFile()); + Call modular = maybeModularNameToModular(qualifier, qualifier.getContainingFile()); if (modular != null) { if (resultSet.getPrefixMatcher().getPrefix().endsWith(".")) { diff --git a/src/org/elixir_lang/psi/Import.java b/src/org/elixir_lang/psi/Import.java index f8072aaac..eaa26cf0d 100644 --- a/src/org/elixir_lang/psi/Import.java +++ b/src/org/elixir_lang/psi/Import.java @@ -271,7 +271,7 @@ private static Call modular(@NotNull Call importCall) { Call modular = null; if (finalArguments != null && finalArguments.length >= 1) { - modular = maybeAliasToModular(finalArguments[0], importCall.getParent()); + modular = maybeModularNameToModular(finalArguments[0], importCall.getParent()); } return modular; diff --git a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java index 735e5423a..cc3ac4275 100644 --- a/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java +++ b/src/org/elixir_lang/psi/impl/ElixirPsiImplUtil.java @@ -5731,7 +5731,7 @@ private static Queue mergeFragments(@NotNull Deque unmergedNod @Contract(pure = true) @Nullable public static Call qualifiedToModular(@NotNull final org.elixir_lang.psi.call.qualification.Qualified qualified) { - return maybeAliasToModular(qualified.qualifier(), qualified.getContainingFile()); + return maybeModularNameToModular(qualified.qualifier(), qualified.getContainingFile()); } @NotNull @@ -5890,13 +5890,28 @@ public static List addHexadecimalEscapeSequenceCodePoints(@NotNull @Sup */ @Contract(pure = true) @Nullable - public static Call maybeAliasToModular(@NotNull final PsiElement maybeAlias, @NotNull PsiElement maxScope) { - PsiElement maybeQualifiableAlias = stripAccessExpression(maybeAlias); + public static Call maybeModularNameToModular(@NotNull final PsiElement maybeModularName, @NotNull PsiElement maxScope) { + PsiElement strippedMaybeModuleName = stripAccessExpression(maybeModularName); Call modular = null; - if (maybeQualifiableAlias instanceof QualifiableAlias) { - QualifiableAlias qualifiableAlias = (QualifiableAlias) maybeQualifiableAlias; + if (strippedMaybeModuleName instanceof ElixirAtom) { + ElixirAtom atom = (ElixirAtom) strippedMaybeModuleName; + PsiReference reference = atom.getReference(); + + if (reference != null) { + final PsiElement resolved = reference.resolve(); + + if (resolved != null && resolved instanceof Call) { + Call call = (Call) resolved; + + if (isModular(call)) { + modular = call; + } + } + } + } else if (strippedMaybeModuleName instanceof QualifiableAlias) { + QualifiableAlias qualifiableAlias = (QualifiableAlias) strippedMaybeModuleName; if (!recursiveKernelImport(qualifiableAlias, maxScope)) { /* need to construct reference directly as qualified aliases don't return a reference except for the From 6ded8885c8c06e89947c4fde66a4c446b35aa4d9 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Sat, 30 Sep 2017 14:26:54 -0500 Subject: [PATCH 5/5] Completions for functions after . at end of file Previously, completions only worked in the middle of a file, where the `.` could parse the next work an existing call, now if that parse doesn't work and the only thing after is a new line, it will still complete. --- .../provider/CallDefinitionClause.java | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java b/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java index 64cb94ca3..b3742109d 100644 --- a/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java +++ b/src/org/elixir_lang/code_insight/completion/provider/CallDefinitionClause.java @@ -8,8 +8,11 @@ import com.intellij.psi.PsiElement; import com.intellij.util.ProcessingContext; import org.apache.commons.lang.math.IntRange; +import org.elixir_lang.psi.ElixirEndOfExpression; +import org.elixir_lang.psi.ElixirTypes; import org.elixir_lang.psi.call.Call; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -20,10 +23,6 @@ import static org.elixir_lang.structure_view.element.CallDefinitionClause.nameArityRange; public class CallDefinitionClause extends CompletionProvider { - /* - * Private Instance Methods - */ - @NotNull private static Iterable callDefinitionClauseLookupElements(@NotNull Call scope) { Call[] childCalls = macroChildCalls(scope); @@ -61,19 +60,13 @@ private static Iterable callDefinitionClauseLookupElements(@NotNu return lookupElementList; } - /* - * Protected Instance Methods - */ - - @Override - protected void addCompletions(@NotNull CompletionParameters parameters, - ProcessingContext context, - @NotNull CompletionResultSet resultSet) { + @Nullable + private static PsiElement maybeModularName(@NotNull CompletionParameters parameters) { PsiElement originalPosition = parameters.getOriginalPosition(); - PsiElement originalParent; + PsiElement maybeModularName = null; if (originalPosition != null) { - originalParent = originalPosition.getParent(); + PsiElement originalParent = originalPosition.getParent(); if (originalParent != null) { PsiElement grandParent = originalParent.getParent(); @@ -81,21 +74,44 @@ protected void addCompletions(@NotNull CompletionParameters parameters, if (grandParent instanceof org.elixir_lang.psi.qualification.Qualified) { org.elixir_lang.psi.qualification.Qualified qualifiedGrandParent = (org.elixir_lang.psi.qualification.Qualified) grandParent; - PsiElement qualifier = qualifiedGrandParent.qualifier(); + maybeModularName = qualifiedGrandParent.qualifier(); + } else if (originalParent instanceof ElixirEndOfExpression) { + final int originalParentOffset = originalParent.getTextOffset(); - Call modular = maybeModularNameToModular(qualifier, qualifier.getContainingFile()); + if (originalParentOffset > 0) { + final PsiElement previousElement = + parameters.getOriginalFile().findElementAt(originalParentOffset - 1); - if (modular != null) { - if (resultSet.getPrefixMatcher().getPrefix().endsWith(".")) { - resultSet = resultSet.withPrefixMatcher(""); + if (previousElement != null && + previousElement.getNode().getElementType() == ElixirTypes.DOT_OPERATOR) { + maybeModularName = previousElement.getPrevSibling(); } - - resultSet.addAllElements( - callDefinitionClauseLookupElements(modular) - ); } } } } + + return maybeModularName; + } + + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, + ProcessingContext context, + @NotNull CompletionResultSet resultSet) { + PsiElement maybeModularName = maybeModularName(parameters); + + if (maybeModularName != null) { + Call modular = maybeModularNameToModular(maybeModularName, maybeModularName.getContainingFile()); + + if (modular != null) { + if (resultSet.getPrefixMatcher().getPrefix().endsWith(".")) { + resultSet = resultSet.withPrefixMatcher(""); + } + + resultSet.addAllElements( + callDefinitionClauseLookupElements(modular) + ); + } + } } }