CPP-51572 [debugger]: fix debug switcher showing in non-python sessions

The problem was noticed in CLion, where mode switcher is shown even if a cpp file is being debugged. The reason of the problem is that we decided to show the switcher even if no handlers are registered (Python DAP/Jupyter are not installed) for promotion purposes.

The fix is to move part of condition (show switcher only during Python debug sessions) from handlers to switcher directly

Merge-request: IJ-MR-217766
Merged-by: Maxim Popov <maxim.popov@jetbrains.com>

(cherry picked from commit 00cedac5434715f87e131d6bfd99ec9ccb18155d)


Merge-request: IJ-MR-217937
Merged-by: Maxim Popov <maxim.popov@jetbrains.com>

GitOrigin-RevId: b2cbda67623ca368400eeeb7b7313a92d567b448
This commit is contained in:
Maxim Popov
2026-08-11 21:24:00 +00:00
committed by intellij-monorepo-bot
parent 9bba7d4448
commit 6706df30f8
2 changed files with 44 additions and 6 deletions
@@ -25,6 +25,8 @@ interface PyDebuggerBackendSwitchHandler {
/**
* Controls visibility of the backend switcher UI.
* Return false to hide the switcher (e.g., when the currently open file uses a remote kernel).
* This is one of two gates: returning true does not show the switcher by itself, since it is also
* hidden on Debug tool window tabs that do not run a Python or Jupyter session.
*/
fun shouldShowSwitcher(project: Project): Boolean = true
@@ -13,6 +13,7 @@ import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ComboBoxAction
import com.intellij.openapi.components.service
@@ -47,9 +48,11 @@ import com.intellij.util.ui.JBUI
import com.jetbrains.python.sdk.findPythonSdk
import kotlinx.coroutines.launch
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.platform.debugger.impl.ui.XDebuggerEntityConverter
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebugSessionListener
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import com.jetbrains.python.icons.PythonIcons
import org.jetbrains.annotations.ApiStatus
import java.lang.ref.WeakReference
@@ -103,12 +106,12 @@ internal class PyDebuggerBackendSwitcherAction : ComboBoxAction(), DumbAware {
return
}
if (!PyDebuggerBackendSwitcherVisibilityPin.isPinned(project)) {
val handlers = PyDebuggerBackendSwitchHandler.EP_NAME.extensionList
if (isPythonDapPluginInstalledAndEnabled() && handlers.all { !it.shouldShowSwitcher(project) }) {
e.presentation.isEnabledAndVisible = false
return
}
if (!isPyDebuggerBackendSwitcherVisible(project, e)) {
// Only the toolbar presence is in question here, so the action stays enabled: invoking it by id
// keeps working, which is how the backend is chosen before the first session exists.
e.presentation.isEnabled = true
e.presentation.isVisible = false
return
}
e.presentation.isEnabledAndVisible = true
@@ -254,6 +257,39 @@ internal class PyDebuggerBackendSwitcherAction : ComboBoxAction(), DumbAware {
}
/**
* Whether the backend switcher belongs on the Debug tool window tab the [e] context points at.
*
* The switcher lives in the tool window header, which is shared by every tab of every product that
* bundles the Python plugin, so it has to opt in per tab rather than default to visible: a C++ or a
* JavaScript session must not be told which Python debugger backend it runs on (CPP-51572).
*/
@ApiStatus.Internal
fun isPyDebuggerBackendSwitcherVisible(project: Project, e: AnActionEvent): Boolean {
if (PyDebuggerBackendSwitcherVisibilityPin.isPinned(project)) return true
// While the PythonDAP plugin is installed, the handlers report whether the current editor and run
// configuration are Python at all, and a unanimous no hides the switcher. That is what keeps it off a
// notebook backed by a remote kernel.
val handlers = PyDebuggerBackendSwitchHandler.EP_NAME.extensionList
if (isPythonDapPluginInstalledAndEnabled() && handlers.all { !it.shouldShowSwitcher(project) }) return false
// Neither the pin nor the handlers look at the Debug tool window, which is why a C++ or a JavaScript tab
// kept the switcher. It belongs on Python and Jupyter tabs only, whether or not the plugin is installed:
// without it the switcher is the promotion surface for debugpy, and those tabs are where it belongs.
return isPythonDebugTab(e)
}
private fun isPythonDebugTab(e: AnActionEvent): Boolean {
// The header toolbar takes its data context from the selected tab, but a tab only publishes a run
// profile when it was started from an execution environment, which a Jupyter or console session
// was not. Those are recognized through the session instead: they run a pydevd-backed process.
if (e.getData(LangDataKeys.RUN_PROFILE) is AbstractPythonRunConfiguration<*>) return true
val sessionProxy = DebuggerUIUtil.getSessionProxy(e) ?: return false
val session = XDebuggerEntityConverter.getSession(sessionProxy) ?: return false
return session.runProfile is AbstractPythonRunConfiguration<*> || session.debugProcess is PyDebugProcess
}
/**
* Returns the SDK of the currently selected run configuration, falling back to the project SDK.
*/