PY-58889 New UI for Python Run configurations

Partial implementation of PY-44688

GitOrigin-RevId: 686c48a6f2649dfebcd079f414cce19559374032
This commit is contained in:
Elizaveta Shashkova
2023-02-21 16:51:46 +00:00
committed by intellij-monorepo-bot
parent 2a110d7272
commit 308f9110d7
16 changed files with 847 additions and 0 deletions
@@ -57,6 +57,9 @@
<applicationService serviceInterface="com.jetbrains.python.run.PyCommonOptionsFormFactory"
serviceImplementation="com.jetbrains.python.PyIdeCommonOptionsFormFactory"/>
<applicationService serviceInterface="com.jetbrains.python.run.PyCommonFragmentsBuilder"
serviceImplementation="com.jetbrains.python.PyIdeCommonFragmentsBuilder"/>
<!-- Console -->
<toolWindow id="Python Console" anchor="bottom" icon="PythonIcons.Python.PythonConsoleToolWindow" canCloseContents="true"
factoryClass="com.jetbrains.python.console.PythonConsoleToolWindowFactory" secondary="false"/>
@@ -0,0 +1,75 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python
import com.intellij.application.options.ModulesComboBox
import com.intellij.execution.ui.CommandLinePanel
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.execution.ui.SettingsEditorFragmentType
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.jetbrains.python.run.AbstractPythonRunConfiguration
import com.jetbrains.python.run.PyCommonFragmentsBuilder
import com.jetbrains.python.run.configuration.PyPathMappingsEditorFragment
import com.jetbrains.python.run.configuration.PySdkComboBox
import com.jetbrains.python.sdk.PySdkListCellRenderer
import java.awt.event.ItemEvent
class PyIdeCommonFragmentsBuilder : PyCommonFragmentsBuilder() {
override fun <T : AbstractPythonRunConfiguration<*>> createEnvironmentFragments(fragments: MutableList<SettingsEditorFragment<T, *>>,
config: T) {
val modules = ModuleManager.getInstance(config.project).modules
var modulesComboBox: ModulesComboBox? = null
if (modules.size > 1) {
modulesComboBox = ModulesComboBox()
val modulesFragment = SettingsEditorFragment(
"py.ide.project",
PyBundle.message("python.run.configuration.fragments.project"),
null,
modulesComboBox, SettingsEditorFragmentType.COMMAND_LINE,
{ s: T, modulesCombo: ModulesComboBox ->
val validModules: List<Module> = config.validModules
modulesCombo.setModules(validModules)
modulesCombo.selectedModule = config.module
},
{ s: T, modulesCombo: ModulesComboBox ->
modulesCombo.selectedModule?.let { selected ->
s.module = selected
}
},
{ true }
)
modulesFragment.isRemovable = false
modulesFragment.setHint(PyBundle.message("python.run.configuration.fragments.project.hint"))
fragments.add(modulesFragment)
}
val sdkComboBox = PySdkComboBox(true) { modulesComboBox?.selectedModule }
val minimumSize = CommandLinePanel.setMinimumWidth(sdkComboBox, 400)
sdkComboBox.preferredSize = minimumSize
sdkComboBox.renderer = PySdkListCellRenderer()
val interpreterFragment: SettingsEditorFragment<T, PySdkComboBox> = SettingsEditorFragment<T, PySdkComboBox>(
"py.ide.interpreter",
PyBundle.message("python.run.configuration.fragments.interpreter.field"),
null,
sdkComboBox, SettingsEditorFragmentType.COMMAND_LINE,
{ s: T, c: PySdkComboBox -> c.reset(s) },
{ s: T, c: PySdkComboBox -> c.apply(s) },
{ true })
interpreterFragment.isRemovable = false
interpreterFragment.setHint(PyBundle.message("python.run.configuration.fragments.interpreter.field"))
fragments.add(interpreterFragment)
modulesComboBox?.addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
(e.item as? Module)?.let {
sdkComboBox.setModule(it)
}
}
}
fragments.add(createWorkingDirectoryFragment(config.project))
fragments.add(createEnvParameters())
fragments.add(PyPathMappingsEditorFragment(sdkComboBox))
}
}
@@ -5,6 +5,8 @@
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceInterface="com.jetbrains.python.run.PyCommonOptionsFormFactory"
serviceImplementation="com.jetbrains.python.run.PyPluginCommonOptionsFormFactory"/>
<applicationService serviceInterface="com.jetbrains.python.run.PyCommonFragmentsBuilder"
serviceImplementation="com.jetbrains.python.run.PyPluginCommonFragmentsBuilder"/>
</extensions>
<actions>
@@ -0,0 +1,19 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run
import com.intellij.execution.ui.SettingsEditorFragment
import com.jetbrains.python.run.configuration.PyInterpreterModeNotifier
import com.jetbrains.python.run.configuration.PyPathMappingsEditorFragment
import javax.swing.JPanel
class PyPluginCommonFragmentsBuilder: PyCommonFragmentsBuilder() {
override fun <T : AbstractPythonRunConfiguration<*>> createEnvironmentFragments(fragments: MutableList<SettingsEditorFragment<T, *>>,
config: T) {
val sdkFragment: SettingsEditorFragment<T, JPanel> = PyPluginSdkFragment()
fragments.add(sdkFragment)
fragments.add(createWorkingDirectoryFragment(config.project))
fragments.add(createEnvParameters())
fragments.add(PyPathMappingsEditorFragment(sdkFragment as PyInterpreterModeNotifier))
}
}
@@ -0,0 +1,165 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run
import com.intellij.application.options.ModulesComboBox
import com.intellij.execution.ui.CommandLinePanel
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.execution.ui.SettingsEditorFragmentType
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.ui.ComboBox
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.configuration.PyInterpreterModeNotifier
import com.jetbrains.python.run.configuration.PySdkComboBox
import com.jetbrains.python.sdk.PythonSdkUtil
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.event.ItemEvent
import java.util.function.Consumer
import javax.swing.JComboBox
import javax.swing.JComponent
import javax.swing.JPanel
class PyPluginSdkFragment<T : AbstractPythonRunConfiguration<*>> : SettingsEditorFragment<T, JPanel>(
"py.plugin.interpreter", null, null, JPanel(GridBagLayout()), SettingsEditorFragmentType.COMMAND_LINE, null, null,
{ true }), PyInterpreterModeNotifier {
private val typeNames = listOf(PyBundle.message("python.run.configuration.fragments.plugin.sdk.of.module"),
PyBundle.message("python.run.configuration.fragments.plugin.specified.interpreter"))
private val SDK_OF_MODULE = 0
private val SDK_FROM_LIST = 1
private var currentMode: Int? = null
private var modulesCombo: ModulesComboBox? = null
private var interpreterCombo: PySdkComboBox? = null
val fields: MutableList<JComponent?> = MutableList(SDK_FROM_LIST + 1) { null }
private val hints: MutableMap<JComponent, String> = mutableMapOf()
private val sdkChooser: JComboBox<String>
private val interpreterModeListeners: MutableList<Consumer<Boolean>> = mutableListOf()
init {
sdkChooser = JComboBox()
sdkChooser.addItem(typeNames[SDK_OF_MODULE])
sdkChooser.addItem(typeNames[SDK_FROM_LIST])
CommandLinePanel.setMinimumWidth(sdkChooser, 200)
component().add(sdkChooser, GridBagConstraints())
hints[sdkChooser] = PyBundle.message("python.run.configuration.fragments.plugin.sdk.chooser.hint")
sdkChooser.addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
if ((e.item as? String) == typeNames[SDK_FROM_LIST]) {
showField(SDK_FROM_LIST)
currentMode = SDK_FROM_LIST
}
else {
showField(SDK_OF_MODULE)
currentMode = SDK_OF_MODULE
}
}
}
sdkChooser.addActionListener { updateRemoteInterpreterMode() }
}
override fun resetEditorFrom(config: T) {
if (currentMode == null) {
initComponents(config)
}
val type = if (config.isUseModuleSdk) SDK_OF_MODULE else SDK_FROM_LIST
if (currentMode != type) {
sdkChooser.selectedItem = typeNames[type]
(fields[SDK_OF_MODULE] as? ComboBox<*>)?.selectedItem = config.module
interpreterCombo?.reset(config)
showField(type)
}
updateRemoteInterpreterMode()
}
private fun showField(type: Int) {
for (i in 0 until fields.size) {
fields[i]?.isVisible = i == type
}
}
private fun initComponents(config: T) {
val modulesComponent = ModulesComboBox()
initComponent(modulesComponent, SDK_OF_MODULE)
val validModules: List<Module> = config.commonOptionsFormData.validModules
modulesComponent.setModules(validModules)
modulesComponent.selectedModule = config.module
fields[SDK_OF_MODULE] = modulesComponent
modulesCombo = modulesComponent
CommandLinePanel.setMinimumWidth(modulesCombo, 400)
hints[modulesComponent] = PyBundle.message("python.run.configuration.fragments.plugin.sdk.of.module.hint")
modulesCombo?.addActionListener { updateRemoteInterpreterMode() }
val interpreterComponent = PySdkComboBox(false) { modulesComponent.selectedModule }
initComponent(interpreterComponent, SDK_FROM_LIST)
interpreterComponent.reset(config)
fields[SDK_FROM_LIST] = interpreterComponent
interpreterCombo = interpreterComponent
CommandLinePanel.setMinimumWidth(interpreterCombo, 400)
hints[interpreterComponent] = PyBundle.message("python.run.configuration.fragments.plugin.specified.interpreter.hint")
interpreterCombo?.addActionListener { updateRemoteInterpreterMode() }
}
private fun initComponent(field: JComponent, index: Int) {
val constraints = GridBagConstraints()
constraints.fill = GridBagConstraints.BOTH
constraints.weightx = 1.0
component().add(field, constraints)
fields[index] = field
}
override fun applyEditorTo(s: T) {
if (currentMode == SDK_OF_MODULE) {
s.isUseModuleSdk = true
val module = (modulesCombo?.selectedItem) as? Module
s.module = module
module?.let {
val moduleSdk = ModuleRootManager.getInstance(it).sdk
s.setSdk(moduleSdk)
}
}
else if (currentMode == SDK_FROM_LIST) {
s.isUseModuleSdk = false
interpreterCombo?.apply(s)
}
}
override fun isRemovable(): Boolean = false
override fun getAllComponents(): Array<JComponent> {
return fields.filterNotNull().toTypedArray() + sdkChooser
}
override fun getHint(component: JComponent?): String? {
return if (component == null) null else hints[component]
}
override fun isRemoteSelected(): Boolean = PythonSdkUtil.isRemote(getSelectedSdk())
override fun addInterpreterModeListener(listener: Consumer<Boolean>) {
interpreterModeListeners.add(listener)
}
private fun updateRemoteInterpreterMode() {
val isRemote = isRemoteSelected()
for (listener in interpreterModeListeners) {
listener.accept(isRemote)
}
}
private fun getSelectedSdk(): Sdk? {
if (currentMode == SDK_OF_MODULE) {
((modulesCombo?.selectedItem) as? Module)?.let {
return ModuleRootManager.getInstance(it).sdk
}
}
else if (currentMode == SDK_FROM_LIST) {
return interpreterCombo?.getSelectedSdk()
}
return null
}
}
@@ -1030,6 +1030,31 @@ form.tox.configuration.environments.to.run=Environments to run:
form.tox.configuration.runcfg.tox=tox
py.module.dependencies.configurable.list.title=Project depends on these projects:
py.sdk.editor.python.interpreter.label.text=Python Interpreter:
python.run.configuration.fragments.chooser.hint=Run Python script or module
python.run.configuration.fragments.script.path.hint=Path to a Python script
python.run.configuration.fragments.module.name.hint=Name of a module
python.run.configuration.fragments.project=Project
python.run.configuration.fragments.project.hint=Select a project
python.run.configuration.fragments.interpreter.field=Python interpreter
python.run.configuration.fragments.interpreter.options=Interpreter options
python.run.configuration.fragments.interpreter.options.hint=Python interpreter options
python.run.configuration.fragments.script.parameters=Parameters
python.run.configuration.fragments.script.parameters.hint=Script parameters
python.run.configuration.fragments.run.with.python.console=Run with Python Console
python.run.configuration.fragments.run.with.python.console.hint=Execute run configuration in Python Console
python.run.configuration.fragments.emulate.terminal=Emulate terminal in output console
python.run.configuration.fragments.emulate.terminal.hint=Emulate terminal in output console
python.run.configuration.fragments.content.roots=Add content roots to PYTHONPATH
python.run.configuration.fragments.content.roots.hint=Add content roots to PYTHONPATH
python.run.configuration.fragments.source.roots=Add source roots to PYTHONPATH
python.run.configuration.fragments.source.roots.hint=Add source roots to PYTHONPATH
python.run.configuration.fragments.python.group=Python
python.run.configuration.fragments.plugin.sdk.chooser.hint=Python interpreter
python.run.configuration.fragments.plugin.sdk.of.module=Use SDK of module
python.run.configuration.fragments.plugin.sdk.of.module.hint=Select module
python.run.configuration.fragments.plugin.specified.interpreter=Use specified interpreter
python.run.configuration.fragments.plugin.specified.interpreter.hint=Select Python interpreter
# Python reStructuredText forms
rest.configuration.editor.open.output.file.in.browser.label.text=Open output file in browser
sphinx.ask.for.working.directory.label.text=Sphinx working directory
@@ -508,6 +508,8 @@
<applicationService
serviceInterface="com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory$Available"
serviceImplementation="com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory$Available$Default"/>
<registryKey key="python.new.run.config" defaultValue="true" description="Python Run configurations new UI"/>
</extensions>
<extensionPoints>
@@ -19,6 +19,7 @@ import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathMappingSettings;
@@ -119,6 +120,11 @@ public abstract class AbstractPythonRunConfiguration<T extends AbstractPythonRun
@NotNull
@Override
public final SettingsEditor<T> getConfigurationEditor() {
if (Registry.is("ide.new.run.config", false)) {
// TODO: actually, we should return result of `PythonExtendedConfigurationEditor.create()` call, but it produces side effects
// investigation needed PY-17716
return createConfigurationEditor();
}
final SettingsEditor<T> runConfigurationEditor = PythonExtendedConfigurationEditor.create(createConfigurationEditor());
final SettingsEditorGroup<T> group = new SettingsEditorGroup<>();
@@ -0,0 +1,80 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run
import com.intellij.execution.ExecutionBundle
import com.intellij.execution.configuration.EnvironmentVariablesComponent
import com.intellij.execution.ui.CommonParameterFragments
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.LabeledComponent
import com.intellij.openapi.ui.TextComponentAccessor
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.Predicates
import com.intellij.ui.components.fields.ExtendableTextField
import java.awt.BorderLayout
import javax.swing.JComponent
@Service
abstract class PyCommonFragmentsBuilder {
abstract fun <T : AbstractPythonRunConfiguration<*>> createEnvironmentFragments(fragments: MutableList<SettingsEditorFragment<T, *>>,
config: T)
fun <T : AbstractPythonRunConfiguration<*>> createWorkingDirectoryFragment(project: Project):
SettingsEditorFragment<T, LabeledComponent<TextFieldWithBrowseButton>> {
val textField = ExtendableTextField(10)
val workingDirectoryField = TextFieldWithBrowseButton(textField)
workingDirectoryField.addBrowseFolderListener(
ExecutionBundle.message("select.working.directory.message"),
null,
project,
FileChooserDescriptorFactory.createSingleFolderDescriptor(),
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT
)
val field = LabeledComponent.create(
workingDirectoryField,
ExecutionBundle.message("run.configuration.working.directory.label"),
BorderLayout.WEST
)
val workingDirectorySettings = SettingsEditorFragment(
"workingDirectory",
ExecutionBundle.message("run.configuration.working.directory.name"), null, field,
{ config: T, component: LabeledComponent<TextFieldWithBrowseButton> -> component.component.text = config.workingDirectory },
{ config: T, component: LabeledComponent<TextFieldWithBrowseButton> -> config.workingDirectory = component.component.text },
Predicates.alwaysTrue()
)
workingDirectorySettings.isRemovable = false
return workingDirectorySettings
}
fun <T : AbstractPythonRunConfiguration<*>> createEnvParameters(): SettingsEditorFragment<T, *> {
val env = EnvironmentVariablesComponent()
env.labelLocation = BorderLayout.WEST
CommonParameterFragments.setMonospaced(env.component.textField)
val fragment = SettingsEditorFragment<T, JComponent>(
"environmentVariables",
ExecutionBundle.message("environment.variables.fragment.name"),
ExecutionBundle.message("group.operating.system"), env,
{ config: T, c: JComponent? ->
env.envs = config.envs
env.isPassParentEnvs = config.isPassParentEnvs
},
{ config: T, _: JComponent? ->
if (!env.isVisible) {
config.envs = emptyMap()
config.isPassParentEnvs = true
}
else {
config.envs = env.envs
config.isPassParentEnvs = env.isPassParentEnvs
}
},
{ true })
fragment.isCanBeHidden = true
fragment.setHint(ExecutionBundle.message("environment.variables.fragment.hint"))
fragment.actionHint = ExecutionBundle.message("set.custom.environment.variables.for.the.process")
return fragment
}
}
@@ -11,6 +11,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
@@ -18,6 +19,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.refactoring.listeners.RefactoringElementAdapter;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.run.configuration.PythonConfigurationFragmentedEditor;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,6 +56,9 @@ public class PythonRunConfiguration extends AbstractPythonRunConfiguration
@Override
protected SettingsEditor<? extends RunConfiguration> createConfigurationEditor() {
if (Registry.is("python.new.run.config", false)) {
return new PythonConfigurationFragmentedEditor(this);
}
return new PythonRunConfigurationEditor(this);
}
@@ -0,0 +1,76 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import com.intellij.diagnostic.logging.LogsGroupFragment
import com.intellij.execution.ui.*
import com.intellij.openapi.components.service
import com.intellij.ui.RawCommandLineEditor
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.AbstractPythonRunConfiguration
import com.jetbrains.python.run.PyCommonFragmentsBuilder
import com.jetbrains.python.run.PythonRunConfigurationExtensionsManager
abstract class AbstractPythonConfigurationFragmentedEditor<T : AbstractPythonRunConfiguration<*>>(val runConfiguration: T) :
RunConfigurationFragmentedEditor<T>(runConfiguration, PythonRunConfigurationExtensionsManager.instance) {
override fun createRunFragments(): MutableList<SettingsEditorFragment<T, *>> {
val fragments: MutableList<SettingsEditorFragment<T, *>> = ArrayList()
val beforeRunComponent = BeforeRunComponent(this)
fragments.add(BeforeRunFragment.createBeforeRun(beforeRunComponent, null))
fragments.addAll(BeforeRunFragment.createGroup())
fragments.add(CommonParameterFragments.createRunHeader())
createEnvironmentFragments(fragments, runConfiguration)
addInterpreterOptions(fragments)
addContentSourceRoots(fragments)
customizeFragments(fragments)
fragments.add(LogsGroupFragment())
return fragments
}
private fun <T : AbstractPythonRunConfiguration<*>> createEnvironmentFragments(fragments: MutableList<SettingsEditorFragment<T, *>>,
runConfiguration: T) {
service<PyCommonFragmentsBuilder>().createEnvironmentFragments(fragments, runConfiguration)
}
abstract fun customizeFragments(fragments: MutableList<SettingsEditorFragment<T, *>>)
private fun <T : AbstractPythonRunConfiguration<*>> addInterpreterOptions(fragments: MutableList<SettingsEditorFragment<T, *>>) {
val interpreterOptionsField = RawCommandLineEditor()
val interpreterOptionsFragment: SettingsEditorFragment<T, RawCommandLineEditor> = SettingsEditorFragment<T, RawCommandLineEditor>(
"py.interpreter.options",
PyBundle.message("python.run.configuration.fragments.interpreter.options"),
PyBundle.message("python.run.configuration.fragments.python.group"),
interpreterOptionsField, SettingsEditorFragmentType.COMMAND_LINE,
{ config: T, field: RawCommandLineEditor -> field.text = config.interpreterOptions },
{ config: T, field: RawCommandLineEditor -> config.interpreterOptions = field.text.trim() },
{ config: T -> !config.interpreterOptions.trim().isEmpty() })
interpreterOptionsFragment.setHint(PyBundle.message("python.run.configuration.fragments.interpreter.options.hint"))
interpreterOptionsFragment.actionHint = PyBundle.message("python.run.configuration.fragments.interpreter.options.hint")
fragments.add(interpreterOptionsFragment)
}
private fun <T : AbstractPythonRunConfiguration<*>> addContentSourceRoots(fragments: MutableList<SettingsEditorFragment<T, *>>) {
val addContentRoots = SettingsEditorFragment.createTag<T>(
"py.add.content.roots",
PyBundle.message("python.run.configuration.fragments.content.roots"),
PyBundle.message("python.run.configuration.fragments.python.group"),
{ it.shouldAddContentRoots() },
{ config, value -> config.setAddContentRoots(value) })
addContentRoots.actionHint = PyBundle.message("python.run.configuration.fragments.content.roots.hint")
fragments.add(addContentRoots)
val addSourceRoots = SettingsEditorFragment.createTag<T>(
"py.add.content.roots",
PyBundle.message("python.run.configuration.fragments.source.roots"),
PyBundle.message("python.run.configuration.fragments.python.group"),
{ it.shouldAddContentRoots() },
{ config, value -> config.setAddContentRoots(value) })
addSourceRoots.actionHint = PyBundle.message("python.run.configuration.fragments.source.roots.hint")
fragments.add(addSourceRoots)
}
}
@@ -0,0 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import java.util.function.Consumer
interface PyInterpreterModeNotifier {
fun addInterpreterModeListener(listener: Consumer<Boolean>)
fun isRemoteSelected(): Boolean
}
@@ -0,0 +1,49 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import com.intellij.execution.ui.CommandLinePanel
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.execution.util.PathMappingsComponent
import com.jetbrains.python.run.AbstractPythonRunConfiguration
import java.awt.BorderLayout
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import javax.swing.JComponent
import javax.swing.JPanel
class PyPathMappingsEditorFragment<T : AbstractPythonRunConfiguration<*>>(val notifier: PyInterpreterModeNotifier) :
SettingsEditorFragment<T, JPanel>("py.path.mappings", null, null, JPanel(GridBagLayout()), null, null, { true }) {
private val pathMappingsComponent = PathMappingsComponent()
init {
pathMappingsComponent.labelLocation = BorderLayout.WEST
CommandLinePanel.setMinimumWidth(component(), 500)
val constrains = GridBagConstraints()
constrains.fill = GridBagConstraints.HORIZONTAL
constrains.weightx = 1.0
constrains.gridx = 0
component().add(pathMappingsComponent, constrains)
pathMappingsComponent.isVisible = notifier.isRemoteSelected()
notifier.addInterpreterModeListener { isRemote ->
pathMappingsComponent.isVisible = isRemote
}
}
override fun getAllComponents(): Array<JComponent> {
return arrayOf(pathMappingsComponent)
}
override fun resetEditorFrom(config: T) {
pathMappingsComponent.setMappingSettings(config.mappingSettings)
}
override fun applyEditorTo(s: T) {
s.mappingSettings = pathMappingsComponent.mappingSettings
}
override fun isRemovable(): Boolean = false
override fun isSelected(): Boolean = true
}
@@ -0,0 +1,137 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import com.intellij.execution.ui.CommandLinePanel
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.execution.ui.SettingsEditorFragmentType
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.PsiFileSystemItem
import com.intellij.ui.TextAccessor
import com.jetbrains.PySymbolFieldWithBrowseButton
import com.jetbrains.extensions.ModuleBasedContextAnchor
import com.jetbrains.extensions.ProjectSdkContextAnchor
import com.jetbrains.isPythonModule
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.PythonRunConfiguration
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.event.ItemEvent
import javax.swing.JComboBox
import javax.swing.JComponent
import javax.swing.JPanel
class PyScriptOrModuleFragment : SettingsEditorFragment<PythonRunConfiguration, JPanel>(
"py.script.field", null, null, JPanel(GridBagLayout()), SettingsEditorFragmentType.COMMAND_LINE, null, null, { true }) {
private val typeNames = listOf("script", "module")
private val SCRIPT_MODE = 0
private val MODULE_MODE = 1
private var currentMode: Int? = null
val fields: MutableList<JComponent?> = MutableList(MODULE_MODE + 1) { null }
val hints: MutableMap<JComponent, String> = mutableMapOf()
private val moduleModeChooser: JComboBox<String>
init {
CommandLinePanel.setMinimumWidth(component(), 500)
moduleModeChooser = JComboBox()
moduleModeChooser.addItem(typeNames[SCRIPT_MODE])
moduleModeChooser.addItem(typeNames[MODULE_MODE])
component().add(moduleModeChooser, GridBagConstraints())
hints[moduleModeChooser] = PyBundle.message("python.run.configuration.fragments.chooser.hint")
moduleModeChooser.addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
if ((e.item as? String) == typeNames[MODULE_MODE]) {
showField(MODULE_MODE)
currentMode = MODULE_MODE
}
else {
showField(SCRIPT_MODE)
currentMode = SCRIPT_MODE
}
}
}
}
override fun resetEditorFrom(config: PythonRunConfiguration) {
if (currentMode == null) {
initComponents(config)
}
val type = if (config.isModuleMode) MODULE_MODE else SCRIPT_MODE
if (currentMode != type) {
currentMode = type
moduleModeChooser.selectedItem = typeNames[type]
showField(type)
(fields[type] as TextAccessor).text = config.scriptName
}
}
private fun showField(type: Int) {
for (i in 0 until fields.size) {
fields[i]?.isVisible = i == type
}
}
private fun initComponents(config: PythonRunConfiguration) {
val scriptField = TextFieldWithBrowseButton()
scriptField.addBrowseFolderListener(TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileDescriptor(), config.project))
initComponent(scriptField, SCRIPT_MODE)
fields[SCRIPT_MODE] = scriptField
hints[scriptField] = PyBundle.message("python.run.configuration.fragments.script.path.hint")
val module = config.module
val contentAnchor = if (module == null) ProjectSdkContextAnchor(config.project, config.sdk) else ModuleBasedContextAnchor(module)
val workingDirectory = config.workingDirectory
val moduleField = PySymbolFieldWithBrowseButton(
contentAnchor,
{ element -> (element is PsiFileSystemItem && isPythonModule(element)) },
if (StringUtil.isEmpty(workingDirectory)) {
null
}
else {
{ LocalFileSystem.getInstance().findFileByPath(workingDirectory)!! }
})
initComponent(moduleField, MODULE_MODE)
fields[MODULE_MODE] = moduleField
hints[moduleField] = PyBundle.message("python.run.configuration.fragments.module.name.hint")
}
private fun initComponent(field: JComponent, index: Int) {
val constraints = GridBagConstraints()
constraints.fill = GridBagConstraints.BOTH
constraints.weightx = 1.0
component().add(field, constraints)
fields[index] = field
}
override fun applyEditorTo(s: PythonRunConfiguration) {
val mode = currentMode
if (mode == null) return
s.isModuleMode = mode == MODULE_MODE
val text = (fields[mode] as? TextAccessor)?.text?.trim()
if (mode == MODULE_MODE) {
s.scriptName = text
}
else {
s.scriptName = text?.let { FileUtil.toSystemIndependentName(it) }
}
}
override fun isRemovable(): Boolean = false
override fun getAllComponents(): Array<JComponent> {
return fields.filterNotNull().toTypedArray() + moduleModeChooser
}
override fun getHint(component: JComponent?): String? {
return if (component == null) null else hints[component]
}
}
@@ -0,0 +1,93 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.util.Computable
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.AbstractPythonRunConfigurationParams
import com.jetbrains.python.sdk.PySdkListCellRenderer
import com.jetbrains.python.sdk.PythonSdkUtil
import java.util.function.Consumer
class PySdkComboBox(private val addDefault: Boolean,
private val moduleProvider: Computable<out Module?>) : ComboBox<Sdk>(), PyInterpreterModeNotifier {
private val interpreterModeListeners: MutableList<Consumer<Boolean>> = mutableListOf()
fun reset(config: AbstractPythonRunConfigurationParams) {
initList()
selectedItem = config.sdk
setUseModuleSdk(config.isUseModuleSdk)
setModule(config.module)
addActionListener {
updateRemoteInterpreterMode()
}
updateRemoteInterpreterMode()
}
private fun initList() {
val pythonSdks: MutableList<Sdk?> = PythonSdkUtil.getAllSdks().toMutableList()
if (addDefault) {
pythonSdks.add(0, null)
}
for (item in pythonSdks) {
addItem(item)
}
}
fun apply(config: AbstractPythonRunConfigurationParams) {
config.sdk = selectedItem as? Sdk
config.isUseModuleSdk = isUseModuleSdk()
}
fun setModule(module: Module?) {
updateDefaultInterpreter(module)
updateRemoteInterpreterMode()
}
private fun updateDefaultInterpreter(module: Module?) {
val sdk = if (module == null) null else ModuleRootManager.getInstance(module).sdk
setRenderer(
if (sdk == null) PySdkListCellRenderer()
else PySdkListCellRenderer(PyBundle.message("python.sdk.rendering.project.default.0", sdk.name), sdk)
)
}
private fun setUseModuleSdk(useModuleSdk: Boolean) {
if (selectedItem != null && useModuleSdk) {
selectedItem = null
}
}
private fun isUseModuleSdk(): Boolean = addDefault && selectedItem == null
fun getSelectedSdk(): Sdk? {
val selectedSdk = selectedItem as? Sdk
if (selectedSdk != null) {
return selectedSdk
}
else {
if (isUseModuleSdk()) {
moduleProvider.get()?.let {
return@getSelectedSdk PythonSdkUtil.findPythonSdk(it)
}
}
}
return null
}
override fun isRemoteSelected(): Boolean = PythonSdkUtil.isRemote(getSelectedSdk())
private fun updateRemoteInterpreterMode() {
val isRemote = isRemoteSelected()
for (listener in interpreterModeListeners) {
listener.accept(isRemote)
}
}
override fun addInterpreterModeListener(listener: Consumer<Boolean>) {
interpreterModeListeners.add(listener)
}
}
@@ -0,0 +1,100 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.run.configuration
import com.intellij.execution.ExecutionBundle
import com.intellij.execution.ui.SettingsEditorFragment
import com.intellij.execution.ui.SettingsEditorFragmentType
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.ui.LabeledComponent
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.RawCommandLineEditor
import com.jetbrains.python.PyBundle
import com.jetbrains.python.run.PythonRunConfiguration
import java.awt.BorderLayout
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
class PythonConfigurationFragmentedEditor(runConfiguration: PythonRunConfiguration) :
AbstractPythonConfigurationFragmentedEditor<PythonRunConfiguration>(runConfiguration) {
override fun customizeFragments(fragments: MutableList<SettingsEditorFragment<PythonRunConfiguration, *>>) {
fragments.add(PyScriptOrModuleFragment())
val scriptParametersFragment: SettingsEditorFragment<PythonRunConfiguration, RawCommandLineEditor> = SettingsEditorFragment<PythonRunConfiguration, RawCommandLineEditor>(
"py.script.parameters",
PyBundle.message("python.run.configuration.fragments.script.parameters"),
PyBundle.message("python.run.configuration.fragments.python.group"),
RawCommandLineEditor(), SettingsEditorFragmentType.COMMAND_LINE,
{ config: PythonRunConfiguration, field: RawCommandLineEditor -> field.text = config.scriptParameters },
{ config: PythonRunConfiguration, field: RawCommandLineEditor -> config.scriptParameters = field.text.trim() },
{ config: PythonRunConfiguration -> !config.scriptParameters.trim().isEmpty() })
scriptParametersFragment.setHint(PyBundle.message("python.run.configuration.fragments.script.parameters.hint"))
scriptParametersFragment.actionHint = PyBundle.message("python.run.configuration.fragments.script.parameters.hint")
fragments.add(scriptParametersFragment)
val runWithConsole = SettingsEditorFragment.createTag<PythonRunConfiguration>(
"py.run.with.python.console",
PyBundle.message("python.run.configuration.fragments.run.with.python.console"),
PyBundle.message("python.run.configuration.fragments.python.group"),
{ it.showCommandLineAfterwards() },
{ config, value -> config.setShowCommandLineAfterwards(value) })
runWithConsole.actionHint = PyBundle.message("python.run.configuration.fragments.run.with.python.console.hint")
fragments.add(runWithConsole)
val emulateTerminal = SettingsEditorFragment.createTag<PythonRunConfiguration>(
"py.emulate.terminal",
PyBundle.message("python.run.configuration.fragments.emulate.terminal"),
PyBundle.message("python.run.configuration.fragments.python.group"),
{ it.emulateTerminal() },
{ config, value -> config.setEmulateTerminal(value) })
emulateTerminal.actionHint = PyBundle.message("python.run.configuration.fragments.emulate.terminal.hint")
fragments.add(emulateTerminal)
val inputFile = TextFieldWithBrowseButton()
inputFile.addBrowseFolderListener(TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileDescriptor(),
runConfiguration.project))
val labeledComponent = LabeledComponent.create<TextFieldWithBrowseButton>(inputFile, ExecutionBundle.message("redirect.input.from"))
labeledComponent.labelLocation = BorderLayout.WEST
val redirectInputFrom: SettingsEditorFragment<PythonRunConfiguration, LabeledComponent<TextFieldWithBrowseButton>> =
SettingsEditorFragment<PythonRunConfiguration, LabeledComponent<TextFieldWithBrowseButton>>(
"py.redirect.input",
ExecutionBundle.message("redirect.input.from.name"),
ExecutionBundle.message("group.operating.system"),
labeledComponent, SettingsEditorFragmentType.COMMAND_LINE,
{ config: PythonRunConfiguration, component: LabeledComponent<TextFieldWithBrowseButton> ->
component.component.text = config.inputFile
},
{ config: PythonRunConfiguration, component: LabeledComponent<TextFieldWithBrowseButton> ->
val filePath = component.component.text
config.isRedirectInput = component.isVisible && StringUtil.isNotEmpty(filePath)
config.inputFile = filePath
},
{ config: PythonRunConfiguration -> config.isRedirectInput })
redirectInputFrom.actionHint = ExecutionBundle.message("read.input.from.the.specified.file")
redirectInputFrom.setHint(ExecutionBundle.message("read.input.from.the.specified.file"))
fragments.add(redirectInputFrom)
val editors = mutableListOf<SettingsEditorFragment<PythonRunConfiguration, *>>()
editors.add(runWithConsole)
editors.add(emulateTerminal)
editors.add(redirectInputFrom)
addSingleSelectionListeners(editors)
}
private fun addSingleSelectionListeners(editors: MutableList<SettingsEditorFragment<PythonRunConfiguration, *>>) {
for ((i, editor) in editors.withIndex()) {
editor.component().addComponentListener(object : ComponentAdapter() {
override fun componentShown(e: ComponentEvent?) {
for ((j, otherEditor) in editors.withIndex()) {
if (i != j) {
otherEditor.isSelected = false
}
}
}
})
}
}
}