diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml
index 583d391ef993..747b4fb11c8f 100644
--- a/python/pluginResources/intellij.python.community.impl.xml
+++ b/python/pluginResources/intellij.python.community.impl.xml
@@ -706,7 +706,6 @@
-
@@ -835,7 +834,6 @@
-
diff --git a/python/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java b/python/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
index 074ca17a9be9..8c6ca4fc7395 100644
--- a/python/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
+++ b/python/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
@@ -108,7 +108,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
@NotNull Consumer onSdkCreated) {
DataContext dataContext = DataManager.getInstance().getDataContext(dataContextComponent);
var moduleOrProject = (module != null) ? new ModuleOrProject.ModuleAndProject(module) : new ModuleOrProject.ProjectOnly(project);
- List actions = AddInterpreterActions.collectAddInterpreterActions(moduleOrProject, onSdkCreated);
+ List actions = AddInterpreterActions.collectAddInterpreterActions(moduleOrProject, onSdkCreated);
return JBPopupFactory.getInstance().createActionGroupPopup(
null,
new DefaultActionGroup(actions),
diff --git a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
index 71ac79c75745..f77ae812ee7b 100644
--- a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
+++ b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
@@ -14,6 +14,8 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.ui.DialogWrapper
+import com.intellij.openapi.util.NlsActions
import com.jetbrains.python.PyBundle
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
@@ -26,17 +28,29 @@ import com.jetbrains.python.sdk.add.v2.PythonAddLocalInterpreterPresenter
import com.jetbrains.python.target.PythonLanguageRuntimeType
import com.jetbrains.python.util.ShowingMessageErrorSync
import org.jetbrains.annotations.ApiStatus
+import org.jetbrains.annotations.Nls
import java.util.function.Consumer
+import java.util.function.Supplier
+import javax.swing.Icon
@ApiStatus.Internal
-fun collectAddInterpreterActions(moduleOrProject: ModuleOrProject, onSdkCreated: Consumer): List {
+abstract class DialogAction(
+ dynamicText: Supplier<@NlsActions.ActionText String>,
+ val icon: Icon,
+ val target: @Nls String,
+) : AnAction(dynamicText, icon) {
+ abstract fun createDialog(): DialogWrapper?
+}
+
+@ApiStatus.Internal
+fun collectAddInterpreterActions(moduleOrProject: ModuleOrProject, onSdkCreated: Consumer): List {
// If module resides on this target, we can't use any target except same target and target types that explicitly allow that
// example: on ``\\wsl$`` you can only use wsl target and dockers
val targetModuleSitsOn = when (moduleOrProject) {
is ModuleAndProject -> PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(moduleOrProject.module)
is ProjectOnly -> null
}
- return mutableListOf().apply {
+ return mutableListOf().apply {
if (targetModuleSitsOn == null) {
add(AddLocalInterpreterAction(moduleOrProject, onSdkCreated::accept))
}
@@ -48,7 +62,7 @@ private fun collectNewInterpreterOnTargetActions(
project: Project,
targetTypeModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
onSdkCreated: Consumer,
-): List =
+): List =
PythonInterpreterTargetEnvironmentFactory.EP_NAME.extensionList
.filter { it.getTargetType().isSystemCompatible() }
.filter { targetTypeModuleSitsOn == null || targetTypeModuleSitsOn.allowCreationTargetOfThisType(it.getTargetType()) }
@@ -59,29 +73,40 @@ private fun collectNewInterpreterOnTargetActions(
private class AddLocalInterpreterAction(
private val moduleOrProject: ModuleOrProject,
private val onSdkCreated: Consumer,
-) : AnAction(PyBundle.messagePointer("python.sdk.action.add.local.interpreter.text"), AllIcons.Nodes.HomeFolder), DumbAware {
+) : DialogAction(
+ dynamicText = PyBundle.messagePointer("python.sdk.action.add.local.interpreter.text"),
+ icon = AllIcons.Nodes.HomeFolder,
+ target = PyBundle.message("sdk.create.targets.local"),
+), DumbAware {
override fun actionPerformed(e: AnActionEvent) {
- addLocalInterpreter(moduleOrProject, onSdkCreated)
+ createDialog().show()
+ }
+
+ override fun createDialog(): PythonAddLocalInterpreterDialog {
+ val dialogPresenter = PythonAddLocalInterpreterPresenter(moduleOrProject, errorSink = ShowingMessageErrorSync).apply {
+ // Model provides flow, but we need to call Consumer
+ sdkCreatedFlow.oneShotConsumer(onSdkCreated)
+ }
+ return PythonAddLocalInterpreterDialog(dialogPresenter)
}
}
@ApiStatus.Internal
fun addLocalInterpreter(moduleOrProject: ModuleOrProject, onSdkCreated: Consumer) {
- val dialogPresenter = PythonAddLocalInterpreterPresenter(moduleOrProject, errorSink = ShowingMessageErrorSync).apply {
- // Model provides flow, but we need to call Consumer
- sdkCreatedFlow.oneShotConsumer(onSdkCreated)
- }
- PythonAddLocalInterpreterDialog(dialogPresenter).show()
+ AddLocalInterpreterAction(moduleOrProject, onSdkCreated).createDialog().show()
}
private class AddInterpreterOnTargetAction(
private val project: Project,
private val targetType: TargetEnvironmentType<*>,
private val onSdkCreated: Consumer,
-) : AnAction(PyBundle.messagePointer("python.sdk.action.add.interpreter.based.on.target.text", targetType.displayName), targetType.icon),
- DumbAware {
+) : DialogAction(
+ dynamicText = PyBundle.messagePointer("python.sdk.action.add.interpreter.based.on.target.text", targetType.displayName),
+ icon = targetType.icon,
+ target = targetType.displayName,
+), DumbAware {
override fun actionPerformed(e: AnActionEvent) {
- val wizard = TargetEnvironmentWizard.createWizard(project, targetType, PythonLanguageRuntimeType.getInstance())
+ val wizard = createDialog()
if (wizard != null && wizard.showAndGet()) {
val model = PyConfigurableInterpreterList.getInstance(project).model
val sdk = (wizard.currentStepObject as? TargetCustomToolWizardStep)?.customTool as? Sdk
@@ -95,6 +120,10 @@ private class AddInterpreterOnTargetAction(
}
}
}
+
+ override fun createDialog(): TargetEnvironmentWizard? {
+ return TargetEnvironmentWizard.createWizard(project, targetType, PythonLanguageRuntimeType.getInstance())
+ }
}
@ApiStatus.Internal
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
index d2bf81a7a5da..1a1f30df1864 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
@@ -45,9 +45,9 @@ import com.jetbrains.python.remote.PyRemoteInterpreterUtil;
import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.sdk.add.PyAddSdkDialog;
-import com.jetbrains.python.target.PyDetectedSdkAdditionalData;
import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
+import com.jetbrains.python.target.PyDetectedSdkAdditionalData;
import com.jetbrains.python.target.PyInterpreterVersionUtil;
import com.jetbrains.python.target.PyTargetAwareAdditionalData;
import one.util.streamex.StreamEx;
@@ -187,11 +187,9 @@ public final class PythonSdkType extends SdkType {
@Nullable Sdk selectedSdk,
@NotNull Consumer super Sdk> sdkCreatedCallback) {
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent));
- PyAddSdkDialog.show(project, null, Arrays.asList(sdkModel.getSdks()), sdk -> {
- if (sdk != null) {
- sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<>(parentComponent));
- sdkCreatedCallback.consume(sdk);
- }
+ PyAddSdkDialog.show(project, null, sdk -> {
+ sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<>(parentComponent));
+ sdkCreatedCallback.consume(sdk);
});
}
diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt
index 724d045e190e..7abc73c9fad3 100644
--- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt
+++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt
@@ -1,38 +1,30 @@
// 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
-import com.intellij.CommonBundle
import com.intellij.openapi.Disposable
-import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.DialogWrapper
-import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.Splitter
import com.intellij.openapi.ui.ValidationInfo
-import com.intellij.openapi.ui.popup.ListItemDescriptorAdapter
import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.UserDataHolder
-import com.intellij.openapi.util.UserDataHolderBase
-import com.intellij.openapi.util.text.StringUtil
+import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.JBCardLayout
import com.intellij.ui.components.DialogPanel
-import com.intellij.ui.components.JBList
-import com.intellij.ui.popup.list.GroupedItemsListRenderer
-import com.intellij.util.ExceptionUtil
-import com.intellij.util.ui.JBUI
import com.jetbrains.python.PyBundle
-import com.jetbrains.python.packaging.PyExecutionException
+import com.jetbrains.python.sdk.DialogAction
+import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.add.PyAddSdkDialog.Companion.show
-import com.jetbrains.python.showErrorDialog
+import com.jetbrains.python.sdk.collectAddInterpreterActions
+import org.jetbrains.annotations.ApiStatus
+import org.jetbrains.annotations.Nls
+import java.awt.BorderLayout
import java.awt.CardLayout
-import java.awt.event.ActionEvent
-import java.io.IOException
import java.util.function.Consumer
-import javax.swing.Action
-import javax.swing.JComponent
-import javax.swing.JPanel
+import javax.swing.*
+import kotlin.coroutines.cancellation.CancellationException
/**
* The dialog may look like the normal dialog with OK, Cancel and Help buttons
@@ -44,274 +36,115 @@ import javax.swing.JPanel
class PyAddSdkDialog private constructor(
private val project: Project,
private val module: Module?,
- private val existingSdks: List,
+ private val sdkAddedCallback: Consumer,
) : DialogWrapper(project) {
- /**
- * This is the main panel that supplies sliding effect for the wizard states.
- */
- private val mainPanel: JPanel = DialogPanel(null, JBCardLayout())
- private var selectedPanel: PyAddSdkView? = null
- private val context = UserDataHolderBase()
- private var panels: List = emptyList()
+ private val dialogActions: List
init {
title = PyBundle.message("python.sdk.add.python.interpreter.title")
+
+ val moduleOrProject = module?.let { ModuleOrProject.ModuleAndProject(it) } ?: ModuleOrProject.ProjectOnly(project)
+ dialogActions = collectAddInterpreterActions(moduleOrProject) {
+ sdkAddedCallback.accept(it)
+ }
+
+ init()
}
override fun createCenterPanel(): JComponent {
- val panels = PyAddSdkProvider.EP_NAME.extensionList
- .mapNotNull {
- safeCreateView(it, project = project, module = module, existingSdks = existingSdks, context = context)
- .registerIfDisposable()
- }
- mainPanel.add(SPLITTER_COMPONENT_CARD_PANE, createCardSplitter(panels))
+ val mainPanel = DialogPanel(null, JBCardLayout())
+ mainPanel.add(SPLITTER_COMPONENT_CARD_PANE, createCardSplitter())
return mainPanel
}
-
- private fun T.registerIfDisposable(): T = apply { (this as? Disposable)?.let { Disposer.register(disposable, it) } }
-
- private var navigationPanelCardLayout: CardLayout? = null
-
- private var southPanel: JPanel? = null
-
- override fun createSouthPanel(): JComponent {
- val regularDialogSouthPanel = super.createSouthPanel()
- val wizardDialogSouthPanel = createWizardSouthPanel()
-
- navigationPanelCardLayout = CardLayout()
-
- val result = JPanel(navigationPanelCardLayout).apply {
- add(regularDialogSouthPanel, REGULAR_CARD_PANE)
- add(wizardDialogSouthPanel, WIZARD_CARD_PANE)
- }
-
- southPanel = result
-
- return result
- }
-
- private fun createWizardSouthPanel(): JPanel {
- assert(value = style != DialogStyle.COMPACT,
- lazyMessage = { "${PyAddSdkDialog::class.java} is not ready for ${DialogStyle.COMPACT} dialog style" })
-
- return doCreateSouthPanel(leftButtons = listOf(),
- rightButtons = listOf(previousButton.value, nextButton.value,
- cancelButton.value))
- }
-
- private val nextAction: Action = object : DialogWrapperAction(PyBundle.message("python.sdk.next")) {
- override fun doAction(e: ActionEvent) {
- selectedPanel?.let {
- if (it.actions.containsKey(PyAddSdkDialogFlowAction.NEXT)) onNext()
- else if (it.actions.containsKey(PyAddSdkDialogFlowAction.FINISH)) {
- onFinish()
- }
- }
- }
- }
-
- private val nextButton = lazy { createJButtonForAction(nextAction) }
-
- private val previousAction = object : DialogWrapperAction(PyBundle.message("python.sdk.previous")) {
- override fun doAction(e: ActionEvent) = onPrevious()
- }
-
- private val previousButton = lazy { createJButtonForAction(previousAction) }
-
- private val cancelButton = lazy { createJButtonForAction(cancelAction) }
+ override fun createSouthPanel(): JComponent = object : JComponent() {}
override fun postponeValidation(): Boolean = false
- override fun doValidateAll(): List = selectedPanel?.validateAll() ?: emptyList()
+ override fun doValidateAll(): List = emptyList()
- fun getOrCreateSdk(): Sdk? = selectedPanel?.getOrCreateSdk()
- private fun createCardSplitter(panels: List): Splitter {
- this.panels = panels
- return Splitter(false, 0.1f).apply {
+ private data class DialogCard(
+ val title: @Nls(capitalization = Nls.Capitalization.Title) String,
+ val icon: Icon,
+ val dialog: DialogWrapper,
+ )
+
+ private fun buildDialogCards(): List {
+ val cards = dialogActions.mapNotNull { action ->
+ val dialogWrapper = try {
+ action.createDialog()
+ }
+ catch (e: CancellationException) {
+ throw e
+ }
+ catch (e: Exception) { // skip broken extensions like Vagrant
+ thisLogger().error(e)
+ null
+ }
+
+ dialogWrapper?.let {
+ DialogCard(action.target, action.icon, it)
+ }
+ }
+
+ cards.forEach { panel ->
+ Disposer.register(disposable, panel.dialog.disposable)
+ Disposer.register(panel.dialog.disposable, Disposable {
+ close(panel.dialog.exitCode)
+ })
+ }
+
+ return cards
+ }
+
+ private fun createCardSplitter(): Splitter {
+ val cards = buildDialogCards()
+
+ val cardLayout = CardLayout()
+ val dialogCardPanel = JPanel(cardLayout).apply {
+ for (card in cards) {
+ add(card.dialog.contentPane, card.title)
+ }
+ }
+
+ val cardSelectionPanel = JPanel(BorderLayout()).apply {
+ border = BorderFactory.createEmptyBorder(0, 8, 0, 12)
+ JComboBox(cards.toTypedArray()).apply {
+ addActionListener {
+ cardLayout.show(dialogCardPanel, (selectedItem as DialogCard).title)
+ }
+ renderer = TargetComboBoxListCellRenderer()
+ toolTipText = PyBundle.message("python.configuration.choose.target.to.run")
+ isFocusable = false
+ }.let { targetComboBox ->
+ add(targetComboBox, BorderLayout.NORTH)
+ }
+ }
+
+ return Splitter(true, 0.01f).apply {
dividerPositionStrategy = Splitter.DividerPositionStrategy.KEEP_FIRST_SIZE
-
- val cardLayout = CardLayout()
- val cardPanel = JPanel(cardLayout).apply {
- preferredSize = JBUI.size(800, 300)
- for (panel in panels) {
- add(panel.component, panel.panelName)
-
- panel.addStateListener(object : PyAddSdkStateListener {
- override fun onComponentChanged() {
- show(mainPanel, panel.component)
-
- selectedPanel?.let { updateWizardActionButtons(it) }
- }
-
- override fun onActionsStateChanged() {
- selectedPanel?.let { updateWizardActionButtons(it) }
- }
- })
- }
- }
- val cardsList = JBList(panels).apply {
- val descriptor = object : ListItemDescriptorAdapter() {
- override fun getTextFor(value: PyAddSdkView) = StringUtil.toTitleCase(value.panelName)
- override fun getIconFor(value: PyAddSdkView) = value.icon
- }
- cellRenderer = object : GroupedItemsListRenderer(descriptor) {
- override fun createItemComponent() = super.createItemComponent().apply {
- border = JBUI.Borders.empty(4, 4, 4, 10)
- }
- }
- addListSelectionListener {
- // Only last even must be processed. Other events may leave UI in inconsistent state
- if (it.valueIsAdjusting) return@addListSelectionListener
- selectedPanel = selectedValue
- isOKActionEnabled = false
-
- cardLayout.show(cardPanel, selectedValue.panelName)
-
- southPanel?.let {
- if (selectedValue.actions.containsKey(PyAddSdkDialogFlowAction.NEXT)) {
- navigationPanelCardLayout?.show(it, WIZARD_CARD_PANE)
- rootPane.defaultButton = nextButton.value
-
- updateWizardActionButtons(selectedValue)
- }
- else {
- navigationPanelCardLayout?.show(it, REGULAR_CARD_PANE)
- rootPane.defaultButton = getButton(okAction)
- }
- }
-
- selectedValue.onSelected()
- }
- selectedPanel = panels.getOrNull(0)
- selectedIndex = 0
- }
-
- firstComponent = cardsList
- secondComponent = cardPanel
+ firstComponent = cardSelectionPanel
+ secondComponent = dialogCardPanel
}
}
-
-
- /**
- * Navigates to the next step of the current wizard view.
- */
- private fun onNext() {
- selectedPanel?.let {
- it.next()
-
- // sliding effect
- swipe(mainPanel, it.component, JBCardLayout.SwipeDirection.FORWARD)
-
- updateWizardActionButtons(it)
+ private class TargetComboBoxListCellRenderer : ColoredListCellRenderer() {
+ override fun customizeCellRenderer(list: JList, value: DialogCard, index: Int, selected: Boolean, hasFocus: Boolean) {
+ icon = value.icon
+ append(value.title)
}
}
- /**
- * Navigates to the previous step of the current wizard view.
- */
- private fun onPrevious() {
- selectedPanel?.let {
- it.previous()
-
- // sliding effect
- if (it.actions.containsKey(PyAddSdkDialogFlowAction.PREVIOUS)) {
- val stepContent = it.component
- val stepContentName = stepContent.hashCode().toString()
-
- (mainPanel.layout as JBCardLayout).swipe(mainPanel, stepContentName, JBCardLayout.SwipeDirection.BACKWARD)
- }
- else {
- // this is the first wizard step
- (mainPanel.layout as JBCardLayout).swipe(mainPanel, SPLITTER_COMPONENT_CARD_PANE, JBCardLayout.SwipeDirection.BACKWARD)
- }
-
- updateWizardActionButtons(it)
- }
- }
-
- /**
- * Tries to create the SDK and closes the dialog if the creation succeeded.
- *
- * @see [doOKAction]
- */
- override fun doOKAction() {
- try {
- selectedPanel?.complete()
- }
- catch (e: IOException) {
- Messages.showErrorDialog(e.localizedMessage, CommonBundle.message("title.error"))
- return
- }
- catch (e: Exception) {
- val cause = ExceptionUtil.findCause(e, PyExecutionException::class.java)
- if (cause != null) {
- showErrorDialog(project, cause.pyError)
- return
- }
- throw e
- }
- close(OK_EXIT_CODE)
- }
-
- private fun onFinish() {
- doOKAction()
- }
-
- private fun updateWizardActionButtons(it: PyAddSdkView) {
- previousButton.value.isEnabled = false
-
- it.actions.forEach { (action, isEnabled) ->
- val actionButton = when (action) {
- PyAddSdkDialogFlowAction.PREVIOUS -> previousButton.value
- PyAddSdkDialogFlowAction.NEXT -> nextButton.value.apply { text = PyBundle.message("python.sdk.next") }
- PyAddSdkDialogFlowAction.FINISH -> nextButton.value.apply { text = PyBundle.message("python.sdk.finish") }
- else -> null
- }
- actionButton?.isEnabled = isEnabled
- }
- }
- /**
- * Fixes the problem when `PyAddDockerSdkProvider.createView` for Docker
- * and Docker Compose types throws [NoClassDefFoundError] exception when
- * `org.jetbrains.plugins.remote-run` plugin is disabled.
- */
- private fun safeCreateView(
- provider: PyAddSdkProvider,
- project: Project,
- module: Module?,
- existingSdks: List,
- context: UserDataHolder,
- ): PyAddSdkView? {
- try {
- return provider.createView(project, module, null, existingSdks, context, this)
- }
- catch (e: NoClassDefFoundError) {
- LOG.info(e)
- return null
- }
- }
-
companion object {
- private val LOG: Logger = Logger.getInstance(PyAddSdkDialog::class.java)
-
private const val SPLITTER_COMPONENT_CARD_PANE = "Splitter"
- private const val REGULAR_CARD_PANE = "Regular"
-
- private const val WIZARD_CARD_PANE = "Wizard"
-
@JvmStatic
- fun show(project: Project, module: Module?, existingSdks: List, sdkAddedCallback: Consumer) {
- val dialog = PyAddSdkDialog(project = project, module = module, existingSdks = existingSdks)
- dialog.init()
-
- val sdk = if (dialog.showAndGet()) dialog.getOrCreateSdk() else null
- sdkAddedCallback.accept(sdk)
+ @ApiStatus.Internal
+ fun show(project: Project, module: Module?, sdkAddedCallback: Consumer) {
+ val dialog = PyAddSdkDialog(project = project, module = module, sdkAddedCallback)
+ dialog.show()
}
-
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt
index 445240f429ef..b275ab27bcd7 100644
--- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt
+++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt
@@ -32,24 +32,9 @@ import javax.swing.JComponent
import javax.swing.JPanel
abstract class PyAddSdkPanel : JPanel(), PyAddSdkView {
- override val actions: Map
- get() = mapOf(PyAddSdkDialogFlowAction.OK.enabled())
-
override val component: Component
get() = this
- /**
- * [component] is permanent. [PyAddSdkStateListener.onComponentChanged] won't
- * be called anyway.
- */
- override fun addStateListener(stateListener: PyAddSdkStateListener): Unit = Unit
-
- override fun previous(): Nothing = throw UnsupportedOperationException()
-
- override fun next(): Nothing = throw UnsupportedOperationException()
-
- override fun complete(): Unit = Unit
-
abstract override val panelName: String
override val icon: Icon = PythonPsiApiIcons.Python
open val sdk: Sdk? = null
@@ -60,8 +45,6 @@ abstract class PyAddSdkPanel : JPanel(), PyAddSdkView {
open fun getStatisticInfo(): InterpreterStatisticsInfo? = null
- override fun onSelected(): Unit = Unit
-
override fun validateAll(): List = emptyList()
open fun addChangeListener(listener: Runnable) {}
diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt
index 89278594c472..b014957bb4e6 100644
--- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt
+++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt
@@ -1,36 +1,9 @@
// 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
-import com.intellij.openapi.extensions.ExtensionPointName
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.projectRoots.Sdk
-import com.intellij.openapi.ui.DialogWrapper
-import com.intellij.openapi.util.UserDataHolder
-import org.jetbrains.annotations.ApiStatus
-
-// TODO: Merge into a common Python SDK provider
+@Deprecated(
+ "Custom Python SDKs support was removed from python plugin for IDEA because of UI/UX unification with PyCharm",
+ level = DeprecationLevel.ERROR
+)
interface PyAddSdkProvider {
- /**
- * Returns [PyAddSdkView] if applicable.
- */
- fun createView(project: Project,
- module: Module?,
- newProjectPath: String?,
- existingSdks: List,
- context: UserDataHolder): PyAddSdkView? = null
- /**
- * Returns [PyAddSdkView] if applicable.
- */
- @ApiStatus.Internal
- fun createView(project: Project,
- module: Module?,
- newProjectPath: String?,
- existingSdks: List,
- context: UserDataHolder,
- dialogWrapper: DialogWrapper): PyAddSdkView? = createView(project, module, newProjectPath, existingSdks, context)
-
- companion object {
- val EP_NAME: ExtensionPointName = ExtensionPointName.create("Pythonid.pyAddSdkProvider")
- }
}
diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkView.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkView.kt
index 2ecd6e6be1a9..7684625f5c0a 100644
--- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkView.kt
+++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkView.kt
@@ -30,13 +30,7 @@ interface PyAddSdkView {
*/
fun getOrCreateSdk(): Sdk?
- fun onSelected()
-
- /**
- * [PyAddSdkStateListener.onActionsStateChanged] is called after changes in
- * [actions].
- */
- val actions: Map
+ fun onSelected(): Unit = Unit
/**
* The [component] *might* return the new [Component] after [next] or
@@ -44,16 +38,6 @@ interface PyAddSdkView {
*/
val component: Component
- /**
- * @throws IllegalStateException
- */
- fun previous()
-
- /**
- * @throws IllegalStateException
- */
- fun next()
-
/**
* Completes SDK creation.
*
@@ -69,7 +53,7 @@ interface PyAddSdkView {
*
* @throws Exception if SDK creation failed for some reason
*/
- fun complete()
+ fun complete(): Unit = Unit
/**
* Returns the list of validation errors. The returned list is empty if there
@@ -79,6 +63,4 @@ interface PyAddSdkView {
*/
@RequiresEdt
fun validateAll(): List
-
- fun addStateListener(stateListener: PyAddSdkStateListener)
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PyLocalAddSdkProvider.kt b/python/src/com/jetbrains/python/sdk/add/v2/PyLocalAddSdkProvider.kt
deleted file mode 100644
index d436135be2d1..000000000000
--- a/python/src/com/jetbrains/python/sdk/add/v2/PyLocalAddSdkProvider.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-// 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.module.Module
-import com.intellij.openapi.observable.properties.AtomicProperty
-import com.intellij.openapi.progress.runBlockingMaybeCancellable
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.projectRoots.Sdk
-import com.intellij.openapi.ui.DialogPanel
-import com.intellij.openapi.ui.DialogWrapper
-import com.intellij.openapi.ui.ValidationInfo
-import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
-import com.intellij.openapi.util.UserDataHolder
-import com.intellij.ui.dsl.builder.panel
-import com.intellij.util.ui.launchOnShow
-import com.jetbrains.python.PyBundle
-import com.jetbrains.python.errorProcessing.ErrorSink
-import com.jetbrains.python.icons.PythonIcons
-import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
-import com.jetbrains.python.sdk.ModuleOrProject
-import com.jetbrains.python.sdk.add.PyAddSdkDialogFlowAction
-import com.jetbrains.python.sdk.add.PyAddSdkProvider
-import com.jetbrains.python.sdk.add.PyAddSdkStateListener
-import com.jetbrains.python.sdk.add.PyAddSdkView
-import com.jetbrains.python.sdk.add.collector.PythonNewInterpreterAddedCollector
-import com.jetbrains.python.util.ShowingMessageErrorSync
-import kotlinx.coroutines.supervisorScope
-import java.awt.Component
-import java.nio.file.Path
-import javax.swing.Icon
-
-class PyLocalAddSdkProvider : PyAddSdkProvider {
- override fun createView(
- project: Project,
- module: Module?,
- newProjectPath: String?,
- existingSdks: List,
- context: UserDataHolder,
- dialogWrapper: DialogWrapper,
- ): PyAddSdkView {
- return V3AddSdkPanel(
- project = project,
- module = module,
- projectPath = Path.of(newProjectPath ?: project.basePath ?: "."),
- dialogWrapper = dialogWrapper
- )
- }
-}
-
-
-class V3AddSdkPanel(val project: Project, val module: Module?, val projectPath: Path, val dialogWrapper: DialogWrapper) : PyAddSdkView {
- override val panelName: String
- get() = PyBundle.message("python.sdk.local")
- override val icon: Icon
- get() = PythonIcons.Python.Virtualenv
-
- private val errorSink: ErrorSink = ShowingMessageErrorSync
-
- private lateinit var mainPanel: PythonAddCustomInterpreter
- private lateinit var model: PythonLocalAddInterpreterModel
-
- private val dialogPanel: DialogPanel = panel {
- model = PythonLocalAddInterpreterModel(ProjectPathFlows.create((projectPath)))
- model.navigator.selectionMode = AtomicProperty(PythonInterpreterSelectionMode.CUSTOM)
- mainPanel = PythonAddCustomInterpreter(
- model = model,
- module = module,
- errorSink = errorSink,
- limitExistingEnvironments = false
- )
- mainPanel.setupUI(this, WHEN_PROPERTY_CHANGED(AtomicProperty(projectPath)))
- }
-
- private var validationInfos: List = emptyList()
-
- init {
- dialogPanel.launchOnShow("V3AddSdkPanel launchOnShow") {
- supervisorScope {
- model.initialize(this@supervisorScope)
- mainPanel.onShown(this@supervisorScope)
- }
- }
-
- dialogPanel.registerValidators(dialogWrapper.disposable) { compInfos ->
- validationInfos = compInfos.values.toList()
- dialogWrapper.isOKActionEnabled = validationInfos.all { it.okEnabled }
- }
- }
-
- override fun getOrCreateSdk(): Sdk? {
- val moduleOrProject = if (module != null) ModuleOrProject.ModuleAndProject(module) else ModuleOrProject.ProjectOnly(project)
-
- dialogPanel.apply()
- val sdkManager = mainPanel.currentSdkManager
- val sdk = runBlockingMaybeCancellable {
- sdkManager.getOrCreateSdkWithModal(moduleOrProject).getOr {
- errorSink.emit(it.error)
- return@runBlockingMaybeCancellable null
- }.also {
- val isPreviouslyConfigured = sdkManager.createStatisticsInfo(PythonInterpreterCreationTargets.LOCAL_MACHINE).previouslyConfigured
- PythonNewInterpreterAddedCollector.logPythonNewInterpreterAdded(it, isPreviouslyConfigured)
- }
- }
- return sdk
- }
-
- override fun onSelected() {
- dialogWrapper.isOKActionEnabled = validateAll().isEmpty()
- }
-
- override val actions: Map
- get() = mapOf(PyAddSdkDialogFlowAction.OK.enabled())
-
- override val component: Component
- get() = dialogPanel
-
- override fun previous(): Unit = throw UnsupportedOperationException()
-
- override fun next(): Unit = throw UnsupportedOperationException()
-
- override fun complete(): Unit = Unit
-
- override fun validateAll(): List = validationInfos
-
- override fun addStateListener(stateListener: PyAddSdkStateListener): Unit = Unit
-}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
index d38bd8e84cf8..662828ac9ebb 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
@@ -1,6 +1,8 @@
// 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.application.ModalityState
+import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.observable.properties.AtomicProperty
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
@@ -38,7 +40,7 @@ internal class PythonAddLocalInterpreterDialog(private val dialogPresenter: Pyth
override fun doOKAction() {
super.doOKAction()
val addEnvironment = mainPanel.currentSdkManager
- PyPackageCoroutine.launch(dialogPresenter.moduleOrProject.project) {
+ PyPackageCoroutine.launch(dialogPresenter.moduleOrProject.project, ModalityState.current().asContextElement()) {
dialogPresenter.okClicked(addEnvironment)
}
}
diff --git a/python/src/com/jetbrains/python/target/ui/PyAddCondaPanelView.kt b/python/src/com/jetbrains/python/target/ui/PyAddCondaPanelView.kt
index 243ae58b41c6..01cc312544d1 100644
--- a/python/src/com/jetbrains/python/target/ui/PyAddCondaPanelView.kt
+++ b/python/src/com/jetbrains/python/target/ui/PyAddCondaPanelView.kt
@@ -108,11 +108,6 @@ internal class PyAddCondaPanelView(private val model: PyAddCondaPanelModel) : Py
override val component: Component
get() = panel
- // Those three functions are from the old (pre-target) interface which is not used anymore
- override fun previous() = Unit
- override fun next() = Unit
- override fun addStateListener(stateListener: PyAddSdkStateListener) = Unit
-
override fun onSelected() {
runWithModalProgressBlocking(model.project, PyBundle.message("python.add.sdk.conda.detecting")) {
reportRawProgress { reporter ->
@@ -121,10 +116,6 @@ internal class PyAddCondaPanelView(private val model: PyAddCondaPanelModel) : Py
}
}
- override val actions: Map = emptyMap()
-
- override fun complete() = Unit
-
override fun validateAll(): List =
panel.validateAll() + (model.getValidationError()?.let { listOf(ValidationInfo(it)) } ?: emptyList())