PY-75549: Simple (aka scratch) project first implementation.

Enable `pycharm.scratchProject`, and go to welcome screen.

GitOrigin-RevId: 9190344e3e8c1e7eb40834ad30c3e8f75dd599fe
This commit is contained in:
Ilya.Kazakevich
2024-10-24 03:51:01 +02:00
committed by intellij-monorepo-bot
parent 20bf407b28
commit be826ca3b4
15 changed files with 456 additions and 7 deletions

View File

@@ -2,7 +2,7 @@
<!--Customization code for both Community and Pro PyCharms-->
<dependencies>
<plugin id="PythonCore"/>
<module name="intellij.platform.whatsNew" />
<module name="intellij.platform.whatsNew"/>
</dependencies>
<projectListeners>
@@ -11,7 +11,15 @@
topic="com.intellij.workspaceModel.ide.JpsProjectLoadedListener"/>
</projectListeners>
<extensionPoints>
<extensionPoint interface="com.intellij.pycharm.community.ide.impl.scratchProject.ScratchFileType"
qualifiedName="Pythonid.scratchFileType" dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<registryKey defaultValue="5" description="Number of primary buttons on welcome screen (other go to 'more actions')"
key="welcome.screen.primaryButtonsCount" restartRequired="true" overrides="true"/>
<registryKey defaultValue="false" description="Scratch project enable" key="pycharm.scratchProject"/>
<applicationInitializedListener implementation="com.intellij.pycharm.community.ide.impl.PyCharmCorePluginConfigurator"/>
<applicationService serviceInterface="com.intellij.lang.IdeLanguageCustomization"
serviceImplementation="com.intellij.pycharm.community.ide.impl.PyCharmPythonIdeLanguageCustomization"
@@ -71,7 +79,8 @@
<directoryIndexExcludePolicy implementation="com.intellij.pycharm.community.ide.impl.PyDirectoryIndexExcludePolicy"/>
<applicationService serviceImplementation="com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeSettings"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeCollector"/>
<statistics.counterUsagesCollector
implementationClass="com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeCollector"/>
<notificationGroup id="PyCharm Professional Advertiser" displayType="STICKY_BALLOON" isLogByDefault="false"
bundle="messages.PyCharmCommunityCustomizationBundle" key="notification.group.pro.advertiser"/>
@@ -103,11 +112,14 @@
</action>
<group id="WelcomeScreen.Platform.NewProject">
<group id="WelcomeScreen.PyScratchFileActionGroup"
class="com.intellij.pycharm.community.ide.impl.scratchProject.impl.PyScratchFileActionGroup" compact="true"/>
<group id="WelcomeScreen.CreateDirectoryProject"
class="com.intellij.pycharm.community.ide.impl.newProjectWizard.impl.PyV3NewProjectStepAction"/>
class="com.intellij.pycharm.community.ide.impl.newProjectWizard.impl.PyV3NewProjectStepAction"/>
<reference ref="WelcomeScreen.OpenDirectoryProject"/>
<add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/>
</group>
</actions>
</idea-plugin>

View File

@@ -59,4 +59,15 @@ sdk.notification.label.set.up.poetry.environment.from.pyproject.toml.dependencie
notification.group.pro.advertiser=PyCharm Professional recommended
new.project.python.group.name=Python
new.project.other.group.name=Other
new.project.other.group.name=Other
scratch.script.text=New Script
scratch.project.generating.env=Preparing environment
scratch.project.filling.file=Filling the template file
scratch.project.error.title=Error Creating Scratch File
scratch.project.error.install.python=Python could not be installed {0}. Try restarting PyCharm. If it did not help, install python manually and try again.
scratch.project.error.create.dir=Could not create {0} because of {1}. Please create it manually, and try again.
scratch.project.error.all.pythons.bad=All pythons on this system are unusable. Check logs for more info.

View File

@@ -41,12 +41,14 @@ import com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcom
import com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeCollector.RunConfigurationResult
import com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeCollector.ScriptResult
import com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcomeCollector.logWelcomeRunConfiguration
import com.intellij.util.concurrency.annotations.RequiresReadLock
import com.intellij.util.concurrency.annotations.RequiresWriteLock
import com.intellij.xdebugger.XDebuggerUtil
import com.jetbrains.python.PythonPluginDisposable
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.run.PythonRunConfigurationProducer
import com.jetbrains.python.sdk.pythonSdk
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.CalledInAny
import org.jetbrains.concurrency.CancellablePromise
import java.util.concurrent.Callable
@@ -199,7 +201,7 @@ internal object PyWelcome {
val psiFile = PsiManager.getInstance(project).findFile(file) ?: error("File $file was just created, but not found in PSI")
writeText(project, psiFile)?.also { line ->
writeText(psiFile)?.also { line ->
PyWelcomeCollector.logWelcomeScript(project, ScriptResult.CREATED)
XDebuggerUtil.getInstance().toggleLineBreakpoint(project, file, line)
@@ -208,7 +210,10 @@ internal object PyWelcome {
return psiFile
}
private fun writeText(project: Project, psiFile: PsiFile): Int? {
@RequiresWriteLock
internal fun writeText(psiFile: PsiFile): Int? {
val project = psiFile.project
val document = PsiDocumentManager.getInstance(project).getDocument(psiFile)
if (document == null) {
LOG.warn("Unable to get document for ${psiFile.virtualFile}")

View File

@@ -0,0 +1,23 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.NlsActions
import com.intellij.psi.PsiFile
import javax.swing.Icon
/**
* On a welcome screen user clicks in [icon] to get a project with [fileName] template filled by [fillFile]
*/
interface ScratchFileType {
companion object {
val EP: ExtensionPointName<ScratchFileType> = ExtensionPointName.create("Pythonid.scratchFileType")
}
val title: @NlsActions.ActionText String
val icon: Icon
val fileName: TemplateFileName
suspend fun fillFile(file: PsiFile, sdk: Sdk)
}

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject
import com.intellij.openapi.util.NlsSafe
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.NotNull
/**
* [nameWithSuffix] will create `[nameNoExt]`N`.[ext] where `N > 0` if file already exists
*/
data class TemplateFileName(private val nameNoExt: @NotNull String, private val ext: @NonNls String) {
fun nameWithSuffix(suffixCount: Int): @NlsSafe String {
val suffix = if (suffixCount == 0) "" else suffixCount.toString()
return "$nameNoExt$suffix.$ext"
}
companion object {
/**
* Creates instance from `file.ext` ie `notebook.ipynb`
*/
fun parse(fileNameWithExt: @NonNls String): TemplateFileName {
val filePaths = fileNameWithExt.split(".")
assert(filePaths.size == 2) { "$fileNameWithExt must be file.ext" }
return TemplateFileName(filePaths[0], filePaths[1])
}
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject.impl
import com.intellij.diagnostic.TestMessageBoxAction
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.scratchProject.ScratchFileType
import com.intellij.util.SystemProperties
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.Result
import kotlinx.coroutines.CoroutineScope
import java.nio.file.Path
/**
* Action displayed on welcome screen to create a project by [scratchFileType]
*/
internal class PyScratchFileAction(private val scratchFileType: ScratchFileType) : AnAction(
scratchFileType.title,
null,
scratchFileType.icon
) {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
private val projectPath = Path.of(SystemProperties.getUserHome()).resolve("PyCharmScratchProject")
@RequiresEdt
override fun actionPerformed(e: AnActionEvent) {
when (val r = createScratchProject(projectPath, scratchFileType, { it.service<MyService>().scope })) {
is Result.Success -> Unit
is Result.Failure -> {
Messages.showErrorDialog(null as Project?, r.error.text, PyCharmCommunityCustomizationBundle.message("scratch.project.error.title"))
}
}
}
}
@Service(Service.Level.PROJECT)
private class MyService(val scope: CoroutineScope)

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject.impl
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.util.registry.Registry
import com.intellij.pycharm.community.ide.impl.scratchProject.ScratchFileType
internal class PyScratchFileActionGroup : ActionGroup() {
private val enabled: Boolean get() = Registry.`is`("pycharm.scratchProject")
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = enabled
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun getChildren(e: AnActionEvent?): Array<out AnAction> =
if (enabled)
(ScratchFileType.EP.extensionList + listOf(ScratchScriptFileType)).map { PyScratchFileAction(it) }.toTypedArray()
else
emptyArray()
}

View File

@@ -0,0 +1,26 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject.impl
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.NlsActions
import com.intellij.psi.PsiFile
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.newProjectWizard.welcome.PyWelcome
import com.intellij.pycharm.community.ide.impl.scratchProject.ScratchFileType
import com.intellij.pycharm.community.ide.impl.scratchProject.TemplateFileName
import com.intellij.util.IconUtil
import com.jetbrains.python.psi.icons.PythonPsiApiIcons
import javax.swing.Icon
object ScratchScriptFileType : ScratchFileType {
override val title: @NlsActions.ActionText String = PyCharmCommunityCustomizationBundle.message("scratch.script.text")
override val icon: Icon = PythonPsiApiIcons.Python_32x32
override val fileName: TemplateFileName = TemplateFileName.parse("script.py")
override suspend fun fillFile(file: PsiFile, sdk: Sdk) {
writeAction {
PyWelcome.writeText(file)
}
}
}

View File

@@ -0,0 +1,250 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.scratchProject.impl
import com.intellij.ide.impl.OpenProjectTask
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.project.modules
import com.intellij.openapi.project.rootManager
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.platform.util.progress.withProgressText
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.scratchProject.ScratchFileType
import com.intellij.pycharm.community.ide.impl.scratchProject.TemplateFileName
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.LocalizedErrorString
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.Result
import com.jetbrains.python.convertErr
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.PySdkToInstallManager
import com.jetbrains.python.sdk.PythonBinary
import com.jetbrains.python.sdk.add.v2.createSdk
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.sdk.installer.installBinary
import kotlinx.coroutines.*
import java.io.IOException
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.pathString
private val logger = fileLogger()
/**
* Creates project in [projectPath] in modal window. Once created, uses [scopeProvider] to get scope
* to launch [scratchFileType] generation in background, returns it as a job.
*
* Pythons are taken from the system (installed if needed) unless provided explicitly [pythons]
*/
@RequiresEdt
fun createScratchProject(
projectPath: Path,
scratchFileType: ScratchFileType,
scopeProvider: (Project) -> CoroutineScope,
pythons: List<Pair<PythonSdkFlavor<*>, Collection<Path>>>? = null,
): Result<Job, LocalizedErrorString> =
runWithModalProgressBlocking(ModalTaskOwner.guess(),
PyCharmCommunityCustomizationBundle.message("scratch.project.generating.env"),
TaskCancellation.cancellable()) {
createProjectAndSdk(projectPath, pythons)
}.mapResult { (project, sdk) ->
Result.Success(scopeProvider(project).launch {
withBackgroundProgress(project, PyCharmCommunityCustomizationBundle.message("scratch.project.filling.file")) {
generateAndOpenFile(projectPath, project, scratchFileType, sdk)
}
})
}
private suspend fun generateAndOpenFile(projectPath: Path, project: Project, fileType: ScratchFileType, sdk: Sdk): PsiFile {
val generateFile = generateFile(projectPath, fileType.fileName)
val psiFile = openFile(project, generateFile)
fileType.fillFile(psiFile, sdk)
return psiFile
}
private suspend fun openFile(project: Project, file: Path): PsiFile {
val vfsFile = withContext(Dispatchers.IO) {
VfsUtil.findFile(file, true) ?: error("Can't find VFS $file")
}
return withContext(Dispatchers.EDT) {
val psiFile = readAction { PsiManager.getInstance(project).findFile(vfsFile) } ?: error("Can't find PSI for $vfsFile")
psiFile.navigate(true)
psiFile
}
}
private suspend fun generateFile(where: Path, templateFileName: TemplateFileName): Path = withContext(Dispatchers.IO) {
repeat(Int.MAX_VALUE) {
val file = where.resolve(templateFileName.nameWithSuffix(it))
try {
file.createFile()
return@withContext file
}
catch (_: FileAlreadyExistsException) {
}
}
error("Too many files in $where")
}
/**
* Creates project with 1 module in [projectPath] and sdk using the highest python.
* Pythons are searched in system ([findPythonsOnSystem]) or provided explicitly [pythonsToUseInsteadOfSystem].
* In former case if no python were found, we [installLatestPython] (not in a latter case, though).
*/
private suspend fun createProjectAndSdk(
projectPath: Path,
pythonsToUseInsteadOfSystem: List<Pair<PythonSdkFlavor<*>, Collection<Path>>>? = null,
): Result<Pair<Project, Sdk>, LocalizedErrorString> {
assert(
pythonsToUseInsteadOfSystem == null || pythonsToUseInsteadOfSystem.isNotEmpty()) { "When provided explicitly, pythons can't be empty" }
val projectPathVfs = createProjectDir(projectPath).getOr { return it.convertErr() }
var pythonBinary = filterLatestPython(pythonsToUseInsteadOfSystem ?: findPythonsOnSystem())
// Only install if pythons weren't provided explicitly, see fun doc
if (pythonBinary == null && pythonsToUseInsteadOfSystem == null) {
// Install
installLatestPython().onFailure { exception ->
// Failed to install python?
logger.warn("Python installation failed", exception)
return Result.Failure(LocalizedErrorString(
PyCharmCommunityCustomizationBundle.message("scratch.project.error.install.python", exception.localizedMessage)))
}
// Find latest python again
pythonBinary = filterLatestPython(findPythonsOnSystem())
}
if (pythonBinary == null) {
return Result.Failure(LocalizedErrorString(PyCharmCommunityCustomizationBundle.message("scratch.project.error.all.pythons.bad")))
}
logger.info("using python $pythonBinary")
val project = openProject(projectPath)
val sdk = getSdk(pythonBinary, project)
val module = project.modules.first()
ensureModuleHasRoot(module, projectPathVfs)
ModuleRootModificationUtil.setModuleSdk(module, sdk)
return Result.Success(Pair(project, sdk))
}
private suspend fun openProject(projectPath: Path): Project {
val projectManager = ProjectManagerEx.getInstanceEx()
val project = projectManager.openProjectAsync(projectPath, OpenProjectTask {
runConfigurators = false
}) ?: error("Failed to open project in $projectPath, check logs")
// There are countless number of reasons `openProjectAsync` might return null
if (project.modules.isEmpty()) {
writeAction {
ModuleManager.getInstance(project).newModule(projectPath, PythonModuleTypeBase.getInstance().id)
}
}
return project
}
private suspend fun getSdk(pythonPath: PythonBinary, project: Project): Sdk =
withProgressText(ProjectBundle.message("progress.text.configuring.sdk")) {
val allJdks = ProjectJdkTable.getInstance().allJdks
val currentSdk = allJdks.firstOrNull { sdk -> sdk.homeDirectory?.toNioPath() == pythonPath }
if (currentSdk != null) return@withProgressText currentSdk
val localPythonVfs = withContext(Dispatchers.IO) { VfsUtil.findFile(pythonPath, true)!! }
return@withProgressText createSdk(localPythonVfs, project.basePath?.let { Path.of(it) }, allJdks)
}
/**
* Creating project != creating directory for it, but we need directory to create template file
*/
private suspend fun createProjectDir(projectPath: Path): Result<VirtualFile, LocalizedErrorString> = withContext(Dispatchers.IO) {
try {
projectPath.createDirectories()
}
catch (e: IOException) {
thisLogger().warn("Couldn't create $projectPath", e)
return@withContext Result.Failure(LocalizedErrorString(
PyCharmCommunityCustomizationBundle.message("scratch.project.error.create.dir", projectPath, e.localizedMessage)))
}
val projectPathVfs = VfsUtil.findFile(projectPath, true)
?: error("Can't find VFS $projectPath")
return@withContext Result.Success(projectPathVfs)
}
private suspend fun ensureModuleHasRoot(module: Module, root: VirtualFile): Unit = writeAction {
with(module.rootManager.modifiableModel) {
try {
if (root in contentRoots) return@writeAction
addContentEntry(root)
}
finally {
commit()
}
}
}
/**
* Looks for system pythons. Returns flavor and all its pythons.
*/
fun findPythonsOnSystem(): List<Pair<PythonSdkFlavor<*>, Collection<Path>>> =
PythonSdkFlavor.getApplicableFlavors(true)
.map { flavor ->
flavor.dropCaches()
flavor to flavor.suggestLocalHomePaths(null, null)
}
.filter { (_, pythons) ->
pythons.isNotEmpty() // No need to have flavors without pythons
}
suspend fun installLatestPython(): kotlin.Result<Unit> = withContext(Dispatchers.IO) {
val pythonToInstall = PySdkToInstallManager.getAvailableVersionsToInstall().toSortedMap().values.last()
return@withContext withContext(Dispatchers.EDT) {
installBinary(pythonToInstall, null) {
}
}
}
/**
* Looks for the latest python among [flavorsToPythons]: each flavour might have 1 or more pythons.
* Broken pythons are filtered out. If `null` is returned, no python found, you probably need to [installLatestPython]
*/
private suspend fun filterLatestPython(flavorsToPythons: List<Pair<PythonSdkFlavor<*>, Collection<Path>>>): PythonBinary? {
var current: Pair<LanguageLevel, Path>? = null
for ((flavor, paths) in flavorsToPythons) {
for (pythonPath in paths) {
val versionString = withContext(Dispatchers.IO) { flavor.getVersionString(pythonPath.pathString) } ?: continue
val languageLevel = flavor.getLanguageLevelFromVersionString(versionString)
// Highest possible, no need to search further
if (languageLevel == LanguageLevel.getLatest()) {
return pythonPath
}
if (current == null || current.first < languageLevel) {
// More recent Python found!
current = Pair(languageLevel, pythonPath)
}
}
}
return current?.second
}

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.pycharm.community.ide.impl.scratchProject.impl;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/**
* See `PY-75549`
*/
@ApiStatus.Internal
package com.intellij.pycharm.community.ide.impl.scratchProject;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -26,4 +26,5 @@ public final class PythonPsiApiIcons {
/** 16x16 */ public static final @NotNull Icon PropertyGetter = load("icons/com/jetbrains/python/psi/propertyGetter.svg", 1495604199, 2);
/** 16x16 */ public static final @NotNull Icon PropertySetter = load("icons/com/jetbrains/python/psi/propertySetter.svg", -1451064081, 2);
/** 16x16 */ public static final @NotNull Icon Python = load("icons/com/jetbrains/python/psi/python.svg", 2008591516, 8);
/** 32x32 */ public static final @NotNull Icon Python_32x32 = load("icons/com/jetbrains/python/psi/python@32x32.svg", -2111231783, 2);
}

View File

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 2C22 2 22 4 22 8L22 13C22 14.6569 20.6569 16 19 16H13C10.2386 16 8 18.2386 8 21V22C4 22 2 22 2 16C2 9.99997 4 9.99997 8 9.99997L15 10C15.5523 10 16 9.55228 16 9C16 8.44771 15.5523 8 15 8H10C10 4 10 2 16 2ZM13 6C13.5523 6 14 5.55228 14 5C14 4.44772 13.5523 4 13 4C12.4477 4 12 4.44772 12 5C12 5.55228 12.4477 6 13 6Z" fill="#4682FA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 10V13C24 15.7614 21.7614 18 19 18H13C11.3431 18 10 19.3431 10 21L10 24C9.99893 28 10 30 16 30C22 30 22 28 22 24.0001L17 24C16.4477 24 16 23.5523 16 23C16 22.4477 16.4477 22 17 22L24 22C28 22.0011 30 22 30 16C30 10 27.9999 10 24 10ZM19 28C19.5523 28 20 27.5523 20 27C20 26.4477 19.5523 26 19 26C18.4477 26 18 26.4477 18 27C18 27.5523 18.4477 28 19 28Z" fill="#FFAF0F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 2C22 2 22 4 22 8L22 13C22 14.6569 20.6569 16 19 16H13C10.2386 16 8 18.2386 8 21V22C4 22 2 22 2 16C2 9.99997 4 9.99997 8 9.99997L15 10C15.5523 10 16 9.55228 16 9C16 8.44771 15.5523 8 15 8H10C10 4 10 2 16 2ZM13 6C13.5523 6 14 5.55228 14 5C14 4.44772 13.5523 4 13 4C12.4477 4 12 4.44772 12 5C12 5.55228 12.4477 6 13 6Z" fill="#548AF7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 10V13C24 15.7614 21.7614 18 19 18H13C11.3431 18 10 19.3431 10 21L10 24C9.99893 28 10 30 16 30C22 30 22 28 22 24.0001L17 24C16.4477 24 16 23.5523 16 23C16 22.4477 16.4477 22 17 22L24 22C28 22.0011 30 22 30 16C30 10 27.9999 10 24 10ZM19 28C19.5523 28 20 27.5523 20 27C20 26.4477 19.5523 26 19 26C18.4477 26 18 26.4477 18 27C18 27.5523 18.4477 28 19 28Z" fill="#F2C55C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -15,6 +15,7 @@ import com.jetbrains.python.run.PythonExecution
import com.jetbrains.python.run.prepareHelperScriptExecution
import com.jetbrains.python.run.target.HelpersAwareLocalTargetEnvironmentRequest
import com.jetbrains.python.sdk.PySdkSettings
import com.jetbrains.python.sdk.PythonBinary
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
@@ -26,7 +27,7 @@ import java.nio.file.Path
@Throws(ExecutionException::class)
@Internal
suspend fun createVirtualenv(
baseInterpreterPath: Path,
baseInterpreterPath: PythonBinary,
venvRoot: Path,
projectBasePath: Path,
inheritSitePackages: Boolean = false,