mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
PY-75549: Simple (aka scratch) project first implementation.
Enable `pycharm.scratchProject`, and go to welcome screen. GitOrigin-RevId: 9190344e3e8c1e7eb40834ad30c3e8f75dd599fe
This commit is contained in:
committed by
intellij-monorepo-bot
parent
20bf407b28
commit
be826ca3b4
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user