/* * Copyright 2012-2014 Sergey Ignatov * * 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 org.intellij.erlang.runconfig; import com.intellij.execution.actions.ConfigurationContext; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.configurations.ConfigurationType; import com.intellij.openapi.module.Module; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiSearchHelper; import com.intellij.util.Processor; import org.intellij.erlang.ErlangFileType; import org.intellij.erlang.psi.ErlangCompositeElement; import org.intellij.erlang.psi.ErlangFunctionCallExpression; import org.intellij.erlang.psi.ErlangRecursiveVisitor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.Set; public abstract class ErlangDebuggableRunConfigurationProducer<RunConfig extends ErlangRunConfigurationBase> extends RunConfigurationProducer<RunConfig> { protected ErlangDebuggableRunConfigurationProducer(ConfigurationType configurationType) { super(configurationType); } @Override protected final boolean setupConfigurationFromContext(RunConfig runConfig, ConfigurationContext context, Ref<PsiElement> ref) { PsiElement location = ref.get(); return location != null && location.isValid() && setupConfigurationFromContextImpl(runConfig, context, location) && setupDebugOptions(runConfig, context); } protected abstract boolean setupConfigurationFromContextImpl(@NotNull RunConfig runConfig, @NotNull ConfigurationContext context, @NotNull PsiElement target); @Override public final boolean isConfigurationFromContext(RunConfig runConfig, ConfigurationContext context) { PsiElement location = context.getPsiLocation(); return location != null && location.isValid() && Comparing.equal(runConfig.getConfigurationModule().getModule(), context.getModule()) && isConfigurationFromContextImpl(runConfig, context, location); } protected abstract boolean isConfigurationFromContextImpl(@NotNull RunConfig runConfig, @NotNull ConfigurationContext context, @NotNull PsiElement location); private boolean setupDebugOptions(@NotNull RunConfig runConfig, @NotNull ConfigurationContext context) { runConfig.setDebugOptions(createDefaultDebugOptions(context.getModule(), runConfig.isUseTestCodePath())); return true; } public static void updateDebugOptions(@NotNull ErlangRunConfigurationBase runConfig) { ErlangRunConfigurationBase.ErlangDebugOptions debugOptions = runConfig.getDebugOptions(); Module module = runConfig.getConfigurationModule().getModule(); if (debugOptions.isAutoUpdateModulesNotToInterpret() && module != null) { debugOptions.setModulesNotToInterpret(getErlangModulesWithCallsToLoadNIF(module, runConfig.isUseTestCodePath())); } } @NotNull private static ErlangRunConfigurationBase.ErlangDebugOptions createDefaultDebugOptions(@Nullable Module module, boolean includeTests) { ErlangRunConfigurationBase.ErlangDebugOptions debugOptions = new ErlangRunConfigurationBase.ErlangDebugOptions(); debugOptions.setModulesNotToInterpret(getErlangModulesWithCallsToLoadNIF(module, includeTests)); return debugOptions; } @NotNull private static Set<String> getErlangModulesWithCallsToLoadNIF(@Nullable final Module module, boolean includeTests) { if (module == null) return Collections.emptySet(); // We want to process Erlang modules in current module, it's dependencies, and tests if includeTests is true GlobalSearchScope scope = GlobalSearchScope .moduleWithDependenciesAndLibrariesScope(module, includeTests) .intersectWith(GlobalSearchScope.moduleWithDependenciesScope(module)); scope = GlobalSearchScope.getScopeRestrictedByFileTypes(scope, ErlangFileType.MODULE); final HashSet<String> modules = new HashSet<>(); Processor<PsiFile> collector = psiFile -> { String moduleName = psiFile.getVirtualFile().getNameWithoutExtension(); if (!modules.contains(moduleName) && containsErlangLoadNifCall(psiFile)) { modules.add(moduleName); } return true; }; PsiSearchHelper.SERVICE.getInstance(module.getProject()).processAllFilesWithWord("load_nif", scope, collector, true); return modules; } private static boolean containsErlangLoadNifCall(PsiFile psiFile) { final Ref<Boolean> result = new Ref<>(); psiFile.accept(new ErlangRecursiveVisitor() { @Override public void visitCompositeElement(@NotNull ErlangCompositeElement o) { if (!Boolean.TRUE.equals(result.get())) { super.visitCompositeElement(o); } } @Override public void visitFunctionCallExpression(@NotNull ErlangFunctionCallExpression functionCallExpression) { if (Boolean.TRUE.equals(result.get())) return; // we're looking for calls to erlang:load_nif/2 if (!"load_nif".equals(functionCallExpression.getName()) || functionCallExpression.getArgumentList().getExpressionList().size() != 2) { super.visitFunctionCallExpression(functionCallExpression); return; } PsiReference reference = functionCallExpression.getReference(); PsiElement resolve = reference != null ? reference.resolve() : null; if (resolve == null) { super.visitFunctionCallExpression(functionCallExpression); return; } boolean isErlangLoadNif = resolve == reference.getElement(); if (!isErlangLoadNif) { VirtualFile virtualFile = resolve.getOriginalElement().getContainingFile().getVirtualFile(); String moduleName = virtualFile != null ? virtualFile.getNameWithoutExtension() : null; isErlangLoadNif = "erlang".equals(moduleName); } if (isErlangLoadNif) { result.set(Boolean.TRUE); return; } super.visitFunctionCallExpression(functionCallExpression); } }); return Boolean.TRUE.equals(result.get()); } }