Repository: spyder-ide/spyder

Issue:
'Show in external file explorer' on editor menu more than once for split panels
When in the editor, every time the panel is split, the gears menu adds an additional option for 'Show in external file explorer'.

**What steps will reproduce the problem?**

1.  Open the editor.
2.  Click on the gears and see 'Show in external file explorer' is listed once.
3.  Split the panel.
4.  Check the gears on the new panel.  'Show in external file explorer' is now listed twice.
5.  Rinse and repeat.  It's added one more time for each split.

**What is the expected output? What do you see instead?**
I expect the menu option to only be listed once.


**Please provide any additional information below**
I believe this was introduced with PR #4337.  In the EditorStack class in widgets editor.py, the incoming argument of actions is appened to(`actions.append(external_fileexp_action)`).  Since this is a list, the append causes it to be changed in the calling routine (EditorSplitter class).  When a new EditorStack is created, the new list with the appended action is sent instead of the original list being sent.

## Versions and main components

* Spyder Version:  3.x and 4.0
* Python Version:  3.6.3
* Qt Version:
* PyQt Version:
* Operating system:  Ubuntu 16.04


## Dependencies

Please go to the menu entry `Help > Optional Dependencies` (or
`Help > Dependencies`), press the button `Copy to clipboard`
and paste the contents below:



Discussion:

Resolving patch:
diff --git a/spyder/widgets/editor.py b/spyder/widgets/editor.py
--- a/spyder/widgets/editor.py
+++ b/spyder/widgets/editor.py
@@ -516,10 +516,9 @@ def __init__(self, parent, actions):
            text= _("Show in external file explorer")
         external_fileexp_action = create_action(self, text,
                                 triggered=self.show_in_external_file_explorer)
-                
-        actions.append(external_fileexp_action)
-        
-        self.menu_actions = actions + [None, fileswitcher_action,
+
+        self.menu_actions = actions + [external_fileexp_action,
+                                       None, fileswitcher_action,
                                        symbolfinder_action,
                                        copy_to_cb_action, None, close_right,
                                        close_all_but_this]


Test patch:
diff --git a/spyder/widgets/tests/test_editor.py b/spyder/widgets/tests/test_editor.py
--- a/spyder/widgets/tests/test_editor.py
+++ b/spyder/widgets/tests/test_editor.py
@@ -21,7 +21,7 @@
 
 # Local imports
 from spyder.utils.fixtures import setup_editor
-from spyder.widgets.editor import EditorStack
+from spyder.widgets.editor import EditorStack, EditorSplitter
 from spyder.widgets.findreplace import FindReplace
 from spyder.py3compat import PY2
 
@@ -82,6 +82,16 @@ def editor_cells_bot(base_editor_bot):
     return editor_stack, finfo.editor, qtbot
 
 
+@pytest.fixture
+def editor_splitter_bot(qtbot):
+    """Create editor splitter."""
+    editor_splitter = EditorSplitter(None, Mock(), [], first=True)
+    qtbot.addWidget(editor_splitter)
+    editor_splitter.show()
+    yield editor_splitter
+    editor_splitter.destroy()
+
+
 # Tests
 #-------------------------------
 def test_find_number_matches(qtbot):
@@ -395,5 +405,30 @@ def test_get_current_word(base_editor_bot):
     assert editor.get_current_word() == 'valid_python_word'
 
 
+def test_editor_splitter_init(editor_splitter_bot):
+    """"Test EditorSplitter.__init__."""
+    es = editor_splitter_bot
+    assert es.orientation() == Qt.Horizontal
+    assert es.testAttribute(Qt.WA_DeleteOnClose)
+    assert not es.childrenCollapsible()
+    assert not es.toolbar_list
+    assert not es.menu_list
+    assert es.register_editorstack_cb == es.plugin.register_editorstack
+    assert es.unregister_editorstack_cb == es.plugin.unregister_editorstack
+
+    # No menu actions in parameter call.
+    assert not es.menu_actions
+    # EditorStack adds its own menu actions to the existing actions.
+    assert es.editorstack.menu_actions
+
+    assert isinstance(es.editorstack, EditorStack)
+    es.plugin.register_editorstack.assert_called_with(es.editorstack)
+    es.plugin.unregister_editorstack.assert_not_called()
+    es.plugin.clone_editorstack.assert_not_called()
+
+    assert es.count() == 1
+    assert es.widget(0) == es.editorstack
+
+
 if __name__ == "__main__":
     pytest.main()