Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Allow plugins to hook into file actions #22564

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

jitseniesen
Copy link
Member

@jitseniesen jitseniesen commented Sep 24, 2024

Description of Changes

In short, this PR allows plugins to hook into Spyder's new / open / save / close file operations. I have the Notebook plugin in mind for this but the functionality is more widely useful. There are only a few user-visible changes, unless the user installs a plugin that uses the new functionality. Thanks to PR #8798 for a first iteration on this idea.

Changes in more detail (there are videos at the end):

  • The PR moves the actions associated to the following items in the File menu from the Editor plugin to the Application plugin, because the actions are no longer specific to the Editor plugin:
    • New file (also a toolbar button)
    • Open (also a toolbar button)
    • Open last closed
    • Open recent submenu
    • Save (also a toolbar button)
    • Save all (also a toolbar button)
    • Save as
    • Save copy as
    • Revert
    • Close
    • Close all
  • As a consequence of this move, shortcut associated with these actions (e.g., Ctrl-N for "new file") move from the "editor" section to the "main" menu. This is one user-visible change.
  • The plugin API gets a new attribute named FILE_EXTENSIONS in which plugins can list file extensions that they can open. If the user opens a file, then Spyder will call the open_file function in the plugin that has the extension of the opened file listed in FILE_EXTENSIONS. If no such plugin is found, then the file is opened in the editor, so we default to the existing behaviour. This works for files opened with the File > Open menu item, the "Open" toolbar button, the File Explorer, the Project Explorer, or from the command line.
  • The Variable Explorer lists all binary file extensions that it can import in FILE_EXTENSIONS. This is another user-visible change: if a binary data file is opened, then it will be imported in the Variable Explorer. The existing behaviour is that they are opened in the editor if opened using File > Open, but imported in the Variable Explorer as given as a command-line argument.
  • The plugin API gets a new attribute named CAN_HANDLE_FILE_ACTIONS which defaults to False.
  • If the plugin with focus has this attribute set to True, then the file actions listed in the first bullet point (except for Open) will call the associated functions in that plugin; otherwise, functions in the Editor plugin will be called. For example, if the user clicks on New file and the plugin with focus has CAN_HANDLE_FILE_ACTIONS set to True, then the create_new_file function in the plugin will be called.
  • The main window now emits the sig_focused_plugin_changed to all plugins if the plugin with focus has changed. The Application plugin uses this signal to keep track of the plugin that has focus, so that file actions can be routed to the appropriate plugin.
  • Plugins can enable and disable file actions for themselves using the new enable_file_action function in the Application plugin. File actions in the Spyder IDE are enabled or disabled depending on whether the plugin that would handle the file action has enabled the action.

Below are some videos to show what this all looks like. Firstly, opening .npy files now imports the data in the Variable Explorer. No variables are defined at the beginning, but after the file is opened, the variable eivecs appears.

open-data.mp4

The other videos require the plugin-open branch in spyder-notebook, which still needs some polishing.

Creating a new file in the Editor works as before. However, if the notebook plugin has focus, then creating a new file opens a new notebook in the notebook plugin.

new-file.mp4

At the beginning, the File > Open recent submenu has no files in it. We then use the File Explorer to open a notebook, File > Close to close it, File > Open last closed to open it again, File > Close to close it again, and the File > Open recent submenu to open it a third time.

open-notebook.mp4

The File > Save as and File > Save copy as actions in the Notebook pane:

save-notebook-as.mp4

Finally, a video to illustrate enabling and disabling file actions. The Editor plugin uses enable_file_action to enable the Save and Save all actions when there is something to save and disable them if there is nothing to save. The Notebook plugin uses enable_file_action at startup to disable the Revert action because it is not implemented. The video shows what this looks like for the user. The Editor plugin has nothing to save in the beginning, so Save and Save all are disabled and Revert is enabled (look at the File menu and the "Save" and "Save all" buttons in the toolbar). Then we give focus to the Notebook plugin has focus; now Save and Save all are enabled and Revert is disabled. Then we give focus to the File Explorer plugin. This plugin has CAN_HANDLE_FILE_ACTIONS set to False so file actions are routed to the Editor and thus Save and Save all are disabled and Revert is enabled. After typing something in the editor, the Save and Save all actions are also enabled in the editor.

enable-actions.mp4

Issue(s) Resolved

Fixes #22354
Fixes #7794

Affirmation

By submitting this Pull Request or typing my (user)name below,
I affirm the Developer Certificate of Origin
with respect to all commits and content included in this PR,
and understand I am releasing the same under Spyder's MIT (Expat) license.

I certify the above statement is true and correct:
Jitse Niesen

@pep8speaks
Copy link

pep8speaks commented Sep 24, 2024

Hello @jitseniesen! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

Line 991:1: W293 blank line contains whitespace

Line 16:80: E501 line too long (96 > 79 characters)

Comment last updated at 2024-12-31 18:06:38 UTC

@jitseniesen jitseniesen added this to the v6.1.0 milestone Sep 24, 2024
@jitseniesen
Copy link
Member Author

@spyder-ide/core-developers I took the same approach as the previous PR by Carlos and I moved the code for File > Open from the Editor plugin to mainwindow.py. However, that file is already a big pile of unrelated stuff, so perhaps another location like one of the plugins would be better? If so, where? From looking at the names, the mainmenu or maybe the application plugin?

@ccordoba12
Copy link
Member

Thanks for working on this @jitseniesen! I opened PR #22576 as a prerequisite of your work because we need to remove some dead code that referenced the open, new, save actions in the Editor.

So, please rebase after that PR is merged.

@jitseniesen
Copy link
Member Author

So, please rebase after that PR is merged.

Thanks, done!

@jitseniesen
Copy link
Member Author

As discussed in the last Core Team meeting:

  • This PR will cover all the file actions: new, open, save, close.
  • Code goes in the Applications plugin.
  • The "new file" action opens a new file in the currently active pane, if possible. For example, if the user is working in the Notebook pane then File > New will open a new notebook.
  • These file actions only make sense for dockable plugins.
  • The Variable Explorer can use the "open file" action to open data files.

@zuckerruebe
Copy link

zuckerruebe commented Nov 11, 2024

Hi @jitseniesen. I see you're actively working on this feature at https://github.com/jitseniesen/spyder/tree/plugin-open-tmp if I'm not mistaken. Exciting! Is there a spyder-notebook branch that you're developing along with your work in plugin-open-tmp? I'd be interested in that one as a starting example for our plugin which will also be based on the functionality in plugin-open-tmp. Thanks!

EDIT: I think I've found it: https://github.com/jitseniesen/spyder-notebook/tree/plugin-open. Never mind.

It is not clear why one was subtracted from the index, but it seems
wrong: `list.insert(index, item)` inserts `item` before the item that
is currently in position `index`, which is what we want here.

Also add a regression test for the bug fix.
This is consistent with how actions are defined in other plugins.
It also prevents a circular import in the next commit.
It is easier to use get_action() to get a reference to the action
when needed. This also simplifies the tests.
This is a refactoring to simplify the code, in preperation for the
next commit in which we also need to trigger application actions
in the editor.
We want to generalize this action (and other actions in the File menu)
so that it can also be used to create new files in other panes, like the
Notebook pane. This commit is preparation and it only moves the code;
upcoming commit will allow other plugins to hook into the "New file" action.

This also moves the shortcut. The Application plugin owns the shortcut, but
does not expose it to the user. Plugins like the Editor plugin that want to
expose the shortcut need to do that themselves. This is because making the
"New file" and similar file shortcuts global would be confusing,
especially since Ctrl-W for "Close file" is already used in other plugins.

Also add a test for the action in the Application plugin.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.

Call the action in the Application plugin in the following situations:
* Double clicking a file in the File Explorer plugin,
* Double clicking a file in the Projects plugin,
* Selecting a file from the current project in the switcher,
so that the file may be opened in a different plugin than the Editor.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This allows other plugins to add files to the list of recent files,
which can be opened with the "File > Open recent" submenu.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
This is preparation for an upcoming commit which will allow other
plugins to hook into the action.
In the main window, keep track of the plugin that has keyboard
focus and emit sig_focused_plugin_changed to all plugin when
this changes.

Use this signal in the Application plugin to also keep track
of this information. Add a test for this.

This will be used in the following commit to dispatch file
operations to the appropriate plugin.
Define an attribute `CAN_HANDLE_FILE_ACTIONS` in plugins, set to False
by default. If it is set to True, then functions in the plugin are
called when the user activates file actions.

Specifically, the following actions are mapped to these functions:
* "New file" action calls create_new_file()
* "Open last closed" action calls open_last_closed_file()
* "Save file" action calls save_file()
* "Save all" action calls save_all()
* "Save as" action calls save_file_as()
* "Save copy as" action calls save_copy_as()
* "Revert file" action calls revert_file()
* "Close file" action calls close_file()
* "Close all" action calls close_all()

Also add tests for all these.
The Application plugin is in charge of whether file actions are
enabled. Other plugins use enable_file_action() to indicate whether
the action is enabled for them. The Application plugin stores this
information and uses it to enable or disable file actions when focus
switches to another plugin.

Add a test for this.
Any plugin can declare file extensions that it can open in the
FILE_EXTENSIONS attribute. If the user opens a file with one of
these extensions, then the open_file method in the plugin is
called. If no plugin is found then the file is opened in the
Editor plugin.

Extend the open_file test to cover this.
When displaying the Open File dialog, the name of the currently displayed
file is used to initialize the dialog. This commit asks the currently
focussed plugin first to provide the current filename, and if that fails,
the current filename of the Editor is used. Also add a test.
If the user opens a binary data file with the Open File action,
for example a .spydata file, then import the data in the Variable
Explorer.
This removes the function open_file from mainwindow.py, which
was only used in one place.
@jitseniesen jitseniesen changed the title PR: Allow plugins to hook into File > Open PR: Allow plugins to hook into file actions Feb 27, 2025
@jitseniesen jitseniesen marked this pull request as ready for review February 27, 2025 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide custom editor widget for given file extension via plugin Allow plugins to hook into File > Open
4 participants