[262] [settings] IJ-CR-210317 IJPL-244826 Disable non-modal settings in CLion Nova and Rider

Migrated control of non-modal settings dialogs to a new Extension Point:
com.intellij.nonModalSettingsPolicy. This resolves a non-deterministic
service override conflict for ShowSettingsUtil between Code With Me
(BackendShowSettingsUtil) and Rider/Radler (RiderShowSettingsUtilImpl):
depending on plugin loading order, BackendShowSettingsUtil could win,
causing CLion Nova and Rider to fall back to the default platform
behavior (non-modal settings enabled by default)

By using an Extension Point with "Modal Wins" aggregation logic:
- Competition for the ShowSettingsUtil service is eliminated
- If any registered policy extension disables non-modal settings, the
  IDE respects that choice regardless of plugin loading order
- A single RiderNonModalSettingsPolicy registered in
  intellij.rider.rdclient.languages covers both Rider and CLion Nova
  across all deployment tiers (monolith, backend, thin-client)

Key changes:
- Defined com.intellij.nonModalSettingsPolicy EP in platform-api with
  aggregation logic in the NonModalSettingsPolicy companion object
- ShowSettingsUtilImpl delegates isNonModalSettingsEnabled() to the EP
- Removed ShowSettingsUtil service overrides from RiderShowSettingsUtilImpl
  and CppFrontendShowSettingsUtilImpl; deleted CppFrontendShowSettingsUtilImpl
- Added NonModalSettingsAdvancedSettingService (platform-impl) as a
  minimal bridge for the Advanced Settings framework visibility check

(cherry picked from commit 2d0edc141be4804163cdda84f6898d31c2168fdd)

GitOrigin-RevId: 8289a8c0879c4c33530244e597da05a8572ac218
This commit is contained in:
Aleksandra Olemskaia
2026-06-25 19:49:41 +00:00
committed by intellij-monorepo-bot
parent 04d55a3308
commit adebb18685
5 changed files with 80 additions and 16 deletions
@@ -0,0 +1,54 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.options
import com.intellij.openapi.extensions.ExtensionPointName
import org.jetbrains.annotations.ApiStatus
/**
* Controls whether a non-modal settings dialog is available
*
* Products that don't support non-modal settings (e.g. Rider, CLion Nova)
* can register an extension to disable the feature and hide the corresponding Advanced Setting.
*
* This policy is intentionally separated from [ShowSettingsUtil] to avoid
* service-override ordering issues when multiple plugins override [ShowSettingsUtil]
* (e.g. CWM's `BackendShowSettingsUtil` and Rider's `RiderShowSettingsUtilImpl`).
*/
@ApiStatus.Internal
interface NonModalSettingsPolicy {
companion object {
@ApiStatus.Internal
val EP_NAME: ExtensionPointName<NonModalSettingsPolicy> = ExtensionPointName.create("com.intellij.nonModalSettingsPolicy")
/**
* Aggregated check for all registered [NonModalSettingsPolicy] extensions.
*/
fun isNonModalSettingsEnabledByAllPolicies(): Boolean {
if (EP_NAME.extensionList.any { !it.isNonModalSettingsEnabled() }) {
return false
}
return System.getProperty("ide.ui.non.modal.settings.window")?.toBoolean()
?: com.intellij.openapi.options.advanced.AdvancedSettings.getBoolean("ide.ui.non.modal.settings.window")
}
/**
* Aggregated check for all registered [NonModalSettingsPolicy] extensions.
*/
fun isNonModalSettingsWindowSettingVisibleInAllPolicies(): Boolean {
return EP_NAME.extensionList.all { it.isNonModalSettingsWindowSettingVisible() }
}
}
/**
* Whether the non-modal settings window feature is enabled for this product.
* When `false`, settings are always shown in a modal dialog.
*/
fun isNonModalSettingsEnabled(): Boolean
/**
* Controls the visibility of the "Show Settings in non-modal window" option in Advanced Settings.
* When `false`, the Advanced Setting is hidden (along with non-modal settings being disabled).
*/
fun isNonModalSettingsWindowSettingVisible(): Boolean
}
@@ -15,9 +15,9 @@ import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableGroup
import com.intellij.openapi.options.NonModalSettingsPolicy
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.options.TabbedConfigurable
import com.intellij.openapi.options.advanced.AdvancedSettings
import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil
import com.intellij.openapi.options.ex.ConfigurableVisitor
import com.intellij.openapi.options.ex.ConfigurableWrapper
@@ -130,17 +130,11 @@ open class ShowSettingsUtilImpl : ShowSettingsUtil() {
}
}
@ApiStatus.Internal
protected open fun isNonModalSettingsEnabled(): Boolean = useNonModalSettingsWindow()
@ApiStatus.Internal
open fun isNonModalSettingsWindowVisible(): Boolean = true
@ApiStatus.Internal
protected open fun doShow(project: Project?, groups: List<ConfigurableGroup>, toSelect: Configurable?, filter: String?) {
val isModal = !(project != null &&
project != ProjectManager.getInstance().defaultProject &&
isNonModalSettingsEnabled() &&
NonModalSettingsPolicy.isNonModalSettingsEnabledByAllPolicies() &&
ModalityState.current() == ModalityState.nonModal())
val filteredGroups = filterEmptyGroups(groups)
@@ -163,8 +157,8 @@ open class ShowSettingsUtilImpl : ShowSettingsUtil() {
// We want to ensure that clients don’t simply replace one API with another,
// but actually rework the invocation to be performed not in EDT.
ThreadingAssertions.assertBackgroundThread()
val isModal = project.isDefault || !isNonModalSettingsEnabled()
val isModal = project.isDefault || !NonModalSettingsPolicy.isNonModalSettingsEnabledByAllPolicies()
withContext(Dispatchers.EDT) {
if (!isModal) {
SettingsNonModalDialogFactory.getInstance().show(project, filterEmptyGroups(groups), null, null)
@@ -367,11 +361,6 @@ private fun <T : Configurable> editConfigurable(
return editor.showAndGet()
}
private fun useNonModalSettingsWindow(): Boolean {
return System.getProperty("ide.ui.non.modal.settings.window")?.toBoolean()
?: AdvancedSettings.getBoolean("ide.ui.non.modal.settings.window")
}
internal fun scheduleDoShowSettingsDialogWithACheckThatProjectIsInitialized(project: Project) {
project.service<CoreUiCoroutineScopeHolder>().coroutineScope.launch {
launch {
@@ -0,0 +1,18 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.options
import org.jetbrains.annotations.ApiStatus
/**
* Service used solely for the `ide.ui.non.modal.settings.window` Advanced Setting visibility check.
*
* The Advanced Settings framework with `property="nonModalSettingsWindow"` calls
* `isNonModalSettingsWindowVisible()` on this service instance to decide whether the setting
* should appear in the Advanced Settings UI.
*
* Actual policy aggregation is delegated to [NonModalSettingsPolicy.isNonModalSettingsWindowSettingVisibleInAllPolicies].
*/
@ApiStatus.Internal
class NonModalSettingsAdvancedSettingService {
fun isNonModalSettingsWindowVisible(): Boolean = NonModalSettingsPolicy.isNonModalSettingsWindowSettingVisibleInAllPolicies()
}
@@ -109,6 +109,8 @@
area="IDEA_PROJECT"
dynamic="true"/>
<extensionPoint name="nonModalSettingsPolicy" interface="com.intellij.openapi.options.NonModalSettingsPolicy" dynamic="true"/>
<extensionPoint name="undoProvider" interface="com.intellij.openapi.command.impl.UndoProvider" dynamic="true"/>
<extensionPoint name="projectUndoProvider" interface="com.intellij.openapi.command.impl.UndoProvider"
area="IDEA_PROJECT" dynamic="true"/>
@@ -1802,8 +1802,9 @@ The refresh itself is toggled by (Preferences | Appearance &amp; Behavior | Syst
<advancedSetting id="search.everywhere.show.results.notification" default="true" groupKey="group.advanced.settings.se"/>
<advancedSetting id="search.everywhere.contributors.wait.timeout" default="2000" groupKey="group.advanced.settings.se"/>
<advancedSetting id="search.everywhere.recent.at.top" default="true" groupKey="group.advanced.settings.se"/>
<applicationService serviceImplementation="com.intellij.openapi.options.NonModalSettingsAdvancedSettingService"/>
<advancedSetting id="ide.ui.non.modal.settings.window" default="true" groupKey="group.advanced.settings.ui"
service="com.intellij.openapi.options.ShowSettingsUtil" property="nonModalSettingsWindow"/>
service="com.intellij.openapi.options.NonModalSettingsAdvancedSettingService" property="nonModalSettingsWindow"/>
<backgroundPostStartupActivity implementation="com.intellij.ide.plugins.DependencyFeatureCollector"/>
<projectService serviceInterface="com.intellij.presentation.FilePresentationService"