mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[python] Support Hatch SDK (PY-60410)
* add new / select existing for local sdks * create a new project with hatch sdk * open hatch-managed project (cherry picked from commit 86e970a39bc44cec34be7c82717806fc4d0009c4) GitOrigin-RevId: 305e5363337e9120261f72e964e7d9e3c1a62c7c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fa9d6dd755
commit
44da124ea0
@@ -39,6 +39,7 @@ object PythonCommunityPluginModules {
|
||||
"intellij.python.terminal",
|
||||
"intellij.python.ml.features",
|
||||
"intellij.python.pyproject",
|
||||
"intellij.python.hatch",
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,5 +38,6 @@
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.venv" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.pyproject" />
|
||||
<orderEntry type="module" module-name="intellij.python.hatch" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -21,8 +21,10 @@
|
||||
serviceImplementation="com.intellij.pycharm.community.ide.impl.PyProjectScopeBuilder"
|
||||
overrides="true"/>
|
||||
|
||||
<refactoring.elementListenerProvider implementation="com.intellij.pycharm.community.ide.impl.miscProject.impl.MiscProjectListenerProvider"/>
|
||||
<statistics.counterUsagesCollector implementationClass="com.intellij.pycharm.community.ide.impl.miscProject.impl.MiscProjectUsageCollector"/>
|
||||
<refactoring.elementListenerProvider
|
||||
implementation="com.intellij.pycharm.community.ide.impl.miscProject.impl.MiscProjectListenerProvider"/>
|
||||
<statistics.counterUsagesCollector
|
||||
implementationClass="com.intellij.pycharm.community.ide.impl.miscProject.impl.MiscProjectUsageCollector"/>
|
||||
<registryKey defaultValue="5" description="Number of primary buttons on welcome screen (other go to 'more actions')"
|
||||
key="welcome.screen.primaryButtonsCount" restartRequired="true" overrides="true"/>
|
||||
<applicationInitializedListener implementation="com.intellij.pycharm.community.ide.impl.PyCharmCorePluginConfigurator"/>
|
||||
@@ -109,8 +111,10 @@
|
||||
id="pipfile" order="before requirementsTxtOrSetupPy"/>
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyPoetrySdkConfiguration"
|
||||
id="poetry"/>
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyHatchSdkConfiguration"
|
||||
id="hatch" order="after poetry"/>
|
||||
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyUvSdkConfiguration"
|
||||
id="uv" order="after poetry"/>
|
||||
id="uv" order="after hatch"/>
|
||||
</extensions>
|
||||
|
||||
<actions resource-bundle="messages.ActionsBundle">
|
||||
|
||||
@@ -58,6 +58,7 @@ sdk.dialog.title.setting.up.poetry.environment=Setting Up Poetry Environment
|
||||
sdk.notification.label.set.up.poetry.environment.from.pyproject.toml.dependencies=File pyproject.toml contains project dependencies. Would you like to set up a poetry environment?
|
||||
notification.group.pro.advertiser=PyCharm Professional recommended
|
||||
|
||||
sdk.set.up.hatch.environment=Set up Hatch 'default' environment
|
||||
sdk.set.up.uv.environment=Set up an uv environment using {0}
|
||||
|
||||
new.project.python.group.name=Python
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.hatch.getHatchService
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.orLogException
|
||||
import com.jetbrains.python.sdk.hatch.createSdk
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
|
||||
class PyHatchSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
companion object {
|
||||
private val LOGGER = Logger.getInstance(PyHatchSdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun getIntention(module: Module): @IntentionName String? {
|
||||
val isReadyAndHaveOwnership = runWithModalProgressBlocking(module.project, "Hatch Project Analysis") {
|
||||
val hatchService = module.getHatchService().getOr { return@runWithModalProgressBlocking false }
|
||||
hatchService.isHatchManagedProject().getOrNull() == true
|
||||
}
|
||||
|
||||
val intention = when {
|
||||
isReadyAndHaveOwnership -> PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
|
||||
else -> null
|
||||
}
|
||||
return intention
|
||||
}
|
||||
|
||||
private fun createSdk(module: Module): Sdk? {
|
||||
val sdk = runBlockingCancellable {
|
||||
val hatchService = module.getHatchService().orLogException(LOGGER)
|
||||
val environment = hatchService?.createVirtualEnvironment()?.orLogException(LOGGER)
|
||||
environment?.createSdk(module)?.orLogException(LOGGER)
|
||||
}?.also {
|
||||
SdkConfigurationUtil.addSdk(it)
|
||||
}
|
||||
return sdk
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = createSdk(module)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = createSdk(module)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
}
|
||||
@@ -144,5 +144,6 @@
|
||||
<orderEntry type="module" module-name="intellij.python.community.services.internal.impl" />
|
||||
<orderEntry type="library" name="io.github.z4kn4fein.semver.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.pyproject" />
|
||||
<orderEntry type="module" module-name="intellij.python.hatch" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -840,6 +840,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
|
||||
<pythonFlavorProvider implementation="com.jetbrains.python.sdk.uv.UvSdkFlavorProvider"/>
|
||||
|
||||
<pythonFlavorProvider implementation="com.jetbrains.python.sdk.hatch.HatchSdkFlavorProvider"/>
|
||||
|
||||
<!-- SDK Flavors -->
|
||||
<pythonSdkFlavor implementation="com.jetbrains.python.sdk.flavors.conda.CondaEnvSdkFlavor"/>
|
||||
<pythonSdkFlavor implementation="com.jetbrains.python.sdk.flavors.MacPythonSdkFlavor"/>
|
||||
|
||||
@@ -551,6 +551,12 @@ sdk.create.custom.venv.install.fix.title=Install {0} {1}
|
||||
sdk.create.custom.venv.run.error.message=Error Running {0}
|
||||
sdk.create.custom.venv.progress.title.detect.executable=Detect executable
|
||||
sdk.create.custom.existing.env.title={0} env use
|
||||
sdk.create.custom.hatch.environment=Environment:
|
||||
sdk.create.custom.hatch.environment.exists=Environment already exists
|
||||
sdk.create.custom.hatch.error.no.environments.to.select=Hatch didn't provide any environment to select
|
||||
sdk.create.custom.hatch.error.execution.failed=Please verify Hatch tool, executed with error: {0} {1}
|
||||
sdk.create.custom.hatch.error.module.is.not.selected=Module is not selected
|
||||
sdk.create.custom.hatch.error.hatch.executable.path.is.not.valid=Hatch executable path is not valid: {0}
|
||||
|
||||
sdk.create.targets.local=Local Machine
|
||||
sdk.create.custom.virtualenv=Virtualenv
|
||||
@@ -558,6 +564,7 @@ sdk.create.custom.conda=Conda
|
||||
sdk.create.custom.pipenv=Pipenv
|
||||
sdk.create.custom.poetry=Poetry
|
||||
sdk.create.custom.uv=uv
|
||||
sdk.create.custom.hatch=Hatch
|
||||
sdk.create.custom.python=Python
|
||||
|
||||
sdk.rendering.detected.grey.text=system
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.PythonHelpersLocator
|
||||
import com.jetbrains.python.execution.PyExecutionFailure
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
@@ -27,6 +26,7 @@ import com.jetbrains.python.errorProcessing.emit
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.nio.file.Path
|
||||
import com.jetbrains.python.Result
|
||||
|
||||
@Internal
|
||||
internal abstract class CustomNewEnvironmentCreator(private val name: String, model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
|
||||
@@ -55,7 +55,7 @@ internal abstract class CustomNewEnvironmentCreator(private val name: String, mo
|
||||
basePythonComboBox.setItems(model.baseInterpreters)
|
||||
}
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): com.jetbrains.python.Result<Sdk, PyError> {
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result<Sdk, PyError> {
|
||||
savePathToExecutableToProperties(null)
|
||||
|
||||
// todo think about better error handling
|
||||
@@ -71,14 +71,13 @@ internal abstract class CustomNewEnvironmentCreator(private val name: String, mo
|
||||
ProjectJdkTable.getInstance().allJdks.asList(),
|
||||
model.myProjectPathFlows.projectPathWithDefault.first().toString(),
|
||||
homePath,
|
||||
false)
|
||||
.getOrElse { return com.jetbrains.python.Result.failure(if (it is PyExecutionFailure) PyError.ExecException(it) else PyError.Message(it.localizedMessage)) }
|
||||
false).getOr { return it }
|
||||
newSdk.persist()
|
||||
|
||||
6
|
||||
module?.excludeInnerVirtualEnv(newSdk)
|
||||
model.addInterpreter(newSdk)
|
||||
|
||||
return com.jetbrains.python.Result.success(newSdk)
|
||||
return Result.success(newSdk)
|
||||
}
|
||||
|
||||
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo =
|
||||
@@ -100,7 +99,7 @@ internal abstract class CustomNewEnvironmentCreator(private val name: String, mo
|
||||
* 5. Reruns `detectExecutable`
|
||||
*/
|
||||
@RequiresEdt
|
||||
private fun createInstallFix(errorSink: ErrorSink): ActionLink {
|
||||
protected fun createInstallFix(errorSink: ErrorSink): ActionLink {
|
||||
return ActionLink(message("sdk.create.custom.venv.install.fix.title", name, "via pip")) {
|
||||
PythonSdkFlavor.clearExecutablesCache()
|
||||
installExecutable(errorSink)
|
||||
@@ -154,7 +153,7 @@ internal abstract class CustomNewEnvironmentCreator(private val name: String, mo
|
||||
*/
|
||||
internal abstract fun savePathToExecutableToProperties(path: Path?)
|
||||
|
||||
protected abstract suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk>
|
||||
protected abstract suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk, PyError>
|
||||
|
||||
internal abstract suspend fun detectExecutable()
|
||||
}
|
||||
@@ -7,11 +7,14 @@ import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.sdk.pipenv.pipEnvPath
|
||||
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.asPythonResult
|
||||
|
||||
internal class EnvironmentCreatorPip(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("pipenv", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.PIPENV
|
||||
@@ -23,8 +26,8 @@ internal class EnvironmentCreatorPip(model: PythonMutableTargetAddInterpreterMod
|
||||
PropertiesComponent.getInstance().pipEnvPath = savingPath
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> =
|
||||
setupPipEnvSdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages)
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk, PyError> =
|
||||
setupPipEnvSdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages).asPythonResult()
|
||||
|
||||
override suspend fun detectExecutable() {
|
||||
model.detectPipEnvExecutable()
|
||||
|
||||
@@ -25,6 +25,9 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.asPythonResult
|
||||
|
||||
internal class EnvironmentCreatorPoetry(model: PythonMutableTargetAddInterpreterModel, private val moduleOrProject: ModuleOrProject?) : CustomNewEnvironmentCreator("poetry", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.POETRY
|
||||
@@ -60,9 +63,9 @@ internal class EnvironmentCreatorPoetry(model: PythonMutableTargetAddInterpreter
|
||||
PropertiesComponent.getInstance().poetryPath = savingPath
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> {
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk, PyError> {
|
||||
module?.let { service<PoetryConfigService>().setInProjectEnv(it) }
|
||||
return setupPoetrySdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages)
|
||||
return setupPoetrySdkUnderProgress(project, module, baseSdks, projectPath, homePath, installPackages).asPythonResult()
|
||||
}
|
||||
|
||||
override suspend fun detectExecutable() {
|
||||
|
||||
@@ -11,6 +11,9 @@ import com.jetbrains.python.sdk.uv.impl.setUvExecutable
|
||||
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.asPythonResult
|
||||
|
||||
internal class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterModel) : CustomNewEnvironmentCreator("uv", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.UV
|
||||
@@ -27,14 +30,14 @@ internal class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterMode
|
||||
setUvExecutable(savingPath)
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk> {
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk, PyError> {
|
||||
if (module == null) {
|
||||
// FIXME: should not happen, proper error
|
||||
return Result.failure(Exception("module is null"))
|
||||
return kotlin.Result.failure<Sdk>(Exception("module is null")).asPythonResult()
|
||||
}
|
||||
|
||||
val python = homePath?.let { Path.of(it) }
|
||||
return setupUvSdkUnderProgress(ModuleOrProject.ModuleAndProject(module), baseSdks, python)
|
||||
return setupUvSdkUnderProgress(ModuleOrProject.ModuleAndProject(module), baseSdks, python).asPythonResult()
|
||||
}
|
||||
|
||||
override suspend fun detectExecutable() {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.python.hatch.HatchConfiguration
|
||||
import com.intellij.python.hatch.PythonVirtualEnvironment
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.onSuccess
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.hatch.createSdk
|
||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
|
||||
internal class HatchExistingEnvironmentSelector(
|
||||
override val model: PythonMutableTargetAddInterpreterModel,
|
||||
val moduleOrProject: ModuleOrProject,
|
||||
) : PythonExistingEnvironmentConfigurator(model) {
|
||||
val interpreterType: InterpreterType = InterpreterType.HATCH
|
||||
val executable: ObservableMutableProperty<String> = propertyGraph.property(model.state.hatchExecutable.get())
|
||||
|
||||
init {
|
||||
propertyGraph.dependsOn(executable, model.state.hatchExecutable, deleteWhenChildModified = false) {
|
||||
model.state.hatchExecutable.get()
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor, errorSink: ErrorSink) {
|
||||
panel.buildHatchFormFields(
|
||||
model = model,
|
||||
hatchExecutableProperty = executable,
|
||||
propertyGraph = propertyGraph,
|
||||
validationRequestor = validationRequestor,
|
||||
isGenerateNewMode = false,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result<Sdk, PyError> {
|
||||
val existingHatchVenv = state.selectedHatchEnv.get()?.pythonVirtualEnvironment as? PythonVirtualEnvironment.Existing
|
||||
?: return Result.failure(HatchUIError.HatchEnvironmentIsNotSelected())
|
||||
val module = (moduleOrProject as? ModuleOrProject.ModuleAndProject)?.module
|
||||
?: return Result.failure(HatchUIError.ModuleIsNotSelected())
|
||||
|
||||
val existingSdk = existingHatchVenv.pythonHomePath.toString().let { venvHomePathString ->
|
||||
ProjectJdkTable.getInstance().allJdks.find { it.homePath == venvHomePathString }
|
||||
}
|
||||
|
||||
val sdk = when {
|
||||
existingSdk != null -> Result.success(existingSdk)
|
||||
else -> existingHatchVenv.createSdk(module)
|
||||
}.onSuccess {
|
||||
val executablePath = executable.get().toPath().getOr { return@onSuccess }
|
||||
HatchConfiguration.persistPathForTarget(hatchExecutablePath = executablePath)
|
||||
}
|
||||
return sdk
|
||||
}
|
||||
|
||||
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
|
||||
val statisticsTarget = target.toStatisticsField()
|
||||
return InterpreterStatisticsInfo(
|
||||
type = InterpreterType.HATCH,
|
||||
target = statisticsTarget,
|
||||
globalSitePackage = false,
|
||||
makeAvailableToAllProjects = false,
|
||||
previouslyConfigured = true,
|
||||
isWSLContext = false,
|
||||
creationMode = InterpreterCreationMode.CUSTOM
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.python.hatch.HatchConfiguration
|
||||
import com.intellij.python.hatch.getHatchService
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.onSuccess
|
||||
import com.jetbrains.python.sdk.hatch.createSdk
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class HatchNewEnvironmentCreator(
|
||||
override val model: PythonMutableTargetAddInterpreterModel,
|
||||
) : CustomNewEnvironmentCreator("hatch", model) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.HATCH
|
||||
override val executable: ObservableMutableProperty<String> = propertyGraph.property(model.state.hatchExecutable.get())
|
||||
|
||||
init {
|
||||
propertyGraph.dependsOn(executable, model.state.hatchExecutable, deleteWhenChildModified = false) {
|
||||
model.state.hatchExecutable.get()
|
||||
}
|
||||
}
|
||||
|
||||
override val installationVersion: String? = null
|
||||
|
||||
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor, errorSink: ErrorSink) {
|
||||
panel.buildHatchFormFields(
|
||||
model = model,
|
||||
hatchExecutableProperty = executable,
|
||||
propertyGraph = propertyGraph,
|
||||
validationRequestor = validationRequestor,
|
||||
isGenerateNewMode = true,
|
||||
installHatchActionLink = createInstallFix(errorSink)
|
||||
) {
|
||||
basePythonComboBox = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun savePathToExecutableToProperties(path: Path?) {
|
||||
val savingPath = path ?: executable.get().nullize()?.let { Path.of(it) } ?: return
|
||||
HatchConfiguration.persistPathForTarget(hatchExecutablePath = savingPath)
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Result<Sdk, PyError> {
|
||||
module ?: return Result.failure(HatchUIError.ModuleIsNotSelected())
|
||||
val selectedEnv = model.state.selectedHatchEnv.get() ?: return Result.failure(HatchUIError.HatchEnvironmentIsNotSelected())
|
||||
|
||||
val hatchExecutablePath = executable.get().toPath().getOr { return it }
|
||||
val hatchService = module.getHatchService(hatchExecutablePath = hatchExecutablePath).getOr { return it }
|
||||
|
||||
val existingHatchVenv = hatchService.createVirtualEnvironment(
|
||||
basePythonBinaryPath = homePath?.let { Path.of(it) },
|
||||
envName = selectedEnv.hatchEnvironment.name
|
||||
).getOr { return it }
|
||||
|
||||
val createdSdk = existingHatchVenv.createSdk(module).onSuccess {
|
||||
HatchConfiguration.persistPathForTarget(hatchExecutablePath = hatchExecutablePath)
|
||||
}
|
||||
return createdSdk
|
||||
}
|
||||
|
||||
override suspend fun detectExecutable() {
|
||||
model.detectHatchExecutable()
|
||||
}
|
||||
}
|
||||
210
python/src/com/jetbrains/python/sdk/add/v2/HatchUIComponents.kt
Normal file
210
python/src/com/jetbrains/python/sdk/add/v2/HatchUIComponents.kt
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
|
||||
import com.intellij.openapi.ui.validation.and
|
||||
import com.intellij.python.hatch.HatchStandaloneEnvironment
|
||||
import com.intellij.python.hatch.PythonVirtualEnvironment
|
||||
import com.intellij.ui.ColoredListCellRenderer
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.ui.dsl.builder.components.ValidationType
|
||||
import com.intellij.ui.dsl.builder.components.validationTooltip
|
||||
import com.intellij.ui.layout.ComponentPredicate
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.icons.PythonIcons
|
||||
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.nio.file.Path
|
||||
import javax.swing.JList
|
||||
|
||||
internal sealed class HatchUIError(message: String) : PyError.Message(message) {
|
||||
class ModuleIsNotSelected : HatchUIError(
|
||||
message("sdk.create.custom.hatch.error.module.is.not.selected")
|
||||
)
|
||||
|
||||
class HatchEnvironmentIsNotSelected : HatchUIError(
|
||||
message("sdk.create.custom.hatch.error.module.is.not.selected")
|
||||
)
|
||||
|
||||
class HatchExecutablePathIsNotValid(hatchExecutablePath: String?) : HatchUIError(
|
||||
message("sdk.create.custom.hatch.error.hatch.executable.path.is.not.valid",
|
||||
hatchExecutablePath)
|
||||
)
|
||||
|
||||
class HatchExecutionFailure(execException: ExecException) : HatchUIError(
|
||||
message("sdk.create.custom.hatch.error.execution.failed",
|
||||
execException.execFailure.command, execException.execFailure.args.joinToString(" ")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun String.toPath(): Result<Path, PyError> {
|
||||
return when (val selectedPath = Path.of(this)) {
|
||||
null -> Result.failure(HatchUIError.HatchExecutablePathIsNotValid(this))
|
||||
else -> Result.success(selectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
private class HatchEnvComboBoxListCellRenderer : ColoredListCellRenderer<HatchStandaloneEnvironment>() {
|
||||
override fun customizeCellRenderer(list: JList<out HatchStandaloneEnvironment?>, value: HatchStandaloneEnvironment?, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
if (value == null) return
|
||||
icon = when (value.pythonVirtualEnvironment) {
|
||||
is PythonVirtualEnvironment.Existing -> PythonIcons.Python.PythonClosed
|
||||
is PythonVirtualEnvironment.NotExisting -> AllIcons.Nodes.Folder
|
||||
}
|
||||
|
||||
append(value.hatchEnvironment.name, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
value.pythonVirtualEnvironment.pythonHomePath?.let { pythonHomePath ->
|
||||
append("\t", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
append(pythonHomePath.toString(), SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Panel.addEnvironmentComboBox(
|
||||
model: PythonMutableTargetAddInterpreterModel,
|
||||
propertyGraph: PropertyGraph,
|
||||
validationRequestor: DialogValidationRequestor,
|
||||
isValidateOnlyNotExisting: Boolean,
|
||||
): ComboBox<HatchStandaloneEnvironment> {
|
||||
val environmentAlreadyExists = propertyGraph.property(false)
|
||||
lateinit var environmentComboBox: ComboBox<HatchStandaloneEnvironment>
|
||||
|
||||
row(message("sdk.create.custom.hatch.environment")) {
|
||||
environmentComboBox = comboBox(emptyList(), HatchEnvComboBoxListCellRenderer())
|
||||
.bindItem(model.state.selectedHatchEnv)
|
||||
.displayLoaderWhen(model.hatchEnvironmentsLoading, scope = model.scope, uiContext = model.uiContext)
|
||||
.validationRequestor(validationRequestor and WHEN_PROPERTY_CHANGED(model.state.selectedHatchEnv))
|
||||
.validationInfo { component ->
|
||||
environmentAlreadyExists.set(false)
|
||||
with(component) {
|
||||
when {
|
||||
!isVisible -> null
|
||||
item == null -> ValidationInfo(message("sdk.create.custom.hatch.error.no.environments.to.select"))
|
||||
isValidateOnlyNotExisting && item?.pythonVirtualEnvironment is PythonVirtualEnvironment.Existing -> {
|
||||
environmentAlreadyExists.set(true)
|
||||
ValidationInfo(message("sdk.create.custom.hatch.environment.exists"))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}
|
||||
|
||||
row("") {
|
||||
validationTooltip(
|
||||
message = message("sdk.create.custom.hatch.environment.exists"),
|
||||
firstActionLink = ActionLink(message("sdk.create.custom.venv.select.existing.link")) {
|
||||
PythonNewProjectWizardCollector.logExistingVenvFixUsed()
|
||||
model.navigator.navigateTo(newMethod = SELECT_EXISTING, newManager = PythonSupportedEnvironmentManagers.HATCH)
|
||||
},
|
||||
validationType = ValidationType.ERROR
|
||||
).align(Align.FILL)
|
||||
}.visibleIf(environmentAlreadyExists)
|
||||
return environmentComboBox
|
||||
}
|
||||
|
||||
private fun Panel.addExecutableSelector(
|
||||
model: PythonMutableTargetAddInterpreterModel,
|
||||
propertyGraph: PropertyGraph,
|
||||
hatchExecutableProperty: ObservableMutableProperty<String>,
|
||||
hatchErrorProperty: ObservableMutableProperty<PyError?>,
|
||||
validationRequestor: DialogValidationRequestor,
|
||||
installHatchActionLink: ActionLink? = null,
|
||||
) {
|
||||
val hatchErrorMessage = propertyGraph.property<@Nls String>("")
|
||||
propertyGraph.dependsOn(hatchErrorMessage, hatchErrorProperty, deleteWhenChildModified = false) {
|
||||
when (val error = hatchErrorProperty.get()) {
|
||||
null -> ""
|
||||
is PyError.Message -> error.message
|
||||
is PyError.ExecException -> HatchUIError.HatchExecutionFailure(error).message
|
||||
}
|
||||
}
|
||||
|
||||
executableSelector(
|
||||
hatchExecutableProperty,
|
||||
validationRequestor,
|
||||
message("sdk.create.custom.venv.executable.path", "hatch"),
|
||||
message("sdk.create.custom.venv.missing.text", "hatch"),
|
||||
installHatchActionLink
|
||||
).validationOnInput { selector ->
|
||||
if (!selector.isVisible) return@validationOnInput null
|
||||
|
||||
if (hatchExecutableProperty.get() != model.state.hatchExecutable.get()) {
|
||||
model.state.hatchExecutable.set(hatchExecutableProperty.get())
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
row("") {
|
||||
validationTooltip(textProperty = hatchErrorMessage, validationType = ValidationType.ERROR).align(Align.FILL)
|
||||
}.visibleIf(object : ComponentPredicate() {
|
||||
override fun addListener(listener: (Boolean) -> Unit) {
|
||||
hatchErrorProperty.afterChange { listener(invoke()) }
|
||||
}
|
||||
|
||||
override fun invoke(): Boolean = hatchErrorProperty.get() != null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
internal fun Panel.buildHatchFormFields(
|
||||
model: PythonMutableTargetAddInterpreterModel,
|
||||
hatchExecutableProperty: ObservableMutableProperty<String>,
|
||||
propertyGraph: PropertyGraph,
|
||||
validationRequestor: DialogValidationRequestor,
|
||||
isGenerateNewMode: Boolean = false,
|
||||
installHatchActionLink: ActionLink? = null,
|
||||
basePythonComboboxReceiver: ((PythonInterpreterComboBox) -> Unit) = { },
|
||||
) {
|
||||
val environmentComboBox = addEnvironmentComboBox(model, propertyGraph, validationRequestor, isValidateOnlyNotExisting = isGenerateNewMode)
|
||||
|
||||
if (isGenerateNewMode) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
val basePythonComboBox = pythonInterpreterComboBox(
|
||||
selectedSdkProperty = model.state.baseInterpreter,
|
||||
model = model,
|
||||
onPathSelected = model::addInterpreter,
|
||||
busyState = model.interpreterLoading
|
||||
).align(Align.FILL).component
|
||||
basePythonComboboxReceiver(basePythonComboBox)
|
||||
}
|
||||
}
|
||||
|
||||
val hatchError = propertyGraph.property<PyError?>(null)
|
||||
addExecutableSelector(model, propertyGraph, hatchExecutableProperty, hatchError, validationRequestor, installHatchActionLink)
|
||||
|
||||
model.hatchEnvironmentsResult.onEach { environmentsResult ->
|
||||
environmentsResult?.let { environmentComboBox.syncWithEnvs(it, isFilterOnlyExisting = !isGenerateNewMode) }
|
||||
hatchError.set((environmentsResult as? Result.Failure)?.error)
|
||||
}.launchIn(model.scope)
|
||||
}
|
||||
|
||||
private fun ComboBox<HatchStandaloneEnvironment>.syncWithEnvs(
|
||||
environmentsResult: Result<List<HatchStandaloneEnvironment>, PyError>,
|
||||
isFilterOnlyExisting: Boolean = false,
|
||||
) {
|
||||
removeAllItems()
|
||||
val environments = environmentsResult.getOr { return }
|
||||
environments.filter { !isFilterOnlyExisting || it.pythonVirtualEnvironment is PythonVirtualEnvironment.Existing }.forEach {
|
||||
addItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,17 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
|
||||
PIPENV to EnvironmentCreatorPip(model),
|
||||
POETRY to EnvironmentCreatorPoetry(model, moduleOrProject),
|
||||
UV to EnvironmentCreatorUv(model),
|
||||
HATCH to HatchNewEnvironmentCreator(model),
|
||||
)
|
||||
|
||||
private val existingInterpreterSelectors = buildMap {
|
||||
put(PYTHON, PythonExistingEnvironmentSelector(model))
|
||||
put(CONDA, CondaExistingEnvironmentSelector(model, errorSink))
|
||||
if (moduleOrProject != null) put(POETRY, PoetryExistingEnvironmentSelector(model, moduleOrProject))
|
||||
if (moduleOrProject != null) put(UV, UvExistingEnvironmentSelector(model, moduleOrProject))
|
||||
if (moduleOrProject != null) {
|
||||
put(POETRY, PoetryExistingEnvironmentSelector(model, moduleOrProject))
|
||||
put(UV, UvExistingEnvironmentSelector(model, moduleOrProject))
|
||||
put(HATCH, HatchExistingEnvironmentSelector(model, moduleOrProject))
|
||||
}
|
||||
}
|
||||
|
||||
val currentSdkManager: PythonAddEnvironment
|
||||
|
||||
@@ -31,6 +31,7 @@ internal class PythonAddLocalInterpreterDialog(private val dialogPresenter: Pyth
|
||||
|
||||
init {
|
||||
title = PyBundle.message("python.sdk.add.python.interpreter.title")
|
||||
setSize(640, 320)
|
||||
isResizable = true
|
||||
init()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import javax.swing.Icon
|
||||
|
||||
import com.intellij.python.hatch.icons.PythonHatchIcons
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class PythonAddSdkService(val coroutineScope: CoroutineScope)
|
||||
@@ -63,6 +63,7 @@ enum class PythonSupportedEnvironmentManagers(val nameKey: String, val icon: Ico
|
||||
POETRY("sdk.create.custom.poetry", POETRY_ICON),
|
||||
PIPENV("sdk.create.custom.pipenv", PIPENV_ICON),
|
||||
UV("sdk.create.custom.uv", UV_ICON),
|
||||
HATCH("sdk.create.custom.hatch", PythonHatchIcons.Logo),
|
||||
PYTHON("sdk.create.custom.python", com.jetbrains.python.psi.icons.PythonPsiApiIcons.Python)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,23 @@ import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.io.NioFiles
|
||||
import com.intellij.python.community.services.internal.impl.PythonWithLanguageLevelImpl
|
||||
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
|
||||
import com.intellij.python.community.services.systemPython.SystemPython
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonService
|
||||
import com.intellij.python.community.services.systemPython.UICustomization
|
||||
import com.intellij.python.hatch.HatchConfiguration
|
||||
import com.intellij.python.hatch.HatchStandaloneEnvironment
|
||||
import com.intellij.python.hatch.getHatchService
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.emit
|
||||
import com.jetbrains.python.failure
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.isFailure
|
||||
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
@@ -33,17 +40,14 @@ import com.jetbrains.python.sdk.pipenv.getPipEnvExecutable
|
||||
import com.jetbrains.python.sdk.poetry.getPoetryExecutable
|
||||
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams, private val systemPythonService: SystemPythonService = SystemPythonService()) {
|
||||
|
||||
@@ -64,6 +68,7 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams, priva
|
||||
val manuallyAddedInterpreters: MutableStateFlow<List<PythonSelectableInterpreter>> = MutableStateFlow(emptyList())
|
||||
private var installable: List<PythonSelectableInterpreter> = emptyList()
|
||||
val condaEnvironments: MutableStateFlow<List<PyCondaEnv>> = MutableStateFlow(emptyList())
|
||||
val hatchEnvironmentsResult: MutableStateFlow<com.jetbrains.python.Result<List<HatchStandaloneEnvironment>, PyError>?> = MutableStateFlow(null)
|
||||
|
||||
var allInterpreters: StateFlow<List<PythonSelectableInterpreter>> = combine(knownInterpreters,
|
||||
detectedInterpreters,
|
||||
@@ -81,6 +86,7 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams, priva
|
||||
|
||||
val interpreterLoading = MutableStateFlow(false)
|
||||
val condaEnvironmentsLoading = MutableStateFlow(false)
|
||||
val hatchEnvironmentsLoading: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
|
||||
open fun createBrowseAction(): () -> String? = TODO()
|
||||
|
||||
@@ -125,6 +131,33 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams, priva
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
suspend fun detectHatchEnvironments(
|
||||
hatchExecutablePathString: String,
|
||||
): com.jetbrains.python.Result<List<HatchStandaloneEnvironment>, PyError> {
|
||||
hatchEnvironmentsLoading.value = true
|
||||
val environmentsResult = withContext(Dispatchers.IO) {
|
||||
val projectPath = myProjectPathFlows.projectPathWithDefault.first()
|
||||
val hatchExecutablePath = NioFiles.toPath(hatchExecutablePathString)
|
||||
?: return@withContext com.jetbrains.python.Result.failure(
|
||||
HatchUIError.HatchExecutablePathIsNotValid(hatchExecutablePathString)
|
||||
)
|
||||
val hatchWorkingDirectory = if (projectPath.isDirectory()) projectPath else projectPath.parent
|
||||
val hatchService = getHatchService(
|
||||
workingDirectoryPath = hatchWorkingDirectory,
|
||||
hatchExecutablePath = hatchExecutablePath,
|
||||
).getOr { return@withContext it }
|
||||
|
||||
val hatchEnvironments = hatchService.findStandaloneEnvironments().getOr { return@withContext it }
|
||||
val availableEnvironments = when {
|
||||
hatchWorkingDirectory == projectPath -> hatchEnvironments
|
||||
else -> HatchStandaloneEnvironment.AVAILABLE_ENVIRONMENTS_FOR_NEW_PROJECT
|
||||
}
|
||||
|
||||
com.jetbrains.python.Result.success(availableEnvironments)
|
||||
}
|
||||
return environmentsResult
|
||||
}
|
||||
|
||||
private suspend fun initInterpreterList() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val existingSdks = PyConfigurableInterpreterList.getInstance(null).getModel().sdks.toList()
|
||||
@@ -215,11 +248,30 @@ abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModel
|
||||
: PythonAddInterpreterModel(params) {
|
||||
override val state: MutableTargetState = MutableTargetState(propertyGraph)
|
||||
|
||||
init {
|
||||
state.hatchExecutable.afterChange { pathString ->
|
||||
scope.launch {
|
||||
hatchEnvironmentsLoading.value = true
|
||||
val hatchEnvironmentResult = detectHatchEnvironments(pathString)
|
||||
withContext(uiContext) {
|
||||
hatchEnvironmentsResult.value = hatchEnvironmentResult
|
||||
if (hatchEnvironmentResult.isFailure) {
|
||||
state.selectedHatchEnv.set(null)
|
||||
}
|
||||
else {
|
||||
hatchEnvironmentsLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun initialize() {
|
||||
super.initialize()
|
||||
detectPoetryExecutable()
|
||||
detectPipEnvExecutable()
|
||||
detectUvExecutable()
|
||||
detectHatchExecutable()
|
||||
}
|
||||
|
||||
suspend fun detectPoetryExecutable() {
|
||||
@@ -245,6 +297,15 @@ abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun detectHatchExecutable() {
|
||||
HatchConfiguration.getOrDetectHatchExecutablePath().getOrNull()?.pathString?.let {
|
||||
withContext(Dispatchers.EDT) {
|
||||
hatchEnvironmentsLoading.value = true
|
||||
state.hatchExecutable.set(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PythonLocalAddInterpreterModel(params: PyInterpreterModelParams)
|
||||
@@ -337,6 +398,8 @@ open class AddInterpreterState(propertyGraph: PropertyGraph) {
|
||||
* Use [PythonAddInterpreterModel.getBaseCondaOrError]
|
||||
*/
|
||||
val baseCondaEnv: ObservableMutableProperty<PyCondaEnv?> = propertyGraph.property(null)
|
||||
|
||||
val selectedHatchEnv: ObservableMutableProperty<HatchStandaloneEnvironment?> = propertyGraph.property(null)
|
||||
}
|
||||
|
||||
class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(propertyGraph) {
|
||||
@@ -344,6 +407,7 @@ class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(pro
|
||||
val newCondaEnvName: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val poetryExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val uvExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val hatchExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val pipenvExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val venvPath: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val inheritSitePackages = propertyGraph.property(false)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.sdk.hatch
|
||||
|
||||
import com.intellij.python.hatch.icons.PythonHatchIcons
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.*
|
||||
import org.jdom.Element
|
||||
import javax.swing.Icon
|
||||
|
||||
|
||||
typealias HatchSdkFlavorData = PyFlavorData.Empty
|
||||
|
||||
object HatchSdkFlavor : CPythonSdkFlavor<HatchSdkFlavorData>() {
|
||||
override fun getIcon(): Icon = PythonHatchIcons.Logo
|
||||
override fun getFlavorDataClass(): Class<HatchSdkFlavorData> = HatchSdkFlavorData::class.java
|
||||
override fun isValidSdkPath(pathStr: String): Boolean = false
|
||||
}
|
||||
|
||||
class HatchSdkFlavorProvider : PythonFlavorProvider {
|
||||
override fun getFlavor(platformIndependent: Boolean): PythonSdkFlavor<*> = HatchSdkFlavor
|
||||
}
|
||||
|
||||
class HatchSdkAdditionalData(data: PythonSdkAdditionalData) : PythonSdkAdditionalData(data) {
|
||||
constructor() : this(
|
||||
data = PythonSdkAdditionalData(PyFlavorAndData(data = HatchSdkFlavorData, flavor = HatchSdkFlavor))
|
||||
)
|
||||
|
||||
override fun save(element: Element) {
|
||||
super.save(element)
|
||||
element.setAttribute(IS_HATCH, "true")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val IS_HATCH = "IS_HATCH"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.hatch
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.hatch.EnvironmentCreationHatchError
|
||||
import com.intellij.python.hatch.PythonVirtualEnvironment
|
||||
import com.intellij.python.hatch.getHatchEnvVirtualProjectPath
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.failure
|
||||
import com.jetbrains.python.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.createSdk
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.io.path.name
|
||||
|
||||
@ApiStatus.Internal
|
||||
suspend fun PythonVirtualEnvironment.Existing.createSdk(module: Module): Result<Sdk, PyError> {
|
||||
val pythonBinary = pythonHomePath.resolvePythonBinary()
|
||||
?: return failure("Cannot find Python Binary")
|
||||
|
||||
val sdk = createSdk(
|
||||
sdkHomePath = pythonBinary,
|
||||
existingSdks = ProjectJdkTable.getInstance().allJdks.asList(),
|
||||
associatedProjectPath = module.project.basePath,
|
||||
suggestedSdkName = suggestHatchSdkName(),
|
||||
sdkAdditionalData = HatchSdkAdditionalData()
|
||||
).getOrElse { exception ->
|
||||
return Result.failure(EnvironmentCreationHatchError(exception.localizedMessage))
|
||||
}.also {
|
||||
it.setAssociationToPath(module.basePath.toString())
|
||||
}
|
||||
return Result.success(sdk)
|
||||
}
|
||||
|
||||
private fun PythonVirtualEnvironment.Existing.suggestHatchSdkName(): @NlsSafe String {
|
||||
val normalizedProjectName = pythonHomePath.getHatchEnvVirtualProjectPath().name
|
||||
val nonDefaultEnvName = pythonHomePath.name.takeIf { it != normalizedProjectName }
|
||||
|
||||
val envNamePrefix = nonDefaultEnvName?.let { "$it@" } ?: ""
|
||||
val sdkName = "Hatch ($envNamePrefix$normalizedProjectName) [$pythonVersion]"
|
||||
return sdkName
|
||||
}
|
||||
@@ -126,6 +126,7 @@ enum class InterpreterType(val value: String) {
|
||||
POETRY("poetry"),
|
||||
PYENV("pyenv"),
|
||||
UV("uv"),
|
||||
HATCH("hatch"),
|
||||
}
|
||||
|
||||
enum class InterpreterCreationMode(val value: String) {
|
||||
|
||||
Reference in New Issue
Block a user