Repository: spyder-ide/spyder

Issue:
Improve IPython console Options menu
The Options menu of the IPython console has grown a lot lately:

![seleccion_001](https://user-images.githubusercontent.com/365293/50369667-5063d100-0567-11e9-8860-59bbc669b9a2.png)

My idea is to shrink it a bit, like this:

```
New console
Specialized consoles > Pylab / Sympy / Cython
----
Restart kernel
Connect to an existing kernel
Remove all variables
----
Rename tab
Show env vars
Show sys.path contents
Show elapsed time
```

Notes:
* `Specialized consoles` would be a submenu but only of the IPython console options menu. The main `Consoles` menu wouldn't have it.
* The last two actions in the screenshot above will be added to all plugins after PR #8192.

@spyder-ide/core-developers, what do you think?


Discussion:
Good thinking; what you have so far is a major improvement. Would these changes also affect the context menu similarly? 

A few comments:

* ``Connect to an existing kernel`` should definitely be in the first group, under ``Specialized consoles``, since it relates to opening a new Console tab (as does the first group) rather than modifying an existing one (as do the other actions in the second group).
* Similarly, since ``Rename Tab`` modifies the current Console tab, rather than is a general action (like the others in its group), it should go in the second group,
* What's the reason for including the ``Specialized consoles`` submenu in the context/options menu but not the main Consoles menu?

Also, a a detail note, I'd suggest naming it something like ``New IPython console``/``New specialized console`` for clarity and parallelism.

Generally I like this.

> What's the reason for including the Specialized consoles submenu in the context/options menu but not the main Consoles menu?

Do we even need a main Console menu? If we do it need it might as well have more things in it. 

I've always wondered why the stop command isn't in the menu and the remove variables is.

This might be out of scope but a restart kernel button next to the remove variables could be useful. I've been trapped in a live updating plot and the stop button wouldn't stop it but restart did trick. But it was hard to get focus long enough to click the drop down with a new figure popping up every second :-)
> Do we even need a main Console menu?

Yeah, I've always wondered that, since it relates to a particular pane. A few core functions from each pane (run profiler, run static analysis, open project, etc) are present in the menus, but the Consoles pane is the only one that gets its own menu, which is just a curiously abbreviated version of the main Consoles menu. IMO since its dedicated to one and only one pane and is based directly off that pane's menu rather than taking up space in another, we should either just make it identical (ideally sharing the same codebase) to reduce maintenance burden and user confusion, or remove it entirely.

> This might be out of scope but a restart kernel button next to the remove variables could be useful.

Maybe out of scope, but agreed—I almost never use Remove Variables but use Restart Kernel all the time.
Ok, so let's go with

```
New console
Specialized consoles > Pylab / Sympy / Cython
Connect to an existing kernel
----
Restart kernel
Remove all variables
Rename tab
----
Show env vars
Show sys.path contents
Show elapsed time
```

> What's the reason for including the Specialized consoles submenu in the context/options menu but not the main Consoles menu?

Because the `Consoles` menu can be much taller than the Options one.

> Also, a a detail note, I'd suggest naming it something like New IPython console/New specialized console for clarity and parallelism.

I think that's too detailed and it'd also make the menu too wide.
About your other comments:

> I've always wondered why the stop command isn't in the menu and the remove variables is.

I thought it was enough with the stop button. Do you think we should add it? (I'm a bit -1 on it).

> This might be out of scope but a restart kernel button next to the remove variables could be useful.

The thing is the restart kernel action is blocking (i.e. it freezes the entire interface) so I wouldn't like people to start using it all the time. But if you feel this is important, please open a new issue about it.
> Because the Consoles menu can be much taller than the Options one.

I'm confused; that seems backward—you mentioned the Consoles menu wouldn't have it, while the option one would, yet its the Console menu that can be much taller as you say.

> I think that's too detailed and it'd also make the menu too wide.

Okay, good point. How about ``New special console`` which is one fewer char vs. ``Specialized consoles``, but is clearer and more consistent.

> I thought it was enough with the stop button. Do you think we should add it? (I'm a bit -1 on it).

Not necessarily, no, but IMO there should either be a clear rationale for why ``Remove all variables`` is in the options menu while the stop button isn't, despite both relating to a specific console and being a button on the toolbar, or remove the former from the toolbar for consistency.

> The thing is the restart kernel action is blocking (i.e. it freezes the entire interface) so I wouldn't like people to start using it all the time.

Okay; I'll think more about it and open an issue if warranted, thanks.


> I'm confused; that seems backward—you mentioned the Consoles menu wouldn't have it, while the option one would, yet its the Console menu that can be much taller as you say.

Yes, we don't need to create a submenu in the Consoles menu because it can be much taller.

> How about New special console

Ok, no problem.

> but IMO there should either be a clear rationale for why Remove all variables is in the options menu while the stop button isn't

There's no rationale. I just thought it was not necessary. So let's add it with this work.
@juanis2112, let's go with this for the Options menu:

```
New console (default settings)
New special console >
    New Pylab console (data plotting)
    New SymPy console (symbolic math)
    New Cython console (Python with C extensions)
Connect to an existing kernel
----
Restart kernel
Interrupt kernel (<- This action will trigger the same action as the stop button)
Remove all variables
Rename tab
----
Show env vars
Show sys.path contents
Show elapsed time
```

And for the `Consoles` menu on top, please organize it like this

```
New console (default settings)
New Pylab console (data plotting)
New SymPy console (symbolic math)
New Cython console (Python with C extensions)
Connect to an existing kernel
----
Restart kernel
Interrupt kernel
Remove all variables
```
I suggest placing ``Interrupt kernel`` above ``Restart kernel``; IMO just seems more intuitive and natural to have interrupt above a complete restart but maybe that's just me.

Is there currently a clear distinction for what goes on the Consoles top-level menu vs. the Consoles pane options menu? If not, I'd consider putting commands that aren't specific to a particular Console sub-tab (e.g. the middle section) only on the Console pane options menu, while including global (first section) or quasi-global (third section) options on both; this also ensures the common sections are identical. Alternatively, if there isn't a clear distinction other than subjection personal perception, since the Consoles menu can be longer anyway I'd just make then identical to be consistent. 

> Yes, we don't need to create a submenu in the Consoles menu because it can be much taller.

Oh, I see—I interpreted your original comment to mean those options wouldn't be included at all.

> There's no rationale. I just thought it was not necessary. So let's add it with this work.

Okay, sure, if you think so.
> I suggest placing Interrupt kernel above Restart kernel

Ok, let's go with this.

> Is there currently a clear distinction for what goes on the Consoles top-level menu vs. the Consoles pane options menu?

I'd like to leave the third section in the Options menu because that's pretty specific to each console.
> I'd like to leave the third section in the Options menu because that's pretty specific to each console.

Well, Sys.Path and Environment variables might vary somewhat from console to console, but not always dramatically so (thus "quasi-global"), while "Show elapsed time" is a Console-global option. Meanwhile, Restart. Interrupt and Remove all variables (along with Rename Tab) take explicitly console-specific actions.

Resolving patch:
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -719,6 +719,8 @@ def create_edit_action(text, tr_text, icon):
 
         # Consoles menu/toolbar
         self.consoles_menu = self.menuBar().addMenu(_("C&onsoles"))
+        self.consoles_menu.aboutToShow.connect(
+                self.update_execution_state_kernel)
 
         # Projects menu
         self.projects_menu = self.menuBar().addMenu(_("&Projects"))
@@ -2046,6 +2048,13 @@ def show_toolbars(self):
         self._update_show_toolbars_action()
 
     # --- Other
+    def update_execution_state_kernel(self):
+        """Handle execution state of the current console."""
+        try:
+            self.ipyconsole.update_execution_state_kernel()
+        except AttributeError:
+            return
+
     def valid_project(self):
         """Handle an invalid active project."""
         try:
diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py
--- a/spyder/plugins/ipythonconsole/plugin.py
+++ b/spyder/plugins/ipythonconsole/plugin.py
@@ -26,8 +26,9 @@
 from qtconsole.client import QtKernelClient
 from qtconsole.manager import QtKernelManager
 from qtpy.QtCore import Qt, Signal, Slot
-from qtpy.QtWidgets import (QApplication, QHBoxLayout, QMessageBox,
-                            QVBoxLayout, QWidget)
+from qtpy.QtWidgets import (QActionGroup, QApplication, QGridLayout,
+                            QGroupBox, QHBoxLayout, QLabel, QMenu, QMessageBox,
+                            QTabWidget, QVBoxLayout, QWidget)
 from traitlets.config.loader import Config, load_pyconfig_files
 from zmq.ssh import tunnel as zmqtunnel
 
@@ -40,8 +41,8 @@
 from spyder.plugins.ipythonconsole.confpage import IPythonConsoleConfigPage
 from spyder.plugins.ipythonconsole.utils.kernelspec import SpyderKernelSpec
 from spyder.plugins.ipythonconsole.utils.style import create_qss_style
+from spyder.utils.qthelpers import create_action, add_actions, MENU_SEPARATOR
 from spyder.plugins.ipythonconsole.utils.ssh import openssh_tunnel
-from spyder.utils.qthelpers import create_action, MENU_SEPARATOR
 from spyder.utils import icon_manager as ima
 from spyder.utils import encoding, programs, sourcecode
 from spyder.utils.programs import get_temp_dir
@@ -107,6 +108,7 @@ def __init__(self, parent, test_dir=None, test_no_stderr=False,
         self.create_new_client_if_empty = True
         self.css_path = css_path
         self.run_cell_filename = None
+        self.interrupt_action = None
 
         # Attrs for testing
         self.test_dir = test_dir
@@ -294,11 +296,30 @@ def get_plugin_actions(self):
                                    icon=ima.icon('ipython_console'),
                                    triggered=self.create_cython_client,
                                    context=Qt.WidgetWithChildrenShortcut)
+        special_console_action_group = QActionGroup(self)
+        special_console_actions = (create_pylab_action, create_sympy_action,
+                                   create_cython_action)
+        add_actions(special_console_action_group, special_console_actions)
+        special_console_menu = QMenu(_("New special console"), self)
+        add_actions(special_console_menu, special_console_actions)
 
         restart_action = create_action(self, _("Restart kernel"),
                                        icon=ima.icon('restart'),
                                        triggered=self.restart_kernel,
                                        context=Qt.WidgetWithChildrenShortcut)
+
+        reset_action = create_action(self, _("Remove all variables"),
+                                     icon=ima.icon('editdelete'),
+                                     triggered=self.reset_kernel,
+                                     context=Qt.WidgetWithChildrenShortcut)
+
+        if self.interrupt_action is None:
+            self.interrupt_action = create_action(
+                self, _("Interrupt kernel"),
+                icon=ima.icon('stop'),
+                triggered=self.interrupt_kernel,
+                context=Qt.WidgetWithChildrenShortcut)
+
         self.register_shortcut(restart_action, context="ipython_console",
                                name="Restart kernel")
 
@@ -314,26 +335,24 @@ def get_plugin_actions(self):
         # Add the action to the 'Consoles' menu on the main window
         main_consoles_menu = self.main.consoles_menu_actions
         main_consoles_menu.insert(0, create_client_action)
-        main_consoles_menu.insert(1, create_pylab_action)
-        main_consoles_menu.insert(2, create_sympy_action)
-        main_consoles_menu.insert(3, create_cython_action)
-        main_consoles_menu += [MENU_SEPARATOR, restart_action,
-                               connect_to_kernel_action,
-                               MENU_SEPARATOR]
+        main_consoles_menu += [special_console_menu, connect_to_kernel_action,
+                               MENU_SEPARATOR,
+                               self.interrupt_action, restart_action,
+                               reset_action]
 
         # Plugin actions
-        self.menu_actions = [create_client_action, create_pylab_action,
-                             create_sympy_action, create_cython_action,
+        self.menu_actions = [create_client_action, special_console_menu,
+                             connect_to_kernel_action,
                              MENU_SEPARATOR,
-                             restart_action, connect_to_kernel_action,
-                             MENU_SEPARATOR, rename_tab_action,
-                             MENU_SEPARATOR]
+                             self.interrupt_action,
+                             restart_action, reset_action, rename_tab_action]
+
+        self.update_execution_state_kernel()
 
         # Check for a current client. Since it manages more actions.
         client = self.get_current_client()
         if client:
             return client.get_options_menu()
-        
         return self.menu_actions
 
     def register_plugin(self):
@@ -1142,6 +1161,27 @@ def restart_kernel(self):
             self.switch_to_plugin()
             client.restart_kernel()
 
+    def reset_kernel(self):
+        """Reset kernel of current client."""
+        client = self.get_current_client()
+        if client is not None:
+            self.switch_to_plugin()
+            client.reset_namespace()
+
+    def interrupt_kernel(self):
+        """Interrupt kernel of current client."""
+        client = self.get_current_client()
+        if client is not None:
+            self.switch_to_plugin()
+            client.stop_button_click_handler()
+
+    def update_execution_state_kernel(self):
+        """Update actions following the execution state of the kernel."""
+        client = self.get_current_client()
+        if client is not None:
+            executing = client.stop_button.isEnabled()
+            self.interrupt_action.setEnabled(executing)
+
     def connect_external_kernel(self, shellwidget):
         """
         Connect an external kernel to the Variable Explorer and Help, if
diff --git a/spyder/plugins/ipythonconsole/widgets/client.py b/spyder/plugins/ipythonconsole/widgets/client.py
--- a/spyder/plugins/ipythonconsole/widgets/client.py
+++ b/spyder/plugins/ipythonconsole/widgets/client.py
@@ -379,10 +379,6 @@ def get_kernel(self):
 
     def get_options_menu(self):
         """Return options menu"""
-        reset_action = create_action(self, _("Remove all variables"),
-                                     icon=ima.icon('editdelete'),
-                                     triggered=self.reset_namespace)
-
         env_action = create_action(
                         self,
                         _("Show environment variables"),
@@ -398,14 +394,15 @@ def get_options_menu(self):
                          )
 
         self.show_time_action.setChecked(self.show_elapsed_time)
-        additional_actions = [reset_action,
-                              MENU_SEPARATOR,
+        additional_actions = [MENU_SEPARATOR,
                               env_action,
                               syspath_action,
                               self.show_time_action]
 
         if self.menu_actions is not None:
-            return self.menu_actions + additional_actions
+            console_menu = self.menu_actions + additional_actions
+            return console_menu
+
         else:
             return additional_actions
 


Test patch: