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 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())