PY-75464 PyPackages(refactor): Extract PyPackagesModuleController abd PyPackageCustomInstallComponents

GitOrigin-RevId: 86da1574f329c34604d484bd0663a39b9a59a111
This commit is contained in:
Nikita.Ashihmin
2024-08-26 17:35:18 +04:00
committed by intellij-monorepo-bot
parent 04a69878f7
commit 016978c8a8
5 changed files with 204 additions and 147 deletions

View File

@@ -12,4 +12,6 @@ class PyPackagesToolWindowModuleAttachListener : ModuleAttachListener {
module.project.service<PyPackagingToolWindowService>().moduleAttached()
}
}
}

View File

@@ -9,52 +9,42 @@ import com.intellij.openapi.actionSystem.DataSink
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.service
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.ui.SimpleToolWindowPanel
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.ui.OnePixelSplitter
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.SearchTextField
import com.intellij.ui.SideBorder
import com.intellij.ui.components.*
import com.intellij.ui.dsl.builder.*
import com.intellij.util.ui.JBUI
import com.intellij.ui.components.JBPanelWithEmptyText
import com.intellij.util.ui.NamedColorUtil
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.icons.PythonIcons
import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification
import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification
import com.jetbrains.python.packaging.toolwindow.details.PyPackageDescriptionController
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.modules.PyPackagesModuleController
import com.jetbrains.python.packaging.toolwindow.packages.PyPackageSearchTextField
import com.jetbrains.python.packaging.toolwindow.packages.PyPackagesListController
import com.jetbrains.python.packaging.toolwindow.ui.PyPackageCustomInstallComponents
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import com.jetbrains.python.sdk.pythonSdk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import javax.swing.*
import javax.swing.event.ListSelectionListener
import javax.swing.BorderFactory
import javax.swing.JComponent
import javax.swing.JPanel
class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindowPanel(false, true), Disposable {
private val moduleController = PyPackagesModuleController(project)
private val descriptionController = PyPackageDescriptionController(project).also {
Disposer.register(this, it)
}
@@ -64,7 +54,7 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
}
internal val packagingScope = PyPackageCoroutine.getIoScope(project)
private val packagingScope = PyPackageCoroutine.getIoScope(project)
private val searchTextField: SearchTextField = PyPackageSearchTextField(project)
@@ -83,12 +73,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
}
private val fromVcsText: String
get() = message("python.toolwindow.packages.add.package.from.vcs")
private val fromDiscText: String
get() = message("python.toolwindow.packages.add.package.from.disc")
init {
val service = project.service<PyPackagingToolWindowService>()
Disposer.register(service, this)
@@ -130,19 +114,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
actionToolbar.targetComponent = this
val installFromLocationLink = DropDownLink(message("python.toolwindow.packages.add.package.action"),
listOf(fromVcsText, fromDiscText)) {
val specification = when (it) {
fromDiscText -> showInstallFromDiscDialog(service)
fromVcsText -> showInstallFromVcsDialog(service)
else -> throw IllegalStateException("Unknown operation")
}
if (specification != null) {
packagingScope.launch {
service.installPackage(specification)
}
}
}
mainPanel = PyPackagesUiComponents.borderPanel {
val topToolbar = PyPackagesUiComponents.boxPanel {
@@ -153,7 +124,7 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
add(searchTextField)
actionToolbar.component.maximumSize = Dimension(70, actionToolbar.component.maximumSize.height)
add(actionToolbar.component)
add(installFromLocationLink)
add(PyPackageCustomInstallComponents.createInstallFromLocationLink(project))
}
add(topToolbar, BorderLayout.NORTH)
add(splitter!!, BorderLayout.CENTER)
@@ -162,121 +133,20 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
}
private fun createLeftPanel(service: PyPackagingToolWindowService): JComponent {
if (project.modules.size == 1) return packageListController.component
if (project.modules.size == 1)
return packageListController.component
val left = JPanel(BorderLayout()).apply {
border = BorderFactory.createEmptyBorder()
}
val modulePanel = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply {
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.RIGHT)
maximumSize = Dimension(80, maximumSize.height)
minimumSize = Dimension(50, minimumSize.height)
}
val moduleList = JBList(ModuleManager.getInstance(project).modules.toList().sortedBy { it.name }).apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION
border = JBUI.Borders.empty()
val itemRenderer = JBLabel("", PythonIcons.Python.PythonClosed, JLabel.LEFT).apply {
border = JBUI.Borders.empty(0, 10)
}
cellRenderer = ListCellRenderer { _, value, _, _, _ -> itemRenderer.text = value.name; itemRenderer }
addListSelectionListener(ListSelectionListener { e ->
if (e.valueIsAdjusting) return@ListSelectionListener
val selectedModule = this@apply.selectedValue
val sdk = selectedModule.pythonSdk ?: return@ListSelectionListener
packagingScope.launch {
service.initForSdk(sdk)
}
})
}
val fileListener = object : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (project.modules.size > 1) {
val newFile = event.newFile ?: return
val module = ModuleUtilCore.findModuleForFile(newFile, project)
packagingScope.launch {
val index = (moduleList.model as DefaultListModel<Module>).indexOf(module)
moduleList.selectionModel.setSelectionInterval(index, index)
}
}
}
}
service.project.messageBus
.connect(service)
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, fileListener)
modulePanel.add(moduleList)
left.add(ScrollPaneFactory.createScrollPane(modulePanel, true), BorderLayout.WEST)
left.add(moduleController.component, BorderLayout.WEST)
left.add(packageListController.component, BorderLayout.CENTER)
return left
}
private fun showInstallFromVcsDialog(service: PyPackagingToolWindowService): PythonVcsPackageSpecification? {
var editable = false
var link = ""
val systems = listOf(message("python.toolwindow.packages.add.package.vcs.git"),
message("python.toolwindow.packages.add.package.vcs.svn"),
message("python.toolwindow.packages.add.package.vcs.hg"),
message("python.toolwindow.packages.add.package.vcs.bzr"))
var vcs = systems.first()
val panel = panel {
row {
comboBox(systems)
.bindItem({ vcs }, { vcs = it!! })
textField()
.columns(COLUMNS_MEDIUM)
.bindText({ link }, { link = it })
.align(AlignX.FILL)
}
row {
checkBox(message("python.toolwindow.packages.add.package.as.editable"))
.bindSelected({ editable }, { editable = it })
}
}
val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet()
if (shouldInstall) {
val prefix = when (vcs) {
message("python.toolwindow.packages.add.package.vcs.git") -> "git+"
message("python.toolwindow.packages.add.package.vcs.svn") -> "svn+"
message("python.toolwindow.packages.add.package.vcs.hg") -> "hg+"
message("python.toolwindow.packages.add.package.vcs.bzr") -> "bzr+"
else -> throw IllegalStateException("Unknown VCS")
}
return PythonVcsPackageSpecification(link, link, prefix, editable)
}
return null
}
private fun showInstallFromDiscDialog(service: PyPackagingToolWindowService): PythonLocalPackageSpecification? {
var editable = false
val textField = TextFieldWithBrowseButton().apply {
addBrowseFolderListener(message("python.toolwindow.packages.add.package.path.selector"), "", service.project,
FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor())
}
val panel = panel {
row(message("python.toolwindow.packages.add.package.path")) {
cell(textField)
.columns(COLUMNS_MEDIUM)
.align(AlignX.FILL)
}
row {
checkBox(message("python.toolwindow.packages.add.package.as.editable"))
.bindSelected({ editable }, { editable = it })
}
}
val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet()
return if (shouldInstall) PythonLocalPackageSpecification(textField.text, textField.text, editable) else null
}
private fun trackOrientation(service: PyPackagingToolWindowService) {
service.project.messageBus.connect(service).subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener {
@@ -347,9 +217,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
companion object {
private const val HORIZONTAL_SPLITTER_KEY = "Python.PackagingToolWindow.Horizontal"
private const val VERTICAL_SPLITTER_KEY = "Python.PackagingToolWindow.Vertical"
val NO_DESCRIPTION: String
get() = message("python.toolwindow.packages.no.description.placeholder")
}
}

View File

@@ -15,7 +15,6 @@ import com.jetbrains.python.PythonHelper
import com.jetbrains.python.PythonHelpersLocator
import com.jetbrains.python.packaging.PyPIPackageRanking
import com.jetbrains.python.packaging.common.PythonPackageDetails
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel.Companion.NO_DESCRIPTION
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
import com.jetbrains.python.run.applyHelperPackageToPythonPath
import com.jetbrains.python.run.buildTargetedCommandLine
@@ -108,4 +107,8 @@ class PyPackageDetailsHtmlRender(val project: Project, val currentSdk: Sdk?) {
}
private fun wrapHtml(html: String): String = "<html><head></head><body><p>$html</p></body></html>"
private val NO_DESCRIPTION: String
get() = message("python.toolwindow.packages.no.description.placeholder")
}

View File

@@ -0,0 +1,81 @@
// 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.modules
import com.intellij.openapi.components.service
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.SideBorder
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBList
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.NamedColorUtil
import com.jetbrains.python.icons.PythonIcons
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import com.jetbrains.python.sdk.pythonSdk
import kotlinx.coroutines.launch
import java.awt.Dimension
import java.awt.FlowLayout
import javax.swing.*
import javax.swing.event.ListSelectionListener
class PyPackagesModuleController(val project: Project) {
val packagingScope = PyPackageCoroutine.getIoScope(project)
val service
get() = project.service<PyPackagingToolWindowService>()
val moduleList = JBList(ModuleManager.getInstance(project).modules.toList().sortedBy { it.name }).apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION
border = JBUI.Borders.empty()
val itemRenderer = JBLabel("", PythonIcons.Python.PythonClosed, JLabel.LEFT).apply {
border = JBUI.Borders.empty(0, 10)
}
cellRenderer = ListCellRenderer { _, value, _, _, _ -> itemRenderer.text = value.name; itemRenderer }
addListSelectionListener(ListSelectionListener { e ->
if (e.valueIsAdjusting) return@ListSelectionListener
val selectedModule = this@apply.selectedValue
val sdk = selectedModule.pythonSdk ?: return@ListSelectionListener
packagingScope.launch {
service.initForSdk(sdk)
}
})
}
private val modulePanel = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply {
border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.RIGHT)
maximumSize = Dimension(80, maximumSize.height)
minimumSize = Dimension(50, minimumSize.height)
}.also {
it.add(moduleList)
}
val component = ScrollPaneFactory.createScrollPane(modulePanel, true)
val fileListener = object : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (project.modules.size > 1) {
val newFile = event.newFile ?: return
val module = ModuleUtilCore.findModuleForFile(newFile, project)
packagingScope.launch {
val index = (moduleList.model as DefaultListModel<Module>).indexOf(module)
moduleList.selectionModel.setSelectionInterval(index, index)
}
}
}
}
init {
project.messageBus
.connect(service)
.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, fileListener)
}
}

View File

@@ -0,0 +1,104 @@
// 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.components.service
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.ui.components.DropDownLink
import com.intellij.ui.components.dialog
import com.intellij.ui.dsl.builder.*
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification
import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import org.jetbrains.annotations.Nls
object PyPackageCustomInstallComponents {
private val fromVcsText: String
get() = message("python.toolwindow.packages.add.package.from.vcs")
private val fromDiscText: String
get() = message("python.toolwindow.packages.add.package.from.disc")
fun createInstallFromLocationLink(project: Project): DropDownLink<@Nls String> {
return DropDownLink(message("python.toolwindow.packages.add.package.action"),
listOf(fromVcsText, fromDiscText)) {
val service = project.service<PyPackagingToolWindowService>()
val specification = when (it) {
fromDiscText -> showInstallFromDiscDialog(service)
fromVcsText -> showInstallFromVcsDialog(service)
else -> throw IllegalStateException("Unknown operation")
}
if (specification != null) {
PyPackageCoroutine.launch(project) {
service.installPackage(specification)
}
}
}
}
private fun showInstallFromVcsDialog(service: PyPackagingToolWindowService): PythonVcsPackageSpecification? {
var editable = false
var link = ""
val systems = listOf(message("python.toolwindow.packages.add.package.vcs.git"),
message("python.toolwindow.packages.add.package.vcs.svn"),
message("python.toolwindow.packages.add.package.vcs.hg"),
message("python.toolwindow.packages.add.package.vcs.bzr"))
var vcs = systems.first()
val panel = panel {
row {
comboBox(systems)
.bindItem({ vcs }, { vcs = it!! })
textField()
.columns(COLUMNS_MEDIUM)
.bindText({ link }, { link = it })
.align(AlignX.FILL)
}
row {
checkBox(message("python.toolwindow.packages.add.package.as.editable"))
.bindSelected({ editable }, { editable = it })
}
}
val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet()
if (shouldInstall) {
val prefix = when (vcs) {
message("python.toolwindow.packages.add.package.vcs.git") -> "git+"
message("python.toolwindow.packages.add.package.vcs.svn") -> "svn+"
message("python.toolwindow.packages.add.package.vcs.hg") -> "hg+"
message("python.toolwindow.packages.add.package.vcs.bzr") -> "bzr+"
else -> throw IllegalStateException("Unknown VCS")
}
return PythonVcsPackageSpecification(link, link, prefix, editable)
}
return null
}
private fun showInstallFromDiscDialog(service: PyPackagingToolWindowService): PythonLocalPackageSpecification? {
var editable = false
val textField = TextFieldWithBrowseButton().apply {
addBrowseFolderListener(message("python.toolwindow.packages.add.package.path.selector"), "", service.project,
FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor())
}
val panel = panel {
row(message("python.toolwindow.packages.add.package.path")) {
cell(textField)
.columns(COLUMNS_MEDIUM)
.align(AlignX.FILL)
}
row {
checkBox(message("python.toolwindow.packages.add.package.as.editable"))
.bindSelected({ editable }, { editable = it })
}
}
val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet()
return if (shouldInstall) PythonLocalPackageSpecification(textField.text, textField.text, editable) else null
}
}