Snap for 11390309 from ac89181d1ffce18454c32f76479d5a073fdc4e80 to studio-jellyfish-release

Change-Id: I807ed72a761c4690466a134c03df8649ca7ff9b7
diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml
index 7da9322..8841b08 100644
--- a/.github/workflows/build-plugin.yml
+++ b/.github/workflows/build-plugin.yml
@@ -28,7 +28,7 @@
         strategy:
             fail-fast: true
             matrix:
-                platform-version: [ 231, 232 ]
+                platform-version: [ 232, 233 ]
         env:
             ORG_GRADLE_PROJECT_buildNumber: ${{ needs.generate-build-number.outputs.build_number }}
             ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }}
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 534d71c..2e232cc 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -20,9 +20,9 @@
     cancel-in-progress: true
 
 env:
-    OLD_IDEA_VERSION: IU-2023.1
-    OLD_CLION_VERSION: CL-2023.1
-    OLD_NATIVE_DEBUG_PLUGIN_VERSION: 231.8109.46
+    OLD_IDEA_VERSION: IU-2023.2
+    OLD_CLION_VERSION: CL-2023.2
+    OLD_NATIVE_DEBUG_PLUGIN: "com.intellij.nativeDebug:232.8660.142"
 
 jobs:
     get-rust-versions:
@@ -85,14 +85,14 @@
                 # Make sequence from two outputs with version is not possible here.
                 # TODO: check on more rustc versions
                 rust-version: ${{ fromJSON(needs.get-rust-versions.outputs.matrix) }}
-                platform-version: [ 231, 232 ]
+                platform-version: [ 232, 233 ]
                 include:
                     - os: ubuntu-latest
                       rust-version: ${{ needs.get-rust-versions.outputs.old }}
-                      platform-version: 231
+                      platform-version: 232
                     - os: windows-latest
                       rust-version: ${{ needs.get-rust-versions.outputs.old }}
-                      platform-version: 231
+                      platform-version: 232
 
         runs-on: ${{ matrix.os }}
         env:
@@ -116,7 +116,7 @@
               run: |
                   echo "ORG_GRADLE_PROJECT_ideaVersion=${{ env.OLD_IDEA_VERSION }}" >> $GITHUB_ENV
                   echo "ORG_GRADLE_PROJECT_clionVersion=${{ env.OLD_CLION_VERSION }}" >> $GITHUB_ENV
-                  echo "ORG_GRADLE_PROJECT_nativeDebugPluginVersion=${{ env.OLD_NATIVE_DEBUG_PLUGIN_VERSION }}" >> $GITHUB_ENV
+                  echo "ORG_GRADLE_PROJECT_nativeDebugPlugin=${{ env.OLD_NATIVE_DEBUG_PLUGIN }}" >> $GITHUB_ENV
 
             - name: Retrieve CLion version
               id: clion-version
@@ -176,7 +176,7 @@
                 # Make sequence from two outputs with version is not possible here.
                 rust-version: ${{ fromJSON(needs.get-rust-versions.outputs.matrix) }}
                 base-ide: [ idea, clion ]
-                platform-version: [ 231, 232 ]
+                platform-version: [ 232, 233 ]
                 # it's enough to verify plugin structure only once per platform version
                 verify-plugin: [ false ]
                 default-edition-for-tests: [ 2021 ]
@@ -184,7 +184,7 @@
                     - os: ubuntu-latest
                       rust-version: ${{ needs.get-rust-versions.outputs.old }}
                       base-ide: idea
-                      platform-version: 231
+                      platform-version: 232
                       verify-plugin: true
                       default-edition-for-tests: 2021
 
@@ -266,7 +266,7 @@
               run: |
                   echo "ORG_GRADLE_PROJECT_ideaVersion=${{ env.OLD_IDEA_VERSION }}" >> $GITHUB_ENV
                   echo "ORG_GRADLE_PROJECT_clionVersion=${{ env.OLD_CLION_VERSION }}" >> $GITHUB_ENV
-                  echo "ORG_GRADLE_PROJECT_nativeDebugPluginVersion=${{ env.OLD_NATIVE_DEBUG_PLUGIN_VERSION }}" >> $GITHUB_ENV
+                  echo "ORG_GRADLE_PROJECT_nativeDebugPlugin=${{ env.OLD_NATIVE_DEBUG_PLUGIN }}" >> $GITHUB_ENV
 
             - name: Set up test env variables
               run: echo "RUST_SRC_WITH_SYMLINK=$HOME/.rust-src" >> $GITHUB_ENV
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
deleted file mode 100644
index e98ff05..0000000
--- a/.github/workflows/code-quality.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: code quality
-on:
-    pull_request:
-
-jobs:
-    qodana:
-        runs-on: ubuntu-latest
-        timeout-minutes: 60
-
-        steps:
-            - uses: actions/checkout@v3
-              with:
-                  fetch-depth: 0
-
-            - name: Set up JDK 17
-              uses: actions/setup-java@v3
-              with:
-                  distribution: corretto
-                  java-version: 17
-
-            - name: Download
-              uses: gradle/gradle-build-action@v2
-              with:
-                  arguments: ":resolveDependencies -Pkotlin.incremental=false --no-daemon"
-                  gradle-home-cache-excludes: |
-                      caches/modules-2/files-2.1/com.jetbrains.intellij.idea
-                      caches/modules-2/files-2.1/com.jetbrains.intellij.clion
-
-            - name: Generate sources
-              uses: gradle/gradle-build-action@v2
-              with:
-                  arguments: ":generateLexer :generateParser debugger:generateGrammarSource -Pkotlin.incremental=false --no-daemon"
-                  gradle-home-cache-excludes: |
-                      caches/modules-2/files-2.1/com.jetbrains.intellij.idea
-                      caches/modules-2/files-2.1/com.jetbrains.intellij.clion
-
-            - name: Qodana
-              uses: JetBrains/qodana-action@v2023.1.5
-              with:
-                  use-caches: false
diff --git a/.github/workflows/rust-nightly.yml b/.github/workflows/rust-nightly.yml
index 55c34fa..e9ed964 100644
--- a/.github/workflows/rust-nightly.yml
+++ b/.github/workflows/rust-nightly.yml
@@ -56,7 +56,7 @@
         strategy:
             fail-fast: true
             matrix:
-                platform-version: [ 231, 232 ]
+                platform-version: [ 232, 233 ]
         env:
             ORG_GRADLE_PROJECT_buildNumber: ${{ needs.generate-build-number.outputs.build_number }}
             ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }}
diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml
index 0a7ec44..0b9bb24 100644
--- a/.github/workflows/rust-release.yml
+++ b/.github/workflows/rust-release.yml
@@ -111,7 +111,7 @@
         strategy:
             fail-fast: true
             matrix:
-                platform-version: [ 231, 232 ]
+                platform-version: [ 232, 233 ]
         env:
             ORG_GRADLE_PROJECT_buildNumber: ${{ needs.generate-build-number.outputs.build_number }}
             ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }}
diff --git a/.idea/runConfigurations/RunCLion.xml b/.idea/runConfigurations/RunCLion.xml
index bb55063..7190f21 100644
--- a/.idea/runConfigurations/RunCLion.xml
+++ b/.idea/runConfigurations/RunCLion.xml
@@ -1,12 +1,12 @@
 <component name="ProjectRunConfigurationManager">
   <configuration default="false" name="RunCLion" type="GradleRunConfiguration" factoryName="Gradle">
-    <log_file alias="idea-231.log" path="$PROJECT_DIR$/plugin/build/clion-sandbox-231/system/log/idea.log" />
     <log_file alias="idea-232.log" path="$PROJECT_DIR$/plugin/build/clion-sandbox-232/system/log/idea.log" />
+    <log_file alias="idea-233.log" path="$PROJECT_DIR$/plugin/build/clion-sandbox-233/system/log/idea.log" />
     <ExternalSystemSettings>
       <option name="executionName" />
       <option name="externalProjectPath" value="$PROJECT_DIR$" />
       <option name="externalSystemIdString" value="GRADLE" />
-      <option name="scriptParameters" value="-PbaseIDE=clion" />
+      <option name="scriptParameters" value="-PideToRun=clion" />
       <option name="taskDescriptions">
         <list />
       </option>
@@ -17,7 +17,10 @@
       </option>
       <option name="vmOptions" value="" />
     </ExternalSystemSettings>
-    <GradleScriptDebugEnabled>false</GradleScriptDebugEnabled>
+    <ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
     <method v="2" />
   </configuration>
 </component>
diff --git a/.idea/runConfigurations/RunIDEA.xml b/.idea/runConfigurations/RunIDEA.xml
index 41f4b0a..19b02d3 100644
--- a/.idea/runConfigurations/RunIDEA.xml
+++ b/.idea/runConfigurations/RunIDEA.xml
@@ -1,12 +1,12 @@
 <component name="ProjectRunConfigurationManager">
   <configuration default="false" name="RunIDEA" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
-    <log_file alias="idea-231.log" path="$PROJECT_DIR$/plugin/build/idea-sandbox-231/system/log/idea.log" />
     <log_file alias="idea-232.log" path="$PROJECT_DIR$/plugin/build/idea-sandbox-232/system/log/idea.log" />
+    <log_file alias="idea-233.log" path="$PROJECT_DIR$/plugin/build/idea-sandbox-233/system/log/idea.log" />
     <ExternalSystemSettings>
       <option name="executionName" />
       <option name="externalProjectPath" value="$PROJECT_DIR$" />
       <option name="externalSystemIdString" value="GRADLE" />
-      <option name="scriptParameters" value="-PbaseIDE=idea" />
+      <option name="scriptParameters" value="-PideToRun=idea" />
       <option name="taskDescriptions">
         <list />
       </option>
@@ -17,7 +17,10 @@
       </option>
       <option name="vmOptions" value="" />
     </ExternalSystemSettings>
-    <GradleScriptDebugEnabled>false</GradleScriptDebugEnabled>
+    <ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
     <method v="2" />
   </configuration>
 </component>
diff --git a/.idea/runConfigurations/RunRustRover.xml b/.idea/runConfigurations/RunRustRover.xml
new file mode 100644
index 0000000..614062d
--- /dev/null
+++ b/.idea/runConfigurations/RunRustRover.xml
@@ -0,0 +1,25 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="RunRustRover" type="GradleRunConfiguration" factoryName="Gradle">
+    <log_file alias="rr-233.log" path="$PROJECT_DIR$/plugin/build/rustRover-sandbox-233/system/log/idea.log" />
+    <ExternalSystemSettings>
+      <option name="executionName" />
+      <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      <option name="externalSystemIdString" value="GRADLE" />
+      <option name="scriptParameters" value="-PideToRun=rustRover" />
+      <option name="taskDescriptions">
+        <list />
+      </option>
+      <option name="taskNames">
+        <list>
+          <option value=":plugin:runIde" />
+        </list>
+      </option>
+      <option name="vmOptions" value="" />
+    </ExternalSystemSettings>
+    <ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
+    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
+    <DebugAllEnabled>false</DebugAllEnabled>
+    <RunAsTest>false</RunAsTest>
+    <method v="2" />
+  </configuration>
+</component>
diff --git a/build.gradle.kts b/build.gradle.kts
index 23da018..fc1da0e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,10 +3,9 @@
 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
 import org.gradle.api.tasks.testing.logging.TestLogEvent
 import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
-import org.jetbrains.intellij.tasks.PatchPluginXmlTask
-import org.jetbrains.intellij.tasks.PrepareSandboxTask
-import org.jetbrains.intellij.tasks.PublishPluginTask
-import org.jetbrains.intellij.tasks.RunIdeTask
+import org.jetbrains.intellij.tasks.*
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 import org.jsoup.Jsoup
 import java.io.Writer
@@ -21,16 +20,15 @@
 val channel = prop("publishChannel")
 val platformVersion = prop("platformVersion").toInt()
 val baseIDE = prop("baseIDE")
+val ideToRun = prop("ideToRun").ifEmpty { baseIDE }
 val ideaVersion = prop("ideaVersion")
 val clionVersion = prop("clionVersion")
-val baseVersion = when (baseIDE) {
-    "idea" -> ideaVersion
-    "clion" -> clionVersion
-    else -> error("Unexpected IDE name: `$baseIDE`")
-}
+val rustRoverVersion = prop("rustRoverVersion")
+val baseVersion = versionForIde(baseIDE)
+val baseVersionForRun = versionForIde(ideToRun)
 
 val tomlPlugin = "org.toml.lang"
-val nativeDebugPlugin: String by project
+val nativeDebugPlugin = if (baseIDE == "idea") prop("nativeDebugPlugin") else "com.intellij.nativeDebug"
 val graziePlugin = "tanvd.grazi"
 val psiViewerPlugin: String by project
 val intelliLangPlugin = "org.intellij.intelliLang"
@@ -38,7 +36,7 @@
 val javaPlugin = "com.intellij.java"
 val javaIdePlugin = "com.intellij.java.ide"
 val javaScriptPlugin = "JavaScript"
-val clionPlugins = listOf("com.intellij.cidr.base", "com.intellij.clion")
+val clionPlugins = listOf("com.intellij.cidr.base", "com.intellij.clion", nativeDebugPlugin)
 val mlCompletionPlugin = "com.intellij.completion.ml.ranking"
 
 val compileNativeCodeTaskName = "compileNativeCode"
@@ -49,8 +47,8 @@
 
 plugins {
     idea
-    kotlin("jvm") version "1.8.22"
-    id("org.jetbrains.intellij") version "1.13.1"
+    kotlin("jvm") version "1.9.0"
+    id("org.jetbrains.intellij") version "1.16.1"
     id("org.jetbrains.grammarkit") version "2022.3.1"
     id("net.saliman.properties") version "1.5.2"
     id("org.gradle.test-retry") version "1.5.3"
@@ -91,7 +89,7 @@
         updateSinceUntilBuild.set(true)
         instrumentCode.set(false)
         ideaDependencyCachePath.set(dependencyCachePath)
-        sandboxDir.set("$buildDir/$baseIDE-sandbox-$platformVersion")
+        sandboxDir.set(layout.buildDirectory.dir("$ideToRun-sandbox-$platformVersion").map { it.asFile.absolutePath })
     }
 
     configure<JavaPluginExtension> {
@@ -101,12 +99,12 @@
 
     tasks {
         withType<KotlinCompile> {
-            kotlinOptions {
-                jvmTarget = VERSION_17.toString()
-                languageVersion = "1.8"
+            compilerOptions {
+                jvmTarget.set(JvmTarget.JVM_17)
+                languageVersion.set(KotlinVersion.DEFAULT)
                 // see https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
-                apiVersion = "1.7"
-                freeCompilerArgs = listOf("-Xjvm-default=all")
+                apiVersion.set(KotlinVersion.KOTLIN_1_8)
+                freeCompilerArgs.set(listOf("-Xjvm-default=all"))
             }
         }
         withType<PatchPluginXmlTask> {
@@ -248,15 +246,28 @@
 val pluginProjects: List<Project>
     get() = rootProject.allprojects.filter { it.name != grammarKitFakePsiDeps }
 
-val channelSuffix = if (channel.isBlank() || channel == "stable") "" else "-$channel"
-val versionSuffix = "-$platformVersion$channelSuffix"
-val majorVersion = "0.4"
-val patchVersion = prop("patchVersion").toInt()
-
-// Special module with run, build and publish tasks
+// Special module with run, build, and publish tasks
 project(":plugin") {
-    version = "$majorVersion.$patchVersion.${prop("buildNumber")}$versionSuffix"
+    val pluginVersion = System.getenv("BUILD_NUMBER") ?: "${platformVersion}.${prop("buildNumber")}"
+    version = if (pluginVersion.contains(".")) {
+        val split = pluginVersion.split(".").toMutableList()
+        split[0] = platformVersion.toString()
+        // From 232 branch, plugin version `232.9921` and `233.9921` were published
+        // These versions were based on IJ platform `232.9921` build
+        //
+        // From 233 branch, plugin versions are `233.8264` and `232.8264`, which are based on IJ platform `233.8264` build
+        // Since we publish versions for 2 platform versions from a single branch, plugin versions has to be hacked this way,
+        // Otherwise newly published `233.8264` version would be considered lower than `233.9921` published earlier
+        //
+        // TODO: For `241`, come up with new plugin versioning to avoid this problem
+        split[1] = (split[1].toIntOrNull()?.plus(10000))?.toString() ?: split[1]
+        split.joinToString(".")
+    } else {
+        pluginVersion
+    }
+
     intellij {
+        version.set(baseVersionForRun)
         pluginName.set("intellij-rust")
         val pluginList = mutableListOf(
             tomlPlugin,
@@ -266,7 +277,7 @@
             javaScriptPlugin,
             mlCompletionPlugin
         )
-        if (baseIDE == "idea") {
+        if (ideToRun == "idea") {
             pluginList += listOf(
                 copyrightPlugin,
                 javaPlugin,
@@ -354,11 +365,16 @@
             finalizedBy(mergePluginJarTask)
             enabled = true
         }
-        buildSearchableOptions {
-            // Force `mergePluginJarTask` be executed before `buildSearchableOptions`
-            // Otherwise, `buildSearchableOptions` task can't load the plugin and searchable options are not built.
+        withType<RunIdeBase> {
+            // Force `mergePluginJarTask` be executed before any task based on `RunIdeBase` (for example, `runIde` or `buildSearchableOptions`).
+            // Otherwise, these tasks fail because of implicit dependency.
             // Should be dropped when jar merging is implemented in `gradle-intellij-plugin` itself
             dependsOn(mergePluginJarTask)
+        }
+        verifyPlugin {
+            dependsOn(mergePluginJarTask)
+        }
+        buildSearchableOptions {
             enabled = prop("enableBuildSearchableOptions").toBoolean()
         }
         withType<PrepareSandboxTask> {
@@ -407,10 +423,10 @@
     task<RunIdeTask>("buildEventsScheme") {
         dependsOn(tasks.prepareSandbox)
         args("buildEventsScheme", "--outputFile=${buildDir.resolve("eventScheme.json").absolutePath}", "--pluginId=org.rust.lang")
-        // BACKCOMPAT: 2023.1. Update value to 232 and this comment
+        // BACKCOMPAT: 2023.2. Update value to 233 and this comment
         // `IDEA_BUILD_NUMBER` variable is used by `buildEventsScheme` task to write `buildNumber` to output json.
         // It will be used by TeamCity automation to set minimal IDE version for new events
-        environment("IDEA_BUILD_NUMBER", "231")
+        environment("IDEA_BUILD_NUMBER", "232")
     }
 }
 
@@ -739,6 +755,12 @@
     extra.properties[name] as? String
         ?: error("Property `$name` is not defined in gradle.properties")
 
+fun versionForIde(ideName: String): String = when (ideName) {
+    "idea" -> ideaVersion
+    "clion" -> clionVersion
+    "rustRover" -> rustRoverVersion
+    else -> error("Unexpected IDE name: `$baseIDE`")
+}
 
 inline operator fun <T : Task> T.invoke(a: T.() -> Unit): T = apply(a)
 
diff --git a/clion/src/232/main/kotlin/org/rust/clion/valgrind/Compat.kt b/clion/src/232/main/kotlin/org/rust/clion/valgrind/Compat.kt
new file mode 100644
index 0000000..c9a83e8
--- /dev/null
+++ b/clion/src/232/main/kotlin/org/rust/clion/valgrind/Compat.kt
@@ -0,0 +1,22 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.clion.valgrind
+
+typealias MemoryProfileOutputPanel = com.jetbrains.cidr.cpp.profiling.ui.MemoryProfileOutputPanel
+typealias EditValgrindSettingsAction = com.jetbrains.cidr.cpp.valgrind.actions.EditValgrindSettingsAction
+typealias CLionProfilingBundle = com.jetbrains.cidr.cpp.CLionProfilingBundle
+typealias ValgrindSettings = com.jetbrains.cidr.cpp.valgrind.ValgrindSettings
+typealias ValgrindConfigurable = com.jetbrains.cidr.cpp.valgrind.ValgrindConfigurable
+typealias ValgrindCommandLineParametersBuilder = com.jetbrains.cidr.cpp.valgrind.ValgrindCommandLineParametersBuilder
+typealias MemoryProfileTreeDataModel = com.jetbrains.cidr.cpp.profiling.MemoryProfileTreeDataModel
+typealias ValgrindUtil = com.jetbrains.cidr.cpp.valgrind.ValgrindUtil
+typealias ValgrindExecutor = com.jetbrains.cidr.cpp.valgrind.ValgrindExecutor
+typealias MemoryProfileConsoleViewWrapper = com.jetbrains.cidr.cpp.profiling.MemoryProfileConsoleViewWrapper
+typealias ValgrindHandler = com.jetbrains.cidr.cpp.valgrind.ValgrindHandler
+typealias ValgrindOutputConsumer = com.jetbrains.cidr.cpp.valgrind.ValgrindOutputConsumer
+typealias MemoryProfileStringAccumulator = com.jetbrains.cidr.cpp.profiling.MemoryProfileStringAccumulator
+typealias MemoryProfileCompositeConsumer = com.jetbrains.cidr.cpp.profiling.MemoryProfileCompositeConsumer
+typealias MemoryProfileFileReader = com.jetbrains.cidr.cpp.profiling.MemoryProfileFileReader
diff --git a/clion/src/233/main/kotlin/org/rust/clion/valgrind/Compat.kt b/clion/src/233/main/kotlin/org/rust/clion/valgrind/Compat.kt
new file mode 100644
index 0000000..1535ef6
--- /dev/null
+++ b/clion/src/233/main/kotlin/org/rust/clion/valgrind/Compat.kt
@@ -0,0 +1,22 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.clion.valgrind
+
+typealias MemoryProfileOutputPanel = com.jetbrains.cidr.cpp.profiling.memory.ui.MemoryProfileOutputPanel
+typealias EditValgrindSettingsAction = com.jetbrains.cidr.cpp.profiling.valgrind.actions.EditValgrindSettingsAction
+typealias CLionProfilingBundle = com.jetbrains.cidr.cpp.profiling.CLionProfilingBundle
+typealias ValgrindSettings = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindSettings
+typealias ValgrindConfigurable = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindConfigurable
+typealias ValgrindCommandLineParametersBuilder = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindCommandLineParametersBuilder
+typealias MemoryProfileTreeDataModel = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileTreeDataModel
+typealias ValgrindUtil = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindUtil
+typealias ValgrindExecutor = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindExecutor
+typealias MemoryProfileConsoleViewWrapper = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileConsoleViewWrapper
+typealias ValgrindHandler = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindHandler
+typealias ValgrindOutputConsumer = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindOutputConsumer
+typealias MemoryProfileStringAccumulator = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileStringAccumulator
+typealias MemoryProfileCompositeConsumer = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileCompositeConsumer
+typealias MemoryProfileFileReader = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileFileReader
diff --git a/clion/src/241/main/kotlin/org/rust/clion/valgrind/Compat.kt b/clion/src/241/main/kotlin/org/rust/clion/valgrind/Compat.kt
new file mode 100644
index 0000000..1535ef6
--- /dev/null
+++ b/clion/src/241/main/kotlin/org/rust/clion/valgrind/Compat.kt
@@ -0,0 +1,22 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.clion.valgrind
+
+typealias MemoryProfileOutputPanel = com.jetbrains.cidr.cpp.profiling.memory.ui.MemoryProfileOutputPanel
+typealias EditValgrindSettingsAction = com.jetbrains.cidr.cpp.profiling.valgrind.actions.EditValgrindSettingsAction
+typealias CLionProfilingBundle = com.jetbrains.cidr.cpp.profiling.CLionProfilingBundle
+typealias ValgrindSettings = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindSettings
+typealias ValgrindConfigurable = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindConfigurable
+typealias ValgrindCommandLineParametersBuilder = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindCommandLineParametersBuilder
+typealias MemoryProfileTreeDataModel = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileTreeDataModel
+typealias ValgrindUtil = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindUtil
+typealias ValgrindExecutor = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindExecutor
+typealias MemoryProfileConsoleViewWrapper = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileConsoleViewWrapper
+typealias ValgrindHandler = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindHandler
+typealias ValgrindOutputConsumer = com.jetbrains.cidr.cpp.profiling.valgrind.ValgrindOutputConsumer
+typealias MemoryProfileStringAccumulator = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileStringAccumulator
+typealias MemoryProfileCompositeConsumer = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileCompositeConsumer
+typealias MemoryProfileFileReader = com.jetbrains.cidr.cpp.profiling.memory.MemoryProfileFileReader
diff --git a/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindConfigurationExtension.kt b/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindConfigurationExtension.kt
index 940fa5c..076cd15 100644
--- a/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindConfigurationExtension.kt
+++ b/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindConfigurationExtension.kt
@@ -28,11 +28,6 @@
 import com.intellij.util.ArrayUtil
 import com.intellij.util.Consumer
 import com.intellij.util.ui.StatusText
-import com.jetbrains.cidr.cpp.CLionProfilingBundle
-import com.jetbrains.cidr.cpp.profiling.*
-import com.jetbrains.cidr.cpp.profiling.ui.MemoryProfileOutputPanel
-import com.jetbrains.cidr.cpp.valgrind.*
-import com.jetbrains.cidr.cpp.valgrind.actions.EditValgrindSettingsAction
 import com.jetbrains.cidr.lang.toolchains.CidrToolEnvironment
 import org.rust.RsBundle
 import org.rust.cargo.runconfig.CargoCommandConfigurationExtension
@@ -87,7 +82,7 @@
             val valgrindParameters = parametersBuilder.build(outputFilePath)
             valgrindParameters.add(programPath)
             cmdLine.parametersList.prependAll(*ArrayUtil.toStringArray(valgrindParameters))
-            toolchain.patchCommandLine(cmdLine)
+            toolchain.patchCommandLine(cmdLine, withSudo = false)
             putUserData<File>(OUTPUT_FILE_PATH_KEY, outputFile, configuration, context)
         } catch (e: IOException) {
             throw ExecutionException(e)
diff --git a/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindRunner.kt b/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindRunner.kt
index 702dc9a..524a000 100644
--- a/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindRunner.kt
+++ b/clion/src/main/kotlin/org/rust/clion/valgrind/RsValgrindRunner.kt
@@ -6,7 +6,6 @@
 package org.rust.clion.valgrind
 
 import com.intellij.execution.configurations.RunProfile
-import com.jetbrains.cidr.cpp.valgrind.ValgrindExecutor
 import org.rust.RsBundle
 import org.rust.cargo.runconfig.RsExecutableRunner
 import org.rust.cargo.runconfig.command.CargoCommandConfiguration
diff --git a/clion/src/main/kotlin/org/rust/clion/valgrind/legacy/RsValgrindRunnerLegacy.kt b/clion/src/main/kotlin/org/rust/clion/valgrind/legacy/RsValgrindRunnerLegacy.kt
index c211142..d73df00 100644
--- a/clion/src/main/kotlin/org/rust/clion/valgrind/legacy/RsValgrindRunnerLegacy.kt
+++ b/clion/src/main/kotlin/org/rust/clion/valgrind/legacy/RsValgrindRunnerLegacy.kt
@@ -7,12 +7,12 @@
 
 import com.intellij.execution.configurations.RunProfile
 import com.intellij.openapi.util.NlsContexts
-import com.jetbrains.cidr.cpp.valgrind.ValgrindExecutor
 import org.rust.RsBundle
 import org.rust.cargo.runconfig.buildtool.CargoBuildManager.isBuildToolWindowAvailable
 import org.rust.cargo.runconfig.command.CargoCommandConfiguration
 import org.rust.cargo.runconfig.legacy.RsAsyncRunner
 import org.rust.clion.valgrind.RsValgrindConfigurationExtension
+import org.rust.clion.valgrind.ValgrindExecutor
 
 @NlsContexts.DialogTitle
 private val ERROR_MESSAGE_TITLE: String = RsBundle.message("dialog.title.unable.to.run.valgrind")
diff --git a/debugger/src/231/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt b/debugger/src/231/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt
deleted file mode 100644
index 6794ffa..0000000
--- a/debugger/src/231/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.debugger
-
-import com.intellij.util.system.CpuArch
-import com.intellij.util.system.OS
-import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBBinUrlProvider
-import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBBinUrlProvider.Bin
-import java.net.URL
-
-object RsDebuggerUrlProvider {
-    fun lldbFrontend(os: OS, arch: CpuArch): URL? = LLDBBinUrlProvider.lldbFrontend.url(os, arch)
-    fun lldb(os: OS, arch: CpuArch): URL? = LLDBBinUrlProvider.lldb.url(os, arch)
-    @Suppress("UNUSED_PARAMETER")
-    fun gdb(os: OS, arch: CpuArch): URL? = null
-
-    private fun Bin.url(os: OS, arch: CpuArch): URL? {
-        return when (os) {
-            // Binaries for macos are universal, i.e. they may work on x86 and arm
-            OS.macOS -> macX64
-            OS.Linux -> {
-                when (arch) {
-                    CpuArch.X86_64 -> linuxX64
-                    CpuArch.ARM64 -> linuxAarch64
-                    else -> null
-                }
-            }
-            OS.Windows -> {
-                when (arch) {
-                    CpuArch.X86_64 -> winX64
-                    CpuArch.ARM64 -> winAarch64
-                    else -> null
-                }
-            }
-            else -> null
-        }
-    }
-}
diff --git a/debugger/src/232/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt b/debugger/src/232/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt
deleted file mode 100644
index 2969d94..0000000
--- a/debugger/src/232/main/kotlin/org/rust/debugger/RsDebuggerUrlProvider.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.debugger
-
-import com.intellij.util.system.CpuArch
-import com.intellij.util.system.OS
-import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
-import java.net.URL
-
-// BACKCOMPAT: 2023.1. Use `UrlProvider` directly
-object RsDebuggerUrlProvider {
-    fun lldbFrontend(os: OS, arch: CpuArch): URL? = UrlProvider.lldbFrontend(os, arch)
-    fun lldb(os: OS, arch: CpuArch): URL? = UrlProvider.lldb(os, arch)
-    fun gdb(os: OS, arch: CpuArch): URL? = UrlProvider.gdb(os, arch)
-}
diff --git a/debugger/src/241/main/resources/META-INF/org.rust.debugger.platform.xml b/debugger/src/241/main/resources/META-INF/org.rust.debugger.platform.xml
new file mode 100644
index 0000000..e51ee43
--- /dev/null
+++ b/debugger/src/241/main/resources/META-INF/org.rust.debugger.platform.xml
@@ -0,0 +1,10 @@
+<idea-plugin >
+    <extensions defaultExtensionNs="com.intellij">
+        <registryKey key="org.rust.debugger.gdb.windows"
+                     defaultValue="true"
+                     description="Enables Rust debugging with GDB on Windows"/>
+        <registryKey key="org.rust.debugger.gdb.wsl"
+                     defaultValue="true"
+                     description="Enables Rust debugging with GDB on WSL"/>
+    </extensions>
+</idea-plugin>
diff --git a/debugger/src/main/kotlin/org/rust/debugger/RsDebuggerToolchainService.kt b/debugger/src/main/kotlin/org/rust/debugger/RsDebuggerToolchainService.kt
index 465c4e4..28179a4 100644
--- a/debugger/src/main/kotlin/org/rust/debugger/RsDebuggerToolchainService.kt
+++ b/debugger/src/main/kotlin/org/rust/debugger/RsDebuggerToolchainService.kt
@@ -24,6 +24,7 @@
 import com.intellij.util.system.CpuArch
 import com.intellij.util.system.OS
 import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
+import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
 import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
 import org.rust.RsBundle
 import org.rust.openapiext.RsPathManager
@@ -152,12 +153,12 @@
     }
 
     private fun lldbUrls(): Pair<URL, URL>? {
-        val lldb = RsDebuggerUrlProvider.lldb(OS.CURRENT, CpuArch.CURRENT) ?: return null
-        val lldbFrontend = RsDebuggerUrlProvider.lldbFrontend(OS.CURRENT, CpuArch.CURRENT) ?: return null
+        val lldb = UrlProvider.lldb(OS.CURRENT, CpuArch.CURRENT) ?: return null
+        val lldbFrontend = UrlProvider.lldbFrontend(OS.CURRENT, CpuArch.CURRENT) ?: return null
         return lldb to lldbFrontend
     }
 
-    private fun gdbUrl(): URL? = RsDebuggerUrlProvider.gdb(OS.CURRENT, CpuArch.CURRENT)
+    private fun gdbUrl(): URL? = UrlProvider.gdb(OS.CURRENT, CpuArch.CURRENT)
 
     @Throws(IOException::class)
     private fun downloadAndUnarchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
diff --git a/debugger/src/test/kotlin/org/rust/debugger/RsDebuggerToolchainServiceTest.kt b/debugger/src/test/kotlin/org/rust/debugger/RsDebuggerToolchainServiceTest.kt
index 0118587..a4aa24b 100644
--- a/debugger/src/test/kotlin/org/rust/debugger/RsDebuggerToolchainServiceTest.kt
+++ b/debugger/src/test/kotlin/org/rust/debugger/RsDebuggerToolchainServiceTest.kt
@@ -5,8 +5,6 @@
 
 package org.rust.debugger
 
-import com.intellij.openapi.application.ApplicationInfo
-import com.intellij.openapi.util.BuildNumber
 import com.intellij.openapi.util.SystemInfo
 import com.intellij.openapi.util.registry.Registry
 import com.intellij.util.PlatformUtils
@@ -44,7 +42,7 @@
     }
 
     fun `test gdb loading and update`() {
-        if (ApplicationInfo.getInstance().build < BUILD_232 || SystemInfo.isMac) return
+        if (SystemInfo.isMac) return
         checkDebuggerLoadingAndUpdate(DebuggerKind.GDB)
     }
 
@@ -96,9 +94,4 @@
             expectedFile.checkExistence()
         }
     }
-
-    companion object {
-        // BACKCOMPAT: 2023.1
-        private val BUILD_232 = BuildNumber.fromString("232")!!
-    }
 }
diff --git a/gradle-232.properties b/gradle-232.properties
index 23437bd..71ea311 100644
--- a/gradle-232.properties
+++ b/gradle-232.properties
@@ -1,8 +1,9 @@
 # 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-2023.2
-clionVersion=CL-2023.2
+ideaVersion=IU-2023.2.1
+clionVersion=CL-2023.2.1
+rustRoverVersion=
 
 # https://plugins.jetbrains.com/plugin/12775-native-debugging-support/versions
 nativeDebugPlugin=com.intellij.nativeDebug:232.8660.142
@@ -11,4 +12,4 @@
 
 # please see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description
 sinceBuild=232.8296
-untilBuild=233.*
+untilBuild=232.*
diff --git a/gradle-231.properties b/gradle-233.properties
similarity index 61%
rename from gradle-231.properties
rename to gradle-233.properties
index 08becf7..70a7b50 100644
--- a/gradle-231.properties
+++ b/gradle-233.properties
@@ -1,14 +1,15 @@
 # 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-2023.1
-clionVersion=CL-2023.1
+ideaVersion=IU-233.13135-EAP-CANDIDATE-SNAPSHOT
+clionVersion=CL-233.13135-EAP-CANDIDATE-SNAPSHOT
+rustRoverVersion=RR-233.11799-EAP-CANDIDATE-SNAPSHOT
 
 # https://plugins.jetbrains.com/plugin/12775-native-debugging-support/versions
-nativeDebugPlugin=com.intellij.nativeDebug:231.8109.91
+nativeDebugPlugin=com.intellij.nativeDebug:233.13135.65
 # https://plugins.jetbrains.com/plugin/227-psiviewer/versions
-psiViewerPlugin=PsiViewer:231-SNAPSHOT
+psiViewerPlugin=PsiViewer:233.2
 
 # please see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description
-sinceBuild=231.7515
-untilBuild=231.*
+sinceBuild=233.8264
+untilBuild=233.*
diff --git a/gradle-231.properties b/gradle-241.properties
similarity index 61%
copy from gradle-231.properties
copy to gradle-241.properties
index 08becf7..1608ff3 100644
--- a/gradle-231.properties
+++ b/gradle-241.properties
@@ -1,14 +1,15 @@
 # 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-2023.1
-clionVersion=CL-2023.1
+ideaVersion=IU-233.13135-EAP-CANDIDATE-SNAPSHOT
+clionVersion=CL-233.13135-EAP-CANDIDATE-SNAPSHOT
+rustRoverVersion=RR-233.11799-EAP-CANDIDATE-SNAPSHOT
 
 # https://plugins.jetbrains.com/plugin/12775-native-debugging-support/versions
-nativeDebugPlugin=com.intellij.nativeDebug:231.8109.91
+nativeDebugPlugin=com.intellij.nativeDebug:233.13135.65
 # https://plugins.jetbrains.com/plugin/227-psiviewer/versions
-psiViewerPlugin=PsiViewer:231-SNAPSHOT
+psiViewerPlugin=PsiViewer:233.2
 
 # please see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description
-sinceBuild=231.7515
-untilBuild=231.*
+sinceBuild=241.17
+untilBuild=241.*
diff --git a/gradle.properties b/gradle.properties
index e0e5479..f7ad3b7 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,11 +1,11 @@
 propertiesPluginEnvironmentNameProperty=platformVersion
-# Supported platforms: 231, 232
-platformVersion=232
-# Supported IDEs: idea, clion
+# Supported platforms: 232, 233, 241
+platformVersion=233
+# Supported IDEs: idea, clion, rustRover
 baseIDE=idea
+ideToRun=
 
-patchVersion=201
-buildNumber=SNAPSHOT
+buildNumber=999999-SNAPSHOT
 
 publishToken=token
 publishChannel=dev
@@ -20,7 +20,7 @@
 org.gradle.jvmargs=-Xmx3g
 
 # Enable Gradle Build Cache. It is also configured in settings.gradle.kts.
-org.gradle.caching=true
+org.gradle.caching=false
 
 systemProp.org.gradle.internal.repository.max.tentatives=10
 systemProp.org.gradle.internal.http.connectionTimeout=120000
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 943f0cb..7f93135 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2b22d05..d11cdd9 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
 networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 65dcd68..0adc8e1 100755
--- a/gradlew
+++ b/gradlew
@@ -83,10 +83,8 @@
 # This is normally unused
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -133,10 +131,13 @@
     fi
 else
     JAVACMD=java
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
+    fi
 fi
 
 # Increase the maximum file descriptors if we can.
@@ -144,7 +145,7 @@
     case $MAX_FD in #(
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
@@ -152,7 +153,7 @@
       '' | soft) :;; #(
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -197,6 +198,10 @@
     done
 fi
 
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
 # Collect all arguments for the java command;
 #   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
 #     shell script including quotes and variable substitutions, so put them in
diff --git a/idea/src/test/kotlin/org/rust/ide/idea/RsIdeaProjectViewTest.kt b/idea/src/test/kotlin/org/rust/ide/idea/RsIdeaProjectViewTest.kt
index 66877c1..b13fcf5 100644
--- a/idea/src/test/kotlin/org/rust/ide/idea/RsIdeaProjectViewTest.kt
+++ b/idea/src/test/kotlin/org/rust/ide/idea/RsIdeaProjectViewTest.kt
@@ -5,6 +5,8 @@
 
 package org.rust.ide.idea
 
+import org.junit.Ignore
 import org.rustSlowTests.RsProjectViewTestBase
 
+@Ignore // TODO RUST-12027 doesn't work on 233
 class RsIdeaProjectViewTest : RsProjectViewTestBase()
diff --git a/profiler/src/231/main/kotlin/org/rust/profiler/perf/compatUtils.kt b/profiler/src/231/main/kotlin/org/rust/profiler/perf/compatUtils.kt
deleted file mode 100644
index ba686b3..0000000
--- a/profiler/src/231/main/kotlin/org/rust/profiler/perf/compatUtils.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.profiler.perf
-
-import com.intellij.execution.process.BaseProcessHandler
-import com.intellij.openapi.project.Project
-import com.intellij.profiler.clion.perf.PerfProfilerProcess
-import com.jetbrains.cidr.lang.toolchains.CidrToolEnvironment
-import java.nio.file.Path
-
-fun createProfilerProcess(
-    handler: BaseProcessHandler<*>,
-    outputPerfDataFile: Path,
-    processName: String,
-    project: Project,
-    attachedTimestamp: Long,
-    toolEnvironment: CidrToolEnvironment,
-): PerfProfilerProcess = PerfProfilerProcess(
-    handler,
-    false,
-    outputPerfDataFile,
-    processName,
-    project,
-    attachedTimestamp,
-    toolEnvironment
-)
diff --git a/profiler/src/main/kotlin/org/rust/profiler/RsCachingStackElementReader.kt b/profiler/src/232/main/kotlin/org/rust/profiler/RsCachingStackElementReader.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/RsCachingStackElementReader.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/RsCachingStackElementReader.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/RsProfilerRunner.kt b/profiler/src/232/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/RsSymbolSearcher.kt b/profiler/src/232/main/kotlin/org/rust/profiler/RsSymbolSearcher.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/RsSymbolSearcher.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/RsSymbolSearcher.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt b/profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceNavigatableNativeCall.kt b/profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceNavigatableNativeCall.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceNavigatableNativeCall.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceNavigatableNativeCall.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt b/profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt b/profiler/src/232/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
similarity index 100%
rename from profiler/src/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
diff --git a/profiler/src/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt b/profiler/src/232/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
similarity index 97%
rename from profiler/src/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
rename to profiler/src/232/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
index f059355..123f1ac 100644
--- a/profiler/src/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
+++ b/profiler/src/232/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
@@ -15,6 +15,7 @@
 import com.intellij.openapi.util.Key
 import com.intellij.openapi.util.SystemInfo
 import com.intellij.profiler.ProfilerToolWindowManager
+import com.intellij.profiler.clion.perf.PerfProfilerProcess
 import com.intellij.profiler.clion.perf.PerfProfilerSettings
 import com.intellij.profiler.clion.perf.PerfUtils
 import com.intellij.profiler.statistics.ProfilerUsageTriggerCollector
@@ -65,7 +66,7 @@
         val perfPath = toolchain.toLocalPath(settings.executablePath.orEmpty())
         val outputFilePath = PerfUtils.createOutputFilePath(toolEnvironment, settings.outputDirectory.nullize())
         cmdLine.addPerfStarter(perfPath, settings.samplingFrequency, settings.defaultCmdArgs, outputFilePath.toString())
-        toolchain.patchCommandLine(cmdLine)
+        toolchain.patchCommandLine(cmdLine, withSudo = false)
         context.putUserData(PERF_OUTPUT_FILE_KEY, outputFilePath)
     }
 
@@ -89,7 +90,7 @@
             ?: throw ExecutionException(RsBundle.message("dialog.message.can.t.get.output.perf.data.file"))
 
         val project = configuration.project
-        val profilerProcess = createProfilerProcess(
+        val profilerProcess = PerfProfilerProcess(
             handler,
             outputFile,
             configuration.name,
diff --git a/profiler/src/232/main/kotlin/org/rust/profiler/perf/compatUtils.kt b/profiler/src/232/main/kotlin/org/rust/profiler/perf/compatUtils.kt
deleted file mode 100644
index 97dc93b..0000000
--- a/profiler/src/232/main/kotlin/org/rust/profiler/perf/compatUtils.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.profiler.perf
-
-import com.intellij.execution.process.BaseProcessHandler
-import com.intellij.openapi.project.Project
-import com.intellij.profiler.clion.perf.PerfProfilerProcess
-import com.jetbrains.cidr.lang.toolchains.CidrToolEnvironment
-import java.nio.file.Path
-
-// BACKCOMPAT: 2023.1. Inline it
-fun createProfilerProcess(
-    handler: BaseProcessHandler<*>,
-    outputPerfDataFile: Path,
-    processName: String,
-    project: Project,
-    attachedTimestamp: Long,
-    toolEnvironment: CidrToolEnvironment,
-): PerfProfilerProcess = PerfProfilerProcess(
-    handler,
-    outputPerfDataFile,
-    processName,
-    project,
-    attachedTimestamp,
-    toolEnvironment
-)
diff --git a/profiler/src/main/resources/org.rust.profiler.xml b/profiler/src/232/main/resources/org.rust.profiler.xml
similarity index 100%
rename from profiler/src/main/resources/org.rust.profiler.xml
rename to profiler/src/232/main/resources/org.rust.profiler.xml
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt
new file mode 100644
index 0000000..7890ebe
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt
@@ -0,0 +1,28 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.process.ProcessInfo
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.UserDataHolder
+import com.intellij.xdebugger.attach.XAttachProcessPresentationGroup
+import org.rust.RsBundle
+import javax.swing.Icon
+
+object RsAttachPresentationGroup: XAttachProcessPresentationGroup {
+    override fun getOrder(): Int = 0
+
+    override fun getGroupName(): String = RsBundle.message("profiler.attach.default.group.title")
+
+    override fun getItemIcon(project: Project, info: ProcessInfo, dataHolder: UserDataHolder): Icon {
+        return AllIcons.RunConfigurations.Application
+    }
+
+    override fun getItemDisplayText(project: Project, info: ProcessInfo, dataHolder: UserDataHolder): String {
+        return info.executableDisplayName
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt
new file mode 100644
index 0000000..b801fc9
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt
@@ -0,0 +1,55 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.clion.NavigatableSymbolSearcher
+import com.intellij.psi.NavigatablePsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.rust.lang.core.psi.RsFunction
+import org.rust.lang.core.psi.ext.qualifiedName
+import org.rust.lang.core.stubs.index.RsNamedElementIndex
+import org.rust.openapiext.getElements
+
+class RsNavigatableSymbolSearcher : NavigatableSymbolSearcher() {
+
+    override fun doFindNavigatableSymbols(qualifiedSignature: String, project: Project): Array<NavigatablePsiElement> {
+        val elements = mutableListOf<RsFunction>()
+        val searchScope = GlobalSearchScope.allScope(project)
+        val qualifiedPath = extractPath(qualifiedSignature)
+
+        runReadAction {
+            val allDeclarations = getElements(RsNamedElementIndex.KEY, qualifiedSignature, project, searchScope)
+                .filterIsInstance<RsFunction>()
+            val appropriateDeclarations = allDeclarations
+                .filter { it.qualifiedName?.substringBeforeLast("::") == qualifiedPath }
+
+            if (appropriateDeclarations.isNotEmpty()) {
+                elements.addAll(appropriateDeclarations)
+            } else {
+                // TODO: return all or nothing?
+                elements.addAll(allDeclarations)
+            }
+        }
+
+        return elements.toTypedArray()
+    }
+
+    companion object {
+        /**
+         * `_<foo::bar123::foo_bar::Qux as baz>::qux` -> `foo::bar123::foo_bar`
+         *
+         * It doesn't work for crates/modules with inappropriate names (e.g. upper-case)
+         *
+         * TODO: add tests
+         */
+        private fun extractPath(signature: String): String = signature
+            .dropWhile { !it.isLetter() }
+            .takeWhile { (it.isLetterOrDigit() && it.isLowerCase()) || it == '_' || it == ':' }
+            .removeSuffix("::")
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt
new file mode 100644
index 0000000..53d1ff6
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt
@@ -0,0 +1,128 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configuration.EnvironmentVariablesData
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.PtyCommandLine
+import com.intellij.execution.process.*
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.profiler.clion.ProfilerEnvironmentHost
+import com.intellij.xdebugger.attach.WslAttachHost
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.RsCapturingProcessHandler
+import org.rust.cargo.runconfig.RsProcessHandler
+import org.rust.cargo.toolchain.BacktraceMode
+import org.rust.cargo.toolchain.RsLocalToolchain
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+import org.rust.openapiext.execute
+import org.rust.stdext.toPath
+import org.rust.stdext.toPathOrNull
+import java.nio.file.Path
+
+class RsProfilerEnvironmentHost : ProfilerEnvironmentHost {
+
+    override fun shouldCheckFilePathExist(project: Project): Boolean {
+        val toolchain = project.toolchain ?: return false
+        return toolchain is RsLocalToolchain
+    }
+
+    override fun getPath(path: String, project: Project): Path =
+        project.toolchain?.toLocalPath(path)?.toPathOrNull() ?: Path.of(path) // TODO: return null
+
+    override fun getEnvPath(path: String?, project: Project): String {
+        if (path == null) return "" // TODO: return null
+        return project.toolchain?.toRemotePath(path).toString()
+    }
+
+    override fun getTempDirectory(project: Project): Path = FileUtil.getTempDirectory().toPath()
+
+    override fun isRemote(project: Project): Boolean {
+        val toolchain = project.toolchain ?: return false
+        return toolchain !is RsLocalToolchain
+    }
+
+    override fun isWSL(project: Project): Boolean = project.toolchain is RsWslToolchain
+
+    override fun getWSLVersion(project: Project): Int {
+        val toolchain = project.toolchain as? RsWslToolchain ?: return -1
+        return toolchain.wslPath.distribution.version
+    }
+
+    override fun createProcessHandler(
+        commandLine: GeneralCommandLine,
+        project: Project,
+        colored: Boolean?,
+        usePty: Boolean?,
+        captureProcessOutput: Boolean?,
+        splitLines: Boolean?, // TODO: take it into account
+        withElevated: Boolean?
+    ): BaseProcessHandler<*> {
+        var tmpCommandLine = commandLine
+
+        if (usePty == true) {
+            tmpCommandLine = PtyCommandLine(commandLine)
+                .withInitialColumns(PtyCommandLine.MAX_COLUMNS)
+                .withConsoleMode(false)
+        }
+
+        val toolchain = project.toolchain ?: throw ExecutionException(RsBundle.message("dialog.message.rust.toolchain.is.not.set"))
+        tmpCommandLine = toolchain.patchCommandLine(tmpCommandLine, withElevated ?: false)
+
+        @Suppress("UnstableApiUsage")
+        return when {
+            withElevated == true -> ElevationService.getInstance().createProcessHandler(tmpCommandLine)
+            colored == true -> RsProcessHandler(tmpCommandLine, colored)
+            captureProcessOutput == true -> RsCapturingProcessHandler.startProcess(tmpCommandLine).unwrap()
+            else -> RsProcessHandler(tmpCommandLine, false)
+        }
+    }
+
+    override fun runProcess(
+        handler: ProcessHandler,
+        indicator: ProgressIndicator,
+        timeout: Int,
+        project: Project
+    ): ProcessOutput = CapturingProcessRunner(handler).runProcess(indicator, timeout)
+
+    override fun getProcessList(project: Project): List<ProcessInfo> =
+        when (val toolchain = project.toolchain) {
+            is RsLocalToolchain -> OSProcessUtil.getProcessList().toList()
+            is RsWslToolchain -> WslAttachHost(toolchain.wslPath.distribution).processList
+            else -> emptyList()
+        }
+
+    override fun sendSignal(pid: Int, signalName: String, project: Project): Int =
+        when (val toolchain = project.toolchain) {
+            is RsLocalToolchain -> {
+                if (SystemInfo.isWindows) {
+                    throw UnsupportedOperationException("Not supported for Windows OS, use winbreak instead")
+                }
+                UnixProcessManager.sendSignal(pid, signalName)
+            }
+
+            is RsWslToolchain -> {
+                toolchain.createGeneralCommandLine(
+                    Path.of("kill"),
+                    Path.of("."),
+                    null,
+                    BacktraceMode.NO,
+                    EnvironmentVariablesData.DEFAULT,
+                    listOf("-s", signalName, pid.toString()),
+                    emulateTerminal = false,
+                    withSudo = false,
+                    patchToRemote = true // ???
+                ).execute(5000)?.exitCode ?: -1
+            }
+
+            else -> -1
+        }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt
new file mode 100644
index 0000000..4e97e96
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt
@@ -0,0 +1,14 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.clion.ProfilerEnvironmentHost
+import com.intellij.profiler.clion.ProfilerEnvironmentHostProvider
+
+class RsProfilerEnvironmentHostProvider: ProfilerEnvironmentHostProvider {
+    override fun getEnvironmentHost(project: Project): ProfilerEnvironmentHost = RsProfilerEnvironmentHost()
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt
new file mode 100644
index 0000000..b05a6d9
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt
@@ -0,0 +1,13 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.profiler.clion.ProfilerPresentationGroup
+import com.intellij.xdebugger.attach.XAttachProcessPresentationGroup
+
+class RsProfilerPresentationGroup : ProfilerPresentationGroup {
+    override fun getPresentationGroup(): XAttachProcessPresentationGroup = RsAttachPresentationGroup
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt
new file mode 100644
index 0000000..cb8781a
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt
@@ -0,0 +1,17 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerRunChecker
+
+class RsProfilerRunChecker : ProfilerRunChecker {
+    override fun canRun(profile: RunProfile): Boolean = false
+    override fun isDTraceProfilerCanBeUsed(): Boolean = SystemInfo.isMac
+    override fun isPerfProfilerCanBeUsed(): Boolean = SystemInfo.isLinux || SystemInfo.isWindows
+    override fun isProfilerCompatible(configuration: RunProfile): Boolean = true
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunner.kt b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
new file mode 100644
index 0000000..2cd3009
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
@@ -0,0 +1,37 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.configurations.RunConfiguration
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerExecutor
+import com.intellij.profiler.clion.ProfilerRunChecker
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.RsExecutableRunner
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+
+class RsProfilerRunner : RsExecutableRunner(ProfilerExecutor.EXECUTOR_ID, RsBundle.message("dialog.title.unable.to.run.profiler")) {
+    override fun getRunnerId(): String = RUNNER_ID
+
+    override fun canRun(executorId: String, profile: RunProfile): Boolean {
+        if (!ProfilerRunChecker.canRun()) return false
+
+        if (SystemInfo.isWindows) {
+            val toolchain = (profile as? RunConfiguration)?.project?.toolchain
+            if (toolchain !is RsWslToolchain) return false
+        }
+
+        return super.canRun(executorId, profile)
+    }
+
+
+    companion object {
+        const val RUNNER_ID: String = "RsProfilerRunner"
+        const val IJ_RUNNER_ID: String = "ProfilerRunner"
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt b/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
new file mode 100644
index 0000000..38eb8d1
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
@@ -0,0 +1,91 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.dtrace
+
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.RunnerSettings
+import com.intellij.execution.impl.ExecutionManagerImpl
+import com.intellij.execution.process.BaseProcessHandler
+import com.intellij.execution.process.OSProcessUtil
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.execution.process.UnixProcessManager
+import com.intellij.execution.runners.ExecutionEnvironment
+import com.intellij.openapi.progress.PerformInBackgroundOption
+import com.intellij.profiler.ProfilerToolWindowManager
+import com.intellij.profiler.clion.DTraceProfilerConfigurationExtension
+import com.intellij.profiler.clion.NativeTargetProcess
+import com.intellij.profiler.clion.ProfilerConfigurationExtension
+import com.intellij.profiler.dtrace.legacyDTraceProfilerConfiguration
+import com.intellij.profiler.installErrorHandlers
+import com.intellij.profiler.statistics.ProfilerUsageTriggerCollector
+import org.rust.RsBundle
+import org.rust.cargo.runconfig.CargoCommandConfigurationExtension
+import org.rust.cargo.runconfig.ConfigurationExtensionContext
+import org.rust.cargo.runconfig.command.CargoCommandConfiguration
+import org.rust.profiler.RsProfilerEnvironmentHost
+import org.rust.profiler.RsProfilerRunner
+import org.rust.profiler.RsProfilerRunner.Companion.IJ_RUNNER_ID
+import org.rust.profiler.legacy.RsProfilerRunnerLegacy
+
+class RsDTraceConfigurationExtension : CargoCommandConfigurationExtension() {
+    private val delegate: ProfilerConfigurationExtension = DTraceProfilerConfigurationExtension()
+
+    override fun isApplicableFor(configuration: CargoCommandConfiguration): Boolean =
+        delegate.isApplicableFor(configuration)
+
+    override fun isEnabledFor(
+        applicableConfiguration: CargoCommandConfiguration,
+        runnerSettings: RunnerSettings?
+    ): Boolean = delegate.isEnabledFor(applicableConfiguration, RsProfilerEnvironmentHost(), runnerSettings)
+
+    override fun patchCommandLine(
+        configuration: CargoCommandConfiguration,
+        environment: ExecutionEnvironment,
+        cmdLine: GeneralCommandLine,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.patchCommandLine(
+            configuration,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            cmdLine,
+            IJ_RUNNER_ID,
+            context
+        )
+    }
+
+    override fun attachToProcess(
+        configuration: CargoCommandConfiguration,
+        handler: ProcessHandler,
+        environment: ExecutionEnvironment,
+        context: ConfigurationExtensionContext
+    ) {
+        val project = configuration.project
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        if (RsProfilerEnvironmentHost().isRemote(project)) return
+        val targetProcess = (handler as? BaseProcessHandler<*>)?.process
+            ?: throw ExecutionException(RsBundle.message("dialog.message.profiler.connection.error.can.t.detect.target.process.id"))
+        ProfilerUsageTriggerCollector.logRecordingStarted(
+            project,
+            legacyDTraceProfilerConfiguration.configurationTypeId,
+            configuration.type.id
+        )
+        val namedProcess = NativeTargetProcess(OSProcessUtil.getProcessID(targetProcess), configuration.name)
+        RsDTraceProfilerProcess.attach(namedProcess, PerformInBackgroundOption.ALWAYS_BACKGROUND, 10000, project)
+            .installErrorHandlers(project)
+            .onError { ExecutionManagerImpl.stopProcess(handler) }
+            .onSuccess { process ->
+                UnixProcessManager.sendSigIntToProcessTree(targetProcess) //wakeup starter and finally run targetProcess code
+                ProfilerToolWindowManager.getInstance(project).addProfilerProcessTab(process)
+            }
+    }
+
+    companion object {
+        private val PROFILER_RUNNER_IDS: List<String> = listOf(RsProfilerRunner.RUNNER_ID, RsProfilerRunnerLegacy.RUNNER_ID)
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt b/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
new file mode 100644
index 0000000..c08c55a
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
@@ -0,0 +1,91 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.dtrace
+
+import com.intellij.openapi.progress.PerformInBackgroundOption
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.DummyCallTreeBuilder
+import com.intellij.profiler.api.*
+import com.intellij.profiler.clion.NativeCallDTraceCachingStackElementReader
+import com.intellij.profiler.clion.dtrace.DTraceProfilerSettings
+import com.intellij.profiler.clion.perf.NavigatableNativeCall
+import com.intellij.profiler.dtrace.DTraceProfilerProcessBase
+import com.intellij.profiler.dtrace.FullDumpParser
+import com.intellij.profiler.dtrace.SimpleProfilerSettingsState
+import com.intellij.profiler.dtrace.cpuProfilerScript
+import com.intellij.profiler.model.NativeCall
+import com.intellij.profiler.model.NativeThread
+import com.intellij.profiler.sudo.SudoProcessHandler
+import com.intellij.profiler.ui.NativeCallStackElementRenderer
+import com.intellij.util.xmlb.XmlSerializer
+import org.jetbrains.concurrency.Promise
+import org.rust.lang.utils.RsDemangler
+
+
+class RsDTraceProfilerProcess private constructor(
+    project: Project,
+    targetProcess: AttachableTargetProcess,
+    attachedTimestamp: Long,
+    dtraceProcessHandler: SudoProcessHandler
+) : DTraceProfilerProcessBase(project, targetProcess, attachedTimestamp, dtraceProcessHandler) {
+
+    // We can't use [com.intellij.profiler.clion.ProfilerUtilsKt.CPP_PROFILER_HELP_TOPIC] directly
+    // because it has `internal` modifier
+    override val helpId: String = "procedures.profiler"
+
+    override fun createDumpParser(): FullDumpParser<BaseCallStackElement> {
+        val cachingStackElementReader = NativeCallDTraceCachingStackElementReader.getInstance(project)
+        return FullDumpParser(
+            { NativeThread(it, "thread with id $it") },
+            cachingStackElementReader::parseStackElement
+        )
+    }
+
+    override fun createDumpWriter(data: NewCallTreeOnlyProfilerData): ProfilerDumpWriter =
+        CollapsedProfilerDumpWriter(data.builder, targetProcess.fullName, attachedTimestamp, { it.fullName() }, { it.name })
+
+    override fun createProfilerData(builder: DummyCallTreeBuilder<BaseCallStackElement>): NewCallTreeOnlyProfilerData =
+        NewCallTreeOnlyProfilerData(builder, NativeCallStackElementRenderer.INSTANCE)
+
+    override fun postProcessData(builder: DummyCallTreeBuilder<BaseCallStackElement>): DummyCallTreeBuilder<BaseCallStackElement> {
+        builder.mapTreeElements {
+            if (it !is NavigatableNativeCall) return@mapTreeElements it
+            val library = it.fullName().substringBeforeLast('`')
+            val fullName = it.fullName().substringAfterLast('`')
+            val demangledName = RsDemangler.tryDemangle(fullName)?.format(skipHash = true) ?: return@mapTreeElements it
+            val path = demangledName.substringBeforeLast("::")
+            val method = demangledName.substringAfterLast("::")
+            val nativeCall = NativeCall(library, path, method)
+            NavigatableNativeCall(nativeCall)
+        }
+        return super.postProcessData(builder)
+    }
+
+    companion object {
+        fun attach(
+            targetProcess: AttachableTargetProcess,
+            backgroundOption: PerformInBackgroundOption,
+            timeoutInMilliseconds: Int,
+            project: Project
+        ): Promise<RsDTraceProfilerProcess> {
+            val settings = DTraceProfilerSettings.instance.state
+
+            // WARNING: Do not use such solution for other needs!
+            // We want to always use -xmangled option because DTrace cannot demangle Rust symbols correctly
+            val element = XmlSerializer.serialize(settings)
+            val settingsCopy = XmlSerializer.deserialize(element, SimpleProfilerSettingsState::class.java)
+            settingsCopy.defaultCmdArgs.add("-xmangled")
+
+            return attachBase(
+                targetProcess,
+                backgroundOption,
+                settingsCopy.cpuProfilerScript(),
+                timeoutInMilliseconds,
+                project
+            ) { handler, _ -> RsDTraceProfilerProcess(project, targetProcess, System.currentTimeMillis(), handler) }
+        }
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt b/profiler/src/233/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
new file mode 100644
index 0000000..2eefc88
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
@@ -0,0 +1,43 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.legacy
+
+import com.intellij.execution.configurations.RunConfiguration
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.NlsContexts
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerExecutor
+import com.intellij.profiler.clion.ProfilerRunChecker
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.buildtool.CargoBuildManager.isBuildToolWindowAvailable
+import org.rust.cargo.runconfig.legacy.RsAsyncRunner
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+
+@NlsContexts.DialogTitle
+private val ERROR_MESSAGE_TITLE: String = RsBundle.message("dialog.title.unable.to.run.profiler")
+
+/**
+ * This runner is used if [isBuildToolWindowAvailable] is false.
+ */
+class RsProfilerRunnerLegacy : RsAsyncRunner(ProfilerExecutor.EXECUTOR_ID, ERROR_MESSAGE_TITLE) {
+    override fun getRunnerId(): String = RUNNER_ID
+
+    override fun canRun(executorId: String, profile: RunProfile): Boolean {
+        if (!ProfilerRunChecker.canRun()) return false
+
+        if (SystemInfo.isWindows) {
+            val toolchain = (profile as? RunConfiguration)?.project?.toolchain
+            if (toolchain !is RsWslToolchain) return false
+        }
+
+        return super.canRun(executorId, profile)
+    }
+
+    companion object {
+        const val RUNNER_ID: String = "RsProfilerRunnerLegacy"
+    }
+}
diff --git a/profiler/src/233/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt b/profiler/src/233/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
new file mode 100644
index 0000000..96c20a1
--- /dev/null
+++ b/profiler/src/233/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
@@ -0,0 +1,72 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.perf
+
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.RunnerSettings
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.execution.runners.ExecutionEnvironment
+import com.intellij.profiler.clion.ProfilerConfigurationExtension
+import com.intellij.profiler.clion.perf.PerfProfilerConfigurationExtension
+import org.rust.cargo.runconfig.CargoCommandConfigurationExtension
+import org.rust.cargo.runconfig.ConfigurationExtensionContext
+import org.rust.cargo.runconfig.command.CargoCommandConfiguration
+import org.rust.profiler.RsProfilerEnvironmentHost
+import org.rust.profiler.RsProfilerRunner
+import org.rust.profiler.RsProfilerRunner.Companion.IJ_RUNNER_ID
+import org.rust.profiler.legacy.RsProfilerRunnerLegacy
+
+class RsPerfConfigurationExtension : CargoCommandConfigurationExtension() {
+    private val delegate: ProfilerConfigurationExtension = PerfProfilerConfigurationExtension()
+
+    override fun isApplicableFor(configuration: CargoCommandConfiguration): Boolean =
+        delegate.isApplicableFor(configuration)
+
+    override fun isEnabledFor(
+        applicableConfiguration: CargoCommandConfiguration,
+        runnerSettings: RunnerSettings?
+    ): Boolean = delegate.isEnabledFor(applicableConfiguration, RsProfilerEnvironmentHost(), runnerSettings)
+
+    override fun patchCommandLine(
+        configuration: CargoCommandConfiguration,
+        environment: ExecutionEnvironment,
+        cmdLine: GeneralCommandLine,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.patchCommandLine(
+            configuration,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            cmdLine,
+            IJ_RUNNER_ID,
+            context
+        )
+        val toolchain = configuration.clean().ok?.toolchain ?: return
+        toolchain.patchCommandLine(cmdLine, withSudo = false)
+    }
+
+    override fun attachToProcess(
+        configuration: CargoCommandConfiguration,
+        handler: ProcessHandler,
+        environment: ExecutionEnvironment,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.attachToProcess(
+            configuration,
+            handler,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            IJ_RUNNER_ID,
+            context
+        )
+    }
+
+    companion object {
+        private val PROFILER_RUNNER_IDS = listOf(RsProfilerRunner.RUNNER_ID, RsProfilerRunnerLegacy.RUNNER_ID)
+    }
+}
diff --git a/profiler/src/233/main/resources/org.rust.profiler.xml b/profiler/src/233/main/resources/org.rust.profiler.xml
new file mode 100644
index 0000000..12d3a4b
--- /dev/null
+++ b/profiler/src/233/main/resources/org.rust.profiler.xml
@@ -0,0 +1,23 @@
+<idea-plugin package="org.rust.profiler">
+    <!--suppress PluginXmlValidity -->
+    <dependencies>
+        <module name="intellij.profiler.asyncOne"/>
+        <module name="intellij.profiler.common"/>
+        <module name="intellij.profiler.clion"/>
+    </dependencies>
+
+    <extensions defaultExtensionNs="com.intellij">
+        <programRunner implementation="org.rust.profiler.RsProfilerRunner"/>
+        <programRunner implementation="org.rust.profiler.legacy.RsProfilerRunnerLegacy"/>
+
+        <profiler.clion.profilerRunChecker implementation="org.rust.profiler.RsProfilerRunChecker"/>
+        <profiler.clion.profilerEnvironmentHostProvider implementation="org.rust.profiler.RsProfilerEnvironmentHostProvider"/>
+        <profiler.clion.navigatableSymbolSearcher implementation="org.rust.profiler.RsNavigatableSymbolSearcher"/>
+        <profiler.clion.profilerPresentationGroup implementation="org.rust.profiler.RsProfilerPresentationGroup"/>
+    </extensions>
+
+    <extensions defaultExtensionNs="org.rust">
+        <runConfigurationExtension implementation="org.rust.profiler.perf.RsPerfConfigurationExtension"/>
+        <runConfigurationExtension implementation="org.rust.profiler.dtrace.RsDTraceConfigurationExtension"/>
+    </extensions>
+</idea-plugin>
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt
new file mode 100644
index 0000000..7890ebe
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsAttachPresentationGroup.kt
@@ -0,0 +1,28 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.process.ProcessInfo
+import com.intellij.icons.AllIcons
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.UserDataHolder
+import com.intellij.xdebugger.attach.XAttachProcessPresentationGroup
+import org.rust.RsBundle
+import javax.swing.Icon
+
+object RsAttachPresentationGroup: XAttachProcessPresentationGroup {
+    override fun getOrder(): Int = 0
+
+    override fun getGroupName(): String = RsBundle.message("profiler.attach.default.group.title")
+
+    override fun getItemIcon(project: Project, info: ProcessInfo, dataHolder: UserDataHolder): Icon {
+        return AllIcons.RunConfigurations.Application
+    }
+
+    override fun getItemDisplayText(project: Project, info: ProcessInfo, dataHolder: UserDataHolder): String {
+        return info.executableDisplayName
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt
new file mode 100644
index 0000000..b801fc9
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsNavigatableSymbolSearcher.kt
@@ -0,0 +1,55 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.clion.NavigatableSymbolSearcher
+import com.intellij.psi.NavigatablePsiElement
+import com.intellij.psi.search.GlobalSearchScope
+import org.rust.lang.core.psi.RsFunction
+import org.rust.lang.core.psi.ext.qualifiedName
+import org.rust.lang.core.stubs.index.RsNamedElementIndex
+import org.rust.openapiext.getElements
+
+class RsNavigatableSymbolSearcher : NavigatableSymbolSearcher() {
+
+    override fun doFindNavigatableSymbols(qualifiedSignature: String, project: Project): Array<NavigatablePsiElement> {
+        val elements = mutableListOf<RsFunction>()
+        val searchScope = GlobalSearchScope.allScope(project)
+        val qualifiedPath = extractPath(qualifiedSignature)
+
+        runReadAction {
+            val allDeclarations = getElements(RsNamedElementIndex.KEY, qualifiedSignature, project, searchScope)
+                .filterIsInstance<RsFunction>()
+            val appropriateDeclarations = allDeclarations
+                .filter { it.qualifiedName?.substringBeforeLast("::") == qualifiedPath }
+
+            if (appropriateDeclarations.isNotEmpty()) {
+                elements.addAll(appropriateDeclarations)
+            } else {
+                // TODO: return all or nothing?
+                elements.addAll(allDeclarations)
+            }
+        }
+
+        return elements.toTypedArray()
+    }
+
+    companion object {
+        /**
+         * `_<foo::bar123::foo_bar::Qux as baz>::qux` -> `foo::bar123::foo_bar`
+         *
+         * It doesn't work for crates/modules with inappropriate names (e.g. upper-case)
+         *
+         * TODO: add tests
+         */
+        private fun extractPath(signature: String): String = signature
+            .dropWhile { !it.isLetter() }
+            .takeWhile { (it.isLetterOrDigit() && it.isLowerCase()) || it == '_' || it == ':' }
+            .removeSuffix("::")
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt
new file mode 100644
index 0000000..53d1ff6
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHost.kt
@@ -0,0 +1,128 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configuration.EnvironmentVariablesData
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.PtyCommandLine
+import com.intellij.execution.process.*
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.profiler.clion.ProfilerEnvironmentHost
+import com.intellij.xdebugger.attach.WslAttachHost
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.RsCapturingProcessHandler
+import org.rust.cargo.runconfig.RsProcessHandler
+import org.rust.cargo.toolchain.BacktraceMode
+import org.rust.cargo.toolchain.RsLocalToolchain
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+import org.rust.openapiext.execute
+import org.rust.stdext.toPath
+import org.rust.stdext.toPathOrNull
+import java.nio.file.Path
+
+class RsProfilerEnvironmentHost : ProfilerEnvironmentHost {
+
+    override fun shouldCheckFilePathExist(project: Project): Boolean {
+        val toolchain = project.toolchain ?: return false
+        return toolchain is RsLocalToolchain
+    }
+
+    override fun getPath(path: String, project: Project): Path =
+        project.toolchain?.toLocalPath(path)?.toPathOrNull() ?: Path.of(path) // TODO: return null
+
+    override fun getEnvPath(path: String?, project: Project): String {
+        if (path == null) return "" // TODO: return null
+        return project.toolchain?.toRemotePath(path).toString()
+    }
+
+    override fun getTempDirectory(project: Project): Path = FileUtil.getTempDirectory().toPath()
+
+    override fun isRemote(project: Project): Boolean {
+        val toolchain = project.toolchain ?: return false
+        return toolchain !is RsLocalToolchain
+    }
+
+    override fun isWSL(project: Project): Boolean = project.toolchain is RsWslToolchain
+
+    override fun getWSLVersion(project: Project): Int {
+        val toolchain = project.toolchain as? RsWslToolchain ?: return -1
+        return toolchain.wslPath.distribution.version
+    }
+
+    override fun createProcessHandler(
+        commandLine: GeneralCommandLine,
+        project: Project,
+        colored: Boolean?,
+        usePty: Boolean?,
+        captureProcessOutput: Boolean?,
+        splitLines: Boolean?, // TODO: take it into account
+        withElevated: Boolean?
+    ): BaseProcessHandler<*> {
+        var tmpCommandLine = commandLine
+
+        if (usePty == true) {
+            tmpCommandLine = PtyCommandLine(commandLine)
+                .withInitialColumns(PtyCommandLine.MAX_COLUMNS)
+                .withConsoleMode(false)
+        }
+
+        val toolchain = project.toolchain ?: throw ExecutionException(RsBundle.message("dialog.message.rust.toolchain.is.not.set"))
+        tmpCommandLine = toolchain.patchCommandLine(tmpCommandLine, withElevated ?: false)
+
+        @Suppress("UnstableApiUsage")
+        return when {
+            withElevated == true -> ElevationService.getInstance().createProcessHandler(tmpCommandLine)
+            colored == true -> RsProcessHandler(tmpCommandLine, colored)
+            captureProcessOutput == true -> RsCapturingProcessHandler.startProcess(tmpCommandLine).unwrap()
+            else -> RsProcessHandler(tmpCommandLine, false)
+        }
+    }
+
+    override fun runProcess(
+        handler: ProcessHandler,
+        indicator: ProgressIndicator,
+        timeout: Int,
+        project: Project
+    ): ProcessOutput = CapturingProcessRunner(handler).runProcess(indicator, timeout)
+
+    override fun getProcessList(project: Project): List<ProcessInfo> =
+        when (val toolchain = project.toolchain) {
+            is RsLocalToolchain -> OSProcessUtil.getProcessList().toList()
+            is RsWslToolchain -> WslAttachHost(toolchain.wslPath.distribution).processList
+            else -> emptyList()
+        }
+
+    override fun sendSignal(pid: Int, signalName: String, project: Project): Int =
+        when (val toolchain = project.toolchain) {
+            is RsLocalToolchain -> {
+                if (SystemInfo.isWindows) {
+                    throw UnsupportedOperationException("Not supported for Windows OS, use winbreak instead")
+                }
+                UnixProcessManager.sendSignal(pid, signalName)
+            }
+
+            is RsWslToolchain -> {
+                toolchain.createGeneralCommandLine(
+                    Path.of("kill"),
+                    Path.of("."),
+                    null,
+                    BacktraceMode.NO,
+                    EnvironmentVariablesData.DEFAULT,
+                    listOf("-s", signalName, pid.toString()),
+                    emulateTerminal = false,
+                    withSudo = false,
+                    patchToRemote = true // ???
+                ).execute(5000)?.exitCode ?: -1
+            }
+
+            else -> -1
+        }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt
new file mode 100644
index 0000000..4e97e96
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerEnvironmentHostProvider.kt
@@ -0,0 +1,14 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.clion.ProfilerEnvironmentHost
+import com.intellij.profiler.clion.ProfilerEnvironmentHostProvider
+
+class RsProfilerEnvironmentHostProvider: ProfilerEnvironmentHostProvider {
+    override fun getEnvironmentHost(project: Project): ProfilerEnvironmentHost = RsProfilerEnvironmentHost()
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt
new file mode 100644
index 0000000..b05a6d9
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerPresentationGroup.kt
@@ -0,0 +1,13 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.profiler.clion.ProfilerPresentationGroup
+import com.intellij.xdebugger.attach.XAttachProcessPresentationGroup
+
+class RsProfilerPresentationGroup : ProfilerPresentationGroup {
+    override fun getPresentationGroup(): XAttachProcessPresentationGroup = RsAttachPresentationGroup
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt
new file mode 100644
index 0000000..cb8781a
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunChecker.kt
@@ -0,0 +1,17 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerRunChecker
+
+class RsProfilerRunChecker : ProfilerRunChecker {
+    override fun canRun(profile: RunProfile): Boolean = false
+    override fun isDTraceProfilerCanBeUsed(): Boolean = SystemInfo.isMac
+    override fun isPerfProfilerCanBeUsed(): Boolean = SystemInfo.isLinux || SystemInfo.isWindows
+    override fun isProfilerCompatible(configuration: RunProfile): Boolean = true
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunner.kt b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
new file mode 100644
index 0000000..2cd3009
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/RsProfilerRunner.kt
@@ -0,0 +1,37 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler
+
+import com.intellij.execution.configurations.RunConfiguration
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerExecutor
+import com.intellij.profiler.clion.ProfilerRunChecker
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.RsExecutableRunner
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+
+class RsProfilerRunner : RsExecutableRunner(ProfilerExecutor.EXECUTOR_ID, RsBundle.message("dialog.title.unable.to.run.profiler")) {
+    override fun getRunnerId(): String = RUNNER_ID
+
+    override fun canRun(executorId: String, profile: RunProfile): Boolean {
+        if (!ProfilerRunChecker.canRun()) return false
+
+        if (SystemInfo.isWindows) {
+            val toolchain = (profile as? RunConfiguration)?.project?.toolchain
+            if (toolchain !is RsWslToolchain) return false
+        }
+
+        return super.canRun(executorId, profile)
+    }
+
+
+    companion object {
+        const val RUNNER_ID: String = "RsProfilerRunner"
+        const val IJ_RUNNER_ID: String = "ProfilerRunner"
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt b/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
new file mode 100644
index 0000000..38eb8d1
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceConfigurationExtension.kt
@@ -0,0 +1,91 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.dtrace
+
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.RunnerSettings
+import com.intellij.execution.impl.ExecutionManagerImpl
+import com.intellij.execution.process.BaseProcessHandler
+import com.intellij.execution.process.OSProcessUtil
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.execution.process.UnixProcessManager
+import com.intellij.execution.runners.ExecutionEnvironment
+import com.intellij.openapi.progress.PerformInBackgroundOption
+import com.intellij.profiler.ProfilerToolWindowManager
+import com.intellij.profiler.clion.DTraceProfilerConfigurationExtension
+import com.intellij.profiler.clion.NativeTargetProcess
+import com.intellij.profiler.clion.ProfilerConfigurationExtension
+import com.intellij.profiler.dtrace.legacyDTraceProfilerConfiguration
+import com.intellij.profiler.installErrorHandlers
+import com.intellij.profiler.statistics.ProfilerUsageTriggerCollector
+import org.rust.RsBundle
+import org.rust.cargo.runconfig.CargoCommandConfigurationExtension
+import org.rust.cargo.runconfig.ConfigurationExtensionContext
+import org.rust.cargo.runconfig.command.CargoCommandConfiguration
+import org.rust.profiler.RsProfilerEnvironmentHost
+import org.rust.profiler.RsProfilerRunner
+import org.rust.profiler.RsProfilerRunner.Companion.IJ_RUNNER_ID
+import org.rust.profiler.legacy.RsProfilerRunnerLegacy
+
+class RsDTraceConfigurationExtension : CargoCommandConfigurationExtension() {
+    private val delegate: ProfilerConfigurationExtension = DTraceProfilerConfigurationExtension()
+
+    override fun isApplicableFor(configuration: CargoCommandConfiguration): Boolean =
+        delegate.isApplicableFor(configuration)
+
+    override fun isEnabledFor(
+        applicableConfiguration: CargoCommandConfiguration,
+        runnerSettings: RunnerSettings?
+    ): Boolean = delegate.isEnabledFor(applicableConfiguration, RsProfilerEnvironmentHost(), runnerSettings)
+
+    override fun patchCommandLine(
+        configuration: CargoCommandConfiguration,
+        environment: ExecutionEnvironment,
+        cmdLine: GeneralCommandLine,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.patchCommandLine(
+            configuration,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            cmdLine,
+            IJ_RUNNER_ID,
+            context
+        )
+    }
+
+    override fun attachToProcess(
+        configuration: CargoCommandConfiguration,
+        handler: ProcessHandler,
+        environment: ExecutionEnvironment,
+        context: ConfigurationExtensionContext
+    ) {
+        val project = configuration.project
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        if (RsProfilerEnvironmentHost().isRemote(project)) return
+        val targetProcess = (handler as? BaseProcessHandler<*>)?.process
+            ?: throw ExecutionException(RsBundle.message("dialog.message.profiler.connection.error.can.t.detect.target.process.id"))
+        ProfilerUsageTriggerCollector.logRecordingStarted(
+            project,
+            legacyDTraceProfilerConfiguration.configurationTypeId,
+            configuration.type.id
+        )
+        val namedProcess = NativeTargetProcess(OSProcessUtil.getProcessID(targetProcess), configuration.name)
+        RsDTraceProfilerProcess.attach(namedProcess, PerformInBackgroundOption.ALWAYS_BACKGROUND, 10000, project)
+            .installErrorHandlers(project)
+            .onError { ExecutionManagerImpl.stopProcess(handler) }
+            .onSuccess { process ->
+                UnixProcessManager.sendSigIntToProcessTree(targetProcess) //wakeup starter and finally run targetProcess code
+                ProfilerToolWindowManager.getInstance(project).addProfilerProcessTab(process)
+            }
+    }
+
+    companion object {
+        private val PROFILER_RUNNER_IDS: List<String> = listOf(RsProfilerRunner.RUNNER_ID, RsProfilerRunnerLegacy.RUNNER_ID)
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt b/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
new file mode 100644
index 0000000..c08c55a
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/dtrace/RsDTraceProfilerProcess.kt
@@ -0,0 +1,91 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.dtrace
+
+import com.intellij.openapi.progress.PerformInBackgroundOption
+import com.intellij.openapi.project.Project
+import com.intellij.profiler.DummyCallTreeBuilder
+import com.intellij.profiler.api.*
+import com.intellij.profiler.clion.NativeCallDTraceCachingStackElementReader
+import com.intellij.profiler.clion.dtrace.DTraceProfilerSettings
+import com.intellij.profiler.clion.perf.NavigatableNativeCall
+import com.intellij.profiler.dtrace.DTraceProfilerProcessBase
+import com.intellij.profiler.dtrace.FullDumpParser
+import com.intellij.profiler.dtrace.SimpleProfilerSettingsState
+import com.intellij.profiler.dtrace.cpuProfilerScript
+import com.intellij.profiler.model.NativeCall
+import com.intellij.profiler.model.NativeThread
+import com.intellij.profiler.sudo.SudoProcessHandler
+import com.intellij.profiler.ui.NativeCallStackElementRenderer
+import com.intellij.util.xmlb.XmlSerializer
+import org.jetbrains.concurrency.Promise
+import org.rust.lang.utils.RsDemangler
+
+
+class RsDTraceProfilerProcess private constructor(
+    project: Project,
+    targetProcess: AttachableTargetProcess,
+    attachedTimestamp: Long,
+    dtraceProcessHandler: SudoProcessHandler
+) : DTraceProfilerProcessBase(project, targetProcess, attachedTimestamp, dtraceProcessHandler) {
+
+    // We can't use [com.intellij.profiler.clion.ProfilerUtilsKt.CPP_PROFILER_HELP_TOPIC] directly
+    // because it has `internal` modifier
+    override val helpId: String = "procedures.profiler"
+
+    override fun createDumpParser(): FullDumpParser<BaseCallStackElement> {
+        val cachingStackElementReader = NativeCallDTraceCachingStackElementReader.getInstance(project)
+        return FullDumpParser(
+            { NativeThread(it, "thread with id $it") },
+            cachingStackElementReader::parseStackElement
+        )
+    }
+
+    override fun createDumpWriter(data: NewCallTreeOnlyProfilerData): ProfilerDumpWriter =
+        CollapsedProfilerDumpWriter(data.builder, targetProcess.fullName, attachedTimestamp, { it.fullName() }, { it.name })
+
+    override fun createProfilerData(builder: DummyCallTreeBuilder<BaseCallStackElement>): NewCallTreeOnlyProfilerData =
+        NewCallTreeOnlyProfilerData(builder, NativeCallStackElementRenderer.INSTANCE)
+
+    override fun postProcessData(builder: DummyCallTreeBuilder<BaseCallStackElement>): DummyCallTreeBuilder<BaseCallStackElement> {
+        builder.mapTreeElements {
+            if (it !is NavigatableNativeCall) return@mapTreeElements it
+            val library = it.fullName().substringBeforeLast('`')
+            val fullName = it.fullName().substringAfterLast('`')
+            val demangledName = RsDemangler.tryDemangle(fullName)?.format(skipHash = true) ?: return@mapTreeElements it
+            val path = demangledName.substringBeforeLast("::")
+            val method = demangledName.substringAfterLast("::")
+            val nativeCall = NativeCall(library, path, method)
+            NavigatableNativeCall(nativeCall)
+        }
+        return super.postProcessData(builder)
+    }
+
+    companion object {
+        fun attach(
+            targetProcess: AttachableTargetProcess,
+            backgroundOption: PerformInBackgroundOption,
+            timeoutInMilliseconds: Int,
+            project: Project
+        ): Promise<RsDTraceProfilerProcess> {
+            val settings = DTraceProfilerSettings.instance.state
+
+            // WARNING: Do not use such solution for other needs!
+            // We want to always use -xmangled option because DTrace cannot demangle Rust symbols correctly
+            val element = XmlSerializer.serialize(settings)
+            val settingsCopy = XmlSerializer.deserialize(element, SimpleProfilerSettingsState::class.java)
+            settingsCopy.defaultCmdArgs.add("-xmangled")
+
+            return attachBase(
+                targetProcess,
+                backgroundOption,
+                settingsCopy.cpuProfilerScript(),
+                timeoutInMilliseconds,
+                project
+            ) { handler, _ -> RsDTraceProfilerProcess(project, targetProcess, System.currentTimeMillis(), handler) }
+        }
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt b/profiler/src/241/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
new file mode 100644
index 0000000..2eefc88
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/legacy/RsProfilerRunnerLegacy.kt
@@ -0,0 +1,43 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.legacy
+
+import com.intellij.execution.configurations.RunConfiguration
+import com.intellij.execution.configurations.RunProfile
+import com.intellij.openapi.util.NlsContexts
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.profiler.clion.ProfilerExecutor
+import com.intellij.profiler.clion.ProfilerRunChecker
+import org.rust.RsBundle
+import org.rust.cargo.project.settings.toolchain
+import org.rust.cargo.runconfig.buildtool.CargoBuildManager.isBuildToolWindowAvailable
+import org.rust.cargo.runconfig.legacy.RsAsyncRunner
+import org.rust.cargo.toolchain.wsl.RsWslToolchain
+
+@NlsContexts.DialogTitle
+private val ERROR_MESSAGE_TITLE: String = RsBundle.message("dialog.title.unable.to.run.profiler")
+
+/**
+ * This runner is used if [isBuildToolWindowAvailable] is false.
+ */
+class RsProfilerRunnerLegacy : RsAsyncRunner(ProfilerExecutor.EXECUTOR_ID, ERROR_MESSAGE_TITLE) {
+    override fun getRunnerId(): String = RUNNER_ID
+
+    override fun canRun(executorId: String, profile: RunProfile): Boolean {
+        if (!ProfilerRunChecker.canRun()) return false
+
+        if (SystemInfo.isWindows) {
+            val toolchain = (profile as? RunConfiguration)?.project?.toolchain
+            if (toolchain !is RsWslToolchain) return false
+        }
+
+        return super.canRun(executorId, profile)
+    }
+
+    companion object {
+        const val RUNNER_ID: String = "RsProfilerRunnerLegacy"
+    }
+}
diff --git a/profiler/src/241/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt b/profiler/src/241/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
new file mode 100644
index 0000000..96c20a1
--- /dev/null
+++ b/profiler/src/241/main/kotlin/org/rust/profiler/perf/RsPerfConfigurationExtension.kt
@@ -0,0 +1,72 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.profiler.perf
+
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.configurations.RunnerSettings
+import com.intellij.execution.process.ProcessHandler
+import com.intellij.execution.runners.ExecutionEnvironment
+import com.intellij.profiler.clion.ProfilerConfigurationExtension
+import com.intellij.profiler.clion.perf.PerfProfilerConfigurationExtension
+import org.rust.cargo.runconfig.CargoCommandConfigurationExtension
+import org.rust.cargo.runconfig.ConfigurationExtensionContext
+import org.rust.cargo.runconfig.command.CargoCommandConfiguration
+import org.rust.profiler.RsProfilerEnvironmentHost
+import org.rust.profiler.RsProfilerRunner
+import org.rust.profiler.RsProfilerRunner.Companion.IJ_RUNNER_ID
+import org.rust.profiler.legacy.RsProfilerRunnerLegacy
+
+class RsPerfConfigurationExtension : CargoCommandConfigurationExtension() {
+    private val delegate: ProfilerConfigurationExtension = PerfProfilerConfigurationExtension()
+
+    override fun isApplicableFor(configuration: CargoCommandConfiguration): Boolean =
+        delegate.isApplicableFor(configuration)
+
+    override fun isEnabledFor(
+        applicableConfiguration: CargoCommandConfiguration,
+        runnerSettings: RunnerSettings?
+    ): Boolean = delegate.isEnabledFor(applicableConfiguration, RsProfilerEnvironmentHost(), runnerSettings)
+
+    override fun patchCommandLine(
+        configuration: CargoCommandConfiguration,
+        environment: ExecutionEnvironment,
+        cmdLine: GeneralCommandLine,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.patchCommandLine(
+            configuration,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            cmdLine,
+            IJ_RUNNER_ID,
+            context
+        )
+        val toolchain = configuration.clean().ok?.toolchain ?: return
+        toolchain.patchCommandLine(cmdLine, withSudo = false)
+    }
+
+    override fun attachToProcess(
+        configuration: CargoCommandConfiguration,
+        handler: ProcessHandler,
+        environment: ExecutionEnvironment,
+        context: ConfigurationExtensionContext
+    ) {
+        if (environment.runner.runnerId !in PROFILER_RUNNER_IDS) return
+        delegate.attachToProcess(
+            configuration,
+            handler,
+            RsProfilerEnvironmentHost(),
+            environment.runnerSettings,
+            IJ_RUNNER_ID,
+            context
+        )
+    }
+
+    companion object {
+        private val PROFILER_RUNNER_IDS = listOf(RsProfilerRunner.RUNNER_ID, RsProfilerRunnerLegacy.RUNNER_ID)
+    }
+}
diff --git a/profiler/src/241/main/resources/org.rust.profiler.xml b/profiler/src/241/main/resources/org.rust.profiler.xml
new file mode 100644
index 0000000..12d3a4b
--- /dev/null
+++ b/profiler/src/241/main/resources/org.rust.profiler.xml
@@ -0,0 +1,23 @@
+<idea-plugin package="org.rust.profiler">
+    <!--suppress PluginXmlValidity -->
+    <dependencies>
+        <module name="intellij.profiler.asyncOne"/>
+        <module name="intellij.profiler.common"/>
+        <module name="intellij.profiler.clion"/>
+    </dependencies>
+
+    <extensions defaultExtensionNs="com.intellij">
+        <programRunner implementation="org.rust.profiler.RsProfilerRunner"/>
+        <programRunner implementation="org.rust.profiler.legacy.RsProfilerRunnerLegacy"/>
+
+        <profiler.clion.profilerRunChecker implementation="org.rust.profiler.RsProfilerRunChecker"/>
+        <profiler.clion.profilerEnvironmentHostProvider implementation="org.rust.profiler.RsProfilerEnvironmentHostProvider"/>
+        <profiler.clion.navigatableSymbolSearcher implementation="org.rust.profiler.RsNavigatableSymbolSearcher"/>
+        <profiler.clion.profilerPresentationGroup implementation="org.rust.profiler.RsProfilerPresentationGroup"/>
+    </extensions>
+
+    <extensions defaultExtensionNs="org.rust">
+        <runConfigurationExtension implementation="org.rust.profiler.perf.RsPerfConfigurationExtension"/>
+        <runConfigurationExtension implementation="org.rust.profiler.dtrace.RsDTraceConfigurationExtension"/>
+    </extensions>
+</idea-plugin>
diff --git a/qodana.yaml b/qodana.yaml
deleted file mode 100644
index c83ad3e..0000000
--- a/qodana.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-version: "1.0"
-linter: jetbrains/qodana-jvm-community:2023.1
-profile:
-  name: qodana.recommended
-exclude:
-  - name: DialogTitleCapitalization
-  - name: IncompleteDestructuring
-  - name: UnstableApiUsage
-  - name: UnstableTypeUsedInSignature
-  - name: PropertyName
-  - name: PrivatePropertyName
diff --git a/scripts/run_pretty_printer_tests.py b/scripts/run_pretty_printer_tests.py
index dee39dd..1bed6a3 100644
--- a/scripts/run_pretty_printer_tests.py
+++ b/scripts/run_pretty_printer_tests.py
@@ -1,3 +1,4 @@
+import argparse
 import os
 import platform
 import stat
@@ -5,8 +6,6 @@
 from enum import Enum, auto
 from os import path
 
-import argparse
-
 from common import execute_command
 
 
@@ -70,12 +69,6 @@
         python = "python3"
     elif host_os == OS.Windows:
         lldb_bundle_path = path.abspath(f"deps/clion-{clion_version}/bin/lldb/win/{arch_name}")
-        # Create symlink to allow `lldb` Python module perform `import _lldb` inside
-        # BACKCOMPAT: 2023.1. `lib/site-packages/lldb/_lldb.pyd` exists since CLion 2023.1
-        lldb_pyd = f"{lldb_bundle_path}/lib/site-packages/lldb/_lldb.pyd"
-        if not path.exists(lldb_pyd):
-            os.symlink(f"{lldb_bundle_path}/bin/liblldb.dll", f"{lldb_bundle_path}/lib/site-packages/lldb/_lldb.pyd")
-
         lldb_path = f"{lldb_bundle_path}/lib/site-packages"
         python = f"{lldb_bundle_path}/bin/python.exe"
     else:
diff --git a/src/231/main/kotlin/org/rust/ide/navigation/compatUtils.kt b/src/231/main/kotlin/org/rust/ide/navigation/compatUtils.kt
deleted file mode 100644
index c63b065..0000000
--- a/src/231/main/kotlin/org/rust/ide/navigation/compatUtils.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.ide.navigation
-
-import com.intellij.codeInsight.navigation.NavigationUtil
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.popup.JBPopup
-import com.intellij.openapi.util.NlsContexts
-import com.intellij.psi.PsiElement
-
-fun getPsiElementPopup(elements: Array<PsiElement>, title: @NlsContexts.PopupTitle String?): JBPopup {
-    return NavigationUtil.getPsiElementPopup(elements, title)
-}
-
-fun hidePopupIfDumbModeStarts(popup: JBPopup, project: Project) {
-    NavigationUtil.hidePopupIfDumbModeStarts(popup, project)
-}
diff --git a/src/231/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt b/src/231/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt
deleted file mode 100644
index d9d666e..0000000
--- a/src/231/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.ide.wordSelection
-
-class RsBlocksAndBodiesSelectionHandlerTest : RsSelectionHandlerTestBase() {
-    fun `test fn body basic`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |<caret>    let x = 1;
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |<selection>    let x = 1;
-        |</selection>}
-        |""",
-
-        """
-        |fn foo() <selection>{
-        |    let x = 1;
-        |}
-        |</selection>""")
-
-    fun `test fun body multiline`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let x = 1;
-        |
-        |<caret>    let x = 1;
-        |    let x = 1;
-        |
-        |    let x = 1;
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = 1;
-        |
-        |<selection>    let x = 1;
-        |</selection>    let x = 1;
-        |
-        |    let x = 1;
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = 1;
-        |
-        |<selection>    let x = 1;
-        |    let x = 1;
-        |</selection>
-        |    let x = 1;
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |<selection>    let x = 1;
-        |
-        |    let x = 1;
-        |    let x = 1;
-        |
-        |    let x = 1;
-        |</selection>}
-        |""")
-
-    fun `test fun body line comments`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    // foo
-        |
-        |<caret>    // foo
-        |    // foo
-        |
-        |    // foo
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    // foo
-        |
-        |<selection>    // foo
-        |</selection>    // foo
-        |
-        |    // foo
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    // foo
-        |
-        |<selection>    // foo
-        |    // foo
-        |</selection>
-        |    // foo
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |<selection>    // foo
-        |
-        |    // foo
-        |    // foo
-        |
-        |    // foo
-        |</selection>}
-        |""")
-
-    fun `test fun block expression`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let block = {
-        |        let x = 1;
-        |
-        |<caret>        let x = 1;
-        |        let x = 1;
-        |
-        |        let x = 1;
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let block = {
-        |        let x = 1;
-        |
-        |<selection>        let x = 1;
-        |</selection>        let x = 1;
-        |
-        |        let x = 1;
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let block = {
-        |        let x = 1;
-        |
-        |<selection>        let x = 1;
-        |        let x = 1;
-        |</selection>
-        |        let x = 1;
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let block = {
-        |<selection>        let x = 1;
-        |
-        |        let x = 1;
-        |        let x = 1;
-        |
-        |        let x = 1;
-        |</selection>    };
-        |}
-        |""")
-
-    fun `test struct body`() = doTestWithTrimmedMargins(
-        """
-        |struct Point {
-        |    a : i32,
-        |
-        |<caret>    a : i32,
-        |    a : i32,
-        |
-        |    a : i32
-        |}
-        |""",
-
-        """
-        |struct Point {
-        |    a : i32,
-        |
-        |<selection>    a : i32,
-        |</selection>    a : i32,
-        |
-        |    a : i32
-        |}
-        |""",
-
-        """
-        |struct Point {
-        |    a : i32,
-        |
-        |<selection>    a : i32,
-        |    a : i32,
-        |</selection>
-        |    a : i32
-        |}
-        |""",
-
-        """
-        |struct Point {
-        |<selection>    a : i32,
-        |
-        |    a : i32,
-        |    a : i32,
-        |
-        |    a : i32
-        |</selection>}
-        |""")
-
-    fun `test struct literal body`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a: 0,
-        |
-        |<caret>        a: 0,
-        |        a: 0,
-        |
-        |        a: 0
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a: 0,
-        |
-        |<selection>        a: 0,
-        |</selection>        a: 0,
-        |
-        |        a: 0
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a: 0,
-        |
-        |<selection>        a: 0,
-        |        a: 0,
-        |</selection>
-        |        a: 0
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |<selection>        a: 0,
-        |
-        |        a: 0,
-        |        a: 0,
-        |
-        |        a: 0
-        |</selection>    };
-        |}
-        |""")
-
-    fun `test struct body last field`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a : i32,
-        |
-        |<caret>        a : i32
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a : i32,
-        |
-        |<selection>        a : i32
-        |</selection>    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |<selection>        a : i32,
-        |
-        |        a : i32
-        |</selection>    };
-        |}
-        |""")
-
-    fun `test struct literal body last field`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a: 0,
-        |
-        |<caret>        a: 0
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |        a: 0,
-        |
-        |<selection>        a: 0
-        |</selection>    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let x = Point {
-        |<selection>        a: 0,
-        |
-        |        a: 0
-        |</selection>    };
-        |}
-        |""")
-
-    fun `test enum body`() = doTestWithTrimmedMargins(
-        """
-        |enum E {
-        |    A,
-        |
-        |<caret>    A,
-        |    A,
-        |
-        |    A
-        |}
-        |""",
-
-        """
-        |enum E {
-        |    A,
-        |
-        |<selection>    A,
-        |</selection>    A,
-        |
-        |    A
-        |}
-        |""",
-
-        """
-        |enum E {
-        |    A,
-        |
-        |<selection>    A,
-        |    A,
-        |</selection>
-        |    A
-        |}
-        |""",
-
-        """
-        |enum E {
-        |<selection>    A,
-        |
-        |    A,
-        |    A,
-        |
-        |    A
-        |</selection>}
-        |""")
-
-    fun `test trait body`() = doTestWithTrimmedMargins(
-        """
-        |trait Trait<T> {
-        |    fn f();
-        |
-        |<caret>    fn f();
-        |    fn f();
-        |
-        |    fn f();
-        |}
-        |""",
-
-        """
-        |trait Trait<T> {
-        |    fn f();
-        |
-        |<selection>    fn f();
-        |</selection>    fn f();
-        |
-        |    fn f();
-        |}
-        |""",
-
-        """
-        |trait Trait<T> {
-        |    fn f();
-        |
-        |<selection>    fn f();
-        |    fn f();
-        |</selection>
-        |    fn f();
-        |}
-        |""",
-
-        """
-        |trait Trait<T> {
-        |<selection>    fn f();
-        |
-        |    fn f();
-        |    fn f();
-        |
-        |    fn f();
-        |</selection>}
-        |""")
-
-
-    fun `test match expression`() = doTestWithTrimmedMargins(
-        """
-        |fn foo() {
-        |    let a = match 1 {
-        |        1 => 1,
-        |
-        |<caret>        2 => 2,
-        |        3 => {
-        |             3
-        |        },
-        |
-        |        _ => 0,
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let a = match 1 {
-        |        1 => 1,
-        |
-        |<selection>        2 => 2,
-        |</selection>        3 => {
-        |             3
-        |        },
-        |
-        |        _ => 0,
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let a = match 1 {
-        |        1 => 1,
-        |
-        |<selection>        2 => 2,
-        |        3 => {
-        |             3
-        |        },
-        |</selection>
-        |        _ => 0,
-        |    };
-        |}
-        |""",
-
-        """
-        |fn foo() {
-        |    let a = match 1 {
-        |<selection>        1 => 1,
-        |
-        |        2 => 2,
-        |        3 => {
-        |             3
-        |        },
-        |
-        |        _ => 0,
-        |</selection>    };
-        |}
-        |""")
-}
diff --git a/src/232/main/kotlin/org/rust/ide/navigation/compatUtils.kt b/src/232/main/kotlin/org/rust/ide/navigation/compatUtils.kt
deleted file mode 100644
index 4af06b7..0000000
--- a/src/232/main/kotlin/org/rust/ide/navigation/compatUtils.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Use of this source code is governed by the MIT license that can be
- * found in the LICENSE file.
- */
-
-package org.rust.ide.navigation
-
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.popup.JBPopup
-import com.intellij.openapi.util.NlsContexts
-import com.intellij.psi.PsiElement
-
-// BACKCOMPAT: 2023.1. Inline it
-fun getPsiElementPopup(elements: Array<PsiElement>, title: @NlsContexts.PopupTitle String?): JBPopup {
-    return com.intellij.codeInsight.navigation.getPsiElementPopup(elements, title)
-}
-
-// BACKCOMPAT: 2023.1. Inline it
-fun hidePopupIfDumbModeStarts(popup: JBPopup, project: Project) {
-    com.intellij.codeInsight.navigation.hidePopupIfDumbModeStarts(popup, project)
-}
diff --git a/src/232/test/kotlin/org/rust/DumbModeTestUtil.kt b/src/232/test/kotlin/org/rust/DumbModeTestUtil.kt
new file mode 100644
index 0000000..4eb009e
--- /dev/null
+++ b/src/232/test/kotlin/org/rust/DumbModeTestUtil.kt
@@ -0,0 +1,28 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.openapi.project.DumbServiceImpl
+import com.intellij.openapi.project.Project
+
+@Suppress("UnstableApiUsage")
+object DumbModeTestUtil {
+    fun startEternalDumbModeTask(project: Project): Token {
+        val dumbService = DumbServiceImpl.getInstance(project)
+        val oldValue = dumbService.isDumb
+        dumbService.isDumb = true
+        return Token(dumbService, oldValue)
+    }
+
+    class Token(
+        private val dumbService: DumbServiceImpl,
+        private val oldValue: Boolean,
+    ) : AutoCloseable {
+        override fun close() {
+            dumbService.isDumb = oldValue
+        }
+    }
+}
diff --git a/src/232/test/kotlin/org/rust/TestCompat.kt b/src/232/test/kotlin/org/rust/TestCompat.kt
new file mode 100644
index 0000000..cb50afa
--- /dev/null
+++ b/src/232/test/kotlin/org/rust/TestCompat.kt
@@ -0,0 +1,11 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.openapi.util.Key
+
+val Key<*>.escapeSequence: String
+    get() = toString()
diff --git a/src/test/kotlin/org/rust/ide/MockBrowserLauncher.kt b/src/232/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
similarity index 100%
rename from src/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
rename to src/232/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
diff --git a/src/233/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt b/src/233/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt
new file mode 100644
index 0000000..cbfeff5
--- /dev/null
+++ b/src/233/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt
@@ -0,0 +1,11 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.ide.debugger.runconfig
+
+import com.intellij.ide.plugins.IdeaPluginDescriptor
+import com.intellij.ide.plugins.PluginManagerCore
+
+fun PluginManagerCore.getLoadedPlugins(): List<IdeaPluginDescriptor> = loadedPlugins
diff --git a/src/233/test/kotlin/org/rust/DumbModeTestUtil.kt b/src/233/test/kotlin/org/rust/DumbModeTestUtil.kt
new file mode 100644
index 0000000..8f7fd69
--- /dev/null
+++ b/src/233/test/kotlin/org/rust/DumbModeTestUtil.kt
@@ -0,0 +1,24 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.DumbModeTestUtils
+
+object DumbModeTestUtil {
+    fun startEternalDumbModeTask(project: Project): Token {
+        return Token(project, DumbModeTestUtils.startEternalDumbModeTask(project))
+    }
+
+    class Token(
+        private val project: Project,
+        private val token: DumbModeTestUtils.EternalTaskShutdownToken
+    ) : AutoCloseable {
+        override fun close() {
+            DumbModeTestUtils.endEternalDumbModeTaskAndWaitForSmartMode(project, token)
+        }
+    }
+}
diff --git a/src/233/test/kotlin/org/rust/TestCompat.kt b/src/233/test/kotlin/org/rust/TestCompat.kt
new file mode 100644
index 0000000..58321a7
--- /dev/null
+++ b/src/233/test/kotlin/org/rust/TestCompat.kt
@@ -0,0 +1,13 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.execution.process.ProcessOutputType
+import com.intellij.openapi.util.Key
+
+// BACKCOMPAT 2023.2: move to the RsAnsiEscapeDecoderTest companion
+val Key<*>.escapeSequence: String
+    get() = (this as? ProcessOutputType)?.escapeSequence ?: toString()
diff --git a/src/233/test/kotlin/org/rust/ide/MockBrowserLauncher.kt b/src/233/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
new file mode 100644
index 0000000..ce47a7f
--- /dev/null
+++ b/src/233/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
@@ -0,0 +1,39 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.ide
+
+import com.intellij.ide.browsers.BrowserLauncher
+import com.intellij.ide.browsers.WebBrowser
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.replaceService
+import java.io.File
+import java.nio.file.Path
+
+class MockBrowserLauncher : BrowserLauncher() {
+    private var lastFile: File? = null
+    private var lastPath: Path? = null
+    var lastUrl: String? = null
+
+    override fun browse(file: File) {
+        lastFile = file
+    }
+
+    override fun browse(file: Path) {
+        lastPath = file
+    }
+
+    override fun browse(url: String, browser: WebBrowser?, project: Project?) {
+        lastUrl = url
+    }
+
+    override fun open(url: String) {}
+
+    fun replaceService(disposable: Disposable) {
+        ApplicationManager.getApplication().replaceService(BrowserLauncher::class.java, this, disposable)
+    }
+}
diff --git a/src/241/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt b/src/241/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt
new file mode 100644
index 0000000..cbfeff5
--- /dev/null
+++ b/src/241/main/kotlin/org/rust/ide/debugger/runconfig/comparUtils.kt
@@ -0,0 +1,11 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.ide.debugger.runconfig
+
+import com.intellij.ide.plugins.IdeaPluginDescriptor
+import com.intellij.ide.plugins.PluginManagerCore
+
+fun PluginManagerCore.getLoadedPlugins(): List<IdeaPluginDescriptor> = loadedPlugins
diff --git a/src/241/test/kotlin/org/rust/DumbModeTestUtil.kt b/src/241/test/kotlin/org/rust/DumbModeTestUtil.kt
new file mode 100644
index 0000000..8f7fd69
--- /dev/null
+++ b/src/241/test/kotlin/org/rust/DumbModeTestUtil.kt
@@ -0,0 +1,24 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.DumbModeTestUtils
+
+object DumbModeTestUtil {
+    fun startEternalDumbModeTask(project: Project): Token {
+        return Token(project, DumbModeTestUtils.startEternalDumbModeTask(project))
+    }
+
+    class Token(
+        private val project: Project,
+        private val token: DumbModeTestUtils.EternalTaskShutdownToken
+    ) : AutoCloseable {
+        override fun close() {
+            DumbModeTestUtils.endEternalDumbModeTaskAndWaitForSmartMode(project, token)
+        }
+    }
+}
diff --git a/src/241/test/kotlin/org/rust/TestCompat.kt b/src/241/test/kotlin/org/rust/TestCompat.kt
new file mode 100644
index 0000000..58321a7
--- /dev/null
+++ b/src/241/test/kotlin/org/rust/TestCompat.kt
@@ -0,0 +1,13 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import com.intellij.execution.process.ProcessOutputType
+import com.intellij.openapi.util.Key
+
+// BACKCOMPAT 2023.2: move to the RsAnsiEscapeDecoderTest companion
+val Key<*>.escapeSequence: String
+    get() = (this as? ProcessOutputType)?.escapeSequence ?: toString()
diff --git a/src/241/test/kotlin/org/rust/ide/MockBrowserLauncher.kt b/src/241/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
new file mode 100644
index 0000000..ce47a7f
--- /dev/null
+++ b/src/241/test/kotlin/org/rust/ide/MockBrowserLauncher.kt
@@ -0,0 +1,39 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust.ide
+
+import com.intellij.ide.browsers.BrowserLauncher
+import com.intellij.ide.browsers.WebBrowser
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.project.Project
+import com.intellij.testFramework.replaceService
+import java.io.File
+import java.nio.file.Path
+
+class MockBrowserLauncher : BrowserLauncher() {
+    private var lastFile: File? = null
+    private var lastPath: Path? = null
+    var lastUrl: String? = null
+
+    override fun browse(file: File) {
+        lastFile = file
+    }
+
+    override fun browse(file: Path) {
+        lastPath = file
+    }
+
+    override fun browse(url: String, browser: WebBrowser?, project: Project?) {
+        lastUrl = url
+    }
+
+    override fun open(url: String) {}
+
+    fun replaceService(disposable: Disposable) {
+        ApplicationManager.getApplication().replaceService(BrowserLauncher::class.java, this, disposable)
+    }
+}
diff --git a/src/main/kotlin/org/rust/cargo/project/settings/RsExternalLinterProjectSettingsService.kt b/src/main/kotlin/org/rust/cargo/project/settings/RsExternalLinterProjectSettingsService.kt
index d83dc35..1bdddcd 100644
--- a/src/main/kotlin/org/rust/cargo/project/settings/RsExternalLinterProjectSettingsService.kt
+++ b/src/main/kotlin/org/rust/cargo/project/settings/RsExternalLinterProjectSettingsService.kt
@@ -29,16 +29,6 @@
     val envs: Map<String, String> get() = state.envs
     val runOnTheFly: Boolean get() = state.runOnTheFly
 
-    override fun noStateLoaded() {
-        val rustSettings = project.rustSettings
-        state.tool = rustSettings.state.externalLinter
-        rustSettings.state.externalLinter = ExternalLinter.DEFAULT
-        state.additionalArguments = rustSettings.state.externalLinterArguments
-        rustSettings.state.externalLinterArguments = ""
-        state.runOnTheFly = rustSettings.state.runExternalLinterOnTheFly
-        rustSettings.state.runExternalLinterOnTheFly = false
-    }
-
     class RsExternalLinterProjectSettings : RsProjectSettingsBase<RsExternalLinterProjectSettings>() {
         @AffectsHighlighting
         var tool by enum(ExternalLinter.DEFAULT)
diff --git a/src/main/kotlin/org/rust/cargo/project/settings/RustProjectSettingsService.kt b/src/main/kotlin/org/rust/cargo/project/settings/RustProjectSettingsService.kt
index 06fb55f..0310afb 100644
--- a/src/main/kotlin/org/rust/cargo/project/settings/RustProjectSettingsService.kt
+++ b/src/main/kotlin/org/rust/cargo/project/settings/RustProjectSettingsService.kt
@@ -16,7 +16,6 @@
 import com.intellij.util.xmlb.annotations.Transient
 import org.rust.cargo.project.configurable.RsProjectConfigurable
 import org.rust.cargo.project.settings.RustProjectSettingsService.RustProjectSettings
-import org.rust.cargo.toolchain.ExternalLinter
 import org.rust.cargo.toolchain.RsToolchainBase
 import org.rust.cargo.toolchain.RsToolchainProvider
 import org.rust.openapiext.isUnitTestMode
@@ -64,12 +63,6 @@
         // provide path to stdlib explicitly.
         @AffectsCargoMetadata
         var explicitPathToStdlib by string()
-        // BACKCOMPAT: 2023.1
-        var externalLinter by enum(ExternalLinter.DEFAULT)
-        // BACKCOMPAT: 2023.1
-        var runExternalLinterOnTheFly by property(false)
-        // BACKCOMPAT: 2023.1
-        var externalLinterArguments by property("") { it.isEmpty() }
         @AffectsHighlighting
         var compileAllTargets by property(true)
         var useOffline by property(false)
diff --git a/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt b/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
index 3ca182a..8dd593f 100644
--- a/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
+++ b/src/main/kotlin/org/rust/cargo/runconfig/command/CargoCommandConfiguration.kt
@@ -9,7 +9,6 @@
 import com.intellij.execution.InputRedirectAware
 import com.intellij.execution.configuration.EnvironmentVariablesData
 import com.intellij.execution.configurations.*
-import com.intellij.execution.impl.statistics.FusAwareRunConfiguration
 import com.intellij.execution.runners.ExecutionEnvironment
 import com.intellij.execution.target.LanguageRuntimeType
 import com.intellij.execution.target.TargetEnvironmentAwareRunProfile
@@ -17,7 +16,6 @@
 import com.intellij.execution.testframework.actions.ConsolePropertiesProvider
 import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties
 import com.intellij.execution.util.ProgramParametersUtil
-import com.intellij.internal.statistic.eventLog.events.EventPair
 import com.intellij.openapi.options.SettingsEditor
 import com.intellij.openapi.options.advanced.AdvancedSettings
 import com.intellij.openapi.project.Project
@@ -212,7 +210,9 @@
     override fun createTestConsoleProperties(executor: Executor): SMTRunnerConsoleProperties? {
         val config = clean().ok ?: return null
         return if (showTestToolWindow(config.cmd)) {
-            CargoTestConsoleProperties(this, executor)
+            val cargoProject = findCargoProject(project, config.cmd.additionalArguments, config.cmd.workingDirectory)
+            val version = cargoProject?.rustcInfo?.version?.semver
+            CargoTestConsoleProperties(this, executor, version)
         } else {
             null
         }
diff --git a/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestConsoleProperties.kt b/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestConsoleProperties.kt
index ee672fb..68825a9 100644
--- a/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestConsoleProperties.kt
+++ b/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestConsoleProperties.kt
@@ -14,10 +14,12 @@
 import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties
 import com.intellij.execution.testframework.sm.runner.SMTestLocator
 import com.intellij.execution.ui.ConsoleViewContentType
+import com.intellij.util.text.SemVer
 
 class CargoTestConsoleProperties(
     config: RunConfiguration,
-    executor: Executor
+    executor: Executor,
+    private val rustcVersion: SemVer?
 ) : SMTRunnerConsoleProperties(config, TEST_FRAMEWORK_NAME, executor), SMCustomMessagesParsing {
 
     init {
@@ -29,7 +31,11 @@
     override fun createTestEventsConverter(
         testFrameworkName: String,
         consoleProperties: TestConsoleProperties
-    ): OutputToGeneralTestEventsConverter = CargoTestEventsConverter(testFrameworkName, consoleProperties)
+    ): OutputToGeneralTestEventsConverter = CargoTestEventsConverter(
+        testFrameworkName,
+        consoleProperties,
+        rustcVersion
+    )
 
     override fun printExpectedActualHeader(printer: Printer, expected: String, actual: String) {
         printer.print("\n", ConsoleViewContentType.ERROR_OUTPUT)
diff --git a/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestEventsConverter.kt b/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestEventsConverter.kt
index 6729cd0..18cf47c 100644
--- a/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestEventsConverter.kt
+++ b/src/main/kotlin/org/rust/cargo/runconfig/test/CargoTestEventsConverter.kt
@@ -15,6 +15,8 @@
 import com.intellij.openapi.util.text.StringUtil.unescapeStringCharacters
 import com.intellij.openapi.util.text.StringUtil.unquoteString
 import com.intellij.util.execution.ParametersListUtil
+import com.intellij.util.text.SemVer
+import com.intellij.util.text.nullize
 import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor
 import org.rust.cargo.project.model.cargoProjects
 import org.rust.cargo.project.model.impl.allPackages
@@ -22,6 +24,7 @@
 import org.rust.cargo.project.workspace.CargoWorkspace
 import org.rust.cargo.project.workspace.PackageOrigin
 import org.rust.cargo.runconfig.test.CargoTestEventsConverter.State.*
+import org.rust.cargo.util.parseSemVer
 import org.rust.openapiext.JsonUtils.tryParseJsonObject
 import org.rust.stdext.removeLast
 import java.io.File
@@ -30,7 +33,8 @@
 
 class CargoTestEventsConverter(
     testFrameworkName: String,
-    consoleProperties: TestConsoleProperties
+    consoleProperties: TestConsoleProperties,
+    private val rustcVersion: SemVer?
 ) : OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) {
     private val project: Project = consoleProperties.project
     private var converterState: State = START_MESSAGE
@@ -165,7 +169,7 @@
                 val duration = getTestDuration(testMessage.name)
                 val (stdout, failedMessage) = parseFailedTestOutput(testMessage.stdout ?: "")
                 if (stdout.isNotEmpty()) messages.add(createTestStdOutMessage(testMessage.name, stdout + '\n'))
-                messages.add(createTestFailedMessage(testMessage.name, failedMessage))
+                messages.add(createTestFailedMessage(testMessage.name, failedMessage, rustcVersion))
                 messages.add(createTestFinishedMessage(testMessage.name, duration))
                 recordSuiteChildFinished(testMessage.name)
                 processFinishedSuites(messages)
@@ -268,6 +272,8 @@
     }
 
     companion object {
+        private val RUSTC_1_73_BETA: SemVer = "1.73.0-beta".parseSemVer()
+
         private const val TARGET_PATH_PART: String = "/target/"
 
         private const val ROOT_SUITE: String = "0"
@@ -278,8 +284,12 @@
 
         private val LINE_NUMBER_RE: Regex = """\s+\(line\s+(?<line>\d+)\)\s*""".toRegex()
 
+        private val ERROR_MESSAGE_RE_OLD: Regex =
+            """thread '.*' panicked at '(?<fullMessage>(assertion failed: `\(left (?<sign>.*) right\)`\s*left: `(?<left>.*?)`,\s*right: `(?<right>.*?)`(: )?)?(?<message>.*))',"""
+                .toRegex(setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL))
+
         private val ERROR_MESSAGE_RE: Regex =
-            """thread '.*' panicked at '(assertion failed: `\(left (?<sign>.*) right\)`\s*left: `(?<left>.*?)`,\s*right: `(?<right>.*?)`(: )?)?(?<message>.*)',"""
+            """thread '.*' panicked at (\S*)\n(?<fullMessage>(assertion `left (?<sign>.*) right` failed(: )?)?(?<message>.*?)\n\s*(left: (?<left>.*?)\n\s*right: (?<right>.*?)\n)?)(note|stack backtrace):"""
                 .toRegex(setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL))
 
         private val NodeId.name: String
@@ -321,12 +331,12 @@
                 .addAttribute("locationHint", CargoTestLocator.getTestUrl(name))
         }
 
-        private fun createTestFailedMessage(test: NodeId, failedMessage: String): ServiceMessageBuilder {
+        private fun createTestFailedMessage(test: NodeId, failedMessage: String, rustcVersion: SemVer?): ServiceMessageBuilder {
             val builder = ServiceMessageBuilder.testFailed(test.name)
                 .addAttribute("nodeId", test)
                 // TODO: pass backtrace here
                 .addAttribute("details", failedMessage)
-            val parseResult = parseErrorMessage(failedMessage)
+            val parseResult = parseErrorMessage(failedMessage, rustcVersion)
             if (parseResult == null) {
                 builder.addAttribute("message", "")
             } else {
@@ -366,9 +376,11 @@
             return FailedTestOutput(stdout, failedMessage)
         }
 
-        private fun parseErrorMessage(failedMessage: String): ErrorMessage? {
-            val groups = ERROR_MESSAGE_RE.find(failedMessage)?.groups ?: return null
-            val message = groups["message"]?.value ?: error("Failed to find `message` capturing group")
+        private fun parseErrorMessage(failedMessage: String, rustcVersion: SemVer?): ErrorMessage? {
+            val regex = if (rustcVersion == null || rustcVersion < RUSTC_1_73_BETA) ERROR_MESSAGE_RE_OLD else ERROR_MESSAGE_RE
+            val groups = regex.find(failedMessage)?.groups ?: return null
+            val message = groups["message"]?.value.nullize() ?: groups["fullMessage"]?.value
+                ?: error("Failed to find `message` or `fullMessage` capturing group")
 
             val diff = if (groups["sign"]?.value == "==") {
                 val left = groups["left"]?.value?.let(::unescape)
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/RsLocalToolchain.kt b/src/main/kotlin/org/rust/cargo/toolchain/RsLocalToolchain.kt
index 81c605b..6d0b6d2 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/RsLocalToolchain.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/RsLocalToolchain.kt
@@ -19,7 +19,7 @@
 
     override val executionTimeoutInMilliseconds: Int = 1000
 
-    override fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine = commandLine
+    override fun patchCommandLine(commandLine: GeneralCommandLine, withSudo: Boolean): GeneralCommandLine = commandLine
 
     override fun toLocalPath(remotePath: String): String = remotePath
 
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/RsToolchainBase.kt b/src/main/kotlin/org/rust/cargo/toolchain/RsToolchainBase.kt
index 7ceeae6..779335c 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/RsToolchainBase.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/RsToolchainBase.kt
@@ -34,7 +34,7 @@
     /**
      * Patches passed command line to make it runnable in remote context.
      */
-    abstract fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine
+    abstract fun patchCommandLine(commandLine: GeneralCommandLine, withSudo: Boolean): GeneralCommandLine
 
     abstract fun toLocalPath(remotePath: String): String
 
@@ -110,7 +110,7 @@
         }
 
         if (patchToRemote) {
-            commandLine = patchCommandLine(commandLine)
+            commandLine = patchCommandLine(commandLine, withSudo)
         }
 
         return commandLine
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/tools/RsTool.kt b/src/main/kotlin/org/rust/cargo/toolchain/tools/RsTool.kt
index a4611a8..cec0947 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/tools/RsTool.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/tools/RsTool.kt
@@ -33,7 +33,7 @@
         .withParameters(parameters)
         .withEnvironment(environment)
         .withCharset(Charsets.UTF_8)
-        .also { toolchain.patchCommandLine(it) }
+        .also { toolchain.patchCommandLine(it, withSudo = false) }
 }
 
 abstract class CargoBinary(binaryName: String, toolchain: RsToolchainBase) : RsTool(binaryName, toolchain) {
diff --git a/src/main/kotlin/org/rust/cargo/toolchain/wsl/RsWslToolchain.kt b/src/main/kotlin/org/rust/cargo/toolchain/wsl/RsWslToolchain.kt
index 7c44d3e..6a877bd 100644
--- a/src/main/kotlin/org/rust/cargo/toolchain/wsl/RsWslToolchain.kt
+++ b/src/main/kotlin/org/rust/cargo/toolchain/wsl/RsWslToolchain.kt
@@ -28,7 +28,7 @@
 
     override val executionTimeoutInMilliseconds: Int = 5000
 
-    override fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
+    override fun patchCommandLine(commandLine: GeneralCommandLine, withSudo: Boolean): GeneralCommandLine {
         commandLine.exePath = toRemotePath(commandLine.exePath)
 
         val parameters = commandLine.parametersList.list.map { toRemotePath(it) }
@@ -49,6 +49,7 @@
         val remoteWorkDir = commandLine.workDirectory?.absolutePath
             ?.let { toRemotePath(it) }
         val options = WSLCommandLineOptions()
+            .setSudo(withSudo)
             .setRemoteWorkingDirectory(remoteWorkDir)
             .addInitCommand("export PATH=\"${linuxPath.systemIndependentPath}:\$PATH\"")
         return distribution.patchCommandLine(commandLine, null, options)
diff --git a/src/main/kotlin/org/rust/ide/actions/RustfmtFileAction.kt b/src/main/kotlin/org/rust/ide/actions/RustfmtFileAction.kt
index 76fcea7..6da4ecb 100644
--- a/src/main/kotlin/org/rust/ide/actions/RustfmtFileAction.kt
+++ b/src/main/kotlin/org/rust/ide/actions/RustfmtFileAction.kt
@@ -24,7 +24,9 @@
 
 class RustfmtFileAction : DumbAwareAction() {
 
-    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
+    override fun getActionUpdateThread(): ActionUpdateThread {
+        return ActionUpdateThread.BGT
+    }
 
     override fun update(e: AnActionEvent) {
         super.update(e)
diff --git a/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt b/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
index 82d9cae..d550e55 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsExternalLinterUtils.kt
@@ -31,7 +31,7 @@
 import com.intellij.util.PathUtil
 import com.intellij.util.io.URLUtil
 import com.intellij.util.messages.MessageBus
-import org.apache.commons.lang.StringEscapeUtils
+import org.apache.commons.lang3.StringEscapeUtils
 import org.jetbrains.annotations.Nls
 import org.rust.RsBundle
 import org.rust.cargo.project.workspace.PackageOrigin
@@ -277,7 +277,7 @@
             val textRange = span.toTextRange(document) ?: return null
 
             @NlsSafe val tooltip = buildString {
-                append(formatMessage(StringEscapeUtils.escapeHtml(message.message)).escapeUrls())
+                append(formatMessage(StringEscapeUtils.escapeHtml4(message.message)).escapeUrls())
                 val code = message.code.formatAsLink()
                 if (code != null) {
                     append(" [$code]")
@@ -285,12 +285,12 @@
 
                 with(mutableListOf<String>()) {
                     if (span.label != null && !message.message.startsWith(span.label)) {
-                        add(StringEscapeUtils.escapeHtml(span.label))
+                        add(StringEscapeUtils.escapeHtml4(span.label))
                     }
 
                     message.children
                         .filter { it.message.isNotBlank() }
-                        .map { "${it.level.capitalized()}: ${StringEscapeUtils.escapeHtml(it.message)}" }
+                        .map { "${it.level.capitalized()}: ${StringEscapeUtils.escapeHtml4(it.message)}" }
                         .forEach { add(it) }
 
                     append(joinToString(prefix = "<br>", separator = "<br>") { formatMessage(it) }.escapeUrls())
diff --git a/src/main/kotlin/org/rust/ide/hints/type/RsChainMethodTypeHintsProvider.kt b/src/main/kotlin/org/rust/ide/hints/type/RsChainMethodTypeHintsProvider.kt
index 839c20d..f1db72a 100644
--- a/src/main/kotlin/org/rust/ide/hints/type/RsChainMethodTypeHintsProvider.kt
+++ b/src/main/kotlin/org/rust/ide/hints/type/RsChainMethodTypeHintsProvider.kt
@@ -37,6 +37,7 @@
 import org.rust.lang.core.types.ty.TyUnknown
 import org.rust.lang.core.types.type
 import org.rust.openapiext.escaped
+import java.lang.ref.WeakReference
 import javax.swing.JComponent
 import javax.swing.JPanel
 
@@ -78,11 +79,8 @@
         return object : FactoryInlayHintsCollector(editor) {
             val typeHintsFactory = RsTypeHintsPresentationFactory(factory, true)
 
-            private val lookupAndIteratorTrait: Pair<ImplLookup?, BoundElement<RsTraitItem>?> by lazy(LazyThreadSafetyMode.PUBLICATION) {
-                val (lookup, items) = (file as? RsFile)?.implLookupAndKnownItems ?: (null to null)
-                val iterator = items?.Iterator?.let { BoundElement(it) }
-                lookup to iterator
-            }
+            val lookupAndIteratorTrait: ThreadLocal<WeakReference<Pair<ImplLookup?, BoundElement<RsTraitItem>?>>?> =
+                ThreadLocal()
 
             override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean {
                 if (DumbService.isDumb(project)) return true
@@ -94,7 +92,13 @@
                     else -> false
                 }
 
-                val (lookup, iterator) = lookupAndIteratorTrait
+                val (lookup, iterator) = lookupAndIteratorTrait.get()?.get() ?: run {
+                    val (lookup, items) = (file as? RsFile)?.implLookupAndKnownItems ?: (null to null)
+                    val iterator = items?.Iterator?.let { BoundElement(it) }
+                    val result = lookup to iterator
+                    lookupAndIteratorTrait.set(WeakReference(result))
+                    result
+                }
 
                 val chain = collectChain(element)
                 val chainExpanded = if (isAttrProcMacro) {
diff --git a/src/main/kotlin/org/rust/ide/inspections/RsWithMacrosInspectionVisitor.kt b/src/main/kotlin/org/rust/ide/inspections/RsWithMacrosInspectionVisitor.kt
index dbb88f4..329ac19 100644
--- a/src/main/kotlin/org/rust/ide/inspections/RsWithMacrosInspectionVisitor.kt
+++ b/src/main/kotlin/org/rust/ide/inspections/RsWithMacrosInspectionVisitor.kt
@@ -15,7 +15,7 @@
  * expanded from the macro. This visitor is intended to be used in [RsLocalInspectionTool] implementations.
  */
 abstract class RsWithMacrosInspectionVisitor : RsVisitor() {
-    private var processingMacros: Boolean = false
+    private var processingMacros: ThreadLocal<Boolean> = ThreadLocal.withInitial { false }
 
     final override fun visitConstant(o: RsConstant) {
         visitConstant2(o)
@@ -153,12 +153,13 @@
     }
 
     private fun visitMacroExpansion(item: RsAttrProcMacroOwner) {
-        if (processingMacros) return
+        if (processingMacros.get()) return
 
         val preparedMacro = item.procMacroAttribute?.attr?.prepareForExpansionHighlighting() ?: return
         val macros = mutableListOf(preparedMacro)
 
-        processingMacros = true
+        processingMacros.set(true)
+        println(Thread.currentThread().name)
 
         while (macros.isNotEmpty()) {
             val macro = macros.removeLast()
@@ -170,6 +171,6 @@
             }
         }
 
-        processingMacros = false
+        processingMacros.set(false)
     }
 }
diff --git a/src/main/kotlin/org/rust/ide/inspections/import/ui.kt b/src/main/kotlin/org/rust/ide/inspections/import/ui.kt
index 06146b1..bff865f 100644
--- a/src/main/kotlin/org/rust/ide/inspections/import/ui.kt
+++ b/src/main/kotlin/org/rust/ide/inspections/import/ui.kt
@@ -5,6 +5,7 @@
 
 package org.rust.ide.inspections.import
 
+import com.intellij.codeInsight.navigation.hidePopupIfDumbModeStarts
 import com.intellij.ide.util.DefaultPsiElementCellRenderer
 import com.intellij.openapi.actionSystem.DataContext
 import com.intellij.openapi.project.Project
@@ -20,7 +21,6 @@
 import org.rust.cargo.icons.CargoIcons
 import org.rust.cargo.project.workspace.PackageOrigin
 import org.rust.ide.icons.RsIcons
-import org.rust.ide.navigation.hidePopupIfDumbModeStarts
 import org.rust.ide.utils.import.ImportCandidate
 import org.rust.openapiext.isUnitTestMode
 import java.awt.BorderLayout
diff --git a/src/main/kotlin/org/rust/ide/lineMarkers/CargoExecutableRunLineMarkerContributor.kt b/src/main/kotlin/org/rust/ide/lineMarkers/CargoExecutableRunLineMarkerContributor.kt
index 84dc6ab..a8dd300 100644
--- a/src/main/kotlin/org/rust/ide/lineMarkers/CargoExecutableRunLineMarkerContributor.kt
+++ b/src/main/kotlin/org/rust/ide/lineMarkers/CargoExecutableRunLineMarkerContributor.kt
@@ -13,6 +13,7 @@
 import org.rust.lang.core.psi.RsElementTypes.IDENTIFIER
 import org.rust.lang.core.psi.RsFunction
 import org.rust.lang.core.psi.ext.elementType
+import org.rust.openapiext.isUnitTestMode
 
 class CargoExecutableRunLineMarkerContributor : RunLineMarkerContributor() {
     override fun getInfo(element: PsiElement): Info? {
@@ -23,7 +24,14 @@
         val actions = ExecutorAction.getActions(0)
         return Info(
             AllIcons.RunConfigurations.TestState.Run,
-            { psiElement -> actions.mapNotNull { getText(it, psiElement) }.joinToString("\n") },
+            { psiElement ->
+                val texts = actions.mapNotNull { getText(it, psiElement) }
+                if (isUnitTestMode) {
+                    texts.firstOrNull().orEmpty()
+                } else {
+                    texts.joinToString("\n")
+                }
+            },
             *actions
         )
     }
diff --git a/src/main/kotlin/org/rust/ide/navigation/goto/RsGotoSuperHandler.kt b/src/main/kotlin/org/rust/ide/navigation/goto/RsGotoSuperHandler.kt
index f9b9e20..efe653b 100644
--- a/src/main/kotlin/org/rust/ide/navigation/goto/RsGotoSuperHandler.kt
+++ b/src/main/kotlin/org/rust/ide/navigation/goto/RsGotoSuperHandler.kt
@@ -6,6 +6,7 @@
 package org.rust.ide.navigation.goto
 
 import com.google.common.annotations.VisibleForTesting
+import com.intellij.codeInsight.navigation.getPsiElementPopup
 import com.intellij.lang.LanguageCodeInsightActionHandler
 import com.intellij.openapi.editor.Editor
 import com.intellij.openapi.project.Project
@@ -14,7 +15,6 @@
 import com.intellij.psi.PsiFile
 import com.intellij.psi.util.PsiTreeUtil
 import org.rust.RsBundle
-import org.rust.ide.navigation.getPsiElementPopup
 import org.rust.lang.core.psi.RsFile
 import org.rust.lang.core.psi.ext.*
 import org.rust.openapiext.toPsiFile
diff --git a/src/main/kotlin/org/rust/ide/newProject/RsNewProjectWizard.kt b/src/main/kotlin/org/rust/ide/newProject/RsNewProjectWizard.kt
index d249c76..8d5d2b4 100644
--- a/src/main/kotlin/org/rust/ide/newProject/RsNewProjectWizard.kt
+++ b/src/main/kotlin/org/rust/ide/newProject/RsNewProjectWizard.kt
@@ -14,8 +14,8 @@
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.roots.ModuleRootModificationUtil
 import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.ui.dsl.builder.AlignX
 import com.intellij.ui.dsl.builder.Panel
-import com.intellij.ui.dsl.gridLayout.HorizontalAlign
 import org.rust.ide.module.RsModuleBuilder
 import org.rust.stdext.toPathOrNull
 import java.nio.file.Path
@@ -35,9 +35,9 @@
             with(builder) {
                 row {
                     cell(peer.component)
-                        .horizontalAlign(HorizontalAlign.FILL)
+                        .align(AlignX.FILL)
                         .validationRequestor { peer.checkValid = Runnable(it) }
-                        .validation { peer.validate() }
+                        .validationInfo { peer.validate() }
                 }
             }
         }
diff --git a/src/main/kotlin/org/rust/ide/refactoring/RsMemberSelectionPanel.kt b/src/main/kotlin/org/rust/ide/refactoring/RsMemberSelectionPanel.kt
index 5f38706..1fe36f7 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/RsMemberSelectionPanel.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/RsMemberSelectionPanel.kt
@@ -11,7 +11,7 @@
 import com.intellij.refactoring.ui.AbstractMemberSelectionTable
 import com.intellij.refactoring.ui.MemberSelectionPanelBase
 import com.intellij.ui.RowIcon
-import org.apache.commons.lang.StringEscapeUtils
+import org.apache.commons.lang3.StringEscapeUtils
 import org.rust.ide.docs.signature
 import org.rust.lang.core.psi.RsModItem
 import org.rust.lang.core.psi.ext.RsItemElement
@@ -26,8 +26,7 @@
     AbstractMemberSelectionTable<RsItemElement, RsMemberInfo>
     >(title, RsMemberSelectionTable(memberInfo))
 
-class RsMemberSelectionTable(memberInfo: List<RsMemberInfo>)
-    : AbstractMemberSelectionTable<RsItemElement, RsMemberInfo>(memberInfo, null, null) {
+class RsMemberSelectionTable(memberInfo: List<RsMemberInfo>) : AbstractMemberSelectionTable<RsItemElement, RsMemberInfo>(memberInfo, null, null) {
 
     init {
         setTableHeader(null)
@@ -54,7 +53,7 @@
             "mod ${member.modName}"
         } else {
             val description = buildString { member.signature(this) }
-            StringEscapeUtils.unescapeHtml(StringUtil.removeHtmlTags(description))
+            StringEscapeUtils.unescapeHtml4(StringUtil.removeHtmlTags(description))
         }
     }
 }
diff --git a/src/main/kotlin/org/rust/ide/refactoring/introduceParameter/ui.kt b/src/main/kotlin/org/rust/ide/refactoring/introduceParameter/ui.kt
index be145dd..ca06351 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/introduceParameter/ui.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/introduceParameter/ui.kt
@@ -4,13 +4,13 @@
  */
 package org.rust.ide.refactoring.introduceParameter
 
+import com.intellij.codeInsight.navigation.hidePopupIfDumbModeStarts
 import com.intellij.codeInsight.unwrap.ScopeHighlighter
 import com.intellij.openapi.editor.Editor
 import com.intellij.openapi.ui.popup.JBPopupFactory
 import com.intellij.openapi.ui.popup.JBPopupListener
 import com.intellij.openapi.ui.popup.LightweightWindowEvent
 import org.rust.RsBundle
-import org.rust.ide.navigation.hidePopupIfDumbModeStarts
 import org.rust.ide.refactoring.MOCK
 import org.rust.lang.core.psi.RsFunction
 import org.rust.lang.core.psi.ext.title
diff --git a/src/main/kotlin/org/rust/ide/refactoring/move/RsMoveTopLevelItemsDialog.kt b/src/main/kotlin/org/rust/ide/refactoring/move/RsMoveTopLevelItemsDialog.kt
index 707cea0..81dc2ea 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/move/RsMoveTopLevelItemsDialog.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/move/RsMoveTopLevelItemsDialog.kt
@@ -23,12 +23,12 @@
 import com.intellij.ui.ColoredTreeCellRenderer
 import com.intellij.ui.SimpleTextAttributes
 import com.intellij.ui.components.JBTextField
+import com.intellij.ui.dsl.builder.AlignY
 import com.intellij.ui.dsl.builder.bindSelected
 import com.intellij.ui.dsl.builder.panel
-import com.intellij.ui.dsl.gridLayout.VerticalAlign
 import com.intellij.util.IncorrectOperationException
 import com.intellij.util.ui.JBUI
-import org.apache.commons.lang.StringEscapeUtils
+import org.apache.commons.lang3.StringEscapeUtils
 import org.jetbrains.annotations.Nls
 import org.rust.RsBundle
 import org.rust.ide.docs.signature
@@ -113,7 +113,7 @@
             row {
                 resizableRow()
                 fullWidthCell(memberPanel)
-                    .verticalAlign(VerticalAlign.FILL)
+                    .align(AlignY.FILL)
             }
             row {
                 checkBox(RefactoringBundle.message("search.for.references"))
@@ -203,7 +203,7 @@
             RsBundle.message("mod.0", member.modName?:"")
         } else {
             val descriptionHTML = buildString { member.signature(this) }
-            val description = StringEscapeUtils.unescapeHtml(StringUtil.removeHtmlTags(descriptionHTML))
+            val description = StringEscapeUtils.unescapeHtml4(StringUtil.removeHtmlTags(descriptionHTML))
             description.replace("(?U)\\s+".toRegex(), " ")
         }
         renderer.append(description, SimpleTextAttributes.REGULAR_ATTRIBUTES)
diff --git a/src/main/kotlin/org/rust/ide/typing/paste/RsImportCopyPasteProcessor.kt b/src/main/kotlin/org/rust/ide/typing/paste/RsImportCopyPasteProcessor.kt
index f00ac01..05f7328 100644
--- a/src/main/kotlin/org/rust/ide/typing/paste/RsImportCopyPasteProcessor.kt
+++ b/src/main/kotlin/org/rust/ide/typing/paste/RsImportCopyPasteProcessor.kt
@@ -18,7 +18,6 @@
 import com.intellij.psi.PsiDocumentManager
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiFile
-import com.intellij.psi.impl.source.tree.injected.changesHandler.range
 import org.rust.ide.fixes.QualifyPathFix
 import org.rust.ide.inspections.import.AutoImportFix
 import org.rust.ide.settings.RsCodeInsightSettings
@@ -69,8 +68,8 @@
 
     override fun getOffsetCount(): Int = 0
 
-    override fun getOffsets(offsets: IntArray?, index: Int): Int = index
-    override fun setOffsets(offsets: IntArray?, index: Int): Int = index
+    override fun getOffsets(offsets: IntArray, index: Int): Int = index
+    override fun setOffsets(offsets: IntArray, index: Int): Int = index
 }
 
 class RsImportCopyPasteProcessor : CopyPastePostProcessor<RsTextBlockTransferableData>() {
@@ -124,12 +123,12 @@
             ?.containingModOrSelf ?: return
         val importCtx = containingMod.firstItem ?: return
 
-        val importOffset = bounds.range.startOffset
+        val importOffset = bounds.textRange.startOffset
 
         val processor = ImportingProcessor(importOffset, data.importMap)
 
         runWriteAction {
-            processElementsInRange(file, bounds.range, processor)
+            processElementsInRange(file, bounds.textRange, processor)
             // We need to import the candidates after visiting all elements, otherwise the relative offsets could be
             // invalidated after an import has been added
             for (candidate in processor.importCandidates) {
diff --git a/src/main/kotlin/org/rust/ide/wordSelection/RsMacroCallSelectionHandler.kt b/src/main/kotlin/org/rust/ide/wordSelection/RsMacroCallSelectionHandler.kt
index fc21d58..c5d82f5 100644
--- a/src/main/kotlin/org/rust/ide/wordSelection/RsMacroCallSelectionHandler.kt
+++ b/src/main/kotlin/org/rust/ide/wordSelection/RsMacroCallSelectionHandler.kt
@@ -118,7 +118,7 @@
     override fun getScrollPane(): JScrollPane { throw notImplemented() }
     override fun isRendererMode(): Boolean { throw notImplemented() }
     override fun setRendererMode(isRendererMode: Boolean) { throw notImplemented() }
-    override fun setFile(vFile: VirtualFile?) { throw notImplemented() }
+    override fun setFile(vFile: VirtualFile) { throw notImplemented() }
     override fun getDataContext(): DataContext { throw notImplemented() }
     override fun processKeyTyped(e: KeyEvent): Boolean { throw notImplemented() }
     override fun setFontSize(fontSize: Int) { throw notImplemented() }
diff --git a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
index e62cca1..60362fc 100644
--- a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
+++ b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
@@ -248,7 +248,8 @@
     private val containingCrate: Crate,
     val items: KnownItems,
     private val paramEnv: ParamEnv,
-    context: RsElement? = null
+    context: RsElement? = null,
+    options: TypeInferenceOptions = TypeInferenceOptions.DEFAULT
 ) {
     // Non-concurrent HashMap and lazy(NONE) are safe here because this class isn't shared between threads
     private val traitSelectionCache: MutableMap<TraitRef, SelectionResult<SelectionCandidate>> = hashMapOf()
@@ -284,8 +285,47 @@
         ancestorItem.implsFromNestedMacros
     }
 
+    /**
+     * Returns `ImplsFilter.ConstBodyInsideImplSignatureFilter` during type inference in such const bodies:
+     *
+     * ```rust
+     * impl Foo< {0} > for Bar< {0} > {}
+     * //        ~~~            ~~~
+     * ```
+     *
+     * TODO RUST-12502 this is a hack to deal with infinite recursion in const eval (issues like RUST-11763)
+     */
+    private val implsFilter: ImplsFilter by lazy(NONE) {
+        val contextPath = context?.inferenceContextOwner as? RsPath ?: return@lazy ImplsFilter.AllowAll
+        val ancestorImpl = contextPath.contexts.withPrevious().find { (it, prev) ->
+            it is RsImplItem && (prev is RsTraitRef || prev == it.typeReference)
+        }?.first as? RsImplItem ?: return@lazy ImplsFilter.AllowAll
+
+        val isInsideTraitImpl = ancestorImpl.traitRef != null
+        ImplsFilter.ConstBodyInsideImplSignatureFilter(containingCrate, allowInherentImpls = isInsideTraitImpl)
+    }
+
+    // TODO RUST-12502 this is a hack to deal with infinite recursion in const eval (issues like RUST-11763)
+    private sealed interface ImplsFilter {
+        fun canProcessImpl(impl: RsCachedImplItem): Boolean
+
+        class ConstBodyInsideImplSignatureFilter(
+            private val containingCrate: Crate,
+            private val allowInherentImpls: Boolean,
+        ) : ImplsFilter {
+            override fun canProcessImpl(impl: RsCachedImplItem): Boolean {
+                return allowInherentImpls && impl.isInherent
+                    || containingCrate !in impl.containingCrates
+            }
+        }
+
+        data object AllowAll : ImplsFilter {
+            override fun canProcessImpl(impl: RsCachedImplItem): Boolean = true
+        }
+    }
+
     val ctx: RsInferenceContext by lazy(NONE) {
-        RsInferenceContext(project, this, items)
+        RsInferenceContext(project, this, items, options)
     }
 
     fun getEnvBoundTransitivelyFor(ty: Ty): Sequence<BoundElement<RsTraitItem>> {
@@ -398,6 +438,7 @@
             .asSequence()
             .filter { useImplsFromCrate(it.containingCrates) }
             .plus(implsFromNestedMacros[tyf].orEmpty())
+            .filter(implsFilter::canProcessImpl)
 
     private fun findPotentialAliases(tyf: TyFingerprint): List<String> =
         indexCache.findPotentialAliases(tyf)
@@ -494,18 +535,18 @@
 
     private fun selectCandidate(ref: TraitRef, recursionDepth: Int): SelectionResult<SelectionCandidate> {
         if (ref.selfTy is TyInfer.TyVar) {
-            return SelectionResult.Ambiguous
+            return SelectionResult.Ambiguous()
         }
         if (ref.selfTy is TyReference && ref.selfTy.referenced is TyInfer.TyVar) {
             // This condition is related to TyFingerprint internals: TyFingerprint should not be created for
             // TyInfer.TyVar, and TyReference is a single special case: it unwraps during TyFingerprint creation
-            return SelectionResult.Ambiguous
+            return SelectionResult.Ambiguous()
         }
 
         val candidateSet = assembleCandidates(ref)
 
         if (candidateSet.ambiguous) {
-            return SelectionResult.Ambiguous
+            return SelectionResult.Ambiguous()
         }
         val candidates = candidateSet.list.filter { it !is ImplCandidate.ExplicitImpl || !it.isNegativeImpl }
 
@@ -531,13 +572,13 @@
                             } else {
                                 i++
                                 if (i > 1) {
-                                    return SelectionResult.Ambiguous
+                                    return SelectionResult.Ambiguous(filtered)
                                 }
                             }
                         }
                         filtered.singleOrNull()
                             ?.let { SelectionResult.Ok(it) }
-                            ?: SelectionResult.Ambiguous
+                            ?: SelectionResult.Ambiguous(filtered)
                     }
                 }
             }
@@ -1414,7 +1455,9 @@
 
 sealed class SelectionResult<out T> {
     object Err : SelectionResult<Nothing>()
-    object Ambiguous : SelectionResult<Nothing>()
+    class Ambiguous(
+        val candidates: List<SelectionCandidate> = emptyList()
+    ) : SelectionResult<Nothing>()
     data class Ok<out T>(
         val result: T
     ) : SelectionResult<T>()
@@ -1425,13 +1468,13 @@
 
     inline fun <R> map(action: (T) -> R): SelectionResult<R> = when (this) {
         is Err -> Err
-        is Ambiguous -> Ambiguous
+        is Ambiguous -> this
         is Ok -> Ok(action(result))
     }
 
     inline fun <R> andThen(action: (T) -> SelectionResult<R>): SelectionResult<R> = when (this) {
         is Err -> Err
-        is Ambiguous -> Ambiguous
+        is Ambiguous -> this
         is Ok -> action(result)
     }
 }
@@ -1442,7 +1485,7 @@
     val subst: Substitution = emptySubstitution
 )
 
-private sealed class SelectionCandidate {
+sealed class SelectionCandidate {
     data class BuiltinCandidate(
         /** `false` if there are no *further* obligations */
         val hasNested: Boolean
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/Fulfillment.kt b/src/main/kotlin/org/rust/lang/core/types/infer/Fulfillment.kt
index ed30cb5..0753f6c 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/Fulfillment.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/Fulfillment.kt
@@ -24,6 +24,8 @@
 
         override fun superVisitWith(visitor: TypeVisitor): Boolean =
             trait.visitWith(visitor)
+
+        override fun toString(): String = trait.toString()
     }
 
     /** where <T as TraitRef>::Name == X */
@@ -86,7 +88,9 @@
  * [registerObligationAt] and to remove satisfied obligations
  * as a side effect of [processObligations].
  */
-class ObligationForest {
+class ObligationForest(
+    private val traceObligations: Boolean
+) {
     enum class NodeState {
         /** Obligations for which selection had not yet returned a non-ambiguous result */
         Pending,
@@ -110,13 +114,27 @@
     private val nodes: MutableList<Node> = mutableListOf()
     private val doneCache: MutableSet<Predicate> = HashSet()
 
+    /** Filled only if [traceObligations] == `true` */
+    val roots: MutableList<Node> = mutableListOf()
+    /** Filled only if [traceObligations] == `true` */
+    val parentToChildren: HashMap<Node, MutableList<Node>> = hashMapOf()
+
     val pendingObligations: Sequence<PendingPredicateObligation> =
         nodes.asSequence().filter { it.state == NodeState.Pending }.map { it.obligation }
 
-    @Suppress("UNUSED_PARAMETER") // TODO use `parent`
     fun registerObligationAt(obligation: PendingPredicateObligation, parent: Node?) {
-        if (doneCache.add(obligation.obligation.predicate))
-            nodes.add(Node(obligation))
+        if (doneCache.add(obligation.obligation.predicate)) {
+            val node = Node(obligation)
+            nodes.add(node)
+
+            if (traceObligations) {
+                if (parent == null) {
+                    roots += node
+                } else {
+                    parentToChildren.getOrPut(parent) { mutableListOf() } += node
+                }
+            }
+        }
     }
 
     fun processObligations(
@@ -156,13 +174,24 @@
     }
 }
 
-class FulfillmentContext(val ctx: RsInferenceContext, val lookup: ImplLookup) {
+class FulfillmentContext(
+    val ctx: RsInferenceContext,
+    val lookup: ImplLookup,
+    traceObligations: Boolean = false,
+) {
 
-    private val obligations: ObligationForest = ObligationForest()
+    private val obligations: ObligationForest = ObligationForest(traceObligations)
 
     val pendingObligations: Sequence<PendingPredicateObligation> =
         obligations.pendingObligations
 
+    /** Non-empty only if `traceObligations == true` */
+    val rootNodes: List<ObligationForest.Node> get() = obligations.roots
+
+    /** Non-empty only if `traceObligations == true` */
+    val parentToChildren: Map<ObligationForest.Node, List<ObligationForest.Node>>
+        get() = obligations.parentToChildren
+
     fun registerPredicateObligation(obligation: Obligation) {
         obligations.registerObligationAt(
             PendingPredicateObligation(ctx.resolveTypeVarsIfPossible(obligation)),
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
index b28dfdc..47f6142 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
@@ -34,12 +34,30 @@
 import org.rust.stdext.RsResult.Err
 import org.rust.stdext.RsResult.Ok
 
-fun inferTypesIn(element: RsInferenceContextOwner): RsInferenceResult {
+fun inferTypesIn(element: RsInferenceContextOwner): RsInferenceResult =
+    inferTypesInWithOptions(element, TypeInferenceOptions.DEFAULT).second
+
+fun inferTypesInWithOptions(
+    element: RsInferenceContextOwner,
+    options: TypeInferenceOptions,
+): Pair<RsInferenceContext, RsInferenceResult> {
     val items = element.knownItems
     val paramEnv = if (element is RsItemElement) ParamEnv.buildFor(element) else ParamEnv.EMPTY
-    val lookup = ImplLookup(element.project, element.containingCrate, items, paramEnv, element)
-    return recursionGuard(element, { lookup.ctx.infer(element) }, memoize = false)
-        ?: error("Can not run nested type inference")
+    val lookup = ImplLookup(element.project, element.containingCrate, items, paramEnv, element, options)
+    return recursionGuard(element, { lookup.ctx to lookup.ctx.infer(element) }, memoize = false)
+        ?: (lookup.ctx to RsInferenceResult.EMPTY)
+}
+
+data class TypeInferenceOptions(
+    /**
+     * If `true`, fills [FulfillmentContext.rootNodes] and [FulfillmentContext.parentToChildren],
+     * so that they can be used later.
+     */
+    val traceObligations: Boolean = false
+) {
+    companion object {
+        val DEFAULT = TypeInferenceOptions()
+    }
 }
 
 sealed class Adjustment: TypeFoldable<Adjustment> {
@@ -206,9 +224,10 @@
 class RsInferenceContext(
     val project: Project,
     val lookup: ImplLookup,
-    val items: KnownItems
+    val items: KnownItems,
+    options: TypeInferenceOptions
 ) : RsInferenceData {
-    val fulfill: FulfillmentContext = FulfillmentContext(this, lookup)
+    val fulfill: FulfillmentContext = FulfillmentContext(this, lookup, options.traceObligations)
     private val exprTypes: MutableMap<RsExpr, Ty> = hashMapOf()
     private val patTypes: MutableMap<RsPat, Ty> = hashMapOf()
     private val patFieldTypes: MutableMap<RsPatField, Ty> = hashMapOf()
@@ -869,7 +888,7 @@
                 when (val selection = lookup.select(predicate.trait, obligation.recursionDepth)) {
                     SelectionResult.Err -> return null
                     is SelectionResult.Ok -> queue += selection.result.nestedObligations
-                    SelectionResult.Ambiguous -> if (predicate.trait.trait.element == unsizeTrait) {
+                    is SelectionResult.Ambiguous -> if (predicate.trait.trait.element == unsizeTrait) {
                         val selfTy = predicate.trait.selfTy
                         val unsizeTy = predicate.trait.trait.singleParamValue
                         if (selfTy is TyInfer.TyVar && unsizeTy is TyTraitObject && typeVarIsSized(selfTy)) {
diff --git a/src/main/kotlin/org/rust/openapiext/utils.kt b/src/main/kotlin/org/rust/openapiext/utils.kt
index f86cee9..18fe3f2 100644
--- a/src/main/kotlin/org/rust/openapiext/utils.kt
+++ b/src/main/kotlin/org/rust/openapiext/utils.kt
@@ -319,8 +319,13 @@
     return ProgressManager.getInstance().runProcessWithProgressSynchronously(process, title, true, this)
 }
 
-inline fun <T : Any> UserDataHolderEx.getOrPut(key: Key<T>, defaultValue: () -> T): T =
-    getUserData(key) ?: putUserDataIfAbsent(key, defaultValue())
+inline fun <T : Any> UserDataHolder.getOrPut(key: Key<T>, defaultValue: () -> T): T {
+    val data = getUserData(key)
+    if (data != null) return data
+    val value = defaultValue()
+    putUserData(key, value)
+    return value
+}
 
 const val PLUGIN_ID: String = "org.rust.lang"
 
diff --git a/src/main/kotlin/org/rust/stdext/Utils.kt b/src/main/kotlin/org/rust/stdext/Utils.kt
index 7a843e9..8391989 100644
--- a/src/main/kotlin/org/rust/stdext/Utils.kt
+++ b/src/main/kotlin/org/rust/stdext/Utils.kt
@@ -10,7 +10,7 @@
 import com.intellij.openapi.util.NlsActions
 import com.intellij.openapi.util.text.StringUtil
 import com.intellij.openapi.vfs.VirtualFile
-import org.apache.commons.lang.RandomStringUtils
+import org.apache.commons.lang3.RandomStringUtils
 import java.nio.file.Files
 import java.nio.file.InvalidPathException
 import java.nio.file.Path
diff --git a/src/main/resources/messages/RsBundle.properties b/src/main/resources/messages/RsBundle.properties
index 38b7773..c4fd022 100644
--- a/src/main/resources/messages/RsBundle.properties
+++ b/src/main/resources/messages/RsBundle.properties
@@ -90,6 +90,7 @@
 dialog.create.project.custom.add.template.title=Add Custom Template
 dialog.create.project.custom.add.template.url.description=cargo-generate supported template. <a href="https://github.com/cargo-generate/cargo-generate/blob/master/TEMPLATES.md">Available templates</a>
 dialog.create.project.custom.add.template.url=Template URL:
+dialog.message.rust.toolchain.is.not.set=The Rust toolchain is not set
 
 # for example: Debug action is not available for `cargo help` command
 notification.0.action.is.not.available.for.1.command={0} action is not available for `{1}` command
@@ -526,6 +527,7 @@
 external.linter=External Linter
 extra.semicolon=Extra semicolon
 from=From:
+profiler.attach.default.group.title=Native
 hint.text.html.table.tr.td.style.color.type.td.td.style.font.family.monospace.td.tr.tr.td.style.color.coerced.type.td.td.style.font.family.monospace.td.tr.table.html=\n                <html>\n                    <table>\n                        <tr>\n                            <td style="color: #909090">Type:</td>\n                            <td style="font-family: monospace;">{0}</td>\n                        </tr>\n                        <tr>\n                            <td style="color: #909090">Coerced type:</td>\n                            <td style="font-family: monospace;">{1}</td>\n                        </tr>\n                    </table>\n                </html>\n
 hint.text.no.members.to.implement.have.been.found=No members to implement have been found
 hint.text.please.convert.innermost.impl.trait.first=Please convert innermost `impl Trait` first
@@ -1454,7 +1456,6 @@
 discriminant.on.a.non.unit.variant=discriminant on a non-unit variant
 or.patterns.syntax=or-patterns syntax
 macro=`macro`
-intention.family.name.replace.with.inclusive.range=Replace with inclusive range
 error.message.struct.inheritance.is.not.supported=Struct inheritance is not supported in Rust
 inspection.message.main.is.async=`{0}` function is not allowed to be `async`
 intention.family.name.replace.with.inclusive.range=Replace with inclusive range
diff --git a/src/test/kotlin/org/rust/RsTestBase.kt b/src/test/kotlin/org/rust/RsTestBase.kt
index 8092e42..9326092 100644
--- a/src/test/kotlin/org/rust/RsTestBase.kt
+++ b/src/test/kotlin/org/rust/RsTestBase.kt
@@ -66,6 +66,9 @@
     private var shouldSkipTestWithCurrentWrapping: Boolean = false
     protected var testWrappingUnwrapper: TestUnwrapper? = null
 
+    open val runInMode: WithDumbMode.SmartDumbMode get() = WithDumbMode.SmartDumbMode.SMART
+    private var dumbModeToken: DumbModeTestUtil.Token? = null
+
     override fun isWriteActionRequired(): Boolean = false
 
     open val dataPath: String = ""
@@ -90,6 +93,11 @@
                 mgr.setMacroExpansionEnabled(true)
             }
         }
+        val mode = findAnnotationInstance<WithDumbMode>()?.mode ?: runInMode
+        if (mode == WithDumbMode.SmartDumbMode.DUMB) {
+            dumbModeToken = DumbModeTestUtil.startEternalDumbModeTask(project)
+        }
+
         RecursionManager.disableMissedCacheAssertions(testRootDisposable)
         tempDirRoot = myFixture.findFileInTempDir(".")
         tempDirRootUrl = tempDirRoot?.url
@@ -100,6 +108,7 @@
         val newTempDirRootUrl = tempDirRoot?.url
 
         com.intellij.testFramework.common.runAll(
+            { dumbModeToken?.close() },
             {
                 // Fixes flaky tests
                 (ProjectLevelVcsManagerEx.getInstance(project) as ProjectLevelVcsManagerImpl).waitForInitialized()
diff --git a/src/test/kotlin/org/rust/WithDumbMode.kt b/src/test/kotlin/org/rust/WithDumbMode.kt
new file mode 100644
index 0000000..d017c5f
--- /dev/null
+++ b/src/test/kotlin/org/rust/WithDumbMode.kt
@@ -0,0 +1,15 @@
+/*
+ * Use of this source code is governed by the MIT license that can be
+ * found in the LICENSE file.
+ */
+
+package org.rust
+
+import java.lang.annotation.Inherited
+
+@Inherited
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class WithDumbMode(val mode: SmartDumbMode = SmartDumbMode.DUMB) {
+    enum class SmartDumbMode { SMART, DUMB }
+}
diff --git a/src/test/kotlin/org/rust/cargo/commands/RustfmtTest.kt b/src/test/kotlin/org/rust/cargo/commands/RustfmtTest.kt
index 808ad46..d19188e 100644
--- a/src/test/kotlin/org/rust/cargo/commands/RustfmtTest.kt
+++ b/src/test/kotlin/org/rust/cargo/commands/RustfmtTest.kt
@@ -18,7 +18,7 @@
 import org.rust.cargo.toolchain.RustChannel
 import org.rust.fileTree
 import org.rust.ide.formatter.RustfmtTestmarks
-import org.rust.launchAction
+import org.rust.launchAnAction
 import org.rust.openapiext.RsProcessExecutionException
 import org.rust.openapiext.saveAllDocuments
 
@@ -530,11 +530,11 @@
     }
 
     private fun reformatFile() {
-        myFixture.launchAction("Cargo.RustfmtFile")
+        myFixture.launchAnAction("Cargo.RustfmtFile")
     }
 
     private fun reformatCargoProject() {
-        myFixture.launchAction("Cargo.RustfmtCargoProject")
+        myFixture.launchAnAction("Cargo.RustfmtCargoProject")
     }
 
     /**
diff --git a/src/test/kotlin/org/rust/cargo/project/RustProjectSettingsServiceTest.kt b/src/test/kotlin/org/rust/cargo/project/RustProjectSettingsServiceTest.kt
index d8bcc70..a99b3de 100644
--- a/src/test/kotlin/org/rust/cargo/project/RustProjectSettingsServiceTest.kt
+++ b/src/test/kotlin/org/rust/cargo/project/RustProjectSettingsServiceTest.kt
@@ -20,10 +20,7 @@
               <option name="compileAllTargets" value="false" />
               <option name="doctestInjectionEnabled" value="false" />
               <option name="explicitPathToStdlib" value="/stdlib" />
-              <option name="externalLinter" value="Clippy" />
-              <option name="externalLinterArguments" value="--no-default-features" />
               <option name="macroExpansionEngine" value="DISABLED" />
-              <option name="runExternalLinterOnTheFly" value="true" />
               <option name="toolchainHomeDirectory" value="/" />
               <option name="useOffline" value="true" />
             </RustProjectSettings>
@@ -49,10 +46,8 @@
               <option name="externalLinterArguments" value="--no-default-features" />
               <option name="macroExpansionEngine" value="DISABLED" />
               <option name="runExternalLinterOnTheFly" value="true" />
-              <option name="runRustfmtOnSave" value="true" /> <!-- Old field -->
               <option name="toolchainHomeDirectory" value="/" />
               <option name="useOffline" value="true" />
-              <option name="useRustfmt" value="true" /> <!-- Old field -->
             </RustProjectSettings>
         """, """
             <RustProjectSettings>
@@ -60,10 +55,7 @@
               <option name="compileAllTargets" value="false" />
               <option name="doctestInjectionEnabled" value="false" />
               <option name="explicitPathToStdlib" value="/stdlib" />
-              <option name="externalLinter" value="Clippy" />
-              <option name="externalLinterArguments" value="--no-default-features" />
               <option name="macroExpansionEngine" value="DISABLED" />
-              <option name="runExternalLinterOnTheFly" value="true" />
               <option name="toolchainHomeDirectory" value="/" />
               <option name="useOffline" value="true" />
             </RustProjectSettings>
diff --git a/src/test/kotlin/org/rust/cargo/project/model/impl/SyncToolWindowTest.kt b/src/test/kotlin/org/rust/cargo/project/model/impl/SyncToolWindowTest.kt
index 8387b0e..dac3ad3 100644
--- a/src/test/kotlin/org/rust/cargo/project/model/impl/SyncToolWindowTest.kt
+++ b/src/test/kotlin/org/rust/cargo/project/model/impl/SyncToolWindowTest.kt
@@ -482,11 +482,11 @@
     }
 
     private fun attachCargoProject(cargoProjectRoot: VirtualFile) {
-        myFixture.launchAction("Cargo.AttachCargoProject", PlatformDataKeys.VIRTUAL_FILE to cargoProjectRoot)
+        myFixture.launchAnAction("Cargo.AttachCargoProject", PlatformDataKeys.VIRTUAL_FILE to cargoProjectRoot)
     }
 
     private fun detachCargoProject(cargoProject: CargoProject) {
-        myFixture.launchAction("Cargo.DetachCargoProject", CargoToolWindow.SELECTED_CARGO_PROJECT to cargoProject)
+        myFixture.launchAnAction("Cargo.DetachCargoProject", CargoToolWindow.SELECTED_CARGO_PROJECT to cargoProject)
     }
 
     private fun checkSyncViewTree(expected: String) {
diff --git a/src/test/kotlin/org/rust/cargo/runconfig/RsAnsiEscapeDecoderTest.kt b/src/test/kotlin/org/rust/cargo/runconfig/RsAnsiEscapeDecoderTest.kt
index 4cf67ec..186390d 100644
--- a/src/test/kotlin/org/rust/cargo/runconfig/RsAnsiEscapeDecoderTest.kt
+++ b/src/test/kotlin/org/rust/cargo/runconfig/RsAnsiEscapeDecoderTest.kt
@@ -13,6 +13,7 @@
 import org.rust.cargo.runconfig.RsAnsiEscapeDecoder.Companion.ANSI_24_BIT_COLOR_FORMAT
 import org.rust.cargo.runconfig.RsAnsiEscapeDecoder.Companion.ANSI_8_BIT_COLOR_FORMAT
 import org.rust.cargo.runconfig.RsAnsiEscapeDecoder.Companion.CSI
+import org.rust.escapeSequence
 
 class RsAnsiEscapeDecoderTest : HeavyPlatformTestCase() {
 
@@ -277,7 +278,7 @@
             val decoder = RsAnsiEscapeDecoder()
             val actualColoredChunks = mutableListOf<Pair<String, String>>()
             val acceptor = AnsiEscapeDecoder.ColoredTextAcceptor { s, attrs ->
-                actualColoredChunks.add(Pair(s, attrs.toString()))
+                actualColoredChunks.add(Pair(s, attrs.escapeSequence))
             }
             decoder.escapeText(text.rawText, text.outputType, acceptor)
             val expectedColoredChunks = mutableListOf<Pair<String, String>>()
diff --git a/src/test/kotlin/org/rust/common.kt b/src/test/kotlin/org/rust/common.kt
index f4dd615..8a3945f 100644
--- a/src/test/kotlin/org/rust/common.kt
+++ b/src/test/kotlin/org/rust/common.kt
@@ -201,7 +201,7 @@
 }
 
 
-fun CodeInsightTestFixture.launchAction(
+fun CodeInsightTestFixture.launchAnAction(
     actionId: String,
     vararg context: Pair<DataKey<*>, *>,
     shouldBeEnabled: Boolean = true
diff --git a/src/test/kotlin/org/rust/ide/actions/RsCreateCrateActionTest.kt b/src/test/kotlin/org/rust/ide/actions/RsCreateCrateActionTest.kt
index 0a4f601..4280031 100644
--- a/src/test/kotlin/org/rust/ide/actions/RsCreateCrateActionTest.kt
+++ b/src/test/kotlin/org/rust/ide/actions/RsCreateCrateActionTest.kt
@@ -13,7 +13,7 @@
 import org.rust.ide.actions.ui.CargoNewCrateSettings
 import org.rust.ide.actions.ui.CargoNewCrateUI
 import org.rust.ide.actions.ui.withMockCargoNewCrateUi
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class RsCreateCrateActionTest : RsWithToolchainTestBase() {
     fun `test create with file context`() {
@@ -39,7 +39,7 @@
                 return CargoNewCrateSettings(binary, name)
             }
         }) {
-            myFixture.launchAction("Rust.NewCargoCrate", CommonDataKeys.VIRTUAL_FILE to file)
+            myFixture.launchAnAction("Rust.NewCargoCrate", CommonDataKeys.VIRTUAL_FILE to file)
         }
 
         val src = if (binary) "main" else "lib"
diff --git a/src/test/kotlin/org/rust/ide/actions/ShareInPlaygroundActionTest.kt b/src/test/kotlin/org/rust/ide/actions/ShareInPlaygroundActionTest.kt
index 45815c9..4cdc269 100644
--- a/src/test/kotlin/org/rust/ide/actions/ShareInPlaygroundActionTest.kt
+++ b/src/test/kotlin/org/rust/ide/actions/ShareInPlaygroundActionTest.kt
@@ -123,7 +123,7 @@
     }
 
     private fun actionLauncher() {
-        myFixture.launchAction("Rust.ShareInPlayground")
+        myFixture.launchAnAction("Rust.ShareInPlayground")
     }
 
     companion object {
diff --git a/src/test/kotlin/org/rust/ide/annotator/AnnotationTestFixtureBase.kt b/src/test/kotlin/org/rust/ide/annotator/AnnotationTestFixtureBase.kt
index 309b5ff..a34fff6 100644
--- a/src/test/kotlin/org/rust/ide/annotator/AnnotationTestFixtureBase.kt
+++ b/src/test/kotlin/org/rust/ide/annotator/AnnotationTestFixtureBase.kt
@@ -12,6 +12,7 @@
 import com.intellij.codeInspection.InspectionProfileEntry
 import com.intellij.codeInspection.SuppressIntentionActionFromFix
 import com.intellij.lang.annotation.HighlightSeverity
+import com.intellij.openapi.application.ApplicationInfo
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.util.TextRange
 import com.intellij.testFramework.ExtensionTestUtil
@@ -195,6 +196,13 @@
         checkInfo: Boolean = false,
         checkWeakWarn: Boolean = false,
     ) {
+        if (ApplicationInfo.getInstance().build.baselineVersion == 233) {
+            // BACKCOMPAT: 2023.2; in 233 a quick-fix is available withing the whole line, so
+            //   `checkFixAvailableInSelectionOnly` tests start behaving differently.
+            //   Also, because of that we probably should stop care about QF availability ranges
+            //   and just remove all tests that use `checkFixAvailableInSelectionOnly`
+            return // Pass
+        }
         configureByText(before.replace("<selection>", "<selection><caret>"))
         checkHighlighting(checkWarn, checkInfo, checkWeakWarn, ignoreExtraHighlighting = false)
         val selections = codeInsightFixture.editor.selectionModel.let { model ->
diff --git a/src/232/test/kotlin/org/rust/ide/hints/codeVision/RsImplementationsCodeVisionTest.kt b/src/test/kotlin/org/rust/ide/hints/codeVision/RsImplementationsCodeVisionTest.kt
similarity index 100%
rename from src/232/test/kotlin/org/rust/ide/hints/codeVision/RsImplementationsCodeVisionTest.kt
rename to src/test/kotlin/org/rust/ide/hints/codeVision/RsImplementationsCodeVisionTest.kt
diff --git a/src/232/test/kotlin/org/rust/ide/hints/codeVision/RsReferenceCodeVisionTest.kt b/src/test/kotlin/org/rust/ide/hints/codeVision/RsReferenceCodeVisionTest.kt
similarity index 100%
rename from src/232/test/kotlin/org/rust/ide/hints/codeVision/RsReferenceCodeVisionTest.kt
rename to src/test/kotlin/org/rust/ide/hints/codeVision/RsReferenceCodeVisionTest.kt
diff --git a/src/232/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt b/src/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt
similarity index 98%
rename from src/232/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt
rename to src/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt
index bafc793..2284e5f 100644
--- a/src/232/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt
+++ b/src/test/kotlin/org/rust/ide/hints/codeVision/RsVcsCodeVisionTestCase.kt
@@ -93,6 +93,6 @@
     """)
 
     private fun doTest(@Language("Rust") text: String) {
-        testProviders(text.trimIndent(), "main.rs", VcsCodeVisionProvider.id)
+        testProviders(text.trimIndent(), "main.rs", VcsCodeVisionProvider().groupId)
     }
 }
diff --git a/src/test/kotlin/org/rust/ide/intentions/ExtractInlineModuleIntentionTest.kt b/src/test/kotlin/org/rust/ide/intentions/ExtractInlineModuleIntentionTest.kt
index ba5d447..a9615bc 100644
--- a/src/test/kotlin/org/rust/ide/intentions/ExtractInlineModuleIntentionTest.kt
+++ b/src/test/kotlin/org/rust/ide/intentions/ExtractInlineModuleIntentionTest.kt
@@ -128,27 +128,14 @@
         )
     }
 
-    fun `test invalid extract inline module`() {
-        doTest(fileTree {
-            rust("main.rs", """
-                mod foo {
-                    // function
-                    fn a() {}
-                }
+    fun `test invalid extract inline module`() = doUnavailableTest("""
+        mod foo {
+            // function
+            fn a() {}
+        }
 
-                fn /*caret*/main() {}
-            """)
-        }, fileTree {
-            rust("main.rs", """
-                mod foo {
-                    // function
-                    fn a() {}
-                }
-
-                fn main() {}
-            """)
-        })
-    }
+        fn /*caret*/main() {}
+    """)
 
     private fun doTest(before: FileTree, after: FileTree) {
         val testProject = before.create()
diff --git a/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt b/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
index ed420dd..561e4f6 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
@@ -7,7 +7,7 @@
 
 import org.intellij.lang.annotations.Language
 import org.rust.RsTestBase
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class ConvertToNamedFieldsRefactoringTest : RsTestBase() {
 
@@ -232,7 +232,7 @@
 
     private fun doAvailableTest(@Language("Rust") before: String, @Language("Rust") after: String) {
         InlineFile(before.trimIndent()).withCaret()
-        myFixture.launchAction("Rust.RsConvertToNamedFields")
+        myFixture.launchAnAction("Rust.RsConvertToNamedFields")
         myFixture.checkResult(replaceCaretMarker(after.trimIndent()))
     }
 }
diff --git a/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt b/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
index 426abf2..13fd937 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
@@ -7,7 +7,7 @@
 
 import org.intellij.lang.annotations.Language
 import org.rust.RsTestBase
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class ConvertToTupleRefactoringTest : RsTestBase() {
     fun `test simple`() = doAvailableTest("""
@@ -190,7 +190,7 @@
 
     private fun doAvailableTest(@Language("Rust") before: String, @Language("Rust") after: String) {
         InlineFile(before.trimIndent()).withCaret()
-        myFixture.launchAction("Rust.RsConvertToTuple")
+        myFixture.launchAnAction("Rust.RsConvertToTuple")
         myFixture.checkResult(replaceCaretMarker(after.trimIndent()))
     }
 }
diff --git a/src/test/kotlin/org/rust/ide/refactoring/RsDowngradeModuleToFileTest.kt b/src/test/kotlin/org/rust/ide/refactoring/RsDowngradeModuleToFileTest.kt
index 6701296..2aeac00 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/RsDowngradeModuleToFileTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/RsDowngradeModuleToFileTest.kt
@@ -10,7 +10,7 @@
 import org.rust.FileTree
 import org.rust.RsTestBase
 import org.rust.fileTree
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class RsDowngradeModuleToFileTest : RsTestBase() {
     fun `test works on file`() = checkAvailable(
@@ -71,7 +71,7 @@
     }
 
     private fun testActionOnElement(element: PsiElement, shouldBeEnabled: Boolean) {
-        myFixture.launchAction(
+        myFixture.launchAnAction(
             "Rust.RsDowngradeModuleToFile",
             CommonDataKeys.PSI_ELEMENT to element,
             shouldBeEnabled = shouldBeEnabled
diff --git a/src/test/kotlin/org/rust/ide/refactoring/RsExtractEnumVariantTest.kt b/src/test/kotlin/org/rust/ide/refactoring/RsExtractEnumVariantTest.kt
index 56c0c27..41c5971 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/RsExtractEnumVariantTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/RsExtractEnumVariantTest.kt
@@ -7,7 +7,7 @@
 
 import org.intellij.lang.annotations.Language
 import org.rust.RsTestBase
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class RsExtractEnumVariantTest : RsTestBase() {
     fun `test not available on empty variant`() = doUnavailableTest("""
@@ -644,6 +644,6 @@
 
     private fun doUnavailableTest(@Language("Rust") code: String) {
         InlineFile(code.trimIndent()).withCaret()
-        myFixture.launchAction("Rust.RsExtractEnumVariant", shouldBeEnabled = false)
+        myFixture.launchAnAction("Rust.RsExtractEnumVariant", shouldBeEnabled = false)
     }
 }
diff --git a/src/test/kotlin/org/rust/ide/refactoring/RsExtractStructFieldsTest.kt b/src/test/kotlin/org/rust/ide/refactoring/RsExtractStructFieldsTest.kt
index 7896519..cbb43ba 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/RsExtractStructFieldsTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/RsExtractStructFieldsTest.kt
@@ -13,7 +13,7 @@
 import org.rust.ide.refactoring.generate.RsStructMemberChooserObject
 import org.rust.ide.refactoring.generate.StructMemberChooserUi
 import org.rust.ide.refactoring.generate.withMockStructMemberChooserUi
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class RsExtractStructFieldsTest : RsTestBase() {
     fun `test unavailable on tuple struct`() = doUnavailableTest("""
@@ -607,6 +607,6 @@
 
     private fun doUnavailableTest(@Language("Rust") code: String) {
         InlineFile(code.trimIndent()).withCaret()
-        myFixture.launchAction("Rust.RsExtractStructFields", shouldBeEnabled = false)
+        myFixture.launchAnAction("Rust.RsExtractStructFields", shouldBeEnabled = false)
     }
 }
diff --git a/src/test/kotlin/org/rust/ide/refactoring/RsInlineTestBase.kt b/src/test/kotlin/org/rust/ide/refactoring/RsInlineTestBase.kt
index 5c68d16..4bd01b4 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/RsInlineTestBase.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/RsInlineTestBase.kt
@@ -8,7 +8,7 @@
 import org.intellij.lang.annotations.Language
 import org.rust.RsTestBase
 import org.rust.hasCaretMarker
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 abstract class RsInlineTestBase : RsTestBase() {
 
@@ -21,7 +21,7 @@
 
     protected fun doUnavailableTest(@Language("Rust") code: String) {
         InlineFile(code.trimIndent()).withCaret()
-        myFixture.launchAction("Inline", shouldBeEnabled = false)
+        myFixture.launchAnAction("Inline", shouldBeEnabled = false)
     }
 
     protected inline fun <reified T : Throwable> expectError(code: String) {
diff --git a/src/test/kotlin/org/rust/ide/refactoring/RsPromoteModuleToDirectoryActionTest.kt b/src/test/kotlin/org/rust/ide/refactoring/RsPromoteModuleToDirectoryActionTest.kt
index 9c8bfaf..dad64f4 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/RsPromoteModuleToDirectoryActionTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/RsPromoteModuleToDirectoryActionTest.kt
@@ -10,7 +10,7 @@
 import org.rust.FileTree
 import org.rust.RsTestBase
 import org.rust.fileTree
-import org.rust.launchAction
+import org.rust.launchAnAction
 
 class RsPromoteModuleToDirectoryActionTest : RsTestBase() {
 
@@ -159,7 +159,7 @@
     }
 
     private fun testActionOnElement(element: PsiElement, shouldBeEnabled: Boolean) {
-        myFixture.launchAction(
+        myFixture.launchAnAction(
             "Rust.RsPromoteModuleToDirectoryAction",
             CommonDataKeys.PSI_ELEMENT to element,
             shouldBeEnabled = shouldBeEnabled
diff --git a/src/232/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt b/src/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt
similarity index 100%
rename from src/232/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt
rename to src/test/kotlin/org/rust/ide/wordSelection/RsBlocksAndBodiesSelectionHandlerTest.kt
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
index 4fa0231..7e925b9 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
@@ -113,6 +113,14 @@
         completionChar: Char = '\n'
     ) = completionFixture.checkCompletion(lookupString, before, after, completionChar)
 
+    protected fun checkCompletion(
+        lookupString: String,
+        tailText: String,
+        @Language("Rust") before: String,
+        @Language("Rust") after: String,
+        completionChar: Char = '\n'
+    ) = completionFixture.checkCompletion(lookupString, tailText, before, after, completionChar)
+
     fun checkCompletionWithLiveTemplate(
         lookupString: String,
         @Language("Rust") before: String,
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
index 3e35f8d..646e438 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
@@ -66,6 +66,24 @@
         }
     }
 
+    fun checkCompletion(
+        lookupString: String,
+        tailText: String,
+        before: IN,
+        @Language("Rust") after: String,
+        completionChar: Char,
+    ) {
+        checkByText(before, after.trimIndent()) {
+            val items = myFixture.completeBasic()
+                ?: return@checkByText // single completion was inserted
+            val lookupItem = items
+                .find { it.lookupString == lookupString && it.presentation.tailText == tailText }
+                ?: error("Lookup string $lookupString not found")
+            myFixture.lookup.currentItem = lookupItem
+            myFixture.type(completionChar)
+        }
+    }
+
     fun checkNoCompletion(code: IN) {
         prepare(code)
         noCompletionCheck()
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsImplForCompletionTest.kt b/src/test/kotlin/org/rust/lang/core/completion/RsImplForCompletionTest.kt
index 7f19b99..6afbe9d 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsImplForCompletionTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsImplForCompletionTest.kt
@@ -5,13 +5,12 @@
 
 package org.rust.lang.core.completion
 
-import com.intellij.openapi.project.DumbServiceImpl
 import org.intellij.lang.annotations.Language
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameter
 import org.rust.RsJUnit4ParameterizedTestRunner
-import java.util.concurrent.atomic.AtomicBoolean
+import org.rust.WithDumbMode
 
 @RunWith(RsJUnit4ParameterizedTestRunner::class)
 @Parameterized.UseParametersRunnerFactory(RsJUnit4ParameterizedTestRunner.RsRunnerForParameters.Factory::class)
@@ -21,24 +20,12 @@
         @JvmStatic
         @Parameterized.Parameters
         fun data(): Iterable<Any> {
-            return listOf(AtomicBoolean(false), AtomicBoolean(true))
+            return listOf(WithDumbMode.SmartDumbMode.SMART, WithDumbMode.SmartDumbMode.DUMB)
         }
     }
 
-    // @Parameter doesn't work properly for primitive Kotlin types,
-    // throws `can't assign to a private member` exception even when the field is public
     @Parameter(0)
-    lateinit var isDumbMode: AtomicBoolean
-
-    override fun setUp() {
-        super.setUp()
-        DumbServiceImpl.getInstance(project).isDumb = isDumbMode.get()
-    }
-
-    override fun tearDown() {
-        DumbServiceImpl.getInstance(project).isDumb = false
-        super.tearDown()
-    }
+    override lateinit var runInMode: WithDumbMode.SmartDumbMode
 
     fun `test impl trait for completion`() = checkCompletion("for", """
         trait A {}
@@ -200,7 +187,7 @@
         @Language("Rust") after: String,
         completionChar: Char = '\n'
     )  {
-        if (isDumbMode.get()) {
+        if (runInMode == WithDumbMode.SmartDumbMode.DUMB) {
             checkCompletion(lookupString, before, after, completionChar)
         } else {
             checkNotContainsCompletion(lookupString, before)
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsKeywordCompletionContributorTest.kt b/src/test/kotlin/org/rust/lang/core/completion/RsKeywordCompletionContributorTest.kt
index 4ea80be..83f3e4b 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsKeywordCompletionContributorTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsKeywordCompletionContributorTest.kt
@@ -5,22 +5,14 @@
 
 package org.rust.lang.core.completion
 
-import com.intellij.openapi.project.DumbServiceImpl
 import org.intellij.lang.annotations.Language
+import org.rust.WithDumbMode
+import org.rust.WithDumbMode.SmartDumbMode.DUMB
+import org.rust.WithDumbMode.SmartDumbMode.SMART
 import org.rust.lang.core.completion.RsKeywordCompletionContributor.Companion.CONDITION_KEYWORDS
 
+@WithDumbMode(DUMB)
 class RsKeywordCompletionContributorTest : RsCompletionTestBase() {
-
-    override fun setUp() {
-        super.setUp()
-        DumbServiceImpl.getInstance(project).isDumb = true
-    }
-
-    override fun tearDown() {
-        DumbServiceImpl.getInstance(project).isDumb = false
-        super.tearDown()
-    }
-
     fun `test break in for loop`() = checkCompletion("break", """
         fn foo() {
             for _ in 0..4 {
@@ -63,6 +55,7 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test break not applied if doesnt start stmt`() = checkNoCompletion("""
         fn foo() {
             while true {
@@ -71,12 +64,14 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test break not applied outside loop`() = checkNoCompletion("""
         fn foo() {
             bre/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test break not applied within closure`() = checkNoCompletion("""
         fn bar() {
             loop {
@@ -141,6 +136,7 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test continue expression outside loop`() = checkNoCompletion("""
         fn foo() {
             let x = cont/*caret*/;
@@ -218,18 +214,21 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test enum not applied if doesnt start stmt within fn`() = checkNoCompletion("""
         fn foo() {
             let en/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test enum not applied within struct`() = checkNoCompletion("""
         struct Foo {
             en/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test enum not applied if doesnt start stmt`() = checkNoCompletion("""
         mod en/*caret*/
     """)
@@ -422,18 +421,22 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test return not applied on file level`() = checkNoCompletion("""
         retu/*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test return not applied within parameters list`() = checkNoCompletion("""
         fn foo(retu/*caret*/) {}
     """)
 
+    @WithDumbMode(SMART)
     fun `test return not applied before block`() = checkNoCompletion("""
         fn foo() retu/*caret*/ {}
     """)
 
+    @WithDumbMode(SMART)
     fun `test return not applied if doesnt start statement`() = checkNoCompletion("""
         const retu/*caret*/
     """)
@@ -556,12 +559,14 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test let else after semicolon`() = checkNoCompletion("""
         fn main() {
             let x = 0; els/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test let else without expression`() = checkNoCompletion("""
         fn main() {
             let x = els/*caret*/
@@ -595,10 +600,12 @@
         fn foo<T>(t: T) -> i32 where /*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in not generic function`() = checkNoCompletion("""
         fn foo() whe/*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in not generic function with ret type`() = checkNoCompletion("""
         fn foo() -> i32 whe/*caret*/
     """)
@@ -635,6 +642,7 @@
         struct Foo<T>(T) where /*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in not generic struct`() = checkNoCompletion("""
         struct Foo whe/*caret*/
     """)
@@ -645,6 +653,7 @@
         enum Foo<T> where /*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in not generic enum`() = checkNoCompletion("""
         enum Foo whe/*caret*/
     """)
@@ -655,16 +664,19 @@
         type Foo<T> where /*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in not generic type alias`() = checkNoCompletion("""
         type Foo whe/*caret*/
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in trait assoc type`() = checkNoCompletion("""
         trait Foo {
             type Bar whe/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test where in impl block assoc type`() = checkNoCompletion("""
         impl Foo for Bar {
             type FooBar whe/*caret*/
@@ -762,12 +774,14 @@
         "fn foo<const /*caret*/>() {}"
     )
 
+    @WithDumbMode(SMART)
     fun `test const parameter before lifetime parameter`() = checkNoCompletion("""
-        "fn foo</*caret*/, 'a>() {}"
+        fn foo</*caret*/, 'a>() {}
     """)
 
+    @WithDumbMode(SMART)
     fun `test const parameter before type parameter`() = checkNoCompletion("""
-        "fn foo</*caret*/, A>() {}"
+        fn foo</*caret*/, A>() {}
     """)
 
     fun `test const parameter before const parameter`() = checkCompletion("const",
@@ -790,8 +804,9 @@
         "fn foo<const C: i32, const /*caret*/>() {}"
     )
 
+    @WithDumbMode(SMART)
     fun `test const parameter before comma`() = checkNoCompletion("""
-        "fn foo<T /*caret*/>() {}"
+        fn foo<T /*caret*/>() {}
     """)
 
     fun `test inside trait`() = checkCompletion(MEMBERS_KEYWORDS, """
@@ -818,12 +833,14 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test enum inside trait`() = checkNoCompletion("""
         pub trait Bar {
             en/*caret*/
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test trait inside trait`() = checkNoCompletion("""
         pub trait Bar {
             tra/*caret*/
@@ -854,6 +871,7 @@
         }
     """)
 
+    @WithDumbMode(SMART)
     fun `test impl inside impl`() = checkNoCompletion("""
         impl Bar for Foo {
             imp/*caret*/
@@ -890,6 +908,7 @@
         "pub union /*caret*/"
     )
 
+    @WithDumbMode(SMART)
     fun `test no union in expr`() = checkNoCompletion("""
         fn foo() {
             let x = 42 + unio/*caret*/;
@@ -1312,18 +1331,15 @@
         }
     """)
 
-    // Smart mode is used for not completion tests to disable additional results
-    // from language agnostic `com.intellij.codeInsight.completion.WordCompletionContributor`
-    override fun checkNoCompletion(@Language("Rust") code: String) {
-        val dumbService = DumbServiceImpl.getInstance(project)
-        val oldValue = dumbService.isDumb
-        try {
-            dumbService.isDumb = false
-            super.checkNoCompletion(code)
-        } finally {
-            dumbService.isDumb = oldValue
+    fun `test keyword completion triggered by spacebar adds only 1 space`() = checkCompletion("l", """
+        fn main() {
+            le/*caret*/;
         }
-    }
+    """, """
+        fn main() {
+            let /*caret*/;
+        }
+    """, completionChar = ' ')
 
     private fun checkCompletion(
         lookupStrings: List<String>,
diff --git a/src/test/kotlin/org/rust/lang/core/type/RsStubOnlyTypeInferenceTest.kt b/src/test/kotlin/org/rust/lang/core/type/RsStubOnlyTypeInferenceTest.kt
index 313de89..a653823 100644
--- a/src/test/kotlin/org/rust/lang/core/type/RsStubOnlyTypeInferenceTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/type/RsStubOnlyTypeInferenceTest.kt
@@ -5,6 +5,8 @@
 
 package org.rust.lang.core.type
 
+import org.junit.Test
+
 class RsStubOnlyTypeInferenceTest : RsTypificationTestBase() {
 
     fun `test const expr`() = stubOnlyTypeInfer("""
@@ -374,4 +376,271 @@
             f;
         } //^ unsafe fn(i8) -> u64
     """)
+
+    // TODO RUST-12502
+    @Test(expected = IllegalStateException::class)
+    fun `test infinite recursion in const evaluation 1`() = stubOnlyTypeInfer("""
+    //- foo.rs
+        pub struct Uint<const LIMBS: usize> { }
+
+        impl<const LIMBS: usize> Uint<LIMBS> {
+            pub const LIMBS: usize = LIMBS;
+        }
+
+        pub type U896 = Uint<{ 896 / 64 }>;
+
+        pub trait Tr<T> {}
+
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{14}> {}
+    //- main.rs
+        mod foo;
+        use foo::*;
+
+        fn main() {
+            let a = foo(U896{});
+            a;
+        } //^ Uint<7>
+
+        fn foo<A: Tr<B>, B>(a: A) -> B { todo!() }
+    """)
+
+    // TODO RUST-12502
+    @Test(expected = IllegalStateException::class)
+    fun `test infinite recursion in const evaluation 2`() = stubOnlyTypeInfer("""
+    //- foo.rs
+        pub struct Uint<const LIMBS: usize> { }
+
+        impl<const LIMBS: usize> Uint<LIMBS> {
+            pub const LIMBS: usize = LIMBS;
+        }
+
+        pub type U896 = Uint<{ 896 / 64 }>;
+
+        pub trait Tr<T> {}
+
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS}> {}
+
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 1}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 2}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 3}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 4}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 5}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 6}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 7}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 8}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 9}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 10}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 11}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 12}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 13}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 14}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 15}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 16}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 17}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 18}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 19}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 20}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 21}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 22}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 23}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 24}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 25}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 26}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 27}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 28}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 29}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 30}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 31}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 32}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 33}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 34}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 35}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 36}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 37}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 38}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 39}> {}
+        impl Tr<Uint<{<U896>::LIMBS / 2}>> for Uint<{<U896>::LIMBS + 40}> {}
+
+    //- main.rs
+        mod foo;
+        use foo::*;
+
+        fn main() {
+            let a = foo(U896{});
+            a;
+        } //^ Uint<7>
+
+        fn foo<A: Tr<B>, B>(a: A) -> B { todo!() }
+    """)
+
+    // Issue RUST-11763. Here we test that there isn't a StackOverflowError.
+    // The code is invalid (it does not compile). The inferred types doesn't matter
+    fun `test no StackOverflow with infinite recursion in const evaluation`() = stubOnlyTypeInfer("""
+    //- foo.rs
+        pub struct Uint<const LIMBS: usize> { }
+
+        pub type U128 = Uint<{ 128 }>;
+
+        impl Uint<0> {
+            const NEXT: usize = 32;
+        }
+
+        impl Uint<32> {
+            const NEXT: usize = 64;
+        }
+
+        impl Uint<{ <Uint<{32}>>::NEXT }> { // Uint<64>
+            const NEXT: usize = 128;
+        }
+
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+        impl Uint<{ <Uint<{64}>>::NEXT }> { const NEXT: usize = 256; }
+    //- main.rs
+        mod foo;
+        use foo::*;
+
+        fn main() {
+            let a = Uint::<{ <Uint::<{ 64 }>>::NEXT }>{};
+            a;
+        } //^ Uint<<unknown>>
+    """)
 }
diff --git a/src/test/kotlin/org/rustSlowTests/cargo/runconfig/test/CargoTestNodeInfoTest.kt b/src/test/kotlin/org/rustSlowTests/cargo/runconfig/test/CargoTestNodeInfoTest.kt
index 951519a..9e46323 100644
--- a/src/test/kotlin/org/rustSlowTests/cargo/runconfig/test/CargoTestNodeInfoTest.kt
+++ b/src/test/kotlin/org/rustSlowTests/cargo/runconfig/test/CargoTestNodeInfoTest.kt
@@ -12,48 +12,120 @@
 import com.intellij.execution.testframework.sm.runner.SMTestProxy
 import com.intellij.execution.ui.ConsoleViewContentType
 import org.intellij.lang.annotations.Language
+import org.rust.MaxRustcVersion
+import org.rust.MinRustcVersion
 import org.rust.cargo.toolchain.RustChannel
 
 class CargoTestNodeInfoTest : CargoTestRunnerTestBase() {
 
-    fun `test int diff`() = checkErrors("""
-       assert_eq!(1, 2);
-    """, "", Diff("1", "2"))
+    @MaxRustcVersion("1.72.1")
+    fun `test int diff (old)`() = checkErrors("""
+        assert_eq!(1, 2);
+    """, """
+        assertion failed: `(left == right)`
+          left: `1`,
+         right: `2`
+    """, Diff("1", "2"))
 
-    fun `test char diff`() = checkErrors("""
-       assert_eq!('a', 'c');
-    """, "", Diff("a", "c"))
+    @MinRustcVersion("1.73.0")
+    fun `test int diff (new)`() = checkErrors("""
+        assert_eq!(1, 2);
+    """, """
+        assertion `left == right` failed
+          left: 1
+         right: 2
+    """, Diff("1", "2"))
 
-    fun `test string diff`() = checkErrors("""
-       assert_eq!("aaa", "bbb");
-    """, "", Diff("aaa", "bbb"))
+    @MaxRustcVersion("1.72.1")
+    fun `test char diff (old)`() = checkErrors("""
+        assert_eq!('a', 'c');
+    """, """
+        assertion failed: `(left == right)`
+          left: `'a'`,
+         right: `'c'`
+    """, Diff("a", "c"))
 
-    fun `test multiline string diff`() = checkErrors("""
-       assert_eq!("a\naa", "bbb");
-    """, "", Diff("a\naa", "bbb"))
+    @MinRustcVersion("1.73.0")
+    fun `test char diff (new)`() = checkErrors("""
+        assert_eq!('a', 'c');
+    """, """
+        assertion `left == right` failed
+          left: 'a'
+         right: 'c'
+    """, Diff("a", "c"))
+
+    @MaxRustcVersion("1.72.1")
+    fun `test string diff (old)`() = checkErrors("""
+        assert_eq!("aaa", "bbb");
+    """, """
+        assertion failed: `(left == right)`
+          left: `"aaa"`,
+         right: `"bbb"`
+    """, Diff("aaa", "bbb"))
+
+    @MinRustcVersion("1.73.0")
+    fun `test string diff (new)`() = checkErrors("""
+        assert_eq!("aaa", "bbb");
+    """, """
+        assertion `left == right` failed
+          left: "aaa"
+         right: "bbb"
+    """, Diff("aaa", "bbb"))
+
+    @MaxRustcVersion("1.72.1")
+    fun `test multiline string diff (old)`() = checkErrors("""
+        assert_eq!("a\naa", "bbb");
+    """, """
+        assertion failed: `(left == right)`
+          left: `"a\naa"`,
+         right: `"bbb"`
+    """, Diff("a\naa", "bbb"))
+
+    @MinRustcVersion("1.73.0")
+    fun `test multiline string diff (new)`() = checkErrors("""
+        assert_eq!("a\naa", "bbb");
+    """, """
+        assertion `left == right` failed
+          left: "a\naa"
+         right: "bbb"
+    """, Diff("a\naa", "bbb"))
 
     fun `test assert_eq with message`() = checkErrors("""
-       assert_eq!(1, 2, "`1` != `2`");
+        assert_eq!(1, 2, "`1` != `2`");
     """, "`1` != `2`", Diff("1", "2"))
 
     fun `test no diff`() = checkErrors("""
-       assert!(1 != 1);
+        assert!(1 != 1);
     """, """assertion failed: 1 != 1""")
 
     fun `test assert with message`() = checkErrors("""
-       assert!("aaa" != "aaa", "message");
+        assert!("aaa" != "aaa", "message");
     """, "message")
 
-    fun `test assert_ne`() = checkErrors("""
-       assert_ne!(123, 123);
-    """, "")
+    @MaxRustcVersion("1.72.1")
+    fun `test assert_ne (old)`() = checkErrors("""
+        assert_ne!(123, 123);
+    """, """
+        assertion failed: `(left != right)`
+          left: `123`,
+         right: `123`
+    """)
+
+    @MinRustcVersion("1.73.0")
+    fun `test assert_ne (new)`() = checkErrors("""
+        assert_ne!(123, 123);
+    """, """
+        assertion `left != right` failed
+          left: 123
+         right: 123
+    """)
 
     fun `test assert_ne with message`() = checkErrors("""
-       assert_ne!(123, 123, "123 == 123");
+        assert_ne!(123, 123, "123 == 123");
     """, "123 == 123")
 
     fun `test unescape error messages`() = checkErrors("""
-       assert_eq!("a\\\\b", "a\\b", "`a\\\\b` != `a\\b`");
+        assert_eq!("a\\\\b", "a\\b", "`a\\\\b` != `a\\b`");
     """, "`a\\\\b` != `a\\b`", Diff("a\\\\b", "a\\b"))
 
     fun `test don't unescape test output`() = checkOutput("""
@@ -185,7 +257,7 @@
         shouldPass: Boolean = false
     ) {
         val testNode = getTestNode(testFnText, shouldPass)
-        assertEquals(message, testNode.errorMessage)
+        assertEquals(message.trimIndent(), testNode.errorMessage?.trimEnd())
         if (diff != null) {
             val diffProvider = testNode.diffViewerProvider ?: error("Diff should be not null")
             assertEquals(diff.actual, diffProvider.left)