mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
PY-75910: Update project name automatically in NPW even if field is hidden.
`getComponent` accepts `projectPathField`. We use `ProjectPathProvider` to update project name from this field. V2 panels are also affected: they now share logic (`ProjectPathFlows`) with project name component. See `com.jetbrains.python.newProjectWizard.projectPath` GitOrigin-RevId: f7b306fbbd9777925274513effd56009a0614f9a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
54c75955e7
commit
75801d4c3e
@@ -8,8 +8,8 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.wm.impl.welcomeScreen.collapsedActionGroup.CollapsedActionGroup
|
||||
import com.intellij.platform.DirectoryProjectGenerator
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.pycharm.community.ide.impl.newProjectWizard.impl.emptyProject.PyV3EmptyProjectGenerator
|
||||
import com.intellij.pycharm.community.ide.impl.newProject.steps.PythonGenerateProjectCallback
|
||||
import com.intellij.pycharm.community.ide.impl.newProjectWizard.impl.emptyProject.PyV3EmptyProjectGenerator
|
||||
import com.jetbrains.python.newProject.PyNewProjectSettings
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator
|
||||
import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep
|
||||
|
||||
@@ -24,12 +24,12 @@ import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.bindSelected
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.PyNewProjectSettings
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.newProjectWizard.promotion.PromoProjectGenerator
|
||||
import com.jetbrains.python.sdk.PyLazySdk
|
||||
import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
|
||||
@@ -52,7 +52,7 @@ class PythonProjectSpecificSettingsStep<T : PyNewProjectSettings>(
|
||||
private val propertyGraph = PropertyGraph()
|
||||
private val projectName = propertyGraph.property("")
|
||||
private val projectLocation = propertyGraph.property("")
|
||||
private val projectLocationFlow = MutableStateFlow(Path.of(SystemProperties.getUserHome()))
|
||||
private val projectLocationFlowStr = MutableStateFlow(projectLocation.get())
|
||||
private val locationHint = propertyGraph.property("").apply {
|
||||
dependsOn(projectName, ::updateHint)
|
||||
dependsOn(projectLocation, ::updateHint)
|
||||
@@ -64,7 +64,7 @@ class PythonProjectSpecificSettingsStep<T : PyNewProjectSettings>(
|
||||
|
||||
init {
|
||||
projectLocation.afterChange {
|
||||
projectLocationFlow.value = Path.of(projectLocation.get())
|
||||
projectLocationFlowStr.value = projectLocation.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ class PythonProjectSpecificSettingsStep<T : PyNewProjectSettings>(
|
||||
|
||||
// Instead of setting this type as default, we limit types to it
|
||||
val onlyAllowedInterpreterTypes = projectGenerator.preferredEnvironmentType?.let { setOf(it) }
|
||||
val interpreterPanel = PythonAddNewEnvironmentPanel(projectLocationFlow, onlyAllowedInterpreterTypes, errorSink = ShowingMessageErrorSync).also { interpreterPanel = it }
|
||||
val interpreterPanel = PythonAddNewEnvironmentPanel(ProjectPathFlows.create(projectLocationFlowStr), onlyAllowedInterpreterTypes, errorSink = ShowingMessageErrorSync).also { interpreterPanel = it }
|
||||
|
||||
mainPanel = panel {
|
||||
row(message("new.project.name")) {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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.newProjectWizard
|
||||
|
||||
import com.intellij.openapi.ui.validation.CHECK_NON_EMPTY
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_RESERVED_WORDS
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_WHITESPACES
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.Result
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
* Returns either error or [Path] for project path
|
||||
*/
|
||||
fun validateProjectPathAndGetPath(baseDirPath: String): Result<Path, @NlsSafe String> {
|
||||
val path = try {
|
||||
Paths.get(baseDirPath)
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
return Result.Failure(e.reason)
|
||||
}
|
||||
|
||||
if (!path.isAbsolute) {
|
||||
return Result.Failure(PyBundle.message("python.sdk.new.error.no.absolute"))
|
||||
}
|
||||
|
||||
for (validator in arrayOf(CHECK_NON_EMPTY, CHECK_NO_WHITESPACES, CHECK_NO_RESERVED_WORDS)) {
|
||||
validator.curry { baseDirPath }.validate()?.let {
|
||||
return Result.Failure(it.message)
|
||||
}
|
||||
}
|
||||
return Result.Success(path)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// 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.newProjectWizard
|
||||
|
||||
import com.intellij.openapi.ui.validation.CHECK_NON_EMPTY
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_WHITESPACES
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.builder.trimmedTextValidation
|
||||
import com.jetbrains.python.ui.flow.bindText
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.nio.file.Path
|
||||
import javax.swing.JTextField
|
||||
import kotlin.io.path.name
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
/**
|
||||
* "New project path" field value.
|
||||
* This flow is used by [PyV3ProjectTypeSpecificUI.advancedSettings] to [bindProjectName]
|
||||
*/
|
||||
typealias ProjectPathFlow = StateFlow<Path>
|
||||
|
||||
|
||||
/**
|
||||
* Binds [PyV3ProjectTypeSpecificSettings] property to the certain cell and updates it each time project path is changes
|
||||
*/
|
||||
fun Cell<JTextField>.bindProjectName(projectPath: ProjectPathFlow, property: KMutableProperty0<@NlsSafe String>) = apply {
|
||||
property.set(getProjectNameFromPath(projectPath.value)) // set the initial value before we get any new value
|
||||
trimmedTextValidation(CHECK_NON_EMPTY, CHECK_NO_WHITESPACES)
|
||||
bindText(property)
|
||||
bindText(projectPath.map { getProjectNameFromPath(it) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects like Django are named after the last part of the path
|
||||
*/
|
||||
private fun getProjectNameFromPath(path: Path): @NlsSafe String = path.name
|
||||
@@ -11,15 +11,14 @@ import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.DirectoryProjectGenerator
|
||||
import com.intellij.platform.ProjectGeneratorPeer
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.newProjectWizard.impl.PyV3GeneratorPeer
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows.Companion.validatePath
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
|
||||
import com.jetbrains.python.util.ErrorSink
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.Nls
|
||||
@@ -40,7 +39,6 @@ abstract class PyV3ProjectBaseGenerator<TYPE_SPECIFIC_SETTINGS : PyV3ProjectType
|
||||
private val _newProjectName: @NlsSafe String? = null,
|
||||
) : DirectoryProjectGenerator<PyV3BaseProjectSettings> {
|
||||
private val baseSettings = PyV3BaseProjectSettings()
|
||||
private val projectPathFlow = MutableStateFlow(Path.of(SystemProperties.getUserHome()))
|
||||
val newProjectName: @NlsSafe String get() = _newProjectName ?: "${name.replace(" ", "")}Project"
|
||||
|
||||
|
||||
@@ -58,12 +56,11 @@ abstract class PyV3ProjectBaseGenerator<TYPE_SPECIFIC_SETTINGS : PyV3ProjectType
|
||||
}
|
||||
|
||||
override fun createPeer(): ProjectGeneratorPeer<PyV3BaseProjectSettings> =
|
||||
PyV3GeneratorPeer(baseSettings, projectPathFlow, typeSpecificUI?.let { Pair(it, typeSpecificSettings) }, allowedInterpreterTypes)
|
||||
PyV3GeneratorPeer(baseSettings, typeSpecificUI?.let { Pair(it, typeSpecificSettings) }, allowedInterpreterTypes)
|
||||
|
||||
override fun validate(baseDirPath: String): ValidationResult =
|
||||
when (val pathOrError = validateProjectPathAndGetPath(baseDirPath)) {
|
||||
when (val pathOrError = validatePath(baseDirPath)) {
|
||||
is Result.Success<Path, *> -> {
|
||||
projectPathFlow.value = pathOrError.result
|
||||
ValidationResult.OK
|
||||
}
|
||||
is Result.Failure<*, @Nls String> -> ValidationResult(pathOrError.error)
|
||||
@@ -72,4 +69,4 @@ abstract class PyV3ProjectBaseGenerator<TYPE_SPECIFIC_SETTINGS : PyV3ProjectType
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
private class MyService(val coroutineScope: CoroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.jetbrains.python.newProjectWizard
|
||||
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.Row
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathProvider
|
||||
|
||||
/**
|
||||
* Binds [PROJECT_SPECIFIC_SETTINGS] to Kotlin DSL UI.
|
||||
@@ -18,8 +19,8 @@ interface PyV3ProjectTypeSpecificUI<PROJECT_SPECIFIC_SETTINGS : PyV3ProjectTypeS
|
||||
|
||||
/**
|
||||
* If you need to show something in "advanced settings".
|
||||
* You also have a flow with project path,
|
||||
* you might bind it to the cell using [bindProjectName] if you need project name.
|
||||
* You also have an api with project name.
|
||||
* you might bind it to the cell using [com.jetbrains.python.newProjectWizard.projectPath.ProjectPathProvider.Companion.bindProjectName] if you need project name.
|
||||
*/
|
||||
val advancedSettings: (Panel.(settings: PROJECT_SPECIFIC_SETTINGS, projectPath: ProjectPathFlow) -> Unit)? get() = null
|
||||
val advancedSettings: (Panel.(PROJECT_SPECIFIC_SETTINGS, ProjectPathProvider) -> Unit)? get() = null
|
||||
}
|
||||
@@ -5,24 +5,26 @@ import com.intellij.ide.util.projectWizard.SettingsStep
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.platform.ProjectGeneratorPeer
|
||||
import com.jetbrains.python.newProjectWizard.ProjectPathFlow
|
||||
import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
|
||||
import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings
|
||||
import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI
|
||||
import com.jetbrains.python.newProjectWizard.impl.projectPath.ProjectPathImpl
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal class PyV3GeneratorPeer<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings>(
|
||||
baseSettings: PyV3BaseProjectSettings,
|
||||
projectPath: ProjectPathFlow,
|
||||
specificUiAndSettings: Pair<PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>, TYPE_SPECIFIC_SETTINGS>?,
|
||||
allowedInterpreterTypes:Set<PythonInterpreterSelectionMode>?
|
||||
private val specificUiAndSettings: Pair<PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>, TYPE_SPECIFIC_SETTINGS>?,
|
||||
private val allowedInterpreterTypes: Set<PythonInterpreterSelectionMode>?,
|
||||
) : ProjectGeneratorPeer<PyV3BaseProjectSettings> {
|
||||
private val settings = baseSettings
|
||||
private val panel: PyV3VUI<*> = PyV3VUI(settings, projectPath, specificUiAndSettings, allowedInterpreterTypes)
|
||||
private lateinit var panel: PyV3VUI<*>
|
||||
|
||||
|
||||
override fun getComponent(myLocationField: TextFieldWithBrowseButton, checkValid: Runnable): JComponent = panel.mainPanel
|
||||
override fun getComponent(projectPathField: TextFieldWithBrowseButton, checkValid: Runnable): JComponent {
|
||||
panel = PyV3VUI(settings, ProjectPathImpl(projectPathField), specificUiAndSettings, allowedInterpreterTypes)
|
||||
return panel.mainPanel
|
||||
}
|
||||
|
||||
override fun buildUI(settingsStep: SettingsStep) = Unit
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.intellij.ui.dsl.builder.bindSelected
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.newProjectWizard.ProjectPathFlow
|
||||
import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
|
||||
import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings
|
||||
import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI
|
||||
import com.jetbrains.python.newProjectWizard.impl.projectPath.ProjectPathImpl
|
||||
import com.jetbrains.python.sdk.add.v2.PySdkCreator
|
||||
import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
|
||||
@@ -18,11 +18,12 @@ import javax.swing.JComponent
|
||||
|
||||
internal class PyV3VUI<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings> @RequiresEdt constructor(
|
||||
baseSettings: PyV3BaseProjectSettings,
|
||||
projectPath: ProjectPathFlow,
|
||||
projectNameProvider: ProjectPathImpl,
|
||||
specificUiAndSettings: Pair<PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>, TYPE_SPECIFIC_SETTINGS>?,
|
||||
allowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = null,
|
||||
) {
|
||||
private val sdkPanel = PythonAddNewEnvironmentPanel(projectPath, allowedInterpreterTypes, ShowingMessageErrorSync)
|
||||
|
||||
private val sdkPanel = PythonAddNewEnvironmentPanel(projectNameProvider.projectPathFlows, allowedInterpreterTypes, ShowingMessageErrorSync)
|
||||
|
||||
private val _mainPanel = panel {
|
||||
val checkBoxRow = row {
|
||||
@@ -32,7 +33,7 @@ internal class PyV3VUI<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings>
|
||||
sdkPanel.buildPanel(this)
|
||||
specificUiAndSettings?.first?.advancedSettings?.let {
|
||||
collapsibleGroup(PyBundle.message("black.advanced.settings.panel.title")) {
|
||||
it(this, specificUiAndSettings.second, projectPath)
|
||||
it(this, specificUiAndSettings.second, projectNameProvider)
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.newProjectWizard.impl.projectPath
|
||||
|
||||
import com.intellij.ui.TextAccessor
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.DocumentListener
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Emits changes in [component] to the [flow] with [debounceDelay]
|
||||
*/
|
||||
internal class DocumentListenerToFlowAdapter(private val component: TextAccessor, debounceDelay: Duration = 10.milliseconds) : DocumentListener {
|
||||
private val _flow = MutableStateFlow<String>(component.text)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
val flow: Flow<String> = _flow.debounce(debounceDelay)
|
||||
override fun insertUpdate(e: DocumentEvent?) {
|
||||
update()
|
||||
}
|
||||
|
||||
override fun removeUpdate(e: DocumentEvent?) {
|
||||
update()
|
||||
}
|
||||
|
||||
override fun changedUpdate(e: DocumentEvent) = Unit
|
||||
|
||||
private fun update() {
|
||||
_flow.value = component.text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.newProjectWizard.impl.projectPath
|
||||
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.showingScope
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
|
||||
/**
|
||||
* Wraps [field] that represents project path, and emits [projectPathFlows] out of it
|
||||
*
|
||||
* [onProjectFileNameChanged] allows caller to receive every [ProjectPathFlows.projectName] event as long as [field] is visible
|
||||
* [fieldShowingScope] shouldn't be changes (except for test purposes)
|
||||
*/
|
||||
class ProjectPathImpl(
|
||||
private val field: TextFieldWithBrowseButton,
|
||||
private val fieldShowingScope: FieldShowingScopeRunner = object : FieldShowingScopeRunner {
|
||||
override fun onShowingScope(code: suspend CoroutineScope.() -> Unit) {
|
||||
field.showingScope("On Project Changed") {
|
||||
code()
|
||||
}
|
||||
}
|
||||
},
|
||||
) : ProjectPathProvider {
|
||||
private val listener = DocumentListenerToFlowAdapter(field)
|
||||
override val projectPathFlows: ProjectPathFlows = ProjectPathFlows.create(listener.flow)
|
||||
|
||||
init {
|
||||
field.addDocumentListener(listener)
|
||||
Disposer.register(field) {
|
||||
field.textField.document.removeDocumentListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
override fun onProjectFileNameChanged(code: suspend (projectPathName: @NlsSafe String) -> Unit) {
|
||||
fieldShowingScope.onShowingScope {
|
||||
projectPathFlows.projectName.collect(code)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun interface FieldShowingScopeRunner {
|
||||
fun onShowingScope(code: (suspend CoroutineScope.() -> Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.newProjectWizard.projectPath
|
||||
|
||||
import com.intellij.openapi.ui.validation.CHECK_NON_EMPTY
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_RESERVED_WORDS
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_WHITESPACES
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows.Companion.create
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.name
|
||||
|
||||
/**
|
||||
* A pack of flows that represents project path changes.
|
||||
* Create with [create] that accepts input flow with project path text and constructs other flows out of it.
|
||||
*/
|
||||
class ProjectPathFlows private constructor(val projectPath: Flow<Path?>) {
|
||||
|
||||
/**
|
||||
* Flow that always emits value. If value is wrong it emits default
|
||||
*/
|
||||
val projectPathWithDefault: Flow<Path> = projectPath.map { it ?: defaultPath }
|
||||
|
||||
/**
|
||||
* Flow emits project file name only when project path is valid
|
||||
*/
|
||||
val projectName: Flow<@NlsSafe String> = projectPath.filterNotNull().map { it.name }
|
||||
|
||||
|
||||
companion object {
|
||||
private val defaultPath = Path(SystemProperties.getUserHome())
|
||||
|
||||
/**
|
||||
* Use [fixedPath] as input
|
||||
*/
|
||||
fun create(fixedPath: Path): ProjectPathFlows = ProjectPathFlows(MutableStateFlow(fixedPath))
|
||||
|
||||
/**
|
||||
* Use [projectPathString] as input i.e `c:\foo`
|
||||
*/
|
||||
fun create(projectPathString: Flow<String>): ProjectPathFlows = ProjectPathFlows(projectPathString.map {
|
||||
withContext(Dispatchers.Default) {
|
||||
validatePath(it).successOrNull
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* checks that [pathAsString] is a valid path and returns it or error
|
||||
*/
|
||||
fun validatePath(pathAsString: String): Result<Path, @NlsSafe String> {
|
||||
val path = try {
|
||||
Paths.get(pathAsString)
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
return Result.Failure(e.reason)
|
||||
}
|
||||
|
||||
if (!path.isAbsolute) {
|
||||
return Result.Failure(PyBundle.message("python.sdk.new.error.no.absolute"))
|
||||
}
|
||||
|
||||
for (validator in arrayOf(CHECK_NON_EMPTY, CHECK_NO_WHITESPACES, CHECK_NO_RESERVED_WORDS)) {
|
||||
validator.curry { pathAsString }.validate()?.let {
|
||||
return Result.Failure(it.message)
|
||||
}
|
||||
}
|
||||
return Result.Success(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.newProjectWizard.projectPath
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.ui.validation.CHECK_NON_EMPTY
|
||||
import com.intellij.openapi.ui.validation.CHECK_NO_WHITESPACES
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.builder.text
|
||||
import com.intellij.ui.dsl.builder.trimmedTextValidation
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathProvider.Companion.bindProjectName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.swing.JTextField
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
/**
|
||||
* API to access project path field.
|
||||
* To be used with [bindProjectName]
|
||||
*/
|
||||
interface ProjectPathProvider {
|
||||
/**
|
||||
* Calls [code] as long as project path field is visible on each project path change.
|
||||
* this code accepts project path name
|
||||
*/
|
||||
@RequiresEdt
|
||||
fun onProjectFileNameChanged(code: suspend (projectFileName: @NlsSafe String) -> Unit)
|
||||
|
||||
/**
|
||||
* [ProjectPathFlows] made out of project path
|
||||
*/
|
||||
val projectPathFlows: ProjectPathFlows
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Binds [com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings] property to the certain cell and updates it each time project path is changes
|
||||
*/
|
||||
@RequiresEdt
|
||||
fun Cell<JTextField>.bindProjectName(projectPathProvider: ProjectPathProvider, property: KMutableProperty0<@NlsSafe String>) = apply {
|
||||
trimmedTextValidation(CHECK_NON_EMPTY, CHECK_NO_WHITESPACES)
|
||||
bindText(property)
|
||||
projectPathProvider.onProjectFileNameChanged {
|
||||
withContext(Dispatchers.EDT) {
|
||||
text(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
@@ -20,15 +19,9 @@ import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import com.jetbrains.python.ui.flow.bindText
|
||||
import com.jetbrains.python.util.ErrorSink
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
|
||||
class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val projectPath: Flow<Path>?, private val errorSink: ErrorSink) : PythonNewEnvironmentCreator(model) {
|
||||
// TODO: DOC
|
||||
class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val errorSink: ErrorSink) : PythonNewEnvironmentCreator(model) {
|
||||
|
||||
private lateinit var pythonVersion: ObservableMutableProperty<LanguageLevel>
|
||||
private lateinit var versionComboBox: ComboBox<LanguageLevel>
|
||||
@@ -44,9 +37,8 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel,
|
||||
row(message("sdk.create.custom.conda.env.name")) {
|
||||
val envName = textField()
|
||||
.bindText(model.state.newCondaEnvName)
|
||||
if (projectPath != null) {
|
||||
envName.bindText(projectPath.map { it.name })
|
||||
}
|
||||
// TODO: DOC
|
||||
envName.bindText(model.myProjectPathFlows.projectName)
|
||||
}
|
||||
|
||||
executableSelector(model.state.condaExecutable,
|
||||
@@ -58,11 +50,7 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel,
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
model.scope.launch(Dispatchers.EDT) {
|
||||
model.state.newCondaEnvName.set(model.projectPath.first().name)
|
||||
}
|
||||
}
|
||||
override fun onShown() = Unit
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result<Sdk> {
|
||||
return model.createCondaEnvironment(NewCondaEnvRequest.EmptyNamedEnv(pythonVersion.get(), model.state.newCondaEnvName.get()))
|
||||
|
||||
@@ -64,7 +64,7 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
|
||||
val newSdk = setupEnvSdk(moduleOrProject.project,
|
||||
module,
|
||||
ProjectJdkTable.getInstance().allJdks.asList(),
|
||||
model.projectPath.first().toString(),
|
||||
model.myProjectPathFlows.projectPathWithDefault.first().toString(),
|
||||
homePath,
|
||||
false)!!
|
||||
addSdk(newSdk)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
@@ -15,5 +13,5 @@ import kotlin.coroutines.CoroutineContext
|
||||
data class PyInterpreterModelParams(
|
||||
val scope: CoroutineScope,
|
||||
val uiContext: CoroutineContext,
|
||||
val projectPathProperty: Flow<Path>? = null,
|
||||
val projectPathFlows: ProjectPathFlows,
|
||||
)
|
||||
@@ -17,7 +17,7 @@ import com.jetbrains.python.util.ErrorSink
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.nio.file.Path
|
||||
|
||||
class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterModel, val moduleOrProject: ModuleOrProject? = null, projectPath: Flow<Path>? = null, errorSink: ErrorSink) {
|
||||
class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterModel, val moduleOrProject: ModuleOrProject? = null, projectPathFlow: Flow<Path>? = null, errorSink: ErrorSink) {
|
||||
|
||||
private val propertyGraph = model.propertyGraph
|
||||
private val selectionMethod = propertyGraph.property(PythonInterpreterSelectionMethod.CREATE_NEW)
|
||||
@@ -28,7 +28,7 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
|
||||
|
||||
private val newInterpreterCreators = mapOf(
|
||||
VIRTUALENV to PythonNewVirtualenvCreator(model),
|
||||
CONDA to CondaNewEnvironmentCreator(model, projectPath, errorSink),
|
||||
CONDA to CondaNewEnvironmentCreator(model, errorSink),
|
||||
PIPENV to PipEnvNewEnvironmentCreator(model),
|
||||
POETRY to PoetryNewEnvironmentCreator(model, moduleOrProject),
|
||||
)
|
||||
|
||||
@@ -12,9 +12,9 @@ import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.swing.JComponent
|
||||
|
||||
@@ -51,7 +51,7 @@ class PythonAddLocalInterpreterDialog(private val dialogPresenter: PythonAddLoca
|
||||
model = PythonLocalAddInterpreterModel(PyInterpreterModelParams(service<PythonAddSdkService>().coroutineScope,
|
||||
// At this moment dialog is not displayed, so there is no modality state
|
||||
// The whole idea of context passing is doubtful
|
||||
Dispatchers.EDT + ModalityState.any().asContextElement(), MutableStateFlow(basePath)))
|
||||
Dispatchers.EDT + ModalityState.any().asContextElement(), ProjectPathFlows.create(basePath)))
|
||||
model.navigator.selectionMode = AtomicProperty(PythonInterpreterSelectionMode.CUSTOM)
|
||||
mainPanel = PythonAddCustomInterpreter(model, errorSink = errorSink)
|
||||
mainPanel.buildPanel(this, WHEN_PROPERTY_CHANGED(AtomicProperty(basePath)))
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.util.ui.showingScope
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.VirtualEnvReader
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode.*
|
||||
@@ -32,19 +33,17 @@ import com.jetbrains.python.statistics.InterpreterType
|
||||
import com.jetbrains.python.util.ErrorSink
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
/**
|
||||
* If `onlyAllowedInterpreterTypes` then only these types are displayed. All types displayed otherwise
|
||||
*/
|
||||
class PythonAddNewEnvironmentPanel(val projectPath: Flow<Path>, onlyAllowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = null, private val errorSink: ErrorSink) : PySdkCreator {
|
||||
class PythonAddNewEnvironmentPanel(val projectPathFlows: ProjectPathFlows, onlyAllowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = null, private val errorSink: ErrorSink) : PySdkCreator {
|
||||
private val propertyGraph = PropertyGraph()
|
||||
private val allowedInterpreterTypes = (onlyAllowedInterpreterTypes ?: PythonInterpreterSelectionMode.entries).also {
|
||||
assert(it.isNotEmpty()) {
|
||||
@@ -65,7 +64,7 @@ class PythonAddNewEnvironmentPanel(val projectPath: Flow<Path>, onlyAllowedInter
|
||||
|
||||
private suspend fun updateVenvLocationHint(): Unit = withContext(Dispatchers.EDT) {
|
||||
val get = selectedMode.get()
|
||||
if (get == PROJECT_VENV) venvHint.set(message("sdk.create.simple.venv.hint", projectPath.first().resolve(VirtualEnvReader.DEFAULT_VIRTUALENV_DIRNAME).toString()))
|
||||
if (get == PROJECT_VENV) venvHint.set(message("sdk.create.simple.venv.hint", projectPathFlows.projectPathWithDefault.first().resolve(VirtualEnvReader.DEFAULT_VIRTUALENV_DIRNAME).toString()))
|
||||
else if (get == BASE_CONDA && PROJECT_VENV in allowedInterpreterTypes) venvHint.set(message("sdk.create.simple.conda.hint"))
|
||||
}
|
||||
|
||||
@@ -75,11 +74,11 @@ class PythonAddNewEnvironmentPanel(val projectPath: Flow<Path>, onlyAllowedInter
|
||||
fun buildPanel(outerPanel: Panel) {
|
||||
//presenter = PythonAddInterpreterPresenter(state, uiContext = Dispatchers.EDT + ModalityState.current().asContextElement())
|
||||
model = PythonLocalAddInterpreterModel(PyInterpreterModelParams(service<PythonAddSdkService>().coroutineScope,
|
||||
Dispatchers.EDT + ModalityState.current().asContextElement(), projectPath))
|
||||
Dispatchers.EDT + ModalityState.current().asContextElement(), projectPathFlows))
|
||||
model.navigator.selectionMode = selectedMode
|
||||
//presenter.controller = model
|
||||
|
||||
custom = PythonAddCustomInterpreter(model, projectPath = projectPath, errorSink = ShowingMessageErrorSync)
|
||||
custom = PythonAddCustomInterpreter(model, errorSink = ShowingMessageErrorSync)
|
||||
|
||||
val validationRequestor = WHEN_PROPERTY_CHANGED(selectedMode)
|
||||
|
||||
@@ -114,7 +113,7 @@ class PythonAddNewEnvironmentPanel(val projectPath: Flow<Path>, onlyAllowedInter
|
||||
row("") {
|
||||
comment("").bindText(venvHint).apply {
|
||||
component.showingScope("Update hint") {
|
||||
projectPath.collect {
|
||||
projectPathFlows.projectPathWithDefault.collect {
|
||||
updateVenvLocationHint()
|
||||
}
|
||||
}
|
||||
@@ -166,7 +165,7 @@ class PythonAddNewEnvironmentPanel(val projectPath: Flow<Path>, onlyAllowedInter
|
||||
model.navigator.saveLastState()
|
||||
return when (selectedMode.get()) {
|
||||
PROJECT_VENV -> {
|
||||
val projectPath = projectPath.first()
|
||||
val projectPath = projectPathFlows.projectPathWithDefault.first()
|
||||
// todo just keep venv path, all the rest is in the model
|
||||
model.setupVirtualenv(projectPath.resolve(VirtualEnvReader.DEFAULT_VIRTUALENV_DIRNAME), projectPath)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
|
||||
import com.intellij.openapi.ui.validation.and
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.dsl.builder.*
|
||||
import com.intellij.ui.dsl.builder.components.validationTooltip
|
||||
@@ -18,9 +19,10 @@ import com.intellij.util.ui.showingScope
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows.Companion.validatePath
|
||||
import com.jetbrains.python.sdk.VirtualEnvReader
|
||||
import com.jetbrains.python.newProjectWizard.validateProjectPathAndGetPath
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.PySdkSettings
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
|
||||
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.PYTHON
|
||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||
@@ -87,7 +89,7 @@ class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel)
|
||||
addInputRule {
|
||||
if (!textField.isVisible) return@addInputRule null // We are hidden, hence valid
|
||||
locationValidationFailed.set(false)
|
||||
val locationPath = when (val path = validateProjectPathAndGetPath(textField.text)) {
|
||||
val locationPath = when (val path = validatePath(textField.text)) {
|
||||
is com.jetbrains.python.Result.Failure -> return@addInputRule ValidationInfo(path.error) // Path is invalid
|
||||
is com.jetbrains.python.Result.Success -> path.result
|
||||
}
|
||||
@@ -135,9 +137,10 @@ class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel)
|
||||
}
|
||||
|
||||
versionComboBox.showingScope("...") {
|
||||
model.projectPath.collect {
|
||||
model.myProjectPathFlows.projectPathWithDefault.collect {
|
||||
if (!locationModified) {
|
||||
val suggestedVirtualEnvPath = model.suggestVenvPath()!! // todo nullability issue
|
||||
|
||||
val suggestedVirtualEnvPath = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(it.toString())) // todo nullability issue
|
||||
model.state.venvPath.set(suggestedVirtualEnvPath)
|
||||
}
|
||||
}
|
||||
@@ -160,9 +163,10 @@ class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel)
|
||||
override fun onShown() {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
model.scope.launch(Dispatchers.EDT + modalityState) {
|
||||
|
||||
val suggestedVirtualEnvPath = model.suggestVenvPath()!! // todo nullability issue
|
||||
model.state.venvPath.set(suggestedVirtualEnvPath)
|
||||
// TODO: Check venv set
|
||||
//
|
||||
//val suggestedVirtualEnvPath = model.suggestVenvPath()!! // todo nullability issue
|
||||
//model.state.venvPath.set(suggestedVirtualEnvPath)
|
||||
|
||||
//val projectBasePath = state.projectPath.get()
|
||||
|
||||
@@ -197,7 +201,7 @@ class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel)
|
||||
// todo remove project path, or move to controller
|
||||
try {
|
||||
val venvPath = Path.of(model.state.venvPath.get())
|
||||
model.setupVirtualenv(venvPath, model.projectPath.first())
|
||||
model.setupVirtualenv(venvPath, model.myProjectPathFlows.projectPathWithDefault.first())
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
Result.failure(e)
|
||||
|
||||
@@ -13,12 +13,12 @@ import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.jetbrains.extensions.failure
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.conda.suggestCondaPath
|
||||
@@ -42,7 +42,9 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams) {
|
||||
open val state = AddInterpreterState(propertyGraph)
|
||||
open val targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null
|
||||
|
||||
val projectPath = params.projectPathProperty ?: MutableStateFlow(Path.of(SystemProperties.getUserHome())) // todo how to populate?
|
||||
// TODO: DOC
|
||||
val myProjectPathFlows: ProjectPathFlows = params.projectPathFlows
|
||||
|
||||
internal val scope = params.scope
|
||||
internal val uiContext = params.uiContext
|
||||
|
||||
@@ -159,7 +161,7 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams) {
|
||||
manuallyAddedInterpreters.value += ExistingSelectableInterpreter(sdk, PySdkUtil.getLanguageLevelForSdk(sdk), sdk.isSystemWide)
|
||||
}
|
||||
|
||||
suspend fun suggestVenvPath(): String? = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectPath.first().toString()))
|
||||
suspend fun suggestVenvPath(): String? = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(myProjectPathFlows.projectPathWithDefault.first().toString()))
|
||||
}
|
||||
|
||||
abstract class PythonMutableTargetAddInterpreterModel(params: PyInterpreterModelParams)
|
||||
|
||||
Reference in New Issue
Block a user