mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
PY-72994 Packages(refactor, feat): Rename Delete package andd Update package
GitOrigin-RevId: 56ddaade343cb5eeb3240fd1090f68b72e42ed86
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c15235e357
commit
134376878d
@@ -742,7 +742,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
|||||||
dynamic="true"/>
|
dynamic="true"/>
|
||||||
|
|
||||||
<extensionPoint qualifiedName="Pythonid.PythonPackagingToolwindowActionProvider"
|
<extensionPoint qualifiedName="Pythonid.PythonPackagingToolwindowActionProvider"
|
||||||
interface="com.jetbrains.python.packaging.toolwindow.PythonPackagingToolwindowActionProvider"
|
interface="com.jetbrains.python.packaging.toolwindow.actions.PythonPackagingToolwindowActionProvider"
|
||||||
dynamic="true"/>
|
dynamic="true"/>
|
||||||
|
|
||||||
<extensionPoint qualifiedName="Pythonid.pythonPackageManagerProvider"
|
<extensionPoint qualifiedName="Pythonid.pythonPackageManagerProvider"
|
||||||
|
|||||||
@@ -1356,8 +1356,9 @@ python.toolwindow.packages.custom.repo.invalid={0} (Authorization failed)
|
|||||||
python.toolwindow.packages.documentation.link=Documentation
|
python.toolwindow.packages.documentation.link=Documentation
|
||||||
python.toolwindow.packages.no.interpreter.text=Select an interpreter to see the installed packages
|
python.toolwindow.packages.no.interpreter.text=Select an interpreter to see the installed packages
|
||||||
python.toolwindow.packages.latest.version.label=latest
|
python.toolwindow.packages.latest.version.label=latest
|
||||||
python.toolwindow.packages.delete.package=Delete Package
|
python.toolwindow.packages.delete.package=Uninstall
|
||||||
python.toolwindow.packages.update.package=Update Package
|
python.toolwindow.packages.update.package=Change Version
|
||||||
|
python.toolwindow.packages.update.package.version=Update {0} -> {1}
|
||||||
python.toolwindow.packages.install.link=Install
|
python.toolwindow.packages.install.link=Install
|
||||||
python.toolwindow.packages.search.text.placeholder=Search for more packages
|
python.toolwindow.packages.search.text.placeholder=Search for more packages
|
||||||
python.toolwindow.packages.description.panel.placeholder=Select a package to view documentation
|
python.toolwindow.packages.description.panel.placeholder=Select a package to view documentation
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package com.jetbrains.python.packaging.conda
|
|||||||
import com.jetbrains.python.PyBundle
|
import com.jetbrains.python.PyBundle
|
||||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||||
import com.jetbrains.python.packaging.toolwindow.PythonPackagingToolwindowActionProvider
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackageInstallAction
|
||||||
import com.jetbrains.python.packaging.toolwindow.PythonPackageInstallAction
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackagingToolwindowActionProvider
|
||||||
import com.jetbrains.python.packaging.toolwindow.SimplePythonPackageInstallAction
|
import com.jetbrains.python.packaging.toolwindow.actions.SimplePythonPackageInstallAction
|
||||||
import org.jetbrains.annotations.ApiStatus
|
import org.jetbrains.annotations.ApiStatus
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import com.jetbrains.python.PyBundle
|
|||||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||||
import com.jetbrains.python.packaging.common.PythonSimplePackageDetails
|
import com.jetbrains.python.packaging.common.PythonSimplePackageDetails
|
||||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||||
import com.jetbrains.python.packaging.toolwindow.PythonPackageInstallAction
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackageInstallAction
|
||||||
import com.jetbrains.python.packaging.toolwindow.PythonPackagingToolwindowActionProvider
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackagingToolwindowActionProvider
|
||||||
import com.jetbrains.python.packaging.toolwindow.SimplePythonPackageInstallAction
|
import com.jetbrains.python.packaging.toolwindow.actions.SimplePythonPackageInstallAction
|
||||||
import org.jetbrains.annotations.ApiStatus
|
import org.jetbrains.annotations.ApiStatus
|
||||||
|
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.ui.JBColor
|
import com.intellij.ui.JBColor
|
||||||
import com.jetbrains.python.PyBundle.message
|
import com.jetbrains.python.PyBundle.message
|
||||||
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.*
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTableModel
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagingTableGroup
|
||||||
import java.awt.Rectangle
|
import java.awt.Rectangle
|
||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
@@ -99,7 +104,7 @@ class PyPackagingTablesView(private val project: Project,
|
|||||||
foreground = JBColor.RED
|
foreground = JBColor.RED
|
||||||
icon = AllIcons.General.Error
|
icon = AllIcons.General.Error
|
||||||
}
|
}
|
||||||
it to headerPanel(label, null)
|
it to PyPackagesUiComponents.headerPanel(label, null)
|
||||||
}
|
}
|
||||||
.forEach {
|
.forEach {
|
||||||
invalidRepositories[it.first] = it.second
|
invalidRepositories[it.first] = it.second
|
||||||
|
|||||||
@@ -46,10 +46,19 @@ import com.jetbrains.python.packaging.PyPackageUtil
|
|||||||
import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification
|
import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification
|
||||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||||
import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification
|
import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackageInstallAction
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.PythonPackagingToolwindowActionProvider
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.PyPackagesViewData
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
|
||||||
|
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||||
import com.jetbrains.python.sdk.pythonSdk
|
import com.jetbrains.python.sdk.pythonSdk
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Component
|
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.FlowLayout
|
import java.awt.FlowLayout
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
@@ -60,7 +69,7 @@ import javax.swing.event.DocumentEvent
|
|||||||
import javax.swing.event.ListSelectionListener
|
import javax.swing.event.ListSelectionListener
|
||||||
|
|
||||||
class PyPackagingToolWindowPanel(private val project: Project, toolWindow: ToolWindow) : SimpleToolWindowPanel(false, true), Disposable {
|
class PyPackagingToolWindowPanel(private val project: Project, toolWindow: ToolWindow) : SimpleToolWindowPanel(false, true), Disposable {
|
||||||
internal val packagingScope = CoroutineScope(Dispatchers.IO)
|
internal val packagingScope = PyPackageCoroutine.getIoScope(project)
|
||||||
private var selectedPackage: DisplayablePackage? = null
|
private var selectedPackage: DisplayablePackage? = null
|
||||||
private var selectedPackageDetails: PythonPackageDetails? = null
|
private var selectedPackageDetails: PythonPackageDetails? = null
|
||||||
|
|
||||||
@@ -172,20 +181,20 @@ class PyPackagingToolWindowPanel(private val project: Project, toolWindow: ToolW
|
|||||||
tablesView = PyPackagingTablesView(project, packageListPanel, this)
|
tablesView = PyPackagingTablesView(project, packageListPanel, this)
|
||||||
|
|
||||||
leftPanel = createLeftPanel(service)
|
leftPanel = createLeftPanel(service)
|
||||||
rightPanel = borderPanel {
|
rightPanel = PyPackagesUiComponents.borderPanel {
|
||||||
add(borderPanel {
|
add(PyPackagesUiComponents.borderPanel {
|
||||||
border = SideBorder(JBColor.GRAY, SideBorder.BOTTOM)
|
border = SideBorder(JBColor.GRAY, SideBorder.BOTTOM)
|
||||||
preferredSize = Dimension(preferredSize.width, 50)
|
preferredSize = Dimension(preferredSize.width, 50)
|
||||||
minimumSize = Dimension(minimumSize.width, 50)
|
minimumSize = Dimension(minimumSize.width, 50)
|
||||||
maximumSize = Dimension(maximumSize.width, 50)
|
maximumSize = Dimension(maximumSize.width, 50)
|
||||||
add(boxPanel {
|
add(PyPackagesUiComponents.boxPanel {
|
||||||
add(Box.createHorizontalStrut(10))
|
add(Box.createHorizontalStrut(10))
|
||||||
add(packageNameLabel)
|
add(packageNameLabel)
|
||||||
add(Box.createHorizontalStrut(10))
|
add(Box.createHorizontalStrut(10))
|
||||||
add(documentationLink)
|
add(documentationLink)
|
||||||
}, BorderLayout.WEST)
|
}, BorderLayout.WEST)
|
||||||
add(boxPanel {
|
add(PyPackagesUiComponents.boxPanel {
|
||||||
alignmentX = Component.RIGHT_ALIGNMENT
|
alignmentX = RIGHT_ALIGNMENT
|
||||||
add(progressBar)
|
add(progressBar)
|
||||||
add(versionSelector)
|
add(versionSelector)
|
||||||
add(versionLabel)
|
add(versionLabel)
|
||||||
@@ -266,8 +275,8 @@ class PyPackagingToolWindowPanel(private val project: Project, toolWindow: ToolW
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainPanel = borderPanel {
|
mainPanel = PyPackagesUiComponents.borderPanel {
|
||||||
val topToolbar = boxPanel {
|
val topToolbar = PyPackagesUiComponents.boxPanel {
|
||||||
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM)
|
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM)
|
||||||
preferredSize = Dimension(preferredSize.width, 30)
|
preferredSize = Dimension(preferredSize.width, 30)
|
||||||
minimumSize = Dimension(minimumSize.width, 30)
|
minimumSize = Dimension(minimumSize.width, 30)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import com.jetbrains.python.packaging.management.PythonPackageManager
|
|||||||
import com.jetbrains.python.packaging.management.packagesByRepository
|
import com.jetbrains.python.packaging.management.packagesByRepository
|
||||||
import com.jetbrains.python.packaging.repository.*
|
import com.jetbrains.python.packaging.repository.*
|
||||||
import com.jetbrains.python.packaging.statistics.PythonPackagesToolwindowStatisticsCollector
|
import com.jetbrains.python.packaging.statistics.PythonPackagesToolwindowStatisticsCollector
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.*
|
||||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||||
import com.jetbrains.python.run.applyHelperPackageToPythonPath
|
import com.jetbrains.python.run.applyHelperPackageToPythonPath
|
||||||
import com.jetbrains.python.run.buildTargetedCommandLine
|
import com.jetbrains.python.run.buildTargetedCommandLine
|
||||||
@@ -197,29 +198,27 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun invokeUpdateLatestVersion() {
|
private suspend fun invokeUpdateLatestVersion() {
|
||||||
serviceScope.launch(Dispatchers.Default) {
|
val proccessPackages = installedPackages
|
||||||
val proccessPackages = installedPackages
|
val updatedPackages = proccessPackages.map { (name, pyPackage: InstalledPackage) ->
|
||||||
val updatedPackages = proccessPackages.map { (name, pyPackage: InstalledPackage) ->
|
val specification = pyPackage.repository.createPackageSpecification(pyPackage.name)
|
||||||
val specification = pyPackage.repository.createPackageSpecification(pyPackage.name)
|
val latestVersion = manager.repositoryManager.getLatestVersion(specification)
|
||||||
val latestVersion = manager.repositoryManager.getLatestVersion(specification)
|
val currentVersion = PyPackageVersionNormalizer.normalize(pyPackage.instance.version)
|
||||||
val currentVersion = PyPackageVersionNormalizer.normalize(pyPackage.instance.version)
|
|
||||||
|
|
||||||
val upgradeTo = if (latestVersion != null && currentVersion != null &&
|
val upgradeTo = if (latestVersion != null && currentVersion != null &&
|
||||||
PyPackageVersionComparator.compare(latestVersion, currentVersion) > 0) {
|
PyPackageVersionComparator.compare(latestVersion, currentVersion) > 0) {
|
||||||
latestVersion
|
latestVersion
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
name to upgradeTo
|
name to upgradeTo
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
installedPackages = installedPackages.map {
|
installedPackages = installedPackages.map {
|
||||||
val newVersion = updatedPackages[it.key]
|
val newVersion = updatedPackages[it.key]
|
||||||
it.key to it.value.withNextVersion(newVersion)
|
it.key to it.value.withNextVersion(newVersion)
|
||||||
}.toMap()
|
}.toMap()
|
||||||
handleSearch(currentQuery)
|
handleSearch(currentQuery)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showPackagingNotification(text: @Nls String) {
|
private suspend fun showPackagingNotification(text: @Nls String) {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
// Copyright 2000-2022 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
|
|
||||||
|
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
|
||||||
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
|
||||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
|
||||||
import org.jetbrains.annotations.Nls
|
|
||||||
|
|
||||||
interface PythonPackagingToolwindowActionProvider {
|
|
||||||
fun getInstallActions(details: PythonPackageDetails, packageManager: PythonPackageManager): List<PythonPackageInstallAction>?
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val EP_NAME = ExtensionPointName.create<PythonPackagingToolwindowActionProvider>("Pythonid.PythonPackagingToolwindowActionProvider")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class PythonPackageInstallAction(internal val text: @Nls String,
|
|
||||||
internal val project: Project) {
|
|
||||||
|
|
||||||
abstract suspend fun installPackage(specification: PythonPackageSpecification)
|
|
||||||
}
|
|
||||||
|
|
||||||
class SimplePythonPackageInstallAction(text: @Nls String,
|
|
||||||
project: Project) : PythonPackageInstallAction(text, project) {
|
|
||||||
override suspend fun installPackage(specification: PythonPackageSpecification) {
|
|
||||||
project.service<PyPackagingToolWindowService>().installPackage(specification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// 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.ui.awt.RelativePoint
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
|
||||||
|
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 java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
internal class ChangeVersionPackageAction(val table: PyPackagesTable<*>) : DumbAwareAction(PyBundle.message("python.toolwindow.packages.update.package")) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project ?: return
|
||||||
|
val pkg = table.selectedItem() as? InstalledPackage ?: return
|
||||||
|
|
||||||
|
PyPackageCoroutine.getIoScope(project).launch {
|
||||||
|
val service = project.service<PyPackagingToolWindowService>()
|
||||||
|
val details = service.detailsForPackage(pkg)
|
||||||
|
withContext(Dispatchers.EDT) {
|
||||||
|
PyPackagesUiComponents.createAvailableVersionsPopup(pkg, details, project, table.controller).show(
|
||||||
|
RelativePoint(e.inputEvent as MouseEvent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
val pkg = table.selectedItem() as? InstalledPackage
|
||||||
|
e.presentation.isEnabledAndVisible = pkg != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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.components.service
|
||||||
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
|
||||||
|
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
internal class DeletePackageAction(val table: PyPackagesTable<*>) : DumbAwareAction(PyBundle.message("python.toolwindow.packages.delete.package")) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project ?: return
|
||||||
|
val pkg = table.selectedItem() as? InstalledPackage ?: return
|
||||||
|
|
||||||
|
val service = project.service<PyPackagingToolWindowService>()
|
||||||
|
|
||||||
|
PyPackageCoroutine.launch(project, Dispatchers.IO) {
|
||||||
|
service.deletePackage(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
e.presentation.isEnabledAndVisible = table.selectedItem() is InstalledPackage
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// 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.components.service
|
||||||
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
|
import com.intellij.ui.awt.RelativePoint
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
|
||||||
|
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
internal class InstallPackageAction(val table: PyPackagesTable<*>) : DumbAwareAction(PyBundle.message("python.toolwindow.packages.install.link")) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project ?: return
|
||||||
|
val service = project.service<PyPackagingToolWindowService>()
|
||||||
|
val pkg = table.selectedItem() as? InstallablePackage ?: return
|
||||||
|
|
||||||
|
PyPackageCoroutine.launch(project, Dispatchers.IO) {
|
||||||
|
val details = service.detailsForPackage(pkg)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val popup = PyPackagesUiComponents.createAvailableVersionsPopup(pkg, details, project, table.controller)
|
||||||
|
popup.show(RelativePoint(e.inputEvent as MouseEvent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
e.presentation.isEnabledAndVisible = table.selectedItem() is InstallablePackage
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 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.project.Project
|
||||||
|
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||||
|
import org.jetbrains.annotations.Nls
|
||||||
|
|
||||||
|
abstract class PythonPackageInstallAction(
|
||||||
|
internal val text: @Nls String,
|
||||||
|
internal val project: Project,
|
||||||
|
) {
|
||||||
|
|
||||||
|
abstract suspend fun installPackage(specification: PythonPackageSpecification)
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// 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.extensions.ExtensionPointName
|
||||||
|
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||||
|
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||||
|
|
||||||
|
interface PythonPackagingToolwindowActionProvider {
|
||||||
|
fun getInstallActions(details: PythonPackageDetails, packageManager: PythonPackageManager): List<PythonPackageInstallAction>?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EP_NAME = ExtensionPointName.create<PythonPackagingToolwindowActionProvider>("Pythonid.PythonPackagingToolwindowActionProvider")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// 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.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.jetbrains.python.packaging.common.PythonPackageSpecification
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import org.jetbrains.annotations.Nls
|
||||||
|
|
||||||
|
class SimplePythonPackageInstallAction(text: @Nls String,
|
||||||
|
project: Project) : PythonPackageInstallAction(text, project) {
|
||||||
|
override suspend fun installPackage(specification: PythonPackageSpecification) {
|
||||||
|
project.service<PyPackagingToolWindowService>().installPackage(specification)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// 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.ui.awt.RelativePoint
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
|
||||||
|
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 java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
internal class UpdatePackageToLatestAction(val table: PyPackagesTable<*>) : DumbAwareAction(PyBundle.message("python.toolwindow.packages.update.package")) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project ?: return
|
||||||
|
val pkg = table.selectedItem()
|
||||||
|
|
||||||
|
val service = project.service<PyPackagingToolWindowService>()
|
||||||
|
if (pkg is InstalledPackage && pkg.canBeUpdated) {
|
||||||
|
PyPackageCoroutine.getIoScope(project).launch {
|
||||||
|
val specification = pkg.repository.createPackageSpecification(pkg.name, pkg.nextVersion!!.presentableText)
|
||||||
|
service.updatePackage(specification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pkg is InstallablePackage) {
|
||||||
|
PyPackageCoroutine.getIoScope(project).launch {
|
||||||
|
val details = service.detailsForPackage(pkg)
|
||||||
|
withContext(Dispatchers.EDT) {
|
||||||
|
PyPackagesUiComponents.createAvailableVersionsPopup(pkg, details, project, table.controller).show(
|
||||||
|
RelativePoint(e.inputEvent as MouseEvent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(e: AnActionEvent) {
|
||||||
|
val pkg = table.selectedItem() as? InstalledPackage
|
||||||
|
|
||||||
|
val currentVersion = pkg?.currentVersion?.presentableText
|
||||||
|
val nextVersion = pkg?.nextVersion?.presentableText
|
||||||
|
if (currentVersion != null && nextVersion != null) {
|
||||||
|
e.presentation.isEnabledAndVisible = true
|
||||||
|
e.presentation.text = PyBundle.message("python.toolwindow.packages.update.package.version", currentVersion, nextVersion)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
e.presentation.isEnabledAndVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Copyright 2000-2021 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.
|
// 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
|
package com.jetbrains.python.packaging.toolwindow.model
|
||||||
|
|
||||||
import com.intellij.openapi.util.NlsSafe
|
import com.intellij.openapi.util.NlsSafe
|
||||||
import com.jetbrains.python.packaging.PyPackageVersion
|
import com.jetbrains.python.packaging.PyPackageVersion
|
||||||
@@ -11,9 +11,11 @@ import com.jetbrains.python.packaging.repository.PyPackageRepository
|
|||||||
sealed class DisplayablePackage(@NlsSafe val name: String, val repository: PyPackageRepository)
|
sealed class DisplayablePackage(@NlsSafe val name: String, val repository: PyPackageRepository)
|
||||||
|
|
||||||
class InstalledPackage(val instance: PythonPackage, repository: PyPackageRepository, val nextVersion: PyPackageVersion? = null) : DisplayablePackage(instance.name, repository) {
|
class InstalledPackage(val instance: PythonPackage, repository: PyPackageRepository, val nextVersion: PyPackageVersion? = null) : DisplayablePackage(instance.name, repository) {
|
||||||
|
val currentVersion = PyPackageVersionNormalizer.normalize(instance.version)
|
||||||
|
|
||||||
val canBeUpdated: Boolean
|
val canBeUpdated: Boolean
|
||||||
get() {
|
get() {
|
||||||
val currentVersion = PyPackageVersionNormalizer.normalize(instance.version) ?: return false
|
currentVersion ?: return false
|
||||||
return nextVersion != null && PyPackageVersionComparator.compare(nextVersion, currentVersion) > 0
|
return nextVersion != null && PyPackageVersionComparator.compare(nextVersion, currentVersion) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +26,9 @@ class InstalledPackage(val instance: PythonPackage, repository: PyPackageReposit
|
|||||||
|
|
||||||
|
|
||||||
class InstallablePackage(name: String, repository: PyPackageRepository) : DisplayablePackage(name, repository)
|
class InstallablePackage(name: String, repository: PyPackageRepository) : DisplayablePackage(name, repository)
|
||||||
|
|
||||||
class ExpandResultNode(var more: Int, repository: PyPackageRepository) : DisplayablePackage("", repository)
|
class ExpandResultNode(var more: Int, repository: PyPackageRepository) : DisplayablePackage("", repository)
|
||||||
|
|
||||||
open class PyPackagesViewData(@NlsSafe val repository: PyPackageRepository, val packages: List<DisplayablePackage>, val exactMatch: Int = -1, val moreItems: Int = 0)
|
open class PyPackagesViewData(@NlsSafe val repository: PyPackageRepository, val packages: List<DisplayablePackage>, val exactMatch: Int = -1, val moreItems: Int = 0)
|
||||||
|
|
||||||
class PyInvalidRepositoryViewData(repository: PyPackageRepository) : PyPackagesViewData(repository, emptyList())
|
class PyInvalidRepositoryViewData(repository: PyPackageRepository) : PyPackagesViewData(repository, emptyList())
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
// 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.ui
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.ui.DoubleClickListener
|
||||||
|
import com.intellij.ui.PopupHandler
|
||||||
|
import com.intellij.ui.SideBorder
|
||||||
|
import com.intellij.ui.awt.RelativePoint
|
||||||
|
import com.intellij.ui.hover.TableHoverListener
|
||||||
|
import com.intellij.ui.table.JBTable
|
||||||
|
import com.intellij.util.ui.ListTableModel
|
||||||
|
import com.intellij.util.ui.NamedColorUtil
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingTablesView
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.ChangeVersionPackageAction
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.DeletePackageAction
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.InstallPackageAction
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.actions.UpdatePackageToLatestAction
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.ExpandResultNode
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.awt.Cursor
|
||||||
|
import java.awt.event.ActionEvent
|
||||||
|
import java.awt.event.KeyEvent
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import javax.swing.AbstractAction
|
||||||
|
import javax.swing.JTable
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
import javax.swing.ListSelectionModel
|
||||||
|
import javax.swing.table.TableCellRenderer
|
||||||
|
|
||||||
|
internal class PyPackagesTable<T : DisplayablePackage>(
|
||||||
|
project: Project,
|
||||||
|
model: ListTableModel<T>,
|
||||||
|
tablesView: PyPackagingTablesView,
|
||||||
|
val controller: PyPackagingToolWindowPanel,
|
||||||
|
) : JBTable(model) {
|
||||||
|
private var lastSelectedRow = -1
|
||||||
|
internal var hoveredColumn = -1
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private val listModel: ListTableModel<T>
|
||||||
|
get() = model as ListTableModel<T>
|
||||||
|
|
||||||
|
var items: List<T>
|
||||||
|
get() = listModel.items
|
||||||
|
set(value) {
|
||||||
|
listModel.items = value.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val service = project.service<PyPackagingToolWindowService>()
|
||||||
|
setShowGrid(false)
|
||||||
|
setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
|
val column = columnModel.getColumn(1)
|
||||||
|
column.minWidth = 130
|
||||||
|
column.maxWidth = 130
|
||||||
|
column.resizable = false
|
||||||
|
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM)
|
||||||
|
rowHeight = 20
|
||||||
|
|
||||||
|
initCrossNavigation(service, tablesView)
|
||||||
|
|
||||||
|
val hoverListener = object : TableHoverListener() {
|
||||||
|
override fun onHover(table: JTable, row: Int, column: Int) {
|
||||||
|
hoveredColumn = column
|
||||||
|
if (column == 1) {
|
||||||
|
table.repaint(table.getCellRect(row, column, true))
|
||||||
|
val currentPackage = items[row]
|
||||||
|
if (currentPackage is InstallablePackage
|
||||||
|
|| (currentPackage is InstalledPackage && currentPackage.canBeUpdated)) {
|
||||||
|
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor = Cursor.getDefaultCursor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hoverListener.addTo(this)
|
||||||
|
|
||||||
|
addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
|
if (e.clickCount != 1 || columnAtPoint(e.point) != 1) return // double click or click on package name column, nothing to be done
|
||||||
|
val hoveredRow = TableHoverListener.getHoveredRow(this@PyPackagesTable)
|
||||||
|
val selectedPackage = this@PyPackagesTable.items[hoveredRow]
|
||||||
|
|
||||||
|
if (selectedPackage is InstallablePackage) {
|
||||||
|
controller.packagingScope.launch(Dispatchers.IO) {
|
||||||
|
val details = service.detailsForPackage(selectedPackage)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
PyPackagesUiComponents.createAvailableVersionsPopup(selectedPackage, details, project, controller).show(RelativePoint(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (selectedPackage is InstalledPackage && selectedPackage.canBeUpdated) {
|
||||||
|
controller.packagingScope.launch(Dispatchers.IO) {
|
||||||
|
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name,
|
||||||
|
selectedPackage.nextVersion!!.presentableText)
|
||||||
|
project.service<PyPackagingToolWindowService>().updatePackage(specification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
selectionModel.addListSelectionListener {
|
||||||
|
if (selectedRow != -1 && selectedRow != lastSelectedRow) {
|
||||||
|
lastSelectedRow = selectedRow
|
||||||
|
tablesView.requestSelection(this)
|
||||||
|
val pkg = model.items[selectedRow]
|
||||||
|
if (pkg !is ExpandResultNode) controller.packageSelected(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object : DoubleClickListener() {
|
||||||
|
override fun onDoubleClick(event: MouseEvent): Boolean {
|
||||||
|
val pkg = model.items[selectedRow]
|
||||||
|
if (pkg is ExpandResultNode) loadMoreItems(service, pkg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}.installOn(this)
|
||||||
|
|
||||||
|
val packageActionGroup = DefaultActionGroup(
|
||||||
|
DeletePackageAction(this),
|
||||||
|
InstallPackageAction(this),
|
||||||
|
UpdatePackageToLatestAction(this),
|
||||||
|
ChangeVersionPackageAction(this),
|
||||||
|
)
|
||||||
|
PopupHandler.installPopupMenu(this, packageActionGroup, "PackagePopup")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun selectedItem(): T? = items.getOrNull(selectedRow)
|
||||||
|
|
||||||
|
private fun initCrossNavigation(service: PyPackagingToolWindowService, tablesView: PyPackagingTablesView) {
|
||||||
|
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ENTER_ACTION)
|
||||||
|
actionMap.put(ENTER_ACTION, object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent?) {
|
||||||
|
if (selectedRow == -1) return
|
||||||
|
val index = selectedRow
|
||||||
|
val selectedItem = selectedItem() ?: return
|
||||||
|
|
||||||
|
if (selectedItem is ExpandResultNode) {
|
||||||
|
loadMoreItems(service, selectedItem)
|
||||||
|
}
|
||||||
|
setRowSelectionInterval(index, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val nextRowAction = actionMap[NEXT_ROW_ACTION]
|
||||||
|
actionMap.put(NEXT_ROW_ACTION, object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent?) {
|
||||||
|
if (selectedRow == -1) return
|
||||||
|
|
||||||
|
if (selectedRow + 1 == items.size) {
|
||||||
|
tablesView.selectNextFrom(this@PyPackagesTable)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextRowAction.actionPerformed(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val prevRowAction = actionMap[PREVIOUS_ROW_ACTION]
|
||||||
|
actionMap.put(PREVIOUS_ROW_ACTION, object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent?) {
|
||||||
|
if (selectedRow == -1) return
|
||||||
|
|
||||||
|
if (selectedRow == 0) {
|
||||||
|
tablesView.selectPreviousOf(this@PyPackagesTable)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prevRowAction.actionPerformed(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun loadMoreItems(service: PyPackagingToolWindowService, node: ExpandResultNode) {
|
||||||
|
val result = service.getMoreResultsForRepo(node.repository, items.size - 1)
|
||||||
|
items = items.dropLast(1) + (result.packages as List<T>)
|
||||||
|
if (result.moreItems > 0) {
|
||||||
|
node.more = result.moreItems
|
||||||
|
items = items + listOf(node) as List<T>
|
||||||
|
}
|
||||||
|
this@PyPackagesTable.revalidate()
|
||||||
|
this@PyPackagesTable.repaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCellRenderer(row: Int, column: Int): TableCellRenderer {
|
||||||
|
return PyPaginationAwareRenderer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearSelection() {
|
||||||
|
lastSelectedRow = -1
|
||||||
|
super.clearSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun removeRow(index: Int) = listModel.removeRow(index)
|
||||||
|
internal fun insertRow(index: Int, pkg: T) = listModel.insertRow(index, pkg)
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NEXT_ROW_ACTION = "selectNextRow"
|
||||||
|
private const val PREVIOUS_ROW_ACTION = "selectPreviousRow"
|
||||||
|
private const val ENTER_ACTION = "ENTER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 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.ui
|
||||||
|
|
||||||
|
import com.intellij.util.ui.ListTableModel
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
|
||||||
|
internal class PyPackagesTableModel<T : DisplayablePackage> : ListTableModel<T>() {
|
||||||
|
override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean = false
|
||||||
|
override fun getColumnCount(): Int = 2
|
||||||
|
override fun getColumnName(column: Int): String = column.toString()
|
||||||
|
override fun getColumnClass(columnIndex: Int): Class<*> = DisplayablePackage::class.java
|
||||||
|
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? = items[rowIndex]
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// 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.ui
|
||||||
|
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||||
|
import com.intellij.openapi.ui.popup.ListPopup
|
||||||
|
import com.intellij.openapi.ui.popup.PopupStep
|
||||||
|
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
||||||
|
import com.intellij.ui.SideBorder
|
||||||
|
import com.intellij.util.ui.JBUI
|
||||||
|
import com.intellij.util.ui.NamedColorUtil
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
object PyPackagesUiComponents {
|
||||||
|
fun createAvailableVersionsPopup(selectedPackage: DisplayablePackage, details: PythonPackageDetails, project: Project, controller: PyPackagingToolWindowPanel): ListPopup {
|
||||||
|
return JBPopupFactory.getInstance().createListPopup(object : BaseListPopupStep<String>(null, details.availableVersions) {
|
||||||
|
override fun onChosen(selectedValue: String?, finalChoice: Boolean): PopupStep<*>? {
|
||||||
|
return doFinalStep {
|
||||||
|
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name, selectedValue)
|
||||||
|
controller.packagingScope.launch(Dispatchers.IO) {
|
||||||
|
project.service<PyPackagingToolWindowService>().installPackage(specification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun boxPanel(init: JPanel.() -> Unit) = object : JPanel() {
|
||||||
|
init {
|
||||||
|
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
||||||
|
alignmentX = LEFT_ALIGNMENT
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun borderPanel(init: JPanel.() -> Unit) = object : JPanel() {
|
||||||
|
init {
|
||||||
|
layout = BorderLayout(0, 0)
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun headerPanel(label: JLabel, component: JComponent?) = object : JPanel() {
|
||||||
|
init {
|
||||||
|
background = UIUtil.getLabelBackground()
|
||||||
|
layout = BorderLayout()
|
||||||
|
border = BorderFactory.createCompoundBorder(SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM), JBUI.Borders.empty(0, 5))
|
||||||
|
preferredSize = Dimension(preferredSize.width, 25)
|
||||||
|
minimumSize = Dimension(minimumSize.width, 25)
|
||||||
|
maximumSize = Dimension(maximumSize.width, 25)
|
||||||
|
|
||||||
|
add(label, BorderLayout.WEST)
|
||||||
|
if (component != null) {
|
||||||
|
addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mouseClicked(e: MouseEvent?) {
|
||||||
|
component.isVisible = !component.isVisible
|
||||||
|
label.icon = if (component.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// 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.ui
|
||||||
|
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
internal class PyPackagingTableGroup<T : DisplayablePackage>(val repository: PyPackageRepository, val table: PyPackagesTable<T>) {
|
||||||
|
@NlsSafe
|
||||||
|
val name: String = repository.name!!
|
||||||
|
|
||||||
|
private var expanded = false
|
||||||
|
private val label = JLabel(name).apply { icon = AllIcons.General.ArrowDown }
|
||||||
|
private val header: JPanel = PyPackagesUiComponents.headerPanel(label, table)
|
||||||
|
private var itemsCount: Int? = null
|
||||||
|
|
||||||
|
|
||||||
|
internal var items: List<T>
|
||||||
|
get() = table.items
|
||||||
|
set(value) {
|
||||||
|
table.items = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun collapse() {
|
||||||
|
expanded = false
|
||||||
|
table.isVisible = false
|
||||||
|
label.icon = if (table.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expand() {
|
||||||
|
expanded = true
|
||||||
|
table.isVisible = true
|
||||||
|
label.icon = if (table.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateHeaderText(newItemCount: Int?) {
|
||||||
|
itemsCount = newItemCount
|
||||||
|
label.text = if (itemsCount == null) name else PyBundle.message("python.toolwindow.packages.custom.repo.searched", name, itemsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addTo(panel: JPanel) {
|
||||||
|
panel.add(header)
|
||||||
|
panel.add(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replace(row: Int, pkg: T) {
|
||||||
|
table.removeRow(row)
|
||||||
|
table.insertRow(row, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFrom(panel: JPanel) {
|
||||||
|
panel.remove(header)
|
||||||
|
panel.remove(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun repaint() {
|
||||||
|
table.invalidate()
|
||||||
|
table.repaint()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// 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.ui
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.ui.hover.TableHoverListener
|
||||||
|
import com.intellij.util.ui.JBUI
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.ExpandResultNode
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
|
||||||
|
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
|
||||||
|
import java.awt.Component
|
||||||
|
import java.awt.font.TextAttribute
|
||||||
|
import javax.swing.BoxLayout
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import javax.swing.JTable
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
internal class PyPaginationAwareRenderer : DefaultTableCellRenderer() {
|
||||||
|
private val nameLabel = JLabel().apply { border = JBUI.Borders.empty(0, 12) }
|
||||||
|
private val versionLabel = JLabel().apply { border = JBUI.Borders.emptyRight(12) }
|
||||||
|
private val linkLabel = JLabel(PyBundle.message("python.toolwindow.packages.install.link")).apply {
|
||||||
|
border = JBUI.Borders.emptyRight(12)
|
||||||
|
foreground = JBUI.CurrentTheme.Link.Foreground.ENABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
private val namePanel = JPanel().apply {
|
||||||
|
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
||||||
|
border = JBUI.Borders.empty()
|
||||||
|
add(nameLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val versionPanel = PyPackagesUiComponents.boxPanel {
|
||||||
|
border = JBUI.Borders.emptyRight(12)
|
||||||
|
add(versionLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTableCellRendererComponent(
|
||||||
|
table: JTable,
|
||||||
|
value: Any?,
|
||||||
|
isSelected: Boolean,
|
||||||
|
hasFocus: Boolean,
|
||||||
|
row: Int,
|
||||||
|
column: Int,
|
||||||
|
): Component {
|
||||||
|
val rowSelected = table.selectedRow == row
|
||||||
|
val tableFocused = table.hasFocus()
|
||||||
|
|
||||||
|
if (value is ExpandResultNode) {
|
||||||
|
if (column == 1) {
|
||||||
|
versionPanel.removeAll()
|
||||||
|
return versionPanel
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nameLabel.text = PyBundle.message("python.toolwindow.packages.load.more", value.more)
|
||||||
|
nameLabel.foreground = UIUtil.getContextHelpForeground()
|
||||||
|
return namePanel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// version column
|
||||||
|
if (column == 1) {
|
||||||
|
versionPanel.background = JBUI.CurrentTheme.Table.background(rowSelected, tableFocused)
|
||||||
|
versionPanel.foreground = JBUI.CurrentTheme.Table.foreground(rowSelected, tableFocused)
|
||||||
|
versionPanel.removeAll()
|
||||||
|
|
||||||
|
if (value is InstallablePackage) {
|
||||||
|
linkLabel.text = PyBundle.message("python.toolwindow.packages.install.link")
|
||||||
|
linkLabel.updateUnderline(table, row)
|
||||||
|
if (rowSelected || TableHoverListener.getHoveredRow(table) == row) {
|
||||||
|
versionPanel.add(linkLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value is InstalledPackage && value.nextVersion != null && value.canBeUpdated) {
|
||||||
|
@NlsSafe val updateLink = value.instance.version + " -> " + value.nextVersion.presentableText
|
||||||
|
linkLabel.text = updateLink
|
||||||
|
linkLabel.updateUnderline(table, row)
|
||||||
|
versionPanel.add(linkLabel)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@NlsSafe val version = (value as InstalledPackage).instance.version
|
||||||
|
versionLabel.text = version
|
||||||
|
versionPanel.add(versionLabel)
|
||||||
|
}
|
||||||
|
return versionPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
// package name column
|
||||||
|
val currentPackage = value as DisplayablePackage
|
||||||
|
|
||||||
|
namePanel.background = JBUI.CurrentTheme.Table.background(rowSelected, tableFocused)
|
||||||
|
namePanel.foreground = JBUI.CurrentTheme.Table.foreground(rowSelected, tableFocused)
|
||||||
|
|
||||||
|
nameLabel.text = currentPackage.name
|
||||||
|
nameLabel.foreground = JBUI.CurrentTheme.Label.foreground()
|
||||||
|
return namePanel
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun JLabel.updateUnderline(table: JTable, currentRow: Int) {
|
||||||
|
val hoveredRow = TableHoverListener.getHoveredRow(table)
|
||||||
|
val hoveredColumn = (table as PyPackagesTable<*>).hoveredColumn
|
||||||
|
val underline = if (hoveredRow == currentRow && hoveredColumn == 1) TextAttribute.UNDERLINE_ON else -1
|
||||||
|
|
||||||
|
val attributes = font.attributes as MutableMap<TextAttribute, Any>
|
||||||
|
attributes[TextAttribute.UNDERLINE] = underline
|
||||||
|
attributes[TextAttribute.LIGATURES] = TextAttribute.LIGATURES_ON
|
||||||
|
font = font.deriveFont(attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
// Copyright 2000-2021 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.packaging.toolwindow
|
|
||||||
|
|
||||||
import com.intellij.icons.AllIcons
|
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
||||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.intellij.openapi.project.DumbAwareAction
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
|
||||||
import com.intellij.openapi.ui.popup.ListPopup
|
|
||||||
import com.intellij.openapi.ui.popup.PopupStep
|
|
||||||
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
|
|
||||||
import com.intellij.openapi.util.NlsSafe
|
|
||||||
import com.intellij.ui.DoubleClickListener
|
|
||||||
import com.intellij.ui.PopupHandler
|
|
||||||
import com.intellij.ui.SideBorder
|
|
||||||
import com.intellij.ui.awt.RelativePoint
|
|
||||||
import com.intellij.ui.hover.TableHoverListener
|
|
||||||
import com.intellij.ui.table.JBTable
|
|
||||||
import com.intellij.util.ui.*
|
|
||||||
import com.intellij.util.ui.JBUI
|
|
||||||
import com.jetbrains.python.PyBundle.message
|
|
||||||
import com.jetbrains.python.packaging.common.PythonPackageDetails
|
|
||||||
import com.jetbrains.python.packaging.repository.PyPackageRepository
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.awt.*
|
|
||||||
import java.awt.event.ActionEvent
|
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import java.awt.event.MouseAdapter
|
|
||||||
import java.awt.event.MouseEvent
|
|
||||||
import java.awt.font.TextAttribute
|
|
||||||
import javax.swing.*
|
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
|
||||||
import javax.swing.table.TableCellRenderer
|
|
||||||
|
|
||||||
|
|
||||||
internal class PyPackagesTable<T : DisplayablePackage>(project: Project,
|
|
||||||
model: ListTableModel<T>,
|
|
||||||
tablesView: PyPackagingTablesView,
|
|
||||||
controller: PyPackagingToolWindowPanel) : JBTable(model) {
|
|
||||||
private var lastSelectedRow = -1
|
|
||||||
internal var hoveredColumn = -1
|
|
||||||
init {
|
|
||||||
val service = project.service<PyPackagingToolWindowService>()
|
|
||||||
setShowGrid(false)
|
|
||||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
|
||||||
val column = columnModel.getColumn(1)
|
|
||||||
column.minWidth = 130
|
|
||||||
column.maxWidth = 130
|
|
||||||
column.resizable = false
|
|
||||||
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM)
|
|
||||||
rowHeight = 20
|
|
||||||
|
|
||||||
initCrossNavigation(service, tablesView)
|
|
||||||
|
|
||||||
val hoverListener = object : TableHoverListener() {
|
|
||||||
override fun onHover(table: JTable, row: Int, column: Int) {
|
|
||||||
hoveredColumn = column
|
|
||||||
if (column == 1) {
|
|
||||||
table.repaint(table.getCellRect(row, column, true))
|
|
||||||
val currentPackage = items[row]
|
|
||||||
if (currentPackage is InstallablePackage
|
|
||||||
|| (currentPackage is InstalledPackage && currentPackage.canBeUpdated)) {
|
|
||||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor = Cursor.getDefaultCursor()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hoverListener.addTo(this)
|
|
||||||
|
|
||||||
addMouseListener(object : MouseAdapter() {
|
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
|
||||||
if (e.clickCount != 1 || columnAtPoint(e.point) != 1) return // double click or click on package name column, nothing to be done
|
|
||||||
val hoveredRow = TableHoverListener.getHoveredRow(this@PyPackagesTable)
|
|
||||||
val selectedPackage = this@PyPackagesTable.items[hoveredRow]
|
|
||||||
|
|
||||||
if (selectedPackage is InstallablePackage) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
val details = service.detailsForPackage(selectedPackage)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
createAvailableVersionsPopup(selectedPackage, details, project, controller).show(RelativePoint(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedPackage is InstalledPackage && selectedPackage.canBeUpdated) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name,
|
|
||||||
selectedPackage.nextVersion!!.presentableText)
|
|
||||||
project.service<PyPackagingToolWindowService>().updatePackage(specification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
selectionModel.addListSelectionListener {
|
|
||||||
if (selectedRow != -1 && selectedRow != lastSelectedRow) {
|
|
||||||
lastSelectedRow = selectedRow
|
|
||||||
tablesView.requestSelection(this)
|
|
||||||
val pkg = model.items[selectedRow]
|
|
||||||
if (pkg !is ExpandResultNode) controller.packageSelected(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object : DoubleClickListener() {
|
|
||||||
override fun onDoubleClick(event: MouseEvent): Boolean {
|
|
||||||
val pkg = model.items[selectedRow]
|
|
||||||
if (pkg is ExpandResultNode) loadMoreItems(service, pkg)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}.installOn(this)
|
|
||||||
|
|
||||||
PopupHandler.installPopupMenu(this, DefaultActionGroup(object : DumbAwareAction({
|
|
||||||
val pkg = if (selectedRow >= 0) model.items[selectedRow] else null
|
|
||||||
if (pkg is InstalledPackage) {
|
|
||||||
message("python.toolwindow.packages.delete.package")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
message("python.toolwindow.packages.install.link")
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.Main) {
|
|
||||||
if (selectedRow == -1) return@launch
|
|
||||||
val pkg = model.items[selectedRow]
|
|
||||||
if (pkg is InstalledPackage) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
service.deletePackage(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pkg is InstallablePackage) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
val details = service.detailsForPackage(pkg)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
createAvailableVersionsPopup(pkg as InstallablePackage, details, project, controller).show(RelativePoint(e.inputEvent as MouseEvent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, object : DumbAwareAction({
|
|
||||||
val pkg = if (selectedRow >= 0) model.items[selectedRow] else null
|
|
||||||
if (pkg is InstalledPackage && pkg.canBeUpdated) {
|
|
||||||
message("python.toolwindow.packages.update.package")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.Main) {
|
|
||||||
if (selectedRow == -1) return@launch
|
|
||||||
val pkg = model.items[selectedRow]
|
|
||||||
if (pkg is InstalledPackage && pkg.canBeUpdated) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
val specification = pkg.repository.createPackageSpecification(pkg.name, pkg.nextVersion!!.presentableText)
|
|
||||||
service.updatePackage(specification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pkg is InstallablePackage) {
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
val details = service.detailsForPackage(pkg)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
createAvailableVersionsPopup(pkg as InstallablePackage, details, project, controller).show(RelativePoint(e.inputEvent as MouseEvent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), "PackagePopup")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createAvailableVersionsPopup(selectedPackage: InstallablePackage, details: PythonPackageDetails, project: Project, controller: PyPackagingToolWindowPanel): ListPopup {
|
|
||||||
return JBPopupFactory.getInstance().createListPopup(object : BaseListPopupStep<String>(null, details.availableVersions) {
|
|
||||||
override fun onChosen(selectedValue: String?, finalChoice: Boolean): PopupStep<*>? {
|
|
||||||
return doFinalStep {
|
|
||||||
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name, selectedValue)
|
|
||||||
controller.packagingScope.launch(Dispatchers.IO) {
|
|
||||||
project.service<PyPackagingToolWindowService>().installPackage(specification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initCrossNavigation(service: PyPackagingToolWindowService, tablesView: PyPackagingTablesView) {
|
|
||||||
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ENTER_ACTION)
|
|
||||||
actionMap.put(ENTER_ACTION, object : AbstractAction() {
|
|
||||||
override fun actionPerformed(e: ActionEvent?) {
|
|
||||||
if (selectedRow == -1) return
|
|
||||||
val index = selectedRow
|
|
||||||
val selectedItem = items[selectedRow]
|
|
||||||
|
|
||||||
if (selectedItem is ExpandResultNode) {
|
|
||||||
loadMoreItems(service, selectedItem)
|
|
||||||
}
|
|
||||||
setRowSelectionInterval(index, index)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val nextRowAction = actionMap[NEXT_ROW_ACTION]
|
|
||||||
actionMap.put(NEXT_ROW_ACTION, object : AbstractAction() {
|
|
||||||
override fun actionPerformed(e: ActionEvent?) {
|
|
||||||
if (selectedRow == -1) return
|
|
||||||
|
|
||||||
if (selectedRow + 1 == items.size) {
|
|
||||||
tablesView.selectNextFrom(this@PyPackagesTable)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nextRowAction.actionPerformed(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
val prevRowAction = actionMap[PREVIOUS_ROW_ACTION]
|
|
||||||
actionMap.put(PREVIOUS_ROW_ACTION, object : AbstractAction() {
|
|
||||||
override fun actionPerformed(e: ActionEvent?) {
|
|
||||||
if (selectedRow == -1) return
|
|
||||||
|
|
||||||
if (selectedRow == 0) {
|
|
||||||
tablesView.selectPreviousOf(this@PyPackagesTable)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
prevRowAction.actionPerformed(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun loadMoreItems(service: PyPackagingToolWindowService, node: ExpandResultNode) {
|
|
||||||
val result = service.getMoreResultsForRepo(node.repository, items.size - 1)
|
|
||||||
items = items.dropLast(1) + (result.packages as List<T>)
|
|
||||||
if (result.moreItems > 0) {
|
|
||||||
node.more = result.moreItems
|
|
||||||
items = items + listOf(node) as List<T>
|
|
||||||
}
|
|
||||||
this@PyPackagesTable.revalidate()
|
|
||||||
this@PyPackagesTable.repaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCellRenderer(row: Int, column: Int): TableCellRenderer {
|
|
||||||
return PyPaginationAwareRenderer()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clearSelection() {
|
|
||||||
lastSelectedRow = -1
|
|
||||||
super.clearSelection()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun addRows(rows: List<T>) = listModel.addRows(rows)
|
|
||||||
internal fun removeRow(index: Int) = listModel.removeRow(index)
|
|
||||||
internal fun insertRow(index: Int, pkg: T) = listModel.insertRow(index, pkg)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private val listModel: ListTableModel<T>
|
|
||||||
get() = model as ListTableModel<T>
|
|
||||||
|
|
||||||
var items: List<T>
|
|
||||||
get() = listModel.items
|
|
||||||
set(value) { listModel.items = value.toMutableList() }
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val NEXT_ROW_ACTION = "selectNextRow"
|
|
||||||
private const val PREVIOUS_ROW_ACTION = "selectPreviousRow"
|
|
||||||
private const val ENTER_ACTION = "ENTER"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class PyPackagesTableModel<T : DisplayablePackage> : ListTableModel<T>() {
|
|
||||||
override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean = false
|
|
||||||
override fun getColumnCount(): Int = 2
|
|
||||||
override fun getColumnName(column: Int): String = column.toString()
|
|
||||||
override fun getColumnClass(columnIndex: Int): Class<*> = DisplayablePackage::class.java
|
|
||||||
|
|
||||||
override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? = items[rowIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun boxPanel(init: JPanel.() -> Unit) = object : JPanel() {
|
|
||||||
init {
|
|
||||||
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
|
||||||
alignmentX = LEFT_ALIGNMENT
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun borderPanel(init: JPanel.() -> Unit) = object : JPanel() {
|
|
||||||
init {
|
|
||||||
layout = BorderLayout(0, 0)
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun headerPanel(label: JLabel, component: JComponent?) = object : JPanel() {
|
|
||||||
init {
|
|
||||||
background = UIUtil.getLabelBackground()
|
|
||||||
layout = BorderLayout()
|
|
||||||
border = BorderFactory.createCompoundBorder(SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.BOTTOM), JBUI.Borders.empty(0, 5))
|
|
||||||
preferredSize = Dimension(preferredSize.width, 25)
|
|
||||||
minimumSize = Dimension(minimumSize.width, 25)
|
|
||||||
maximumSize = Dimension(maximumSize.width, 25)
|
|
||||||
|
|
||||||
add(label, BorderLayout.WEST)
|
|
||||||
if (component != null) {
|
|
||||||
addMouseListener(object : MouseAdapter() {
|
|
||||||
override fun mouseClicked(e: MouseEvent?) {
|
|
||||||
component.isVisible = !component.isVisible
|
|
||||||
label.icon = if (component.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PyPaginationAwareRenderer : DefaultTableCellRenderer() {
|
|
||||||
private val nameLabel = JLabel().apply { border = JBUI.Borders.empty(0, 12) }
|
|
||||||
private val versionLabel = JLabel().apply { border = JBUI.Borders.emptyRight(12) }
|
|
||||||
private val linkLabel = JLabel(message("python.toolwindow.packages.install.link")).apply {
|
|
||||||
border = JBUI.Borders.emptyRight(12)
|
|
||||||
foreground = JBUI.CurrentTheme.Link.Foreground.ENABLED
|
|
||||||
}
|
|
||||||
|
|
||||||
private val namePanel = JPanel().apply {
|
|
||||||
layout = BoxLayout(this, BoxLayout.X_AXIS)
|
|
||||||
border = JBUI.Borders.empty()
|
|
||||||
add(nameLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val versionPanel = boxPanel {
|
|
||||||
border = JBUI.Borders.emptyRight(12)
|
|
||||||
add(versionLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTableCellRendererComponent(table: JTable,
|
|
||||||
value: Any?,
|
|
||||||
isSelected: Boolean,
|
|
||||||
hasFocus: Boolean,
|
|
||||||
row: Int,
|
|
||||||
column: Int): Component {
|
|
||||||
val rowSelected = table.selectedRow == row
|
|
||||||
val tableFocused = table.hasFocus()
|
|
||||||
|
|
||||||
if (value is ExpandResultNode) {
|
|
||||||
if (column == 1) {
|
|
||||||
versionPanel.removeAll()
|
|
||||||
return versionPanel
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nameLabel.text = message("python.toolwindow.packages.load.more", value.more)
|
|
||||||
nameLabel.foreground = UIUtil.getContextHelpForeground()
|
|
||||||
return namePanel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// version column
|
|
||||||
if (column == 1) {
|
|
||||||
versionPanel.background = JBUI.CurrentTheme.Table.background(rowSelected, tableFocused)
|
|
||||||
versionPanel.foreground = JBUI.CurrentTheme.Table.foreground(rowSelected, tableFocused)
|
|
||||||
versionPanel.removeAll()
|
|
||||||
|
|
||||||
if (value is InstallablePackage) {
|
|
||||||
linkLabel.text = message("python.toolwindow.packages.install.link")
|
|
||||||
linkLabel.updateUnderline(table, row)
|
|
||||||
if (rowSelected || TableHoverListener.getHoveredRow(table) == row) {
|
|
||||||
versionPanel.add(linkLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (value is InstalledPackage && value.nextVersion != null && value.canBeUpdated) {
|
|
||||||
@NlsSafe val updateLink = value.instance.version + " -> " + value.nextVersion.presentableText
|
|
||||||
linkLabel.text = updateLink
|
|
||||||
linkLabel.updateUnderline(table, row)
|
|
||||||
versionPanel.add(linkLabel)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@NlsSafe val version = (value as InstalledPackage).instance.version
|
|
||||||
versionLabel.text = version
|
|
||||||
versionPanel.add(versionLabel)
|
|
||||||
}
|
|
||||||
return versionPanel
|
|
||||||
}
|
|
||||||
|
|
||||||
// package name column
|
|
||||||
val currentPackage = value as DisplayablePackage
|
|
||||||
|
|
||||||
namePanel.background = JBUI.CurrentTheme.Table.background(rowSelected, tableFocused)
|
|
||||||
namePanel.foreground = JBUI.CurrentTheme.Table.foreground(rowSelected, tableFocused)
|
|
||||||
|
|
||||||
nameLabel.text = currentPackage.name
|
|
||||||
nameLabel.foreground = JBUI.CurrentTheme.Label.foreground()
|
|
||||||
return namePanel
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun JLabel.updateUnderline(table: JTable, currentRow: Int) {
|
|
||||||
val hoveredRow = TableHoverListener.getHoveredRow(table)
|
|
||||||
val hoveredColumn = (table as PyPackagesTable<*>).hoveredColumn
|
|
||||||
val underline = if (hoveredRow == currentRow && hoveredColumn == 1) TextAttribute.UNDERLINE_ON else -1
|
|
||||||
|
|
||||||
val attributes = font.attributes as MutableMap<TextAttribute, Any>
|
|
||||||
attributes[TextAttribute.UNDERLINE] = underline
|
|
||||||
attributes[TextAttribute.LIGATURES] = TextAttribute.LIGATURES_ON
|
|
||||||
font = font.deriveFont(attributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class PyPackagingTableGroup<T: DisplayablePackage>(val repository: PyPackageRepository, val table: PyPackagesTable<T>) {
|
|
||||||
@NlsSafe val name: String = repository.name!!
|
|
||||||
|
|
||||||
private var expanded = false
|
|
||||||
private val label = JLabel(name).apply { icon = AllIcons.General.ArrowDown }
|
|
||||||
private val header: JPanel = headerPanel(label, table)
|
|
||||||
private var itemsCount: Int? = null
|
|
||||||
|
|
||||||
|
|
||||||
internal var items: List<T>
|
|
||||||
get() = table.items
|
|
||||||
set(value) {
|
|
||||||
table.items = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collapse() {
|
|
||||||
expanded = false
|
|
||||||
table.isVisible = false
|
|
||||||
label.icon = if (table.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
|
||||||
}
|
|
||||||
|
|
||||||
fun expand() {
|
|
||||||
expanded = true
|
|
||||||
table.isVisible = true
|
|
||||||
label.icon = if (table.isVisible) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateHeaderText(newItemCount: Int?) {
|
|
||||||
itemsCount = newItemCount
|
|
||||||
label.text = if (itemsCount == null) name else message("python.toolwindow.packages.custom.repo.searched", name, itemsCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addTo(panel: JPanel) {
|
|
||||||
panel.add(header)
|
|
||||||
panel.add(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun replace(row: Int, pkg: T) {
|
|
||||||
table.removeRow(row)
|
|
||||||
table.insertRow(row, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeFrom(panel: JPanel) {
|
|
||||||
panel.remove(header)
|
|
||||||
panel.remove(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun repaint() {
|
|
||||||
table.invalidate()
|
|
||||||
table.repaint()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.jetbrains.python.packaging.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.platform.util.coroutines.childScope
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
@Service(Service.Level.PROJECT)
|
||||||
|
class PyPackageCoroutine(val project: Project, val coroutineScope: CoroutineScope) {
|
||||||
|
val ioScope = coroutineScope.childScope("Jupyter IO scope", context = Dispatchers.IO)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun launch(project: Project, context: CoroutineContext = Dispatchers.Main, body: suspend CoroutineScope.() -> Unit) =
|
||||||
|
project.service<PyPackageCoroutine>().coroutineScope.launch(context, block = body)
|
||||||
|
|
||||||
|
fun getIoScope(project: Project): CoroutineScope = project.service<PyPackageCoroutine>().ioScope
|
||||||
|
fun getScope(project: Project): CoroutineScope = project.service<PyPackageCoroutine>().coroutineScope
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user