From 0e789f4a9fb34db959eefc0a2f40cb5705b0ca5a Mon Sep 17 00:00:00 2001
From: Carlos Cordoba <ccordoba12@gmail.com>
Date: Mon, 8 Jul 2024 17:49:30 -0500
Subject: [PATCH] IPython console: Use submenus in envs menu if there are more
 than 20

That way users will be able to easily go the category for which they
want to start an env.
---
 spyder/plugins/ipythonconsole/api.py          |   8 --
 .../ipythonconsole/widgets/main_widget.py     | 133 ++++++++++++++----
 2 files changed, 109 insertions(+), 32 deletions(-)

diff --git a/spyder/plugins/ipythonconsole/api.py b/spyder/plugins/ipythonconsole/api.py
index 5e4b4f2407b..ed19744783e 100644
--- a/spyder/plugins/ipythonconsole/api.py
+++ b/spyder/plugins/ipythonconsole/api.py
@@ -133,11 +133,3 @@ class IPythonConsoleWidgetCornerWidgets:
     ResetButton = "reset_button"
     InterruptButton = "interrupt_button"
     TimeElapsedLabel = "time_elapsed_label"
-
-
-class EnvironmentConsolesMenuSections:
-    Default = "default_section"
-    Conda = "conda_section"
-    Pyenv = "pyenv_section"
-    Custom = "custom_section"
-    Other = "other_section"
diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py
index 2bdbc03474d..6f1e7596454 100644
--- a/spyder/plugins/ipythonconsole/widgets/main_widget.py
+++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py
@@ -37,7 +37,6 @@
 from spyder.config.base import get_home_dir, running_under_pytest
 from spyder.plugins.ipythonconsole.api import (
     ClientContextMenuActions,
-    EnvironmentConsolesMenuSections,
     IPythonConsoleWidgetActions,
     IPythonConsoleWidgetMenus,
     IPythonConsoleWidgetCornerWidgets,
@@ -71,13 +70,27 @@
 logger = logging.getLogger(__name__)
 
 
-# =============================================================================
 # ---- Constants
-# =============================================================================
+# -----------------------------------------------------------------------------
 MAIN_BG_COLOR = SpyderPalette.COLOR_BACKGROUND_1
 
+# Leaving these enums here because they don't need to be part of the public API
+class EnvironmentConsolesSubmenus:
+    CondaMenu = 'conda_environments_menu'
+    PyenvMenu = 'pyenv_environment_menu'
+    CustomMenu = 'custom_environment_menu'
 
-# --- Widgets
+
+class EnvironmentConsolesMenuSections:
+    Default = "default_section"
+    Conda = "conda_section"
+    Pyenv = "pyenv_section"
+    Custom = "custom_section"
+    Other = "other_section"
+    Submenus = "submenus_section"
+
+
+# ---- Widgets
 # ----------------------------------------------------------------------------
 class IPythonConsoleWidget(PluginMainWidget, CachedKernelMixin):
     """
@@ -367,7 +380,7 @@ def get_focus_widget(self):
             return client.get_control()
 
     def setup(self):
-        # --- Main menu
+        # --- Console environments menu
         self.console_environment_menu = self.create_menu(
             IPythonConsoleWidgetMenus.EnvironmentConsoles,
             _('New console in environment')
@@ -381,7 +394,21 @@ def setup(self):
             self._update_environment_menu
         )
 
-        # --- Options menu actions
+        # Submenus
+        self.conda_envs_menu = self.create_menu(
+            EnvironmentConsolesSubmenus.CondaMenu,
+            "Conda",
+        )
+        self.pyenv_envs_menu = self.create_menu(
+            EnvironmentConsolesSubmenus.PyenvMenu,
+            "Pyenv",
+        )
+        self.custom_envs_menu = self.create_menu(
+            EnvironmentConsolesSubmenus.CustomMenu,
+            _("Custom"),
+        )
+
+        # --- Main and options menu actions
         self.create_client_action = self.create_action(
             IPythonConsoleWidgetActions.CreateNewClient,
             text=_("New console (default settings)"),
@@ -1132,7 +1159,11 @@ def _create_client_for_kernel(self):
 
     def _update_environment_menu(self):
         """Update submenu with entries for available interpreters."""
+        # Clear menu and submenus before rebuilding them
         self.console_environment_menu.clear_actions()
+        self.conda_envs_menu.clear_actions()
+        self.pyenv_envs_menu.clear_actions()
+        self.custom_envs_menu.clear_actions()
 
         internal_action = None
         conda_actions = []
@@ -1200,37 +1231,91 @@ def _update_environment_menu(self):
                 section=EnvironmentConsolesMenuSections.Default
             )
 
-        # Add other envs to their respective sections but only if there are two
-        # or more per category. Otherwise we group them in a single section
-        # called "Other". We do that because having many menu sections with a
-        # single entry makes the UI look odd.
-        for action in conda_actions:
+        # Add other envs to their respective submenus or sections
+        max_actions_in_menu = 20
+        n_categories = len(
+            [
+                actions
+                for actions in [conda_actions, pyenv_actions, custom_actions]
+                if len(actions) > 0
+            ]
+        )
+        actions = conda_actions + pyenv_actions + custom_actions
+
+        # We use submenus if there are more envs than we'd like to see
+        # displayed in the consoles menu, and there are at least two non-empty
+        # categories.
+        if len(actions) > max_actions_in_menu and n_categories > 1:
+            conda_menu = self.conda_envs_menu
             self.add_item_to_menu(
-                action,
+                conda_menu,
                 menu=self.console_environment_menu,
-                section=EnvironmentConsolesMenuSections.Conda
-                if len(conda_actions) > 1
-                else EnvironmentConsolesMenuSections.Other,
+                section=EnvironmentConsolesMenuSections.Submenus,
             )
 
-        for action in pyenv_actions:
+            pyenv_menu = self.pyenv_envs_menu
             self.add_item_to_menu(
-                action,
+                pyenv_menu,
                 menu=self.console_environment_menu,
-                section=EnvironmentConsolesMenuSections.Pyenv
-                if len(pyenv_actions) > 1
-                else EnvironmentConsolesMenuSections.Other,
+                section=EnvironmentConsolesMenuSections.Submenus,
             )
 
-        for action in custom_actions:
+            custom_menu = self.custom_envs_menu
             self.add_item_to_menu(
-                action,
+                custom_menu,
                 menu=self.console_environment_menu,
-                section=EnvironmentConsolesMenuSections.Custom
+                section=EnvironmentConsolesMenuSections.Submenus,
+            )
+
+            # Submenus don't have sections
+            conda_section = pyenv_section = custom_section = None
+        else:
+            # If there are few envs, we add their actions to the consoles menu.
+            # But we use sections only if there are two or more envs per
+            # category. Otherwise we group them in a single section called
+            # "Other". We do that because having many menu sections with a
+            # single entry makes the UI look odd.
+            conda_menu = (
+                pyenv_menu
+            ) = custom_menu = self.console_environment_menu
+
+            conda_section = (
+                EnvironmentConsolesMenuSections.Conda
+                if len(conda_actions) > 1
+                else EnvironmentConsolesMenuSections.Other
+            )
+
+            pyenv_section = (
+                EnvironmentConsolesMenuSections.Pyenv
+                if len(pyenv_actions) > 1
+                else EnvironmentConsolesMenuSections.Other
+            )
+
+            custom_section = (
+                EnvironmentConsolesMenuSections.Custom
                 if len(custom_actions) > 1
-                else EnvironmentConsolesMenuSections.Other,
+                else EnvironmentConsolesMenuSections.Other
+            )
+
+        # Add actions to menu or submenus
+        for action in actions:
+            if action in conda_actions:
+                menu = conda_menu
+                section = conda_section
+            elif action in pyenv_actions:
+                menu = pyenv_menu
+                section = pyenv_section
+            else:
+                menu = custom_menu
+                section = custom_section
+
+            self.add_item_to_menu(
+                action,
+                menu=menu,
+                section=section,
             )
 
+        # Render consoles menu and submenus
         self.console_environment_menu.render()
 
     def find_connection_file(self, connection_file):