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:
Ilya.Kazakevich
2025-06-09 19:24:35 +00:00
committed by intellij-monorepo-bot
parent 02118dde3c
commit 93d8518b8d
18 changed files with 172 additions and 34 deletions
@@ -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)
}
+28 -1
View File
@@ -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() {