PY-49271 Packages(feat): Provide an ability to pass arbitrary options to pip install

GitOrigin-RevId: 29a1f81fd5cb17923385b478c19864957c87cebf
This commit is contained in:
Nikita.Ashihmin
2024-08-24 15:03:38 +04:00
committed by intellij-monorepo-bot
parent c3d76fcc00
commit 39ac2bc2ae
7 changed files with 76 additions and 32 deletions

View File

@@ -864,6 +864,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
<actions resource-bundle="messages.PyBundle">
<group id="PyPackageToolwindowContext">
<action id="PyInstallPackage" class="com.jetbrains.python.packaging.toolwindow.actions.InstallPackageAction"/>
<action id="PyInstallWithOptionPackage" class="com.jetbrains.python.packaging.toolwindow.actions.InstallWithOptionsPackageAction"/>
<action id="PyDeletePackage" class="com.jetbrains.python.packaging.toolwindow.actions.DeletePackageAction"/>
<action id="PyChangeVersionPackage" class="com.jetbrains.python.packaging.toolwindow.actions.ChangeVersionPackageAction"/>
<action id="PyUpdateToLatestPackage" class="com.jetbrains.python.packaging.toolwindow.actions.UpdatePackageToLatestAction"/>

View File

@@ -118,10 +118,10 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PipBasedPackageManager(p
val commandLineString = StringUtil.join(commandLine, " ")
val handler = CapturingProcessHandler(process, targetedCommandLine.charset, commandLineString)
val result = withBackgroundProgress<Unit>(project, text, true) {
val result = withBackgroundProgress(project, text, true) {
withRawProgressReporter<ProcessOutput?> {
handler.runProcess(10 * 60 * 1000)
}
} as ProcessOutput
}
result.checkSuccess(thisLogger())
@@ -133,5 +133,4 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PipBasedPackageManager(p
else result.stdout
}
}
}

View File

@@ -67,8 +67,10 @@ class PyPackagingTablesView(private val project: Project,
if (existingRepo != null) {
// recreate order of the repositories -- it might have changed in the package manager (e.g. Sdk switch)
existingRepo.removeFrom(container)
val selectedItem = existingRepo.table.selectedItem()
existingRepo.items = withExpander
existingRepo.addTo(container)
selectedItem?.let { existingRepo.table.selectPackage(it) }
}
else {
val newTable = PyPackagesTable(project, PyPackagesTableModel(), this, controller)

View File

@@ -140,6 +140,8 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
super.uiDataSnapshot(sink)
}
fun getSelectedPackage(): DisplayablePackage? = descriptionController.selectedPackage.get()
private fun initOrientation(service: PyPackagingToolWindowService, horizontal: Boolean) {
val second = if (splitter?.secondComponent == rightPanel) rightPanel else noPackagePanel
val proportionKey = if (horizontal) HORIZONTAL_SPLITTER_KEY else VERTICAL_SPLITTER_KEY

View File

@@ -38,8 +38,7 @@ import org.jetbrains.annotations.Nls
@Service(Service.Level.PROJECT)
class PyPackagingToolWindowService(val project: Project, val serviceScope: CoroutineScope) : Disposable {
var toolWindowPanel: PyPackagingToolWindowPanel? = null
private set
private var toolWindowPanel: PyPackagingToolWindowPanel? = null
lateinit var manager: PythonPackageManager
private var installedPackages: Map<String, InstalledPackage> = emptyMap()
internal var currentSdk: Sdk? = null
@@ -67,6 +66,8 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
fun handleSearch(query: String) {
val prevSelected = toolWindowPanel?.getSelectedPackage()
currentQuery = query
if (query.isNotEmpty()) {
searchJob?.cancel()
@@ -85,6 +86,7 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
if (isActive) {
withContext(Dispatchers.Main) {
toolWindowPanel?.showSearchResult(installed, packagesFromRepos + invalidRepositories)
prevSelected?.name?.let { toolWindowPanel?.selectPackageName(it) }
}
}
}
@@ -96,26 +98,24 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
}.toList()
toolWindowPanel?.resetSearch(installedPackages.values.toList(), packagesByRepository + invalidRepositories)
prevSelected?.name?.let { toolWindowPanel?.selectPackageName(it) }
}
}
suspend fun installPackage(specification: PythonPackageSpecification, options: List<String> = emptyList()) {
PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project)
val result = manager.installPackage(specification, options = options)
toolWindowPanel?.selectPackageName(specification.name)
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.installed", specification.name))
}
suspend fun deletePackage(selectedPackage: InstalledPackage) {
PythonPackagesToolwindowStatisticsCollector.uninstallPackageEvent.log(project)
val result = manager.uninstallPackage(selectedPackage.instance)
toolWindowPanel?.selectPackageName(selectedPackage.name)
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.deleted", selectedPackage.name))
}
suspend fun updatePackage(specification: PythonPackageSpecification) {
val result = manager.updatePackage(specification)
toolWindowPanel?.selectPackageName(specification.name)
if (result.isSuccess) showPackagingNotification(message("python.packaging.notification.updated", specification.name, specification.versionSpecs))
}

View File

@@ -0,0 +1,57 @@
// 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.packaging.toolwindow.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.packaging.common.PythonPackageDetails
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackage
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class InstallWithOptionsPackageAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val pkg = e.selectedPackage as? InstallablePackage ?: return
PyPackageCoroutine.launch(project, Dispatchers.IO) {
val service = PyPackagingToolWindowService.getInstance(project)
val details = service.detailsForPackage(pkg)
installWithOptions(project, details)
}
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.selectedPackage is InstallablePackage
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
companion object {
internal suspend fun installWithOptions(project: Project, details: PythonPackageDetails, version: String? = null) {
val optionsString = withContext(Dispatchers.EDT) {
Messages.showInputDialog(project,
message("package.install.with.options.dialog.message"),
message("action.PyInstallWithOptionPackage.text"),
Messages.getQuestionIcon()
)
} ?: return
val options = optionsString.split(' ').map { it.trim() }.filter { it.isNotBlank() }
val specification = details.repository.createPackageSpecification(details.name, version ?: details.availableVersions.first())
project.service<PyPackagingToolWindowService>().installPackage(specification, options)
}
}
}

View File

@@ -5,7 +5,6 @@ import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.newui.OneLineProgressIndicator
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.UiDataProvider
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.service
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.AtomicProperty
@@ -15,7 +14,6 @@ import com.intellij.openapi.observable.util.isNotNull
import com.intellij.openapi.observable.util.not
import com.intellij.openapi.observable.util.transform
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
@@ -32,6 +30,7 @@ import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.packaging.PyPackageUtil
import com.jetbrains.python.packaging.common.PythonPackageDetails
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.actions.InstallWithOptionsPackageAction
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
@@ -39,7 +38,6 @@ import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.Nls
import java.awt.BorderLayout
import java.awt.Font
@@ -75,19 +73,8 @@ class PyPackageDescriptionController(val project: Project) : Disposable {
private val installWithOptionAction: Action = wrapAction(message("action.PyInstallWithOptionPackage.text"), message("progress.text.installing")) {
val details = selectedPackageDetails.get() ?: return@wrapAction
val optionsString = withContext(Dispatchers.EDT) {
Messages.showInputDialog(project,
message("package.install.with.options.dialog.message"),
message("action.PyInstallWithOptionPackage.text"),
Messages.getQuestionIcon()
)
} ?: return@wrapAction
val options = optionsString.split(' ').map { it.trim() }.filter { it.isNotBlank() }
val specification = details.repository.createPackageSpecification(details.name, details.availableVersions.first())
project.service<PyPackagingToolWindowService>().installPackage(specification, options)
val version = versionSelector.text.takeIf { it != latestText }
InstallWithOptionsPackageAction.installWithOptions(project, details,version)
}
@@ -96,13 +83,6 @@ class PyPackageDescriptionController(val project: Project) : Disposable {
private val progressEnabledProperty = AtomicBooleanProperty(false)
private val progressIndicatorComponent = JPanel()
//private val progressBar: JProgressBar = JProgressBar(JProgressBar.HORIZONTAL).apply {
// maximumSize.width = 200
// minimumSize.width = 200
// preferredSize.width = 200
// isVisible = false
// isIndeterminate = true
//}
private val htmlPanel: JCEFHtmlPanel = PyPackagingJcefHtmlPanel(project).also { panel ->
Disposer.register(this, panel)
@@ -214,7 +194,10 @@ class PyPackageDescriptionController(val project: Project) : Disposable {
private fun suggestInstallPackage(selectedValue: String) {
if (selectedPackage.get() !is InstalledPackage)
return
updatePackageVersion(selectedValue)
wrapInvokeOp(message("progress.text.installing")) {
updatePackageVersion(selectedValue)
}
}
private fun calculateVersionText() = (selectedPackage.get() as? InstalledPackage)?.currentVersion?.presentableText ?: latestText