From bd9be168d24aaf9207b29af9b2c8fc3eed6f07b0 Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Fri, 17 Jul 2026 13:31:00 +0300 Subject: [PATCH 1/4] fix python --- gradle-262.properties | 4 ++-- .../python/learning/newproject/compat.kt | 5 +++++ .../python/learning/newproject/compat.kt | 5 +++++ .../python/learning/newproject/compat.kt | 5 +++++ .../python/sdk/configuration/compat.kt | 7 ++++++- .../python/learning/newproject/compat.kt | 7 +++++++ .../PySdkVirtualEnvCompatibilityTest.kt | 15 +++++++++++++++ .../newproject/PyCourseProjectGenerator.kt | 19 +++++++++---------- 8 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 intellij-plugin/hs-Python/branches/262/testSrc/org/hyperskill/academy/python/learning/newproject/PySdkVirtualEnvCompatibilityTest.kt diff --git a/gradle-262.properties b/gradle-262.properties index 28b1bd768..be6392542 100644 --- a/gradle-262.properties +++ b/gradle-262.properties @@ -4,7 +4,7 @@ customUntilBuild=262.* # Existent IDE versions can be found in the following repos: # https://www.jetbrains.com/intellij-repository/releases/ # https://www.jetbrains.com/intellij-repository/snapshots/ -ideaVersion=IU-262.8665-EAP-CANDIDATE-SNAPSHOT -clionVersion=CL-262-EAP-SNAPSHOT +ideaVersion=IU-2026.2 +clionVersion=CL-2026.2 pycharmVersion=PC-262-EAP-SNAPSHOT riderVersion=RD-2026.2-EAP9-SNAPSHOT diff --git a/intellij-plugin/hs-Python/branches/252/src/org/hyperskill/academy/python/learning/newproject/compat.kt b/intellij-plugin/hs-Python/branches/252/src/org/hyperskill/academy/python/learning/newproject/compat.kt index 5732c19b7..5f4f7e574 100644 --- a/intellij-plugin/hs-Python/branches/252/src/org/hyperskill/academy/python/learning/newproject/compat.kt +++ b/intellij-plugin/hs-Python/branches/252/src/org/hyperskill/academy/python/learning/newproject/compat.kt @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks private val LOG = logger() +internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk { + val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation") + return PyDetectedSdk(homePath) +} + /** * If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python * and returns the freshly detected system-wide SDK, or `null` if the installation failed. diff --git a/intellij-plugin/hs-Python/branches/253/src/org/hyperskill/academy/python/learning/newproject/compat.kt b/intellij-plugin/hs-Python/branches/253/src/org/hyperskill/academy/python/learning/newproject/compat.kt index 5732c19b7..5f4f7e574 100644 --- a/intellij-plugin/hs-Python/branches/253/src/org/hyperskill/academy/python/learning/newproject/compat.kt +++ b/intellij-plugin/hs-Python/branches/253/src/org/hyperskill/academy/python/learning/newproject/compat.kt @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks private val LOG = logger() +internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk { + val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation") + return PyDetectedSdk(homePath) +} + /** * If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python * and returns the freshly detected system-wide SDK, or `null` if the installation failed. diff --git a/intellij-plugin/hs-Python/branches/261/src/org/hyperskill/academy/python/learning/newproject/compat.kt b/intellij-plugin/hs-Python/branches/261/src/org/hyperskill/academy/python/learning/newproject/compat.kt index 5732c19b7..5f4f7e574 100644 --- a/intellij-plugin/hs-Python/branches/261/src/org/hyperskill/academy/python/learning/newproject/compat.kt +++ b/intellij-plugin/hs-Python/branches/261/src/org/hyperskill/academy/python/learning/newproject/compat.kt @@ -9,6 +9,11 @@ import com.jetbrains.python.sdk.detectSystemWideSdks private val LOG = logger() +internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk { + val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation") + return PyDetectedSdk(homePath) +} + /** * If [sdk] is an "install Python" suggestion ([PySdkToInstall]), downloads and installs Python * and returns the freshly detected system-wide SDK, or `null` if the installation failed. diff --git a/intellij-plugin/hs-Python/branches/262/src/com/jetbrains/python/sdk/configuration/compat.kt b/intellij-plugin/hs-Python/branches/262/src/com/jetbrains/python/sdk/configuration/compat.kt index 16a9c1dea..ff75f1484 100644 --- a/intellij-plugin/hs-Python/branches/262/src/com/jetbrains/python/sdk/configuration/compat.kt +++ b/intellij-plugin/hs-Python/branches/262/src/com/jetbrains/python/sdk/configuration/compat.kt @@ -16,12 +16,16 @@ import com.intellij.openapi.util.UserDataHolderBase import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.platform.ide.progress.ModalTaskOwner import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import com.intellij.python.venv.sdk.flavors.VirtualEnvSdkFlavor import com.intellij.util.concurrency.annotations.RequiresEdt import com.jetbrains.python.packaging.PyPackageManagers import com.jetbrains.python.packaging.PyTargetEnvCreationManager +import com.jetbrains.python.sdk.PythonSdkAdditionalData import com.jetbrains.python.sdk.PythonSdkType import com.jetbrains.python.sdk.baseDir import com.jetbrains.python.sdk.excludeInnerVirtualEnv +import com.jetbrains.python.sdk.flavors.PyFlavorAndData +import com.jetbrains.python.sdk.flavors.PyFlavorData import com.jetbrains.python.sdk.impl.PySdkBundle import com.jetbrains.python.sdk.setAssociationToModule import com.jetbrains.python.sdk.setAssociationToPath @@ -108,5 +112,6 @@ private fun createSdkByGenerateTask( val homePath = ProgressManager.getInstance().run(generateSdkHomePath) val homeFile = StandardFileSystems.local().refreshAndFindFileByPath(homePath) ?: throw ExecutionException("Python interpreter is not found at $homePath") - return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile, PythonSdkType.getInstance(), null, null) + val additionalData = PythonSdkAdditionalData(PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance())) + return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile, PythonSdkType.getInstance(), additionalData, null) } diff --git a/intellij-plugin/hs-Python/branches/262/src/org/hyperskill/academy/python/learning/newproject/compat.kt b/intellij-plugin/hs-Python/branches/262/src/org/hyperskill/academy/python/learning/newproject/compat.kt index b9a24afe9..869f24589 100644 --- a/intellij-plugin/hs-Python/branches/262/src/org/hyperskill/academy/python/learning/newproject/compat.kt +++ b/intellij-plugin/hs-Python/branches/262/src/org/hyperskill/academy/python/learning/newproject/compat.kt @@ -9,6 +9,13 @@ import com.jetbrains.python.sdk.detectSystemWideSdks private val LOG = logger() +/** + * Python 262 requires every SDK used to launch Python to have [com.jetbrains.python.sdk.PythonSdkAdditionalData]. + * [PySdkToCreateVirtualEnv] already has it; converting it to a [com.jetbrains.python.sdk.PyDetectedSdk] + * would discard that data and make virtual environment creation fail. + */ +internal fun prepareSdkForVirtualEnvCreation(sdk: PySdkToCreateVirtualEnv): Sdk = sdk + @Suppress("DEPRECATION_ERROR") internal fun installSdkIfSuggested(sdk: Sdk?): Sdk? { if (sdk !is PySdkToInstallCompat) return null diff --git a/intellij-plugin/hs-Python/branches/262/testSrc/org/hyperskill/academy/python/learning/newproject/PySdkVirtualEnvCompatibilityTest.kt b/intellij-plugin/hs-Python/branches/262/testSrc/org/hyperskill/academy/python/learning/newproject/PySdkVirtualEnvCompatibilityTest.kt new file mode 100644 index 000000000..59a2ffcba --- /dev/null +++ b/intellij-plugin/hs-Python/branches/262/testSrc/org/hyperskill/academy/python/learning/newproject/PySdkVirtualEnvCompatibilityTest.kt @@ -0,0 +1,15 @@ +package org.hyperskill.academy.python.learning.newproject + +import com.intellij.testFramework.LightPlatformTestCase +import com.jetbrains.python.sdk.PythonSdkAdditionalData + +class PySdkVirtualEnvCompatibilityTest : LightPlatformTestCase() { + fun testSdkKeepsAdditionalDataForVirtualEnvCreation() { + val sdk = PySdkToCreateVirtualEnv.create("Python 3.14", "/usr/bin/python3.14", "3.14") + + val preparedSdk = prepareSdkForVirtualEnvCreation(sdk) + + assertSame(sdk, preparedSdk) + assertInstanceOf(preparedSdk.sdkAdditionalData, PythonSdkAdditionalData::class.java) + } +} diff --git a/intellij-plugin/hs-Python/src/org/hyperskill/academy/python/learning/newproject/PyCourseProjectGenerator.kt b/intellij-plugin/hs-Python/src/org/hyperskill/academy/python/learning/newproject/PyCourseProjectGenerator.kt index caec582a6..0f0bf4e79 100644 --- a/intellij-plugin/hs-Python/src/org/hyperskill/academy/python/learning/newproject/PyCourseProjectGenerator.kt +++ b/intellij-plugin/hs-Python/src/org/hyperskill/academy/python/learning/newproject/PyCourseProjectGenerator.kt @@ -34,13 +34,10 @@ open class PyCourseProjectGenerator( // See `installSdkIfSuggested` implementations in `branches//src`. val installedSdk = installSdkIfSuggested(sdk) if (installedSdk != null) { - createAndAddVirtualEnv(project, projectSettings, installedSdk) - sdk = projectSettings.sdk + sdk = createAndAddVirtualEnv(project, projectSettings, installedSdk) } else if (sdk is PySdkToCreateVirtualEnv) { - val homePath = sdk.homePath ?: error("Home path is not passed during fake python sdk creation") - createAndAddVirtualEnv(project, projectSettings, PyDetectedSdk(homePath)) - sdk = projectSettings.sdk + sdk = createAndAddVirtualEnv(project, projectSettings, prepareSdkForVirtualEnvCreation(sdk)) } sdk = updateSdkIfNeeded(project, sdk) LOG.warn("PyCourseProjectGenerator: After updateSdkIfNeeded, sdk = ${sdk?.name} at ${sdk?.homePath}") @@ -48,14 +45,15 @@ open class PyCourseProjectGenerator( LOG.warn("PyCourseProjectGenerator: After setDirectoryProjectSdk") if (sdk == null) { LOG.warn("PyCourseProjectGenerator: SDK is null, skipping package installation") - return } - LOG.warn("PyCourseProjectGenerator: About to install packages") - installRequiredPackages(project, sdk) + else { + LOG.warn("PyCourseProjectGenerator: About to install packages") + installRequiredPackages(project, sdk) + } super.afterProjectGenerated(project, projectSettings, openCourseParams, onConfigurationFinished) } - private fun createAndAddVirtualEnv(project: Project, settings: PyProjectSettings, baseSdk: Sdk) { + private fun createAndAddVirtualEnv(project: Project, settings: PyProjectSettings, baseSdk: Sdk): Sdk? { val virtualEnvPath = project.basePath + "/.idea/VirtualEnvironment" val existingSdks = PyConfigurableInterpreterList.getInstance(null).allPythonSdks val module = ModuleManager.getInstance(project).sortedModules.firstOrNull() @@ -76,7 +74,7 @@ open class PyCourseProjectGenerator( } catch (e: Exception) { LOG.warn("Failed to create virtual env in $virtualEnvPath", e) - return + return null } settings.sdk = sdk SdkConfigurationUtil.addSdk(sdk) @@ -84,6 +82,7 @@ open class PyCourseProjectGenerator( if (module != null) { setAssociationToModule(sdk, module) } + return sdk } companion object { From 4cdb799ec448ad416b879ac2cc203f7cd961d193 Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Fri, 17 Jul 2026 18:55:25 +0300 Subject: [PATCH 2/4] fix several issues with plugin --- buildSrc/src/main/kotlin/intellijUtils.kt | 16 ++++++--- gradle/wrapper/gradle-wrapper.properties | 2 +- intellij-plugin/build.gradle.kts | 12 +++++++ .../internal/hyperskill-java-build.gradle.ft | 8 +++-- .../internal/java-build.gradle.ft | 6 ++-- .../custom command/contents/build.gradle | 6 ++-- .../contents/build.gradle | 6 ++-- .../hyperskill-kotlin-build.gradle.ft | 8 +++-- .../internal/kotlin-build.gradle.ft | 6 ++-- intellij-plugin/hs-Python/build.gradle.kts | 30 ++++++++++++++++ .../hs-Python/resources/hs-Python.xml | 4 ++- .../internal/hyperskill-scala-build.gradle.ft | 6 ++-- .../internal/scala-build.gradle.ft | 6 ++-- .../academy/learning/EduProjectActivity.kt | 8 +++-- .../academy/learning/EduSettings.kt | 4 +-- .../learning/actions/EduActionUtils.kt | 5 ++- .../learning/checker/EduTaskCheckerBase.kt | 7 ++-- .../newproject/ui/CourseCardComponent.kt | 2 +- .../elements/FrameworkTaskUpdateInfo.kt | 4 ++- .../academy/learning/EduSettingsTest.kt | 33 +++++++---------- .../yaml/YamlChangedAfterEventTest.kt | 14 ++++---- .../jvm/gradle/GradleStartupActivity.kt | 35 +++++++++++++++++++ .../hs-sql/hs-sql-jvm/build.gradle.kts | 25 +++++++++++++ .../internal/sql-java-build.gradle.ft | 6 ++-- .../internal/sql-kotlin-build.gradle.ft | 6 ++-- .../hs-sql-jvm/resources/hs-sql-jvm.xml | 2 ++ .../jvm/gradle/SqlGradleStartupActivity.kt | 4 +-- .../academy/sql/jvm/gradle/sqlUtils.kt | 15 ++++++-- 28 files changed, 212 insertions(+), 74 deletions(-) diff --git a/buildSrc/src/main/kotlin/intellijUtils.kt b/buildSrc/src/main/kotlin/intellijUtils.kt index b859e1170..ed8c0aa6a 100644 --- a/buildSrc/src/main/kotlin/intellijUtils.kt +++ b/buildSrc/src/main/kotlin/intellijUtils.kt @@ -123,11 +123,17 @@ val Project.csharpPlugins: List // Plugins which we add to tests for all modules. // It's the most common plugins which affect the behavior of the plugin code val Project.commonTestPlugins: List - get() = listOf( - imagesPlugin, // adds `svg` file type and makes IDE consider .svg files as text ones - yamlPlugin, // makes IDE consider .yaml files as text ones and affects formatting of yaml files - jsonPlugin, // dependency of a lot of other bundled plugin - ) + get() = buildList { + add(imagesPlugin) // adds `svg` file type and makes IDE consider .svg files as text ones + add(yamlPlugin) // makes IDE consider .yaml files as text ones and affects formatting of yaml files + add(jsonPlugin) // dependency of a lot of other bundled plugin + if (environmentName.toInt() >= 262) { + // Since 2026.2 the SM/test runner modules are provided by this separate bundled plugin. + add("intellij.testRunner.plugin") + // Loads the extension point used by JCEF-aware UI code in the test application. + add("com.intellij.modules.jcef") + } + } data class TypeWithVersion(val type: IntelliJPlatformType, val version: String) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb84db68d..ff6c715df 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/intellij-plugin/build.gradle.kts b/intellij-plugin/build.gradle.kts index 5d36b17c2..be60d2308 100644 --- a/intellij-plugin/build.gradle.kts +++ b/intellij-plugin/build.gradle.kts @@ -92,6 +92,17 @@ dependencies { intellijPlatform { intellijIde(baseVersion) + // hs-core tests are compiled in this module. Since 2026.2 these APIs are + // no longer part of the IDE's core classpath, so their modules must be + // repeated here instead of relying on hs-core's non-transitive platform dependencies. + bundledModulesSince( + baseVersion, 262, + "intellij.platform.smRunner", + "intellij.platform.testRunner", + "intellij.platform.ui.jcef", + "intellij.libraries.jcef", + "intellij.platform.sqlite", + ) pluginModule(implementation(project("hs-jvm-core"))) pluginModule(implementation(project("hs-Java"))) @@ -168,6 +179,7 @@ tasks { // This is needed because hs-core tests use relative path "testData/" for test resources test { workingDir = project("hs-core").projectDir + systemProperty("idea.home.path", intellijPlatform.platformPath.toString()) } // Copy hs-core.xml and Hyperskill.xml to main plugin JAR for XInclude resolution. diff --git a/intellij-plugin/hs-Java/resources/fileTemplates/internal/hyperskill-java-build.gradle.ft b/intellij-plugin/hs-Java/resources/fileTemplates/internal/hyperskill-java-build.gradle.ft index 24270502d..56413946a 100644 --- a/intellij-plugin/hs-Java/resources/fileTemplates/internal/hyperskill-java-build.gradle.ft +++ b/intellij-plugin/hs-Java/resources/fileTemplates/internal/hyperskill-java-build.gradle.ft @@ -64,13 +64,15 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - testImplementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + testImplementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } wrapper { gradleVersion = hs.gradle.version -} \ No newline at end of file +} diff --git a/intellij-plugin/hs-Java/resources/fileTemplates/internal/java-build.gradle.ft b/intellij-plugin/hs-Java/resources/fileTemplates/internal/java-build.gradle.ft index 8e8f4760c..38371fb67 100644 --- a/intellij-plugin/hs-Java/resources/fileTemplates/internal/java-build.gradle.ft +++ b/intellij-plugin/hs-Java/resources/fileTemplates/internal/java-build.gradle.ft @@ -53,9 +53,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-Java/testData/archive/createCourseArchive/custom command/contents/build.gradle b/intellij-plugin/hs-Java/testData/archive/createCourseArchive/custom command/contents/build.gradle index 8d0d1c310..a730581e0 100644 --- a/intellij-plugin/hs-Java/testData/archive/createCourseArchive/custom command/contents/build.gradle +++ b/intellij-plugin/hs-Java/testData/archive/createCourseArchive/custom command/contents/build.gradle @@ -53,9 +53,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-Java/testData/archive/createCourseArchive/do not override existing jdk level/contents/build.gradle b/intellij-plugin/hs-Java/testData/archive/createCourseArchive/do not override existing jdk level/contents/build.gradle index 8d0d1c310..a730581e0 100644 --- a/intellij-plugin/hs-Java/testData/archive/createCourseArchive/do not override existing jdk level/contents/build.gradle +++ b/intellij-plugin/hs-Java/testData/archive/createCourseArchive/do not override existing jdk level/contents/build.gradle @@ -53,9 +53,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/hyperskill-kotlin-build.gradle.ft b/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/hyperskill-kotlin-build.gradle.ft index 6eb648617..ce4d491c1 100644 --- a/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/hyperskill-kotlin-build.gradle.ft +++ b/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/hyperskill-kotlin-build.gradle.ft @@ -76,13 +76,15 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - testImplementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + testImplementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } wrapper { gradleVersion = hs.gradle.version -} \ No newline at end of file +} diff --git a/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/kotlin-build.gradle.ft b/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/kotlin-build.gradle.ft index 9a2c9efb3..49ec5d867 100644 --- a/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/kotlin-build.gradle.ft +++ b/intellij-plugin/hs-Kotlin/resources/fileTemplates/internal/kotlin-build.gradle.ft @@ -85,9 +85,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-Python/build.gradle.kts b/intellij-plugin/hs-Python/build.gradle.kts index 0890bc4b1..f006d8645 100644 --- a/intellij-plugin/hs-Python/build.gradle.kts +++ b/intellij-plugin/hs-Python/build.gradle.kts @@ -2,12 +2,33 @@ plugins { id("intellij-plugin-module-conventions") } +private val pythonPlatformModuleDependenciesMarker = "" +private val pythonPlatformModuleDependencies = if (environmentName.toInt() >= 262) { + listOf( + "intellij.libraries.lucene.common", + "intellij.python.sdk", + ).joinToString("\n") { " " } +} +else { + "" +} + dependencies { intellijPlatform { // needed to load `org.toml.lang plugin` for Python plugin in tests val ideVersion = if (isRiderIDE) ideaVersion else baseVersion intellijIde(ideVersion) bundledModulesSince(ideVersion, 262, "intellij.platform.smRunner", "intellij.platform.testRunner") + if (platformBranch(ideVersion) >= 262) { + // In 2026.2 these libraries moved under separate bundled plugins. PythonCore + // reaches them through JSON/spellchecker and its JCEF integration, so their + // owning plugins must be loaded in the test sandbox. + testBundledPlugins( + "intellij.libraries.misc.plugin", + "com.intellij.modules.jcef", + "intellij.structureView.plugin", + ) + } intellijPlugins(pythonPlugin) testIntellijPlugins(tomlPlugin) @@ -18,3 +39,12 @@ dependencies { testImplementation(project(":intellij-plugin:hs-core", "testOutput")) } + +tasks.processResources { + inputs.property("pythonPlatformModuleDependencies", pythonPlatformModuleDependencies) + filesMatching("hs-Python.xml") { + filter { line -> + if (pythonPlatformModuleDependenciesMarker in line) pythonPlatformModuleDependencies else line + } + } +} diff --git a/intellij-plugin/hs-Python/resources/hs-Python.xml b/intellij-plugin/hs-Python/resources/hs-Python.xml index 8a9388856..09fe76a39 100644 --- a/intellij-plugin/hs-Python/resources/hs-Python.xml +++ b/intellij-plugin/hs-Python/resources/hs-Python.xml @@ -2,6 +2,8 @@ + + @@ -31,4 +33,4 @@ id="HyperskillEducational.Python.InstallDependenciesWorkaround" class="org.hyperskill.academy.python.learning.InstallDependenciesWorkaroundAction"/> - \ No newline at end of file + diff --git a/intellij-plugin/hs-Scala/resources/fileTemplates/internal/hyperskill-scala-build.gradle.ft b/intellij-plugin/hs-Scala/resources/fileTemplates/internal/hyperskill-scala-build.gradle.ft index ef82209c9..22fb6d9b6 100644 --- a/intellij-plugin/hs-Scala/resources/fileTemplates/internal/hyperskill-scala-build.gradle.ft +++ b/intellij-plugin/hs-Scala/resources/fileTemplates/internal/hyperskill-scala-build.gradle.ft @@ -44,9 +44,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - testImplementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + testImplementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-Scala/resources/fileTemplates/internal/scala-build.gradle.ft b/intellij-plugin/hs-Scala/resources/fileTemplates/internal/scala-build.gradle.ft index 2ade5f7cb..93a31e8f5 100644 --- a/intellij-plugin/hs-Scala/resources/fileTemplates/internal/scala-build.gradle.ft +++ b/intellij-plugin/hs-Scala/resources/fileTemplates/internal/scala-build.gradle.ft @@ -58,9 +58,11 @@ project(':util') { } } +def utilProject = rootProject.project(':util') + configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduProjectActivity.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduProjectActivity.kt index f4df7650c..6a2638b07 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduProjectActivity.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduProjectActivity.kt @@ -82,9 +82,11 @@ class EduProjectActivity : ProjectActivity { SyncChangesStateManager.getInstance(project).updateSyncChangesState(course) - writeAction { - course.visitTasks { - setHighlightLevelForFilesInTask(it, project) + withContext(Dispatchers.EDT) { + writeAction { + course.visitTasks { + setHighlightLevelForFilesInTask(it, project) + } } } } diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduSettings.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduSettings.kt index 978134eee..d550ee874 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduSettings.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/EduSettings.kt @@ -26,7 +26,7 @@ fun isJCEFSupported(): Boolean { } @State(name = "HsSettings", storages = [Storage("hyperskill.xml")]) -class EduSettings : PersistentStateComponent { +class EduSettings(private val jcefSupportedOverride: Boolean? = null) : PersistentStateComponent { @Transient @Volatile private var _user: StepikUser? = null @@ -91,7 +91,7 @@ class EduSettings : PersistentStateComponent { } private fun initialJavaUiLibrary(): JavaUILibrary { - return if (isJCEFSupported()) { + return if (jcefSupportedOverride ?: isJCEFSupported()) { JavaUILibrary.JCEF } else { diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/actions/EduActionUtils.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/actions/EduActionUtils.kt index c633382b1..ba7b06039 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/actions/EduActionUtils.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/actions/EduActionUtils.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.application.impl.TestOnlyThreading import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.progress.ProgressIndicator @@ -63,7 +64,9 @@ object EduActionUtils { } while (true) { try { - UIUtil.dispatchAllInvocationEvents() + TestOnlyThreading.releaseTheAcquiredWriteIntentLockThenExecuteActionAndTakeWriteIntentLockBack { + UIUtil.dispatchAllInvocationEvents() + } future[10, TimeUnit.MILLISECONDS] return } diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/EduTaskCheckerBase.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/EduTaskCheckerBase.kt index 0de5aef23..3e5235527 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/EduTaskCheckerBase.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/checker/EduTaskCheckerBase.kt @@ -10,6 +10,7 @@ import com.intellij.execution.process.ProcessOutputType import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.application.runInEdt import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.wm.ToolWindowId @@ -25,7 +26,6 @@ import org.hyperskill.academy.learning.courseFormat.EduTestInfo.Companion.firstF import org.hyperskill.academy.learning.courseFormat.ext.getAllTestDirectories import org.hyperskill.academy.learning.courseFormat.ext.getAllTestFiles import org.hyperskill.academy.learning.courseFormat.tasks.EduTask -import org.hyperskill.academy.learning.runReadActionInSmartMode abstract class EduTaskCheckerBase(task: EduTask, private val envChecker: EnvironmentChecker, project: Project) : TaskChecker(task, project) { @@ -42,7 +42,10 @@ abstract class EduTaskCheckerBase(task: EduTask, private val envChecker: Environ return possibleError } - val configurations = runReadActionInSmartMode(project) { createTestConfigurations() } + DumbService.getInstance(project).waitForSmartMode() + // Run configuration producers may create platform state under a write action. + // IDEA 2026.2 rejects that write action when the producer is wrapped in a read action. + val configurations = invokeAndWaitIfNeeded { createTestConfigurations() } configurations.forEach { it.isActivateToolWindowBeforeRun = activateRunToolWindow } diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt index 170f91b41..c76607592 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt @@ -106,7 +106,7 @@ open class CourseCardComponent(val course: Course) : JPanel(BorderLayout()) { open fun onHoverEnded() {} private fun scrollToVisible() { - val parent = parent as JComponent + val parent = parent as? JComponent ?: return val bounds = bounds if (!parent.visibleRect.contains(bounds)) { parent.scrollRectToVisible(bounds) diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/update/elements/FrameworkTaskUpdateInfo.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/update/elements/FrameworkTaskUpdateInfo.kt index fc669f1df..d81811679 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/update/elements/FrameworkTaskUpdateInfo.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/update/elements/FrameworkTaskUpdateInfo.kt @@ -216,8 +216,10 @@ data class FrameworkTaskUpdateInfo( } if (virtualChangedFile != null) { - writeAction { + withContext(Dispatchers.EDT) { FileDocumentManager.getInstance().reloadFiles(virtualChangedFile) + } + writeAction { ReadOnlyAttributeUtil.setReadOnlyAttribute(virtualChangedFile, false) } } diff --git a/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/EduSettingsTest.kt b/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/EduSettingsTest.kt index 7f2827a01..2a7538ea5 100644 --- a/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/EduSettingsTest.kt +++ b/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/EduSettingsTest.kt @@ -1,15 +1,12 @@ package org.hyperskill.academy.learning -import com.intellij.ui.jcef.JBCefApp -import io.mockk.every -import io.mockk.mockkStatic import org.junit.Test class EduSettingsTest : EduSettingsServiceTestBase() { @Test - fun `test serialization with JCEF`() = withJCEFSupported(true) { - val settings = EduSettings() + fun `test serialization with JCEF`() { + val settings = EduSettings(jcefSupportedOverride = true) settings.checkState( """ @@ -21,8 +18,8 @@ class EduSettingsTest : EduSettingsServiceTestBase() { } @Test - fun `test serialization with Swing`() = withJCEFSupported(false) { - val settings = EduSettings() + fun `test serialization with Swing`() { + val settings = EduSettings(jcefSupportedOverride = false) settings.checkState( """ @@ -34,8 +31,8 @@ class EduSettingsTest : EduSettingsServiceTestBase() { } @Test - fun `test set Swing explicitly settings`() = withJCEFSupported(true) { - val settings = EduSettings() + fun `test set Swing explicitly settings`() { + val settings = EduSettings(jcefSupportedOverride = true) settings.setJavaUiLibrary(JavaUILibrary.SWING, true) settings.checkState( """ @@ -48,8 +45,8 @@ class EduSettingsTest : EduSettingsServiceTestBase() { } @Test - fun `test switch to JCEF from Swing`() = withJCEFSupported(true) { - val settings = EduSettings() + fun `test switch to JCEF from Swing`() { + val settings = EduSettings(jcefSupportedOverride = true) settings.loadStateAndCheck( """ @@ -66,8 +63,8 @@ class EduSettingsTest : EduSettingsServiceTestBase() { } @Test - fun `test switch to Swing from JCEF`() = withJCEFSupported(false) { - val settings = EduSettings() + fun `test switch to Swing from JCEF`() { + val settings = EduSettings(jcefSupportedOverride = false) settings.loadStateAndCheck( """ @@ -84,8 +81,8 @@ class EduSettingsTest : EduSettingsServiceTestBase() { } @Test - fun `test preserve user choice`() = withJCEFSupported(true) { - val settings = EduSettings() + fun `test preserve user choice`() { + val settings = EduSettings(jcefSupportedOverride = true) settings.loadStateAndCheck( """ @@ -101,10 +98,4 @@ class EduSettingsTest : EduSettingsServiceTestBase() { ) } - private fun withJCEFSupported(value: Boolean, action: () -> Unit) { - mockkStatic(JBCefApp::class) { - every { JBCefApp.isSupported() } returns value - action() - } - } } diff --git a/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/yaml/YamlChangedAfterEventTest.kt b/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/yaml/YamlChangedAfterEventTest.kt index f4ba3f56c..86ae91401 100644 --- a/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/yaml/YamlChangedAfterEventTest.kt +++ b/intellij-plugin/hs-core/testSrc/org/hyperskill/academy/learning/yaml/YamlChangedAfterEventTest.kt @@ -32,12 +32,12 @@ class YamlChangedAfterEventTest : YamlTestCase() { val expectedConfig = """ |type: edu |files: - | - name: file1.txt - | visible: true - | learner_created: true - | - name: userFile.txt - | visible: true - | learner_created: true + |- name: file1.txt + | visible: true + | learner_created: true + |- name: userFile.txt + | visible: true + | learner_created: true |status: Unchecked |record: -1 |""".trimMargin() @@ -85,4 +85,4 @@ class YamlChangedAfterEventTest : YamlTestCase() { return course } -} \ No newline at end of file +} diff --git a/intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/gradle/GradleStartupActivity.kt b/intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/gradle/GradleStartupActivity.kt index 1cc824782..7d09e2e0e 100644 --- a/intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/gradle/GradleStartupActivity.kt +++ b/intellij-plugin/hs-jvm-core/src/org/hyperskill/academy/jvm/gradle/GradleStartupActivity.kt @@ -1,12 +1,15 @@ package org.hyperskill.academy.jvm.gradle +import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.application.writeAction import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VfsUtilCore import kotlinx.coroutines.suspendCancellableCoroutine import org.hyperskill.academy.jvm.gradle.generation.EduGradleUtils import org.hyperskill.academy.jvm.gradle.generation.EduGradleUtils.setupGradleProject @@ -14,6 +17,8 @@ import org.hyperskill.academy.jvm.gradle.generation.EduGradleUtils.updateGradleS import org.hyperskill.academy.learning.EduUtilsKt.isEduProject import org.hyperskill.academy.learning.StudyTaskManager import org.hyperskill.academy.learning.courseFormat.hyperskill.HyperskillCourse +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.io.IOException import kotlin.coroutines.resume class GradleStartupActivity : ProjectActivity { @@ -23,6 +28,7 @@ class GradleStartupActivity : ProjectActivity { return } if (EduGradleUtils.isConfiguredWithGradle(project)) { + migrateLegacyBuildGradle(project) updateGradleSettings(project) } @@ -52,6 +58,30 @@ class GradleStartupActivity : ProjectActivity { } } + private suspend fun migrateLegacyBuildGradle(project: Project) { + if (ApplicationInfo.getInstance().build.baselineVersion != IDEA_2026_2_BASELINE) return + + val projectDir = project.guessProjectDir() ?: return + val buildFile = projectDir.findChild(GradleConstants.DEFAULT_SCRIPT_NAME) ?: return + if (buildFile.isDirectory) return + + try { + val originalContent = VfsUtilCore.loadText(buildFile) + val migratedContent = originalContent.replace(LEGACY_UTIL_SOURCE_SET_REFERENCE) { matchResult -> + "rootProject.${matchResult.value}" + } + if (migratedContent == originalContent) return + + writeAction { + VfsUtil.saveText(buildFile, migratedContent) + } + LOG.info("Migrated legacy util sourceSets references in ${buildFile.path}") + } + catch (e: IOException) { + LOG.warn("Failed to migrate legacy util sourceSets references in ${buildFile.path}", e) + } + } + private fun ensureUtilModuleDirectoryExists(project: Project) { val projectDir = project.guessProjectDir() ?: return val utilDir = projectDir.findChild(UTIL_MODULE_NAME) @@ -64,6 +94,11 @@ class GradleStartupActivity : ProjectActivity { companion object { private val LOG = Logger.getInstance(GradleStartupActivity::class.java) + + private const val IDEA_2026_2_BASELINE = 262 private const val UTIL_MODULE_NAME = "util" + + private val LEGACY_UTIL_SOURCE_SET_REFERENCE = + Regex("""(?" +private val databasePlatformModuleDependencies = if (environmentName.toInt() >= 262) { + listOf( + "intellij.database.impl", + ).joinToString("\n") { " " } +} +else { + "" +} + dependencies { intellijPlatform { intellijIde(ideaVersion) intellijPlugins(jvmPlugins) intellijPlugins(sqlPlugins) + // Since 2026.2 Database Tools APIs are split into content modules instead of + // being exposed transitively by the `com.intellij.database` plugin. + bundledModulesSince( + ideaVersion, 262, + "intellij.database.impl", + ) // Workaround to make tests work - the module is not loaded automatically bundledModule("com.intellij.modules.ultimate") } @@ -19,3 +35,12 @@ dependencies { testImplementation(project(":intellij-plugin:hs-sql", "testOutput")) testImplementation(project(":intellij-plugin:hs-jvm-core", "testOutput")) } + +tasks.processResources { + inputs.property("databasePlatformModuleDependencies", databasePlatformModuleDependencies) + filesMatching("hs-sql-jvm.xml") { + filter { line -> + if (databasePlatformModuleDependenciesMarker in line) databasePlatformModuleDependencies else line + } + } +} diff --git a/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-java-build.gradle.ft b/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-java-build.gradle.ft index 910b4b582..6937d2e9f 100644 --- a/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-java-build.gradle.ft +++ b/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-java-build.gradle.ft @@ -29,12 +29,12 @@ subprojects { } } -project(':util') +def utilProject = rootProject.project(':util') configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-kotlin-build.gradle.ft b/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-kotlin-build.gradle.ft index bab41be02..15ecfe3f8 100644 --- a/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-kotlin-build.gradle.ft +++ b/intellij-plugin/hs-sql/hs-sql-jvm/resources/fileTemplates/internal/sql-kotlin-build.gradle.ft @@ -58,12 +58,12 @@ subprojects { } } -project(':util') +def utilProject = rootProject.project(':util') configure(subprojects.findAll { it.name != 'util' }) { dependencies { - implementation project(':util').sourceSets.main.output - testImplementation project(':util').sourceSets.test.output + implementation utilProject.sourceSets.main.output + testImplementation utilProject.sourceSets.test.output } } diff --git a/intellij-plugin/hs-sql/hs-sql-jvm/resources/hs-sql-jvm.xml b/intellij-plugin/hs-sql/hs-sql-jvm/resources/hs-sql-jvm.xml index 8bee861d9..e91b83bd4 100644 --- a/intellij-plugin/hs-sql/hs-sql-jvm/resources/hs-sql-jvm.xml +++ b/intellij-plugin/hs-sql/hs-sql-jvm/resources/hs-sql-jvm.xml @@ -7,6 +7,8 @@ + + diff --git a/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/SqlGradleStartupActivity.kt b/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/SqlGradleStartupActivity.kt index 9699b277e..305577020 100644 --- a/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/SqlGradleStartupActivity.kt +++ b/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/SqlGradleStartupActivity.kt @@ -11,8 +11,6 @@ import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.ide.progress.withBackgroundProgress -import com.intellij.sql.dialects.SqlDialectMappings -import com.intellij.sql.dialects.h2.H2Dialect import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.hyperskill.academy.learning.course @@ -36,7 +34,7 @@ class SqlGradleStartupActivity : ProjectActivity { // Use withContext(Dispatchers.Main) instead of invokeAndWaitIfNeeded withContext(Dispatchers.Main) { // Dependency on concrete database kind/SQL dialect - SqlDialectMappings.getInstance(project).setMapping(null, H2Dialect.INSTANCE) + setH2DialectMapping(project, null) createDataSources(project, course.allTasks) } diff --git a/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/sqlUtils.kt b/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/sqlUtils.kt index 854ac525e..846936df6 100644 --- a/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/sqlUtils.kt +++ b/intellij-plugin/hs-sql/hs-sql-jvm/src/org/hyperskill/academy/sql/jvm/gradle/sqlUtils.kt @@ -17,6 +17,7 @@ import com.intellij.database.dataSource.artifacts.DatabaseArtifactManager import com.intellij.database.model.DasDataSource import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.actions.ConfigurationContext +import com.intellij.lang.Language import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.progress.ProgressIndicator @@ -29,7 +30,7 @@ import com.intellij.platform.util.progress.reportRawProgress import com.intellij.psi.PsiManager import com.intellij.sql.SqlFileType import com.intellij.sql.dialects.SqlDialectMappings -import com.intellij.sql.dialects.h2.H2Dialect +import com.intellij.sql.dialects.SqlLanguageDialect import com.intellij.util.containers.addIfNotNull import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.await @@ -45,6 +46,7 @@ import org.hyperskill.academy.sql.jvm.gradle.compat.DatabaseArtifactLoaderCompat import org.jetbrains.annotations.VisibleForTesting private val LOG = Logger.getInstance("#org.hyperskill.academy.sql.jvm.gradle.SqlUtils") +private const val H2_DIALECT_ID = "H2" fun Task.findDataSource(project: Project): LocalDataSource? { val url = databaseUrl(project) @@ -307,10 +309,19 @@ private fun setSqlMappingForInitScripts(project: Project, tasks: List) { for (task in tasks) { val initSql = task.findInitSqlFile(project) ?: continue // Dependency on concrete database kind/SQL dialect - SqlDialectMappings.getInstance(project).setMapping(initSql, H2Dialect.INSTANCE) + setH2DialectMapping(project, initSql) } } +internal fun setH2DialectMapping(project: Project, file: VirtualFile?) { + val h2Dialect = Language.findLanguageByID(H2_DIALECT_ID) as? SqlLanguageDialect + if (h2Dialect == null) { + LOG.warn("Can't find SQL dialect by `$H2_DIALECT_ID` id") + return + } + SqlDialectMappings.getInstance(project).setMapping(file, h2Dialect) +} + private fun collectInitializeConfigurations(project: Project, tasks: List): List { val databaseConfigurations = mutableListOf() From 8886ea2b27c751eba9e81fd4dd9353305d3c9d21 Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Fri, 17 Jul 2026 19:08:38 +0300 Subject: [PATCH 3/4] fix courses hover effect --- gradle.properties | 2 +- .../academy/learning/newproject/ui/CourseCardComponent.kt | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index eab601468..ac07086c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # supported values: 252, 253, 261, 262 environmentName=262 -pluginVersion=2026.15 +pluginVersion=2026.16 # type of IDE (IDEA, CLion, etc.) used to build/test running # for more details see `Different IDEs` section in `PlatformVersions.md` diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt index c76607592..22df30733 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/newproject/ui/CourseCardComponent.kt @@ -1,5 +1,6 @@ package org.hyperskill.academy.learning.newproject.ui +import com.intellij.ui.ColorUtil import com.intellij.ui.Gray import com.intellij.ui.JBColor import com.intellij.ui.components.panels.NonOpaquePanel @@ -86,7 +87,10 @@ open class CourseCardComponent(val course: Course) : JPanel(BorderLayout()) { } private fun updateColors(background: Color) { - UIUtil.setBackgroundRecursively(this, background) + // Some themes define wildcard hover backgrounds with alpha. Applying such a color recursively + // makes every nested panel darken it again and leaves text from previous paints visible. + val opaqueBackground = ColorUtil.alphaBlending(background, SelectCourseBackgroundColor) + UIUtil.setBackgroundRecursively(this, opaqueBackground) } fun setSelection(isSelectedOrHover: Boolean, scroll: Boolean = false) { From fc96e3dbdafb0f97a6a13b1aabdf3c7a7d4d8790 Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Fri, 17 Jul 2026 19:11:06 +0300 Subject: [PATCH 4/4] change supported versions --- .github/workflows/ci.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d0dfbd6cf..dedb80800 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,9 +17,8 @@ jobs: strategy: matrix: environmentName: - - 252 - - 253 - 261 + - 262 fail-fast: false steps: - name: Checkout code