[python] Rewrite PythonHelpersLocator

1. `PythonHelpersLocator` is an API to get helpers. It is aware of PyCharm Community helpers but also aware of some EP that provides additional helper paths.
2. EP implementations for PyCharm Prof and Jupyter that provide additional (prof) helpers.
It will help avoid problems with which Locator to use from Professional, Community or Jupiter plugins.


Merge-request: IJ-MR-140027
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>

GitOrigin-RevId: c7c34f323247002699866f12f6ff5a08cf6a18ff
This commit is contained in:
Egor.Eliseev
2024-07-23 12:04:22 +00:00
committed by intellij-monorepo-bot
parent 71e4ebad19
commit 0be08ded36
22 changed files with 108 additions and 92 deletions

View File

@@ -21,11 +21,17 @@ import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.util.PathUtil
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.NonNls
import java.io.File
import org.jetbrains.annotations.TestOnly
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.io.path.pathString
abstract class PythonHelpersLocator {
interface PythonHelpersLocator {
companion object {
val LOG = Logger.getInstance(PythonHelpersLocator::class.java)
private const val PROPERTY_HELPERS_LOCATION = "idea.python.helpers.path"
@@ -36,108 +42,117 @@ abstract class PythonHelpersLocator {
/**
* @return A list of Path objects representing the roots of the Python Helpers.
*/
@Internal
@JvmStatic
fun getHelpersRoots(): List<Path> = EP_NAME.extensionList.first().getRoots()
fun getHelpersRoots(): List<Path> = EP_NAME.extensionList.mapNotNull { it.getRoot() }
/**
* Retrieves the root file of the Community Helpers extension.
* Community Helpers root always should be first in an extension list
* Retrieves a path to the root file of the Community Helpers extension.
*
* @return The root File of the Community Helpers extension.
* @return The root Path of the Community Helpers extension.
*/
@Internal
@JvmStatic
fun getCommunityHelpersRoot(): File = EP_NAME.extensionList.first().getRoots().first().toFile()
fun getCommunityHelpersRoot(): Path = EP_NAME.extensionList.first().getCommunityHelpersRootPath()
/**
* Retrieves File of a helper file given its resource name.
* Retrieves Path of a helper file given its resource name.
*
* @param resourceName The name of the helper resource file.
* @return File of the helper file, or null if the file does not exist.
* @return Path of the helper file, or null if the file does not exist.
*/
@JvmStatic
fun findFileInHelpers(resourceName: String): File? {
@RequiresBackgroundThread
fun findPathInHelpers(resourceName: String): Path? {
for (helperRoot in getHelpersRoots()) {
val file = File(helperRoot.toFile(), resourceName)
if (file.exists())
return file
val path = Path.of(helperRoot.pathString, resourceName)
if (path.exists())
return path
}
LOG.warn("File $resourceName does not exist in helpers root")
return null
}
@Internal
@TestOnly
@JvmStatic
@RequiresBackgroundThread
fun getPythonCommunityPath(): Path {
val pathFromUltimate = Path.of(PathManager.getHomePath(), "community/python")
if (pathFromUltimate.exists()) {
return pathFromUltimate
}
return Path.of(PathManager.getHomePath(), "python")
}
/**
* Retrieves the absolute path of a helper file given its resource name.
*
* @param resourceName The name of the helper resource file.
* @param resourceName The name of the helper resource file (for example, `pydev` or `jupyter_debug`).
* @return The absolute path of the helper file, or null if the file does not exist.
*/
@Internal
@JvmStatic
fun findPathInHelpers(@NonNls resourceName: String): String = findFileInHelpers(resourceName)?.absolutePath ?: ""
@JvmStatic
fun getPythonCommunityPath(): String {
val pathFromUltimate = File(PathManager.getHomePath(), "community/python")
if (pathFromUltimate.exists()) {
return pathFromUltimate.path
}
return File(PathManager.getHomePath(), "python").path
}
@RequiresBackgroundThread
fun findPathStringInHelpers(@NonNls resourceName: String): String = findPathInHelpers(resourceName)?.absolutePathString() ?: ""
@Deprecated("Use {@link PythonHelpersLocator#findPathInHelpers}.", ReplaceWith("findPathInHelpers(resourceName)"))
@JvmStatic
fun getHelperPath(@NonNls resourceName: String): String = findPathInHelpers(resourceName)
fun getHelperPath(@NonNls resourceName: String): String = findPathStringInHelpers(resourceName)
}
// Always add Community Helpers root on the first place of root's list
protected open fun getRoots(): List<Path> = listOf(getCommunityHelpersRootFile().toPath().normalize())
@Internal
fun getRoot(): Path? = getCommunityHelpersRootPath().normalize()
private fun getCommunityHelpersRootFile(): File {
private fun getCommunityHelpersRootPath(): Path {
val property = System.getProperty(PROPERTY_HELPERS_LOCATION)
return if (property != null) {
File(property)
Path.of(property)
}
else getHelpersRoot(COMMUNITY_HELPERS_MODULE_NAME, "/python/helpers").also {
assertHelpersLayout(it)
}
}
protected open fun getHelpersRoot(moduleName: String, relativePath: String): File =
@Internal
@RequiresBackgroundThread
fun getHelpersRoot(moduleName: String, relativePath: String): Path =
findRootByJarPath(PathUtil.getJarPathForClass(PythonHelpersLocator::class.java), moduleName, relativePath)
protected fun findRootByJarPath(jarPath: String, moduleName: String, relativePath: String): File {
@Internal
fun findRootByJarPath(jarPath: String, moduleName: String, relativePath: String): Path {
return if (PluginManagerCore.isRunningFromSources()) {
File(PathManager.getCommunityHomePath() + relativePath)
Path.of(PathManager.getCommunityHomePath(), relativePath)
}
else {
getPluginBaseDir(jarPath)?.let {
File(it, PathUtil.getFileName(relativePath))
} ?: File(File(jarPath).parentFile, moduleName)
Path.of(it.absolutePathString(), PathUtil.getFileName(relativePath))
} ?: Path.of(Path(jarPath).parent.absolutePathString(), moduleName)
}
}
private fun getPluginBaseDir(jarPath: String): File? {
private fun getPluginBaseDir(jarPath: String): Path? {
if (jarPath.endsWith(".jar")) {
val jarFile = File(jarPath)
val path = Path.of(jarPath)
LOG.assertTrue(jarFile.exists(), "jar file cannot be null")
return jarFile.parentFile.parentFile
LOG.assertTrue(path.exists(), "$path to plugin base bir does not exists")
return path.parent.parent
}
return null
}
private fun assertHelpersLayout(root: File): File {
val path = root.absolutePath
LOG.assertTrue(root.exists(), "Helpers root does not exist $path")
@RequiresBackgroundThread
private fun assertHelpersLayout(root: Path): Path {
LOG.assertTrue(root.exists(), "Helpers root does not exist $root")
listOf("generator3", "pycharm", "pycodestyle.py", "pydev", "syspath.py", "typeshed").forEach { child ->
LOG.assertTrue(File(root, child).exists(), "No '$child' inside $path")
LOG.assertTrue(root.resolve(child).exists(), "No '$child' inside $root")
}
return root
}
}
internal class PythonHelpersLocatorDefault : PythonHelpersLocator()
internal class PythonHelpersLocatorDefault : PythonHelpersLocator

View File

@@ -30,7 +30,7 @@ public final class PyStdlibUtil {
@Nullable
private static Set<String> loadStdlibPackagesList() {
final Logger log = Logger.getInstance(PyStdlibUtil.class.getName());
final String helperPath = PythonHelpersLocator.findPathInHelpers("/tools/stdlib_packages.txt");
final String helperPath = PythonHelpersLocator.findPathStringInHelpers("/tools/stdlib_packages.txt");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(helperPath), StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.toSet());
}

View File

@@ -158,7 +158,7 @@ object PyTypeShed {
get() {
val paths = listOf("${PathManager.getConfigPath()}/typeshed",
"${PathManager.getConfigPath()}/../typeshed",
PythonHelpersLocator.findPathInHelpers("typeshed"))
PythonHelpersLocator.findPathStringInHelpers("typeshed"))
return paths.asSequence()
.filter { File(it).exists() }
.firstOrNull()

View File

@@ -81,7 +81,7 @@ public final class PyUserSkeletonsUtil {
private static List<String> getPossibleUserSkeletonsPaths() {
final List<String> result = new ArrayList<>();
result.add(PathManager.getConfigPath() + File.separator + USER_SKELETONS_DIR);
result.add(PythonHelpersLocator.findPathInHelpers(USER_SKELETONS_DIR));
result.add(PythonHelpersLocator.findPathStringInHelpers(USER_SKELETONS_DIR));
return result;
}

View File

@@ -203,7 +203,7 @@ public class PyDocumentationBuilder {
private void buildForKeyword(@NotNull String name) {
try {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/python_keywords/" + name),
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/python_keywords/" + name),
StandardCharsets.UTF_8)) {
final String text = FileUtil.loadTextAndClose(reader);
final String converted = StringUtil.convertLineSeparators(text, "\n");

View File

@@ -42,7 +42,7 @@ public final class UnsupportedFeaturesUtil {
private static void fillTestCaseMethods() throws IOException {
final Logger log = Logger.getInstance(UnsupportedFeaturesUtil.class.getName());
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/class_method_versions.xml"))) {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/class_method_versions.xml"))) {
final XMLReader xr = XMLReaderFactory.createXMLReader();
final ClassMethodsParser parser = new ClassMethodsParser();
xr.setContentHandler(parser);
@@ -55,7 +55,7 @@ public final class UnsupportedFeaturesUtil {
private static void fillMaps() throws IOException {
Logger log = Logger.getInstance(UnsupportedFeaturesUtil.class.getName());
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathInHelpers("/tools/versions.xml"))) {
try (FileReader reader = new FileReader(PythonHelpersLocator.findPathStringInHelpers("/tools/versions.xml"))) {
XMLReader xr = XMLReaderFactory.createXMLReader();
VersionsParser parser = new VersionsParser();
xr.setContentHandler(parser);

View File

@@ -97,7 +97,7 @@ public class PyLegacySkeletonGenerator extends PySkeletonGenerator {
public List<String> getCommandLine() {
final List<String> commandLine = new ArrayList<>();
commandLine.add(mySdk.getHomePath());
commandLine.add(PythonHelpersLocator.findPathInHelpers(GENERATOR3));
commandLine.add(PythonHelpersLocator.findPathStringInHelpers(GENERATOR3));
commandLine.add("-d");
commandLine.add(mySkeletonsPath);
if (!ContainerUtil.isEmpty(myAssemblyRefs)) {

View File

@@ -12,10 +12,8 @@ import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import static com.jetbrains.python.PythonHelpersLocator.*;
@@ -90,12 +88,12 @@ public enum PythonHelper implements HelperPackage {
private static @NotNull PathHelperPackage findModule(String moduleEntryPoint, String path, boolean asModule, String[] thirdPartyDependencies) {
List<HelperDependency> dependencies = HelperDependency.findThirdPartyDependencies(thirdPartyDependencies);
if (findFileInHelpers(path + ".zip") != null) {
if (findPathInHelpers(path + ".zip") != null) {
return new ModuleHelperPackage(moduleEntryPoint, path + ".zip", dependencies);
}
if (!asModule && new File(findFileInHelpers(path), moduleEntryPoint + ".py").isFile()) {
return new ScriptPythonHelper(moduleEntryPoint + ".py", findFileInHelpers(path), dependencies);
Path pathInHelpers = findPathInHelpers(path);
if (!asModule && pathInHelpers != null && new File(pathInHelpers.toFile(), moduleEntryPoint + ".py").isFile()) {
return new ScriptPythonHelper(moduleEntryPoint + ".py", pathInHelpers.toFile(), dependencies);
}
return new ModuleHelperPackage(moduleEntryPoint, path, dependencies);
@@ -112,7 +110,7 @@ public enum PythonHelper implements HelperPackage {
}
PythonHelper(String helperScript) {
myModule = new ScriptPythonHelper(helperScript, getCommunityHelpersRoot(), Collections.emptyList());
myModule = new ScriptPythonHelper(helperScript, getCommunityHelpersRoot().toFile(), Collections.emptyList());
}
public abstract static class PathHelperPackage implements HelperPackage {
@@ -186,7 +184,7 @@ public enum PythonHelper implements HelperPackage {
private final String myModuleName;
public ModuleHelperPackage(String moduleName, String relativePath, @NotNull List<HelperDependency> dependencies) {
super(findPathInHelpers(relativePath), dependencies);
super(findPathStringInHelpers(relativePath), dependencies);
this.myModuleName = moduleName;
}
@@ -254,7 +252,7 @@ public enum PythonHelper implements HelperPackage {
}
private static @NotNull File getHelpersThirdPartyDir() {
return findFileInHelpers("third_party");
return Objects.requireNonNull(findPathInHelpers("third_party")).toFile();
}
}

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.console
import com.intellij.execution.target.TargetEnvironment
import com.intellij.execution.target.value.*
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
@@ -215,10 +216,13 @@ open class PydevConsoleRunnerFactory : PythonConsoleRunnerFactory() {
paths.add(getTargetEnvironmentValueForLocalPath(Path.of(projectRoot)))
val targetEnvironmentRequest = findPythonTargetInterpreter(sdk, project)
// // Community Helpers root should be first in the list
val communityHelpers = targetEnvironmentRequest.preparePyCharmHelpers().helpers.first()
for (helper in listOf("pycharm", "pydev")) {
paths.add(communityHelpers.targetPathFun.getRelativeTargetPath(helper))
val communityHelpers = targetEnvironmentRequest.preparePyCharmHelpers().helpers.find { it.localPath.endsWith("helpers") }
if (communityHelpers != null) {
for (helper in listOf("pycharm", "pydev")) {
paths.add(communityHelpers.targetPathFun.getRelativeTargetPath(helper))
}
} else {
Logger.getInstance(PydevConsoleRunnerFactory::class.java).error("Python Community helpers dir path not found")
}
val pathStr = paths.joinToStringFunction(separator = ", ", transform = String::toStringLiteral)

View File

@@ -113,7 +113,7 @@ public final class PyCythonExtensionWarning {
throw new ExecutionException(PyBundle.message("debugger.cython.python.run.configuration.should.be.selected"));
}
final String interpreterPath = runConfiguration.getInterpreterPath();
final String helpersPath = PythonHelpersLocator.getCommunityHelpersRoot().getPath();
final String cythonPathString = PythonHelpersLocator.findPathStringInHelpers(SETUP_CYTHON_PATH);
final String cythonExtensionsDir = PyDebugRunner.CYTHON_EXTENSIONS_DIR;
final String[] cythonArgs =
@@ -121,7 +121,7 @@ public final class PyCythonExtensionWarning {
final List<String> cmdline = new ArrayList<>();
cmdline.add(interpreterPath);
cmdline.add(FileUtil.join(helpersPath, FileUtil.toSystemDependentName(SETUP_CYTHON_PATH)));
cmdline.add(cythonPathString);
cmdline.addAll(Arrays.asList(cythonArgs));
LOG.info("Compile Cython Extensions " + StringUtil.join(cmdline, " "));

View File

@@ -230,7 +230,7 @@ public abstract class PyPackageManagerImplBase extends PyPackageManager {
}
protected @Nullable String getHelperPath(final @NotNull String helper) throws ExecutionException {
return PythonHelpersLocator.findPathInHelpers(helper);
return PythonHelpersLocator.findPathStringInHelpers(helper);
}
protected static @NotNull List<String> makeSafeToDisplayCommand(@NotNull List<String> cmdline) {

View File

@@ -374,7 +374,7 @@ public class PyTargetEnvironmentPackageManager extends PyPackageManagerImplBase
private static @NotNull HelperPackage getPipHelperPackage() {
return new PythonHelper.ScriptPythonHelper(PIP_WHEEL_NAME + "/" + PyPackageUtil.PIP,
PythonHelpersLocator.getCommunityHelpersRoot(),
PythonHelpersLocator.getCommunityHelpersRoot().toFile(),
Collections.emptyList());
}

View File

@@ -266,7 +266,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
// todo[akniazev]: this workaround should can be removed when PY-57134 is fixed
val helperLocation = if (localSdk.sdkFlavor.getLanguageLevel(localSdk).isPython2) "py2only" else "py3only"
val path = PythonHelpersLocator.findPathInHelpers(helperLocation)
val path = PythonHelpersLocator.findPathStringInHelpers(helperLocation)
pythonExecution.applyHelperPackageToPythonPath(listOf(path), helpersAwareTargetRequest)
pythonExecution.addParameter("rst2html_no_code")

View File

@@ -62,7 +62,7 @@ public abstract class PythonRemoteInterpreterManager {
}
public static void addHelpersMapping(@NotNull RemoteSdkProperties data, @NotNull PyRemotePathMapper pathMapper) {
pathMapper.addMapping(PythonHelpersLocator.getCommunityHelpersRoot().getPath(), data.getHelpersPath(), PyPathMappingType.HELPERS);
pathMapper.addMapping(PythonHelpersLocator.getCommunityHelpersRoot().toString(), data.getHelpersPath(), PyPathMappingType.HELPERS);
}
public static @NotNull PyRemotePathMapper appendBasicMappings(@Nullable Project project,

View File

@@ -878,8 +878,8 @@ public abstract class PythonCommandLineState extends CommandLineState {
if (isDebug && PythonSdkFlavor.getFlavor(sdkHome) instanceof JythonSdkFlavor) {
//that fixes Jython problem changing sys.argv on execfile, see PY-8164
pythonPath.add(PythonHelpersLocator.findPathInHelpers("pycharm"));
pythonPath.add(PythonHelpersLocator.findPathInHelpers("pydev"));
pythonPath.add(PythonHelpersLocator.findPathStringInHelpers("pycharm"));
pythonPath.add(PythonHelpersLocator.findPathStringInHelpers("pydev"));
}
return pythonPath;

View File

@@ -95,7 +95,7 @@ private fun collectPythonPath(context: Context,
if (isDebug && context.sdk?.let { PythonSdkFlavor.getFlavor(it) } is JythonSdkFlavor) {
//that fixes Jython problem changing sys.argv on execfile, see PY-8164
for (helpersResource in listOf("pycharm", "pydev")) {
val helperPath = PythonHelpersLocator.findPathInHelpers(helpersResource)
val helperPath = PythonHelpersLocator.findPathStringInHelpers(helpersResource)
val targetHelperPath = targetPath(Path.of(helperPath))
pythonPath.add(targetHelperPath)
}

View File

@@ -69,7 +69,7 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Empty> {
public @NotNull Collection<@NotNull Path> suggestLocalHomePaths(final @Nullable Module module, final @Nullable UserDataHolder context) {
Set<String> candidates = new TreeSet<>();
findInCandidatePaths(candidates, "python.exe", "jython.bat", "pypy.exe");
findInstallations(candidates, "python.exe", PythonHelpersLocator.getCommunityHelpersRoot().getParent());
findInstallations(candidates, "python.exe", PythonHelpersLocator.getCommunityHelpersRoot().getParent().toString());
return ContainerUtil.map(candidates, Path::of);
}

View File

@@ -187,7 +187,7 @@ public class PySkeletonRefresher {
}
private static int readGeneratorVersion() {
File versionFile = PythonHelpersLocator.findFileInHelpers("generator3/version.txt");
File versionFile = Objects.requireNonNull(PythonHelpersLocator.findPathInHelpers("generator3/version.txt")).toFile();
try (Reader reader = new InputStreamReader(new FileInputStream(versionFile), StandardCharsets.UTF_8)) {
return PySkeletonHeader.fromVersionString(StreamUtil.readText(reader).trim());
}

View File

@@ -19,6 +19,7 @@ import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
@@ -236,7 +237,7 @@ public abstract class PythonTestCommandLineStateBase<T extends AbstractPythonRun
@Override
public void customizeEnvironmentVars(Map<String, String> envs, boolean passParentEnvs) {
super.customizeEnvironmentVars(envs, passParentEnvs);
envs.put("PYCHARM_HELPERS_DIR", PythonHelpersLocator.findPathInHelpers("pycharm"));
envs.put("PYCHARM_HELPERS_DIR", PythonHelpersLocator.findPathStringInHelpers("pycharm"));
}
@Override
@@ -245,11 +246,14 @@ public abstract class PythonTestCommandLineStateBase<T extends AbstractPythonRun
boolean passParentEnvs) {
super.customizePythonExecutionEnvironmentVars(helpersAwareTargetRequest, envs, passParentEnvs);
var helpersTargetPath = helpersAwareTargetRequest.preparePyCharmHelpers();
// Community Helpers root should be first in the list
var communityTargetPathFun = helpersTargetPath.getHelpers().get(0).getTargetPathFun();
Function<TargetEnvironment, String> targetPycharmHelpersPath =
TargetEnvironmentFunctions.getRelativeTargetPath(communityTargetPathFun, "pycharm");
envs.put("PYCHARM_HELPERS_DIR", targetPycharmHelpersPath);
var communityHelpersPath = helpersTargetPath.getHelpers().stream().filter(it -> it.getLocalPath().endsWith("helpers")).findFirst();
if (communityHelpersPath.isPresent()) {
Function<TargetEnvironment, String> targetPycharmHelpersPath =
TargetEnvironmentFunctions.getRelativeTargetPath(communityHelpersPath.get().getTargetPathFun(), "pycharm");
envs.put("PYCHARM_HELPERS_DIR", targetPycharmHelpersPath);
} else {
Logger.getInstance(this.getClass()).error("Python Community helpers dir path not found");
}
}
protected abstract HelperPackage getRunner();

View File

@@ -62,7 +62,7 @@ public class PythonDebuggerPydevTest extends PyEnvTestCase {
@NotNull
@Override
public String getTestDataPath() {
return PythonHelpersLocator.getPythonCommunityPath();
return PythonHelpersLocator.getPythonCommunityPath().toString();
}
@NotNull
@@ -94,7 +94,7 @@ public class PythonDebuggerPydevTest extends PyEnvTestCase {
@NotNull
@Override
public String getTestDataPath() {
return PythonHelpersLocator.getPythonCommunityPath();
return PythonHelpersLocator.getCommunityHelpersRoot().getParent().toString();
}
@Override

View File

@@ -30,7 +30,7 @@ public class PythonGeneratorTest extends PyEnvTestCase {
@NotNull
@Override
protected String getTestDataPath() {
return PythonHelpersLocator.getPythonCommunityPath();
return PythonHelpersLocator.getPythonCommunityPath().toString();
}
});
}

View File

@@ -432,11 +432,6 @@ public abstract class PyTestCase extends UsefulTestCase {
handler.invoke(myFixture.getProject(), editor, myFixture.getFile(), ((EditorEx)editor).getDataContext());
}
public static String getHelpersPath() {
return new File(PythonHelpersLocator.getPythonCommunityPath(), "helpers").getPath();
}
/**
* Compares sets with string sorting them and displaying one-per-line to make comparision easier
*