Repository: spyder-ide/spyder
Pull request: PR: Add a "Show in external file browser" shortcut (Editor)
I finally spent the time to (somewhat) figure out how to use git... (a certain john smith accurately [sums up](https://stackoverflow.com/questions/25101983/can-i-revert-commits-directly-on-github#comment73500278_25102008) my feelings on using git as a full time engineer, and occasional programmer)

I also realized I somehow have two accounts on github, so please ignore the other one -_-

### Issue(s) Resolved

<!--- List the issue(s) below, in the form "Fixes #1234"; one per line --->

Fixes  #13158


### Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the [Developer Certificate of Origin](https://developercertificate.org)
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

<!--- TYPE YOUR USER/NAME AFTER THE FOLLOWING: --->
I certify the above statement is true and correct: athompson673

<!--- Thanks for your help making Spyder better for everyone! --->


Problem statement:
Feature request: keyboard shortcut for "Show in external file explorer"
**Feature request: keyboard shortcut for "Show in external file explorer"**

I use this feature quite often, and as a fan of keyboard shortcuts it would be very useful. I am using the "win + E" to open explorer all the time on windows, and I believe using something like "Ctrl + E" makes some amount of sense to open an explorer window to that file. I think it would also be totally fine to leave it with no pre-bound shortcut, and leave it to the user to decide to bind one.

I tried adding the shortcut myself, but I have not been successful in figuring out how to add new shortcuts, so I have no pull request to submit. I got so far as to get a listing in the settings menu, but nothing happened when the key combination was pressed. I did this by adding an entry to `config.main.DEFAULTS`:

```
DEFAULTS = [
    ...
    ("shortcuts", 
        {
         ...
         'editor/show in external file explorer': "Ctrl+E",
```

 and `plugins.editor.widgets.editor.EditorStack.create_shortcuts` 

```
 external_fileexp = CONF.config_shortcut(
     self.show_in_external_file_explorer,
     context="Editor",
     name="Show in external file explorer",
     parent=self)
```

* Spyder version: 4.1.3
* Python version: 3.8.2
* Qt version: 5.12.8
* PyQt version: 5.12.3
* Operating System name/version: Windows 10

Hints from issue discussion:
Hi @athompson6735 thanks for the suggestion and trying to implement it! Indeed it could be nice to have a shortcut for that action. Maybe if you want to contribute we could guide you into how that could be done :) what do you guys think @spyder-ide/core-developers ?

I agree that it'd be really nice to have. Thanks @athompson6735 for offering your help with that!

Since you already found out how to add that shortcut, my only recommendation is for you to read our [Contributing guide](https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md), so you understand how to create a successful pull request.

By the way, since this feature is not a major addition to Spyder, you can do it against our `4.x` branch, so it's available in our next version (4.2.0 at this point, because 4.1.4 is about to be released).
At least on my Linux system, Ctrl+E moves the cursor to the end of line. It's not a problem for me, I don't think I use that short cut, but if there are users that do use that short cut it might be annoying for them to change it.
I do use Win-E myself when I have to use Windows Explorer (I normally use MultiCommander as my file manager), but if a shortcut is added by default, IMO should be something a little less likely to conflict with others, e.g. adding additional modifier keys.

Although, per @jitseniesen 's concern, if the shorcut is set to belong to the File Explorer pane, wouldn't it only apply there and not override Ctrl-E other places?
@CAM-Gerlach my intent was actually for the context to be the editor. I tend to use native windows explorer rather than the built in file browser and I think those using this feature would likely be doing the same. Would it be acceptable to simply let the default be unbound? I certainly don't want to stomp on any other default or common shortcuts.

I have the shortcut working now, but I cannot figure out how to add the shortcut hint to the context menu. Can anyone point me in the right direction? **EDIT** solved this one.
![Untitled](https://user-images.githubusercontent.com/12206499/86389646-9e7d8880-bc64-11ea-9c11-30112b7c3820.png)

I am also having the problem that after setting a binding and re-opening preferences, the shortcuts menu shows the binding as \<None>. The shortcut still works, and continues to do so after re-starting spyder, but the preferences menu suggests it is unbound.

Ah, I see @athompson6735 — sorry for the misunderstanding. I actually do the same thing, just with MultiCommander instead of the stock Windows Explorer. I assume you're using something like `openURL` from QDesktopServices to do so in a cross-platform way that will respect the user's default file explorer preferences. I don't think macOS has a way to configure this and while you technically can on Windows, it doesn't work in 100% of contexts but I can verify that it does work here.

> I have the shortcut working now, but I cannot figure out how to add the shortcut hint to the context menu.

Were you not able to get it working with self.register_shortcut(fileexplorer_action, context="Editor", name="Show in external file explorer") after creating your action `fileexplorer_action`, just like is done for `gotoline_action` right above it in `spyder/plugins/editor/plugin.py`?
I think that this sounds like a short cut that would be best left unset be default. At least it can't be ctrl-e. Going to the end of the line in the editor will likely be used more than this. I know there is already an 'end' key but it usually quite out of the way.
@CAM-Gerlach I figured out the shortcut hint. I had to modify the existing definition for the action in `EditorStack.__init__` and add the `shortcut` and `context` keywords. I also found I had to register the callback with the plugin.
```
external_fileexp_action = create_action(self, text, #XXX here
                        triggered=self.show_in_external_file_explorer,
                        shortcut=CONF.get_shortcut(context="Editor",
                                name="show in external file explorer"),
                        context=Qt.WidgetShortcut)
        
parent.plugin.register_shortcut(external_fileexp_action, 
                                context="Editor",
                                name="show in external file explorer", 
                                add_shortcut_to_tip=True)
```
I'm still tracking down a number of other issues, and I'm running out of ideas. The two big problems I can find are solved by not attempting to set the default to nothing.

Here are the issues:
1.  - There has to be an entry for `'editor/show in external file explorer'` in the DEFAULTS or spyder won't start with a `configparser` error. 
     - A default value of `''` in `config.main.DEFAULTS` will initialize the command to be un-set, but attempting to restore from default will throw an error. 
     - Similarly if `None` is used, the command is un-set, but then if attempting to "restore from default" an `AttributeError` is thrown. 
     - I can't find any other definitions that have no default binding, so I wonder if it's even possible to do so? It seems like "Ctrl+E" is no good for some people.

![Untitled](https://user-images.githubusercontent.com/12206499/86402981-cc6dc780-bc7a-11ea-9bed-6ffc164c3cf5.png)

2. The entry in the settings menu always shows blank whether a shortcut is set or not. Setting a new shortcut will cause it to display in settings only until the window is closed. This is solved by setting the default to a valid key combination.
![Untitled](https://user-images.githubusercontent.com/12206499/86405606-a565c480-bc7f-11ea-85da-1bdfc129b989.png)


3. Clearing a set shortcut does not take effect until after a restart.



oops, wrong button 😐
Wow, thanks for all your work and your thorough description of the issues you've faced. Seems like all of them trace back to having a default shortcut as unset.. .@ccordoba12 is that even possible with our current config architecture? If not, we could just set it to something suitably esoteric I guess.

> oops, wrong button

Haha, happens to all of us ^_^

Commit message: follow pep8 for changes made

Patch to review:
diff --git a/spyder/config/main.py b/spyder/config/main.py
index a36456a4205..100d9c294ad 100644
--- a/spyder/config/main.py
+++ b/spyder/config/main.py
@@ -446,6 +446,7 @@
               'editor/split horizontally': "Ctrl+_",
               'editor/close split panel': "Alt+Shift+W",
               'editor/docstring': "Ctrl+Alt+D",
+              'editor/show in external file explorer': '',
               # -- In Breakpoints
               '_/switch to breakpoints': "Ctrl+Shift+B",
               # ---- Consoles (in widgets/shell) ----
diff --git a/spyder/plugins/editor/widgets/editor.py b/spyder/plugins/editor/widgets/editor.py
index 5aa1aaa33ea..6d0ef07dea6 100644
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -515,8 +515,19 @@ def __init__(self, parent, actions):
            text=_("Show in Finder")
         else:
            text= _("Show in external file explorer")
-        external_fileexp_action = create_action(self, text,
-                                triggered=self.show_in_external_file_explorer)
+        external_fileexp_action = create_action(
+            self, text,
+            triggered=self.show_in_external_file_explorer,
+            shortcut=CONF.get_shortcut(context="Editor",
+                                       name="show in external file explorer"),
+            context=Qt.WidgetShortcut)
+
+        if self.parent() is not None:
+            plugin = self.parent().plugin
+            plugin.register_shortcut(external_fileexp_action,
+                                     context="Editor",
+                                     name="show in external file explorer",
+                                     add_shortcut_to_tip=True)
 
         self.menu_actions = actions + [external_fileexp_action,
                                        None, switcher_action,
@@ -836,6 +847,12 @@ def create_shortcuts(self):
             name="close split panel",
             parent=self)
 
+        external_fileexp = CONF.config_shortcut(
+            self.show_in_external_file_explorer,
+            context="Editor",
+            name="show in external file explorer",
+            parent=self)
+
         # Return configurable ones
         return [inspect, set_breakpoint, set_cond_breakpoint, gotoline, tab,
                 tabshift, run_selection, new_file, open_file, save_file,
@@ -846,7 +863,7 @@ def create_shortcuts(self):
                 go_to_next_cell, go_to_previous_cell, re_run_last_cell,
                 prev_warning, next_warning, split_vertically,
                 split_horizontally, close_split,
-                prevtab, nexttab]
+                prevtab, nexttab, external_fileexp]
 
     def get_shortcut_data(self):
         """
@@ -3559,6 +3576,11 @@ def file_renamed_in_data_in_editorstack(self, editorstack_id_str,
             if str(id(editorstack)) != editorstack_id_str:
                 editorstack.rename_in_data(original_filename, filename)
 
+    def register_shortcut(self, qaction_or_qshortcut, context, name,
+                          add_shortcut_to_tip=False):
+        """Fake"""
+        pass
+
     def register_widget_shortcuts(self, widget):
         """Fake!"""
         pass
diff --git a/spyder/preferences/shortcuts.py b/spyder/preferences/shortcuts.py
index e830e602805..5b92b39f0fb 100644
--- a/spyder/preferences/shortcuts.py
+++ b/spyder/preferences/shortcuts.py
@@ -434,8 +434,11 @@ def set_sequence_to_default(self):
         """Set the new sequence to the default value defined in the config."""
         sequence = CONF.get_default(
             'shortcuts', "{}/{}".format(self.context, self.name))
-        self._qsequences = sequence.split(', ')
-        self.update_warning()
+        if sequence:
+            self._qsequences = sequence.split(', ')
+            self.update_warning()
+        else:
+            self.unbind_shortcut()
 
     def back_new_sequence(self):
         """Remove the last subsequence from the sequence compound."""


Code review comments:
diff_hunk: @@ -515,8 +515,19 @@ def __init__(self, parent, actions):
            text=_("Show in Finder")
         else:
            text= _("Show in external file explorer")
-        external_fileexp_action = create_action(self, text,
-                                triggered=self.show_in_external_file_explorer)
+        external_fileexp_action = create_action(
+            self, text,
+            triggered=self.show_in_external_file_explorer,
+            shortcut=CONF.get_shortcut(context="Editor",
+                                       name="show in external file explorer"),
+            context=Qt.WidgetShortcut)
+
+        if self.parent() is not None:
+            plugin = self.parent().plugin
+            plugin.register_shortcut(external_fileexp_action,
+                                     context="Editor",
+                                     name="show in external file explorer",
+                                     add_shortcut_to_tip=True)
path: spyder/plugins/editor/widgets/editor.py
text: @user1:
This is not the right way to add a new shortcut. You should register it instead in the `create_shortcuts` method below.

Then the problems you mentioned will simply go away.

@author:
I must have left that part in assuming it was still needed from my testing. I already actually had an entry in `create_shortcuts` in fact.

@user1:
Great!