Repository: spyder-ide/spyder

Issue:
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




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 ^_^
I'm looking at `spyder\preferences\shortcuts.py ShortcutEditor.set_sequence_to_default` and think adding a simple test to check if the default is an empty string should suffice. That makes the most sense to me so that all entries are strings. If this solves it, I would like to point out that I'm no good at using git, and would appreciate if someone more familiar with it could just do a bit of copy paste for me.
I think it's pretty much working. There is now a new capability to add default keybinds as an empty string to make them not set by default, as well as a new keybind for "Show in external file explorer". I wouldn't expect any of these edits to have side effects, but a second set of eyes always helps. I did notice that any keybind when cleared isn't actually cleared until after spyder is restarted. I didn't immediately find out how to fix that, and it doesn't bother me that much. Maybe another time...


Because I don't know how to use git.... here's the edits I made.

replace [this](https://github.com/spyder-ide/spyder/blob/4.x/spyder/plugins/editor/widgets/editor.py#L518-L519) with:
```
        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)
```

add this [here](https://github.com/spyder-ide/spyder/blob/4.x/spyder/plugins/editor/widgets/editor.py#L838)

```
        external_fileexp = CONF.config_shortcut( #XXX here
            self.show_in_external_file_explorer,
            context="Editor",
            name="show in external file explorer",
            parent=self)
```
and be sure to add it to the function return [here](https://github.com/spyder-ide/spyder/blob/4.x/spyder/plugins/editor/widgets/editor.py#L849)
```
                prevtab, nexttab, external_fileexp]
```

Now we add the capability for default unset shortcut with an empty string:

replace [this](https://github.com/spyder-ide/spyder/blob/4.x/spyder/preferences/shortcuts.py#L437-L438) with
```
        if sequence:
            self._qsequences = sequence.split(', ')
            self.update_warning()
        else:
            self.unbind_shortcut()
```
and finally add an entry to the default config [around here](https://github.com/spyder-ide/spyder/blob/4.x/spyder/config/main.py#L448)
```
              'editor/show in external file explorer': '',
```


> 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.

There shouldn't be a problem with unset shortcuts, but I'm not really sure.
@ccordoba12 the problem with unset shortcuts is only when it's the default. Assuming an empty string represents a default of unset, `ShortcutEditor.set_sequence_to_default` will set `self.new_qsequence = QKeySequence('')`. Then when calling `ShortcutEditor.check_conflicts`, it will match every single other shortcut bound or not. This will then display a list of conflicts a mile long which breaks the gui element (dialog buttons are pushed off screen with no way to get at them). This could be solved by handling the case where `self.new_qsequence.isEmpty()` in `check_conflicts` or by handling an empty default in `set_sequence_to_default` . I chose the latter, which actually makes it work with any false-like default not just an empty string. 

(all referring to "spyder/preferences/shortcuts.py")
> I chose the latter, which actually makes it work with any false-like default not just an empty string.

Sounds good, Thanks for your help with this @athompson6735!

Resolving patch:
diff --git a/spyder/config/main.py b/spyder/config/main.py
--- 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
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -515,8 +515,12 @@ 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)
 
         self.menu_actions = actions + [external_fileexp_action,
                                        None, switcher_action,
@@ -836,6 +840,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 +856,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):
         """
diff --git a/spyder/preferences/shortcuts.py b/spyder/preferences/shortcuts.py
--- 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."""


Test patch: