mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-85634 Convert PyInterpreterInspection from Java to Kotlin
GitOrigin-RevId: 7a967a6bb948ec0ad5fd61f1bb44bb1e6620a5f4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
473e007508
commit
6fc79e3d03
@@ -1,424 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.inspections;
|
||||
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.util.InspectionMessage;
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName;
|
||||
import com.intellij.codeInspection.util.IntentionName;
|
||||
import com.intellij.ide.actions.ShowSettingsUtilImpl;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil;
|
||||
import com.intellij.openapi.options.ex.ConfigurableVisitor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
|
||||
import com.intellij.openapi.util.UserDataHolderBase;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.PythonIdeLanguageCustomization;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.sdk.PyDetectedSdk;
|
||||
import com.jetbrains.python.sdk.PySdkExtKt;
|
||||
import com.jetbrains.python.sdk.PySdkPopupFactory;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer;
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfoWithTool;
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration;
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension;
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil;
|
||||
import com.jetbrains.python.ui.PyUiUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.jetbrains.python.inspections.PyInterpreterInspectionExKt.detectSdkForModulesForJvmIn;
|
||||
import static com.jetbrains.python.inspections.PyInterpreterInspectionExKt.findAllSortedForModuleForJvm;
|
||||
|
||||
|
||||
public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
private static final @NotNull Pattern NAME = Pattern.compile("Python (?<version>\\d\\.\\d+)\\s*(\\((?<name>.+?)\\))?");
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
|
||||
final boolean isOnTheFly,
|
||||
final @NotNull LocalInspectionToolSession session) {
|
||||
return new Visitor(holder, PyInspectionVisitor.getContext(session));
|
||||
}
|
||||
|
||||
public static class Visitor extends PyInspectionVisitor {
|
||||
|
||||
public Visitor(@Nullable ProblemsHolder holder,
|
||||
@NotNull TypeEvalContext context) {
|
||||
super(holder, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyFile(@NotNull PyFile node) {
|
||||
if (isFileIgnored(node)) return;
|
||||
@Nullable final Module module = ModuleUtilCore.findModuleForPsiElement(node);
|
||||
@Nullable final Sdk sdk = PyBuiltinCache.findSdkForFile(node);
|
||||
final boolean pyCharm = PythonIdeLanguageCustomization.isMainlyPythonIde();
|
||||
|
||||
final List<LocalQuickFix> fixes = new ArrayList<>();
|
||||
if (sdk == null) {
|
||||
final @InspectionMessage String message;
|
||||
if (pyCharm) {
|
||||
message = PyPsiBundle.message("INSP.interpreter.no.python.interpreter.configured.for.project");
|
||||
}
|
||||
else {
|
||||
message = PyPsiBundle.message("INSP.interpreter.no.python.interpreter.configured.for.module");
|
||||
}
|
||||
registerProblemWithCommonFixes(node, message, module, fixes, pyCharm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void registerProblemWithCommonFixes(PyFile node,
|
||||
@InspectionMessage String message,
|
||||
@Nullable Module module,
|
||||
List<LocalQuickFix> fixes,
|
||||
boolean pyCharm) {
|
||||
if (module != null && pyCharm) {
|
||||
final String sdkName = ProjectRootManager.getInstance(node.getProject()).getProjectSdkName();
|
||||
ContainerUtil.addIfNotNull(fixes, getSuitableSdkFix(sdkName, module));
|
||||
}
|
||||
if (module != null && pyCharm) {
|
||||
fixes.add(new ConfigureInterpreterFix());
|
||||
}
|
||||
else {
|
||||
fixes.add(new InterpreterSettingsQuickFix(module));
|
||||
}
|
||||
|
||||
registerProblem(node, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private static @Nullable LocalQuickFix getSuitableSdkFix(@Nullable String name, @NotNull Module module) {
|
||||
// this method is based on com.jetbrains.python.sdk.PySdkExtKt.suggestAssociatedSdkName
|
||||
// please keep it in sync with the mentioned method and com.jetbrains.python.PythonSdkConfigurator.configureSdk
|
||||
|
||||
final List<Sdk> existingSdks = getExistingSdks();
|
||||
|
||||
final var associatedSdk = PySdkExtKt.mostPreferred(PySdkExtKt.filterAssociatedSdks(module, existingSdks));
|
||||
if (associatedSdk != null) return new UseExistingInterpreterFix(associatedSdk, module);
|
||||
|
||||
final UserDataHolderBase context = new UserDataHolderBase();
|
||||
|
||||
final List<CreateSdkInfoWithTool> createSdkInfos = findAllSortedForModuleForJvm(module);
|
||||
if (!createSdkInfos.isEmpty()) {
|
||||
return new UseProvidedInterpreterFix(module, createSdkInfos.getFirst());
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
final Matcher matcher = NAME.matcher(name);
|
||||
if (matcher.matches()) {
|
||||
final String venvName = matcher.group("name");
|
||||
if (venvName != null) {
|
||||
final PyDetectedSdk detectedAssociatedViaRootNameEnv = detectAssociatedViaRootNameEnv(venvName, module, existingSdks, context);
|
||||
if (detectedAssociatedViaRootNameEnv != null) {
|
||||
return new UseDetectedInterpreterFix(detectedAssociatedViaRootNameEnv, existingSdks, true, module);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final PyDetectedSdk detectedSystemWideSdk = detectSystemWideSdk(matcher.group("version"), module, existingSdks, context);
|
||||
if (detectedSystemWideSdk != null) {
|
||||
return new UseDetectedInterpreterFix(detectedSystemWideSdk, existingSdks, false, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PyCondaSdkCustomizer.Companion.getInstance().getSuggestSharedCondaEnvironments()) {
|
||||
final var sharedCondaEnv = PySdkExtKt.mostPreferred(PySdkExtKt.filterSharedCondaEnvs(module, existingSdks));
|
||||
if (sharedCondaEnv != null) {
|
||||
return new UseExistingInterpreterFix(sharedCondaEnv, module);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should use SystemPythonService here as well, postponing as it's quite unlikely we get here (although we can)
|
||||
final var systemWideSdk = PySdkExtKt.mostPreferred(PySdkExtKt.filterSystemWideSdks(existingSdks));
|
||||
if (systemWideSdk != null) {
|
||||
return new UseExistingInterpreterFix(systemWideSdk, module);
|
||||
}
|
||||
|
||||
PyProjectSdkConfigurationExtension configurator = PyCondaSdkCustomizer.Companion.getInstance().getFallbackConfigurator();
|
||||
if (configurator != null) {
|
||||
var fallbackCreateSdkInfo =
|
||||
PyCondaSdkCustomizer.Companion.checkEnvironmentAndPrepareSdkCreatorBlocking(configurator, module);
|
||||
if (fallbackCreateSdkInfo != null) {
|
||||
return new UseProvidedInterpreterFix(module, fallbackCreateSdkInfo);
|
||||
}
|
||||
}
|
||||
|
||||
final var detectedSystemWideSdk = ContainerUtil.getFirstItem(PySdkExtKt.detectSystemWideSdks(module, existingSdks));
|
||||
if (detectedSystemWideSdk != null) {
|
||||
return new UseDetectedInterpreterFix(detectedSystemWideSdk, existingSdks, false, module);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Unmodifiable @NotNull List<Sdk> getExistingSdks() {
|
||||
final ProjectSdksModel model = new ProjectSdksModel();
|
||||
model.reset(null);
|
||||
return ContainerUtil.filter(model.getSdks(), it -> it.getSdkType() instanceof PythonSdkType);
|
||||
}
|
||||
|
||||
private static @Nullable PyDetectedSdk detectAssociatedViaRootNameEnv(@NotNull String associatedName,
|
||||
@NotNull Module module,
|
||||
@NotNull List<Sdk> existingSdks,
|
||||
@NotNull UserDataHolderBase context) {
|
||||
return findAssociatedViaRootNameEnv(
|
||||
associatedName,
|
||||
PySdkExtKt.detectVirtualEnvs(module, existingSdks, context),
|
||||
Visitor::getVirtualEnvRootName
|
||||
);
|
||||
}
|
||||
|
||||
private static @Nullable PyDetectedSdk detectSystemWideSdk(@NotNull String version,
|
||||
@NotNull Module module,
|
||||
@NotNull List<Sdk> existingSdks,
|
||||
@NotNull UserDataHolderBase context) {
|
||||
final LanguageLevel parsedVersion = LanguageLevel.fromPythonVersion(version);
|
||||
|
||||
if (parsedVersion.toString().equals(version)) {
|
||||
return ContainerUtil.find(
|
||||
PySdkExtKt.detectSystemWideSdks(module, existingSdks, context),
|
||||
sdk -> PySdkExtKt.getGuessedLanguageLevel(sdk) == parsedVersion
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable PyDetectedSdk findAssociatedViaRootNameEnv(@NotNull String associatedName,
|
||||
@NotNull List<PyDetectedSdk> envs,
|
||||
@NotNull Function<PyDetectedSdk, String> envRootName) {
|
||||
return StreamEx
|
||||
.of(envs)
|
||||
.filter(sdk -> associatedName.equals(envRootName.apply(sdk)))
|
||||
.max(
|
||||
Comparator
|
||||
.comparing(PySdkExtKt::getGuessedLanguageLevel)
|
||||
.thenComparing(PyDetectedSdk::getHomePath)
|
||||
)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static @Nullable String getVirtualEnvRootName(@NotNull PyDetectedSdk sdk) {
|
||||
final String path = sdk.getHomePath();
|
||||
return path == null ? null : getEnvRootName(PythonSdkUtil.getVirtualEnvRoot(path));
|
||||
}
|
||||
|
||||
private static @Nullable String getEnvRootName(@Nullable File envRoot) {
|
||||
return envRoot == null ? null : PathUtil.getFileName(envRoot.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFileIgnored(@NotNull PyFile pyFile) {
|
||||
return PyInspectionExtension.EP_NAME.getExtensionList().stream().anyMatch(ep -> ep.ignoreInterpreterWarnings(pyFile));
|
||||
}
|
||||
|
||||
public static final class InterpreterSettingsQuickFix implements LocalQuickFix {
|
||||
|
||||
private final @Nullable Module myModule;
|
||||
|
||||
public InterpreterSettingsQuickFix(@Nullable Module module) {
|
||||
myModule = module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getFamilyName() {
|
||||
return PlatformUtils.isPyCharm()
|
||||
? PyPsiBundle.message("INSP.interpreter.interpreter.settings")
|
||||
: PyPsiBundle.message("INSP.interpreter.configure.python.interpreter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
showPythonInterpreterSettings(project, myModule);
|
||||
}
|
||||
|
||||
public static void showPythonInterpreterSettings(@NotNull Project project, @Nullable Module module) {
|
||||
final var id = "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable";
|
||||
final var group = ConfigurableExtensionPointUtil.getConfigurableGroup(project, true);
|
||||
if (ConfigurableVisitor.findById(id, Collections.singletonList(group)) != null) {
|
||||
ShowSettingsUtilImpl.showSettingsDialog(project, id, null);
|
||||
return;
|
||||
}
|
||||
|
||||
final ProjectSettingsService settingsService = ProjectSettingsService.getInstance(project);
|
||||
if (module == null || justOneModuleInheritingSdk(project, module)) {
|
||||
settingsService.openProjectSettings();
|
||||
}
|
||||
else {
|
||||
settingsService.openModuleSettings(module);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean justOneModuleInheritingSdk(@NotNull Project project, @NotNull Module module) {
|
||||
return ProjectRootManager.getInstance(project).getProjectSdk() == null &&
|
||||
ModuleRootManager.getInstance(module).isSdkInherited() &&
|
||||
ModuleManager.getInstance(project).getModules().length < 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConfigureInterpreterFix implements LocalQuickFix {
|
||||
|
||||
@Override
|
||||
public @IntentionFamilyName @NotNull String getFamilyName() {
|
||||
return PyPsiBundle.message("INSP.interpreter.configure.python.interpreter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(final @NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement element = descriptor.getPsiElement();
|
||||
if (element == null) return;
|
||||
|
||||
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
|
||||
if (module == null) return;
|
||||
|
||||
PySdkPopupFactory.Companion.createAndShow(module);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UseProvidedInterpreterFix implements LocalQuickFix {
|
||||
|
||||
private final @NotNull Module myModule;
|
||||
|
||||
private final @NotNull CreateSdkInfoWithTool myCreateSdkInfo;
|
||||
|
||||
private UseProvidedInterpreterFix(@NotNull Module module,
|
||||
@NotNull CreateSdkInfoWithTool createSdkInfo) {
|
||||
myModule = module;
|
||||
myCreateSdkInfo = createSdkInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionFamilyName @NotNull String getFamilyName() {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getName() {
|
||||
return myCreateSdkInfo.getCreateSdkInfo().getIntentionName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
if (!detectSdkForModulesForJvmIn(project)) {
|
||||
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingCreateSdkInfo(myModule, myCreateSdkInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
|
||||
// The quick fix doesn't change the code and is suggested on a file level
|
||||
return IntentionPreviewInfo.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class UseInterpreterFix<T extends Sdk> implements LocalQuickFix {
|
||||
|
||||
protected final @NotNull T mySdk;
|
||||
|
||||
protected UseInterpreterFix(@NotNull T sdk) {
|
||||
mySdk = sdk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionFamilyName @NotNull String getFamilyName() {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @IntentionName @NotNull String getName() {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.interpreter", PySdkPopupFactory.Companion.shortenNameInPopup(mySdk, 75));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UseExistingInterpreterFix extends UseInterpreterFix<Sdk> {
|
||||
|
||||
private final @NotNull Module myModule;
|
||||
|
||||
private UseExistingInterpreterFix(@NotNull Sdk existingSdk, @NotNull Module module) {
|
||||
super(existingSdk);
|
||||
myModule = module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PyUiUtil.clearFileLevelInspectionResults(descriptor.getPsiElement().getContainingFile());
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
PyProjectSdkConfiguration.INSTANCE.setReadyToUseSdkSync(project, myModule, mySdk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UseDetectedInterpreterFix extends UseInterpreterFix<PyDetectedSdk> {
|
||||
|
||||
private final @NotNull List<Sdk> myExistingSdks;
|
||||
|
||||
private final boolean doAssociate;
|
||||
|
||||
private final @NotNull Module myModule;
|
||||
|
||||
private UseDetectedInterpreterFix(@NotNull PyDetectedSdk detectedSdk,
|
||||
@NotNull List<Sdk> existingSdks,
|
||||
boolean associate,
|
||||
@NotNull Module module) {
|
||||
super(detectedSdk);
|
||||
myExistingSdks = existingSdks;
|
||||
doAssociate = associate;
|
||||
myModule = module;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PyUiUtil.clearFileLevelInspectionResults(descriptor.getPsiElement().getContainingFile());
|
||||
PySdkExtKt.setupSdkLaunch(mySdk, myModule, myExistingSdks, doAssociate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.inspections
|
||||
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.codeInspection.util.InspectionMessage
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.ide.actions.ShowSettingsUtilImpl.Companion.showSettingsDialog
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager.Companion.getInstance
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.options.ConfigurableGroup
|
||||
import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil
|
||||
import com.intellij.openapi.options.ex.ConfigurableVisitor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService
|
||||
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
|
||||
import com.intellij.openapi.util.Condition
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.util.PlatformUtils
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.containers.toArray
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.PythonIdeLanguageCustomization
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache.Companion.findSdkForFile
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.PySdkPopupFactory.Companion.createAndShow
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer.Companion.checkEnvironmentAndPrepareSdkCreatorBlocking
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer.Companion.instance
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfoWithTool
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.configureSdkUsingCreateSdkInfo
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.ui.PyUiUtil
|
||||
import one.util.streamex.StreamEx
|
||||
import java.io.File
|
||||
import java.util.function.Function
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class PyInterpreterInspection : PyInspection() {
|
||||
override fun buildVisitor(
|
||||
holder: ProblemsHolder,
|
||||
isOnTheFly: Boolean,
|
||||
session: LocalInspectionToolSession
|
||||
): PsiElementVisitor {
|
||||
return Visitor(holder, PyInspectionVisitor.getContext(session))
|
||||
}
|
||||
|
||||
class Visitor(
|
||||
holder: ProblemsHolder?,
|
||||
context: TypeEvalContext
|
||||
) : PyInspectionVisitor(holder, context) {
|
||||
override fun visitPyFile(node: PyFile) {
|
||||
if (isFileIgnored(node)) return
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(node)
|
||||
val sdk = findSdkForFile(node)
|
||||
val pyCharm = PythonIdeLanguageCustomization.isMainlyPythonIde()
|
||||
|
||||
val fixes: MutableList<LocalQuickFix?> = ArrayList<LocalQuickFix?>()
|
||||
if (sdk == null) {
|
||||
val message: @InspectionMessage String
|
||||
if (pyCharm) {
|
||||
message = PyPsiBundle.message("INSP.interpreter.no.python.interpreter.configured.for.project")
|
||||
}
|
||||
else {
|
||||
message = PyPsiBundle.message("INSP.interpreter.no.python.interpreter.configured.for.module")
|
||||
}
|
||||
registerProblemWithCommonFixes(node, message, module, fixes, pyCharm)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun registerProblemWithCommonFixes(
|
||||
node: PyFile,
|
||||
@InspectionMessage message: @InspectionMessage String,
|
||||
module: Module?,
|
||||
fixes: MutableList<LocalQuickFix?>,
|
||||
pyCharm: Boolean
|
||||
) {
|
||||
if (module != null && pyCharm) {
|
||||
val sdkName = ProjectRootManager.getInstance(node.getProject()).getProjectSdkName()
|
||||
ContainerUtil.addIfNotNull<LocalQuickFix?>(fixes, getSuitableSdkFix(sdkName, module))
|
||||
}
|
||||
if (module != null && pyCharm) {
|
||||
fixes.add(ConfigureInterpreterFix())
|
||||
}
|
||||
else {
|
||||
fixes.add(InterpreterSettingsQuickFix(module))
|
||||
}
|
||||
|
||||
registerProblem(node, message, *fixes.toArray<LocalQuickFix?>(LocalQuickFix.EMPTY_ARRAY))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getSuitableSdkFix(name: String?, module: Module): LocalQuickFix? {
|
||||
// this method is based on com.jetbrains.python.sdk.PySdkExtKt.suggestAssociatedSdkName
|
||||
// please keep it in sync with the mentioned method and com.jetbrains.python.PythonSdkConfigurator.configureSdk
|
||||
|
||||
val existingSdks: List<Sdk> = existingSdks
|
||||
|
||||
val associatedSdk = mostPreferred(filterAssociatedSdks(module, existingSdks))
|
||||
if (associatedSdk != null) return UseExistingInterpreterFix(associatedSdk, module)
|
||||
|
||||
val context = UserDataHolderBase()
|
||||
|
||||
val createSdkInfos: List<CreateSdkInfoWithTool> = findAllSortedForModuleForJvm(module)
|
||||
if (!createSdkInfos.isEmpty()) {
|
||||
return UseProvidedInterpreterFix(module, createSdkInfos.first())
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
val matcher: Matcher = NAME.matcher(name)
|
||||
if (matcher.matches()) {
|
||||
val venvName = matcher.group("name")
|
||||
if (venvName != null) {
|
||||
val detectedAssociatedViaRootNameEnv: PyDetectedSdk? = detectAssociatedViaRootNameEnv(venvName, module, existingSdks, context)
|
||||
if (detectedAssociatedViaRootNameEnv != null) {
|
||||
return UseDetectedInterpreterFix(detectedAssociatedViaRootNameEnv, existingSdks, true, module)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val detectedSystemWideSdk: PyDetectedSdk? = detectSystemWideSdk(matcher.group("version"), module, existingSdks, context)
|
||||
if (detectedSystemWideSdk != null) {
|
||||
return UseDetectedInterpreterFix(detectedSystemWideSdk, existingSdks, false, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (instance.suggestSharedCondaEnvironments) {
|
||||
val sharedCondaEnv = mostPreferred(filterSharedCondaEnvs(module, existingSdks))
|
||||
if (sharedCondaEnv != null) {
|
||||
return UseExistingInterpreterFix(sharedCondaEnv, module)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should use SystemPythonService here as well, postponing as it's quite unlikely we get here (although we can)
|
||||
val systemWideSdk = mostPreferred(filterSystemWideSdks(existingSdks))
|
||||
if (systemWideSdk != null) {
|
||||
return UseExistingInterpreterFix(systemWideSdk, module)
|
||||
}
|
||||
|
||||
val configurator = instance.fallbackConfigurator
|
||||
if (configurator != null) {
|
||||
val fallbackCreateSdkInfo =
|
||||
checkEnvironmentAndPrepareSdkCreatorBlocking(configurator, module)
|
||||
if (fallbackCreateSdkInfo != null) {
|
||||
return UseProvidedInterpreterFix(module, fallbackCreateSdkInfo)
|
||||
}
|
||||
}
|
||||
|
||||
val detectedSystemWideSdk = ContainerUtil.getFirstItem<PyDetectedSdk>(detectSystemWideSdks(module, existingSdks))
|
||||
if (detectedSystemWideSdk != null) {
|
||||
return UseDetectedInterpreterFix(detectedSystemWideSdk, existingSdks, false, module)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private val existingSdks: List<Sdk>
|
||||
get() {
|
||||
val model = ProjectSdksModel()
|
||||
model.reset(null)
|
||||
return ContainerUtil.filter(model.getSdks(),
|
||||
Condition { it.getSdkType() is PythonSdkType })
|
||||
}
|
||||
|
||||
private fun detectAssociatedViaRootNameEnv(
|
||||
associatedName: String,
|
||||
module: Module,
|
||||
existingSdks: List<Sdk>,
|
||||
context: UserDataHolderBase
|
||||
): PyDetectedSdk? {
|
||||
return Companion.findAssociatedViaRootNameEnv(
|
||||
associatedName,
|
||||
detectVirtualEnvs(module, existingSdks, context),
|
||||
Function { sdk: PyDetectedSdk? -> Companion.getVirtualEnvRootName(sdk!!) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun detectSystemWideSdk(
|
||||
version: String,
|
||||
module: Module,
|
||||
existingSdks: List<Sdk>,
|
||||
context: UserDataHolderBase
|
||||
): PyDetectedSdk? {
|
||||
val parsedVersion: LanguageLevel = LanguageLevel.fromPythonVersion(version)!!
|
||||
|
||||
if (parsedVersion.toString() == version) {
|
||||
return ContainerUtil.find<PyDetectedSdk>(
|
||||
detectSystemWideSdks(module, existingSdks, context),
|
||||
Condition { sdk: PyDetectedSdk -> sdk.guessedLanguageLevel == parsedVersion }
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findAssociatedViaRootNameEnv(
|
||||
associatedName: String,
|
||||
envs: List<PyDetectedSdk>,
|
||||
envRootName: Function<PyDetectedSdk?, String?>
|
||||
): PyDetectedSdk? {
|
||||
return StreamEx
|
||||
.of(envs)
|
||||
.filter { sdk: PyDetectedSdk -> associatedName == envRootName.apply(sdk) }
|
||||
.max(compareBy<PyDetectedSdk> { it.guessedLanguageLevel }.thenBy { it.homePath })
|
||||
.orElse(null)
|
||||
}
|
||||
|
||||
private fun getVirtualEnvRootName(sdk: PyDetectedSdk): String? {
|
||||
val path = sdk.getHomePath()
|
||||
return if (path == null) null else getEnvRootName(PythonSdkUtil.getVirtualEnvRoot(path))
|
||||
}
|
||||
|
||||
private fun getEnvRootName(envRoot: File?): String? {
|
||||
return if (envRoot == null) null else PathUtil.getFileName(envRoot.getPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InterpreterSettingsQuickFix(private val myModule: Module?) : LocalQuickFix {
|
||||
override fun getFamilyName(): String {
|
||||
return if (PlatformUtils.isPyCharm())
|
||||
PyPsiBundle.message("INSP.interpreter.interpreter.settings")
|
||||
else
|
||||
PyPsiBundle.message("INSP.interpreter.configure.python.interpreter")
|
||||
}
|
||||
|
||||
override fun startInWriteAction(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
showPythonInterpreterSettings(project, myModule)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun showPythonInterpreterSettings(project: Project, module: Module?) {
|
||||
val id = "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
|
||||
val group = ConfigurableExtensionPointUtil.getConfigurableGroup(project, true)
|
||||
if (ConfigurableVisitor.findById(id, mutableListOf<ConfigurableGroup?>(group)) != null) {
|
||||
showSettingsDialog(project, id, null)
|
||||
return
|
||||
}
|
||||
|
||||
val settingsService = ProjectSettingsService.getInstance(project)
|
||||
if (module == null || justOneModuleInheritingSdk(project, module)) {
|
||||
settingsService.openProjectSettings()
|
||||
}
|
||||
else {
|
||||
settingsService.openModuleSettings(module)
|
||||
}
|
||||
}
|
||||
|
||||
private fun justOneModuleInheritingSdk(project: Project, module: Module): Boolean {
|
||||
return ProjectRootManager.getInstance(project).getProjectSdk() == null &&
|
||||
ModuleRootManager.getInstance(module).isSdkInherited() && getInstance(project).modules.size < 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigureInterpreterFix : LocalQuickFix {
|
||||
@IntentionFamilyName
|
||||
override fun getFamilyName(): @IntentionFamilyName String {
|
||||
return PyPsiBundle.message("INSP.interpreter.configure.python.interpreter")
|
||||
}
|
||||
|
||||
override fun startInWriteAction(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val element = descriptor.getPsiElement()
|
||||
if (element == null) return
|
||||
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(element)
|
||||
if (module == null) return
|
||||
|
||||
createAndShow(module)
|
||||
}
|
||||
}
|
||||
|
||||
private class UseProvidedInterpreterFix(
|
||||
private val myModule: Module,
|
||||
private val myCreateSdkInfo: CreateSdkInfoWithTool
|
||||
) : LocalQuickFix {
|
||||
@IntentionFamilyName
|
||||
override fun getFamilyName(): @IntentionFamilyName String {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter")
|
||||
}
|
||||
|
||||
@IntentionName
|
||||
override fun getName(): @IntentionName String {
|
||||
return myCreateSdkInfo.createSdkInfo.intentionName
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
if (!detectSdkForModulesForJvmIn(project)) {
|
||||
configureSdkUsingCreateSdkInfo(myModule, myCreateSdkInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
|
||||
// The quick fix doesn't change the code and is suggested on a file level
|
||||
return IntentionPreviewInfo.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class UseInterpreterFix<T : Sdk> protected constructor(protected val mySdk: T) : LocalQuickFix {
|
||||
@IntentionFamilyName
|
||||
override fun getFamilyName(): @IntentionFamilyName String {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter")
|
||||
}
|
||||
|
||||
@IntentionName
|
||||
override fun getName(): @IntentionName String {
|
||||
return PyPsiBundle.message("INSP.interpreter.use.interpreter", PySdkPopupFactory.Companion.shortenNameInPopup(mySdk!!, 75))
|
||||
}
|
||||
|
||||
override fun startInWriteAction(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class UseExistingInterpreterFix(existingSdk: Sdk, private val myModule: Module) : UseInterpreterFix<Sdk>(existingSdk) {
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
PyUiUtil.clearFileLevelInspectionResults(descriptor.getPsiElement().getContainingFile())
|
||||
ApplicationManager.getApplication().executeOnPooledThread(
|
||||
Runnable {
|
||||
PyProjectSdkConfiguration.setReadyToUseSdkSync(project, myModule, mySdk)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class UseDetectedInterpreterFix(
|
||||
detectedSdk: PyDetectedSdk,
|
||||
private val myExistingSdks: List<Sdk>,
|
||||
private val doAssociate: Boolean,
|
||||
private val myModule: Module
|
||||
) : UseInterpreterFix<PyDetectedSdk>(detectedSdk) {
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
PyUiUtil.clearFileLevelInspectionResults(descriptor.getPsiElement().getContainingFile())
|
||||
mySdk.setupSdkLaunch(myModule, myExistingSdks, doAssociate)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NAME: Pattern = Pattern.compile("Python (?<version>\\d\\.\\d+)\\s*(\\((?<name>.+?)\\))?")
|
||||
|
||||
private fun isFileIgnored(pyFile: PyFile): Boolean {
|
||||
return PyInspectionExtension.EP_NAME.extensionList.stream().anyMatch { ep: PyInspectionExtension? ->
|
||||
ep!!.ignoreInterpreterWarnings(pyFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user