mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
PY-78033/Sort-Base-interpreter-list
After this refactoring we how have `comparators.kt` with sorting logic that is used both by `SystemPython` and `PythonSelectableInterpreter` (ViewModel thing for V2). Tests are also added. GitOrigin-RevId: 9d04405829f31874d15cb893d9261c7997cb2dd5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
02118dde3c
commit
93d8518b8d
@@ -97,6 +97,7 @@ jvm_library(
|
||||
"//python/services/system-python:system-python_test_lib",
|
||||
"//platform/eel",
|
||||
"//python/services/shared",
|
||||
"//python/services/shared:shared_test_lib",
|
||||
"//python/poetry",
|
||||
"//python/python-venv:community-impl-venv",
|
||||
"//python/python-venv:community-impl-venv_test_lib",
|
||||
|
||||
@@ -35,6 +35,7 @@ jvm_library(
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/python-parser:parser",
|
||||
"//python/services/shared",
|
||||
"//python/services/shared:shared_test_lib",
|
||||
"//platform/eel-provider",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//python/openapi:community",
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl.Companion.concurrentLimit
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl.Companion.createByPythonBinary
|
||||
import com.intellij.python.community.services.shared.LanguageLevelComparator
|
||||
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
@@ -26,7 +28,7 @@ import kotlin.io.path.relativeTo
|
||||
class VanillaPythonWithLanguageLevelImpl internal constructor(
|
||||
override val pythonBinary: PythonBinary,
|
||||
override val languageLevel: LanguageLevel,
|
||||
) : VanillaPythonWithLanguageLevel {
|
||||
) : VanillaPythonWithLanguageLevel, Comparable<PythonWithLanguageLevel> {
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -82,4 +84,5 @@ class VanillaPythonWithLanguageLevelImpl internal constructor(
|
||||
return "$pythonString ($languageLevel)"
|
||||
}
|
||||
|
||||
override fun compareTo(other: PythonWithLanguageLevel): Int = LanguageLevelComparator.compare(this, other)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
### auto-generated section `build intellij.python.community.services.shared` start
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources")
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources", "jvm_test")
|
||||
|
||||
jvm_resources(
|
||||
name = "shared_resources",
|
||||
@@ -21,4 +21,31 @@ jvm_library(
|
||||
],
|
||||
runtime_deps = [":shared_resources"]
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "shared_test_lib",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["tests/**/*.kt", "tests/**/*.java"], allow_empty = True),
|
||||
associates = [":shared"],
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/openapi:community",
|
||||
"//python/openapi:community_test_lib",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//platform/eel-provider",
|
||||
"@lib//:junit5",
|
||||
"@lib//:kotlinx-coroutines-core",
|
||||
"//platform/testFramework/junit5",
|
||||
"//platform/testFramework/junit5:junit5_test_lib",
|
||||
"@lib//:hamcrest",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service/execService.python:execService.python_test_lib",
|
||||
],
|
||||
runtime_deps = [":shared_resources"]
|
||||
)
|
||||
|
||||
jvm_test(
|
||||
name = "shared_test",
|
||||
runtime_deps = [":shared_test_lib"]
|
||||
)
|
||||
### auto-generated section `build intellij.python.community.services.shared` end
|
||||
@@ -5,6 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.community.services.shared
|
||||
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
|
||||
/**
|
||||
* Something with language level
|
||||
*/
|
||||
interface LanguageLevelHolder {
|
||||
val languageLevel: LanguageLevel
|
||||
}
|
||||
@@ -2,21 +2,15 @@
|
||||
package com.intellij.python.community.services.shared
|
||||
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
|
||||
/**
|
||||
* Python (vanilla, conda, whatever) with known language level.
|
||||
*/
|
||||
interface PythonWithLanguageLevel : Comparable<PythonWithLanguageLevel>, PythonWithName {
|
||||
val languageLevel: LanguageLevel
|
||||
interface PythonWithLanguageLevel : PythonWithName, LanguageLevelHolder {
|
||||
|
||||
/**
|
||||
* Convert python to something that can be executed on [java.util.concurrent.ExecutorService]
|
||||
*/
|
||||
val asExecutablePython: ExecutablePython
|
||||
|
||||
|
||||
// Backward: first python is the highest
|
||||
override fun compareTo(other: PythonWithLanguageLevel): Int = languageLevel.compareTo(other.languageLevel) * -1
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.community.services.shared
|
||||
|
||||
interface PythonWithUi : PythonWithLanguageLevel {
|
||||
val ui: UICustomization?
|
||||
}
|
||||
/**
|
||||
* Python that has both [languageLevel] and [ui]
|
||||
*/
|
||||
interface PythonWithUi : PythonWithLanguageLevel, UiHolder
|
||||
@@ -10,4 +10,6 @@ data class UICustomization(
|
||||
*/
|
||||
val title: @Nls String,
|
||||
val icon: Icon? = null,
|
||||
)
|
||||
) : Comparable<UICustomization> {
|
||||
override fun compareTo(other: UICustomization): Int = title.compareTo(other.title)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.community.services.shared
|
||||
|
||||
interface UiHolder {
|
||||
/**
|
||||
* UI hints on how to display this python to the end user: icon, title, etc
|
||||
*/
|
||||
val ui: UICustomization?
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.community.services.shared
|
||||
|
||||
import java.util.*
|
||||
|
||||
|
||||
object LanguageLevelComparator : Comparator<LanguageLevelHolder> {
|
||||
override fun compare(o1: LanguageLevelHolder, o2: LanguageLevelHolder): Int =
|
||||
// Backward: first python is the highest
|
||||
o1.languageLevel.compareTo(o2.languageLevel) * -1
|
||||
}
|
||||
|
||||
object UiComparator : Comparator<UiHolder> {
|
||||
override fun compare(o1: UiHolder, o2: UiHolder): Int =
|
||||
Objects.compare(o1.ui, o2.ui, Comparator.nullsFirst(UICustomization::compareTo))
|
||||
}
|
||||
|
||||
class LanguageLevelWithUiComparator<T> : Comparator<T> where T : LanguageLevelHolder, T : UiHolder {
|
||||
override fun compare(o1: T, o2: T): Int =
|
||||
LanguageLevelComparator.compare(o1, o2) * 10 + UiComparator.compare(o1, o2)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
/**
|
||||
* Various details for pythons, not intended to be used directly (except for comparators).
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.services.shared;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.junit5Tests.unit.comparators
|
||||
|
||||
import com.intellij.python.community.services.shared.LanguageLevelHolder
|
||||
import com.intellij.python.community.services.shared.LanguageLevelWithUiComparator
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.intellij.python.community.services.shared.UiHolder
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
|
||||
class ComparatorsTest {
|
||||
|
||||
@Test
|
||||
fun testComparators() {
|
||||
val mocks = arrayOf(
|
||||
MockLevel(LanguageLevel.PYTHON314),
|
||||
MockLevel(LanguageLevel.PYTHON310),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("A")),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("Z")),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("B")),
|
||||
MockLevel(LanguageLevel.PYTHON27),
|
||||
MockLevel(LanguageLevel.PYTHON313),
|
||||
)
|
||||
val set = TreeSet(LanguageLevelWithUiComparator<MockLevel>())
|
||||
set.addAll(mocks)
|
||||
MatcherAssert.assertThat("", set, Matchers.contains(
|
||||
MockLevel(LanguageLevel.PYTHON314),
|
||||
MockLevel(LanguageLevel.PYTHON313),
|
||||
MockLevel(LanguageLevel.PYTHON310),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("A")),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("B")),
|
||||
MockLevel(LanguageLevel.PYTHON310, ui = UICustomization("Z")),
|
||||
MockLevel(LanguageLevel.PYTHON27)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private data class MockLevel(
|
||||
override val languageLevel: LanguageLevel,
|
||||
override val ui: UICustomization? = null,
|
||||
) : LanguageLevelHolder, UiHolder
|
||||
@@ -61,6 +61,7 @@ jvm_library(
|
||||
"//python/testFramework",
|
||||
"@lib//:hamcrest",
|
||||
"//python/services/shared",
|
||||
"//python/services/shared:shared_test_lib",
|
||||
"//python/services/internal-impl:python-community-services-internal-impl",
|
||||
"//python/services/internal-impl:python-community-services-internal-impl_test_lib",
|
||||
"//platform/util",
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.python.community.impl.venv.createVenv
|
||||
import com.intellij.python.community.services.shared.LanguageLevelWithUiComparator
|
||||
import com.intellij.python.community.services.shared.PythonWithUi
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
|
||||
@@ -55,7 +56,11 @@ fun SystemPythonService(): SystemPythonService = ApplicationManager.getApplicati
|
||||
*
|
||||
* Instances could be obtained with [SystemPythonService]
|
||||
*/
|
||||
class SystemPython internal constructor(private val impl: VanillaPythonWithLanguageLevel, override val ui: UICustomization?) : VanillaPythonWithLanguageLevel by impl, PythonWithUi {
|
||||
class SystemPython internal constructor(private val delegate: VanillaPythonWithLanguageLevel, override val ui: UICustomization?) : VanillaPythonWithLanguageLevel by delegate, PythonWithUi, Comparable<SystemPython> {
|
||||
|
||||
private companion object {
|
||||
val comparator = LanguageLevelWithUiComparator<SystemPython>()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
@@ -63,21 +68,23 @@ class SystemPython internal constructor(private val impl: VanillaPythonWithLangu
|
||||
|
||||
other as SystemPython
|
||||
|
||||
if (impl != other.impl) return false
|
||||
if (delegate != other.delegate) return false
|
||||
if (ui != other.ui) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = impl.hashCode()
|
||||
var result = delegate.hashCode()
|
||||
result = 31 * result + (ui?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SystemPython(impl=$impl, ui=$ui)"
|
||||
return "SystemPython(delegate=$delegate, ui=$ui)"
|
||||
}
|
||||
|
||||
override fun compareTo(other: SystemPython): Int = comparator.compare(this, other)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,7 +84,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
|
||||
else {
|
||||
cache.get(eelApi.descriptor)
|
||||
}.sorted()
|
||||
} ?: searchPythonsPhysicallyNoCache(eelApi)
|
||||
} ?: searchPythonsPhysicallyNoCache(eelApi).sorted()
|
||||
|
||||
|
||||
class MyServiceState : BaseState() {
|
||||
|
||||
@@ -15,8 +15,7 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.io.NioFiles
|
||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithLanguageLevelImpl
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithLanguageLevel
|
||||
import com.intellij.python.community.services.shared.*
|
||||
import com.intellij.python.community.services.systemPython.SystemPython
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonService
|
||||
import com.intellij.python.hatch.HatchConfiguration
|
||||
@@ -48,7 +47,8 @@ import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.apache.commons.lang3.builder.CompareToBuilder
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
@@ -83,6 +83,12 @@ abstract class PythonAddInterpreterModel(
|
||||
val interpreterLoading: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
val condaEnvironmentsLoading: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
|
||||
@TestOnly
|
||||
@ApiStatus.Internal
|
||||
fun addDetected(detected: DetectedSelectableInterpreter) {
|
||||
_detectedInterpreters.value += detected
|
||||
}
|
||||
|
||||
// If the project is provided, sdks associated with it will be kept in the list of interpreters. If not, then they will be filtered out.
|
||||
open fun initialize(scope: CoroutineScope) {
|
||||
merge(
|
||||
@@ -111,7 +117,7 @@ abstract class PythonAddInterpreterModel(
|
||||
manuallyAddedInterpreters,
|
||||
) { known, detected, added ->
|
||||
added + known + detected
|
||||
}.stateIn(scope, started = SharingStarted.Eagerly, initialValue = emptyList())
|
||||
}.map { it.sorted() }.stateIn(scope, started = SharingStarted.Eagerly, initialValue = emptyList())
|
||||
|
||||
this.baseInterpreters = allInterpreters.map { all ->
|
||||
all.filter { it.isBasePython() }
|
||||
@@ -365,7 +371,11 @@ class PythonLocalAddInterpreterModel(projectPathFlows: ProjectPathFlows) : Pytho
|
||||
}
|
||||
|
||||
|
||||
sealed class PythonSelectableInterpreter : Comparable<PythonSelectableInterpreter> {
|
||||
sealed class PythonSelectableInterpreter : Comparable<PythonSelectableInterpreter>, UiHolder, LanguageLevelHolder {
|
||||
companion object {
|
||||
private val comparator = LanguageLevelWithUiComparator<PythonSelectableInterpreter>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Base python is some system python (not venv) which can be used as a base for venv.
|
||||
* In terms of flavors we call it __not__ [PythonSdkFlavor.isPlatformIndependent]
|
||||
@@ -375,15 +385,12 @@ sealed class PythonSelectableInterpreter : Comparable<PythonSelectableInterprete
|
||||
}
|
||||
|
||||
abstract val homePath: String
|
||||
abstract val languageLevel: LanguageLevel
|
||||
open val uiCustomization: UICustomization? = null
|
||||
abstract override val languageLevel: LanguageLevel
|
||||
override val ui: UICustomization? = null
|
||||
override fun toString(): String = "PythonSelectableInterpreter(homePath='$homePath')"
|
||||
|
||||
override fun compareTo(other: PythonSelectableInterpreter): Int =
|
||||
CompareToBuilder()
|
||||
.append(uiCustomization?.hashCode(), other.uiCustomization?.hashCode())
|
||||
.append(other.languageLevel, languageLevel)
|
||||
.toComparison()
|
||||
comparator.compare(this, other)
|
||||
}
|
||||
|
||||
class ExistingSelectableInterpreter(
|
||||
@@ -411,11 +418,11 @@ class DetectedSelectableInterpreter(
|
||||
override val homePath: String,
|
||||
override val languageLevel: LanguageLevel,
|
||||
private val isBase: Boolean,
|
||||
override val uiCustomization: UICustomization? = null,
|
||||
override val ui: UICustomization? = null,
|
||||
) : PythonSelectableInterpreter() {
|
||||
override suspend fun isBasePython(): Boolean = isBase
|
||||
override fun toString(): String {
|
||||
return "DetectedSelectableInterpreter(homePath='$homePath', languageLevel=$languageLevel, isBase=$isBase, uiCustomization=$uiCustomization)"
|
||||
return "DetectedSelectableInterpreter(homePath='$homePath', languageLevel=$languageLevel, isBase=$isBase, uiCustomization=$ui)"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -436,7 +443,7 @@ class ManuallyAddedSelectableInterpreter(
|
||||
class InstallableSelectableInterpreter(val sdk: PySdkToInstall) : PythonSelectableInterpreter() {
|
||||
override suspend fun isBasePython(): Boolean = true
|
||||
override val homePath: String = ""
|
||||
override val languageLevel = PySdkUtil.getLanguageLevelForSdk(sdk)
|
||||
override val languageLevel: LanguageLevel = PySdkUtil.getLanguageLevelForSdk(sdk)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -159,8 +159,8 @@ internal fun SimpleColoredComponent.customizeForPythonInterpreter(isLoading: Boo
|
||||
|
||||
when (interpreter) {
|
||||
is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> {
|
||||
icon = IconLoader.getTransparentIcon(interpreter.uiCustomization?.icon ?: PythonPsiApiIcons.Python)
|
||||
val title = interpreter.uiCustomization?.title ?: message("sdk.rendering.detected.grey.text")
|
||||
icon = IconLoader.getTransparentIcon(interpreter.ui?.icon ?: PythonPsiApiIcons.Python)
|
||||
val title = interpreter.ui?.title ?: message("sdk.rendering.detected.grey.text")
|
||||
append(String.format("Python %-4s", interpreter.languageLevel))
|
||||
append(" (" + replaceHomePathToTilde(interpreter.homePath) + ") $title", SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user