diff --git a/platform/platform-api/src/com/intellij/openapi/options/NonModalSettingsPolicy.kt b/platform/platform-api/src/com/intellij/openapi/options/NonModalSettingsPolicy.kt new file mode 100644 index 000000000000..e10f223c4d60 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/options/NonModalSettingsPolicy.kt @@ -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 = 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 +} + diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ShowSettingsUtilImpl.kt b/platform/platform-impl/src/com/intellij/ide/actions/ShowSettingsUtilImpl.kt index ea23d36cc236..8ef8e2df5b82 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ShowSettingsUtilImpl.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/ShowSettingsUtilImpl.kt @@ -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, 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 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().coroutineScope.launch { launch { diff --git a/platform/platform-impl/src/com/intellij/openapi/options/NonModalSettingsAdvancedSettingService.kt b/platform/platform-impl/src/com/intellij/openapi/options/NonModalSettingsAdvancedSettingService.kt new file mode 100644 index 000000000000..84d8dca8278c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/options/NonModalSettingsAdvancedSettingService.kt @@ -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() +} diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index ac6b7f19b7fb..f0514290c432 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -109,6 +109,8 @@ area="IDEA_PROJECT" dynamic="true"/> + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 656867f2e4ac..94ef33df2fac 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -1802,8 +1802,9 @@ The refresh itself is toggled by (Preferences | Appearance & Behavior | Syst + + service="com.intellij.openapi.options.NonModalSettingsAdvancedSettingService" property="nonModalSettingsWindow"/>