From 0ffeb1eec8db1393bea4008553f1065b8f4164f7 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Thu, 4 Jun 2020 19:27:29 +0100 Subject: [PATCH 01/10] Widgets: Return client instead of filename in .create_new_client() --- spyder_notebook/notebookplugin.py | 3 ++- spyder_notebook/widgets/notebooktabwidget.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 91fc8710..27fd1b75 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -247,7 +247,8 @@ def create_new_client(self, filename=None): self.set_option('main/spyder_pythonpath', self.main.get_spyder_pythonpath()) - filename = self.tabwidget.create_new_client(filename) + client = self.tabwidget.create_new_client(filename) + filename = client.filename if NOTEBOOK_TMPDIR not in filename: self.add_to_recent(filename) self.setup_menu_actions() diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index f3446561..cc2a4d46 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -116,8 +116,8 @@ def create_new_client(self, filename=None): Returns ------- - filename : str or None - File name of notebook that is opened, or None if unsuccessful. + client : NotebookClient or None + Notebook client that is opened, or None if unsuccessful. """ # Generate the notebook name (in case of a new one) if not filename: @@ -147,7 +147,7 @@ def create_new_client(self, filename=None): # See issue 93 self.untitled_num -= 1 self.maybe_create_welcome_client() - return + return None welcome_client = self.maybe_create_welcome_client() client = NotebookClient(self, filename, self.actions) @@ -156,7 +156,7 @@ def create_new_client(self, filename=None): client.load_notebook() if welcome_client: self.setCurrentIndex(0) - return filename + return client def maybe_create_welcome_client(self): """ From 4645002bfc59a83f1789bbf8bd5a95d23d7ef77d Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Thu, 4 Jun 2020 21:23:56 +0100 Subject: [PATCH 02/10] Widgets: Add default args to constructor of NotebookTabWidget --- spyder_notebook/widgets/example_app.py | 2 +- spyder_notebook/widgets/notebooktabwidget.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder_notebook/widgets/example_app.py b/spyder_notebook/widgets/example_app.py index 195d0209..e3717fc9 100755 --- a/spyder_notebook/widgets/example_app.py +++ b/spyder_notebook/widgets/example_app.py @@ -40,7 +40,7 @@ class NotebookAppMainWindow(QMainWindow): def __init__(self): super().__init__() - self.tabwidget = NotebookTabWidget(self, None, None, None) + self.tabwidget = NotebookTabWidget(self) self.tabwidget.maybe_create_welcome_client() self.setCentralWidget(self.tabwidget) self._setup_menu() diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index cc2a4d46..ec0d5fea 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -54,7 +54,7 @@ class NotebookTabWidget(Tabs): Number used in file name of newly created notebooks. """ - def __init__(self, parent, actions, menu, corner_widgets): + def __init__(self, parent, actions=None, menu=None, corner_widgets=None): """ Constructor. From ce05db67b7b28d8219c10790371058f98082f3cb Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Fri, 5 Jun 2020 09:37:04 +0100 Subject: [PATCH 03/10] Widgets: Don't possibly create welcome tab in .create_new_client() Instead, create the welcome tab in the plugin at startup. This makes for a cleaner API. --- spyder_notebook/notebookplugin.py | 2 ++ spyder_notebook/tests/test_plugin.py | 4 +--- spyder_notebook/widgets/notebooktabwidget.py | 6 ------ 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 27fd1b75..862bf456 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -160,7 +160,9 @@ def register_plugin(self): super().register_plugin() self.focus_changed.connect(self.main.plugin_focus_changed) self.ipyconsole = self.main.ipyconsole + self.tabwidget.maybe_create_welcome_client() self.create_new_client() + self.tabwidget.setCurrentIndex(0) # bring welcome tab to top # Connect to switcher self.switcher = self.main.switcher diff --git a/spyder_notebook/tests/test_plugin.py b/spyder_notebook/tests/test_plugin.py index 7bb87f02..2e0e6bc5 100644 --- a/spyder_notebook/tests/test_plugin.py +++ b/spyder_notebook/tests/test_plugin.py @@ -12,7 +12,6 @@ import os.path as osp import shutil import sys -import tempfile # Third-party library imports from flaky import flaky @@ -21,7 +20,6 @@ from qtpy.QtWebEngineWidgets import WEBENGINE from qtpy.QtCore import Qt, QTimer from qtpy.QtWidgets import QFileDialog, QApplication, QLineEdit -from spyder.config.base import get_home_dir # Local imports from spyder_notebook.notebookplugin import NotebookPlugin @@ -98,8 +96,8 @@ def notebook(qtbot): notebook. The latter tab is selected.""" notebook_plugin = NotebookPlugin(None, testing=True) qtbot.addWidget(notebook_plugin) + notebook_plugin.tabwidget.maybe_create_welcome_client() notebook_plugin.create_new_client() - notebook_plugin.tabwidget.setCurrentIndex(1) return notebook_plugin diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index ec0d5fea..08f9f9a4 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -105,9 +105,6 @@ def create_new_client(self, filename=None): """ Create a new notebook or load a pre-existing one. - This function also creates and selects a welcome tab, if no tabs are - present. - Parameters ---------- filename : str, optional @@ -149,13 +146,10 @@ def create_new_client(self, filename=None): self.maybe_create_welcome_client() return None - welcome_client = self.maybe_create_welcome_client() client = NotebookClient(self, filename, self.actions) self.add_tab(client) client.register(server_info) client.load_notebook() - if welcome_client: - self.setCurrentIndex(0) return client def maybe_create_welcome_client(self): From da9b7233de4cc3d0a154318686bc2f2a7194b3d2 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Fri, 5 Jun 2020 09:55:33 +0100 Subject: [PATCH 04/10] Widgets: Add function identifying newly created clients Add a convenience function NotebookTabWidget.is_newly_created(). Use this function in the code. Add tests. --- spyder_notebook/notebookplugin.py | 5 +-- spyder_notebook/widgets/notebooktabwidget.py | 30 ++++++++++--- .../widgets/tests/test_notebooktabwidget.py | 44 +++++++++++++++++++ 3 files changed, 69 insertions(+), 10 deletions(-) create mode 100755 spyder_notebook/widgets/tests/test_notebooktabwidget.py diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 862bf456..c33346a7 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -19,7 +19,6 @@ from spyder.api.plugins import SpyderPluginWidget from spyder.config.base import _ from spyder.utils import icon_manager as ima -from spyder.utils.programs import get_temp_dir from spyder.utils.qthelpers import (create_action, create_toolbutton, add_actions, MENU_SEPARATOR) from spyder.utils.switcher import shorten_paths @@ -29,7 +28,6 @@ from spyder_notebook.widgets.notebooktabwidget import NotebookTabWidget -NOTEBOOK_TMPDIR = osp.join(get_temp_dir(), 'notebooks') FILTER_TITLE = _("Jupyter notebooks") FILES_FILTER = "{} (*.ipynb)".format(FILTER_TITLE) PACKAGE_PATH = osp.dirname(__file__) @@ -250,8 +248,7 @@ def create_new_client(self, filename=None): self.main.get_spyder_pythonpath()) client = self.tabwidget.create_new_client(filename) - filename = client.filename - if NOTEBOOK_TMPDIR not in filename: + if not self.tabwidget.is_newly_created(client): self.add_to_recent(filename) self.setup_menu_actions() diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index 08f9f9a4..f73b2f86 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -224,14 +224,11 @@ def save_notebook(self, client): Client of notebook to be saved. """ client.save() - - # Check filename to find out whether notebook is newly created - path = client.get_filename() - dirname, basename = osp.split(path) - if dirname != NOTEBOOK_TMPDIR or not basename.startswith('untitled'): + if not self.is_newly_created(client): return # Read file to see whether notebook is empty + path = client.get_filename() wait_save = QEventLoop() QTimer.singleShot(1000, wait_save.quit) wait_save.exec_() @@ -243,7 +240,7 @@ def save_notebook(self, client): # Ask user to save notebook with new filename buttons = QMessageBox.Yes | QMessageBox.No text = _("{0} has been modified.
" - "Do you want to save changes?").format(basename) + "Do you want to save changes?").format(osp.basename(path)) answer = QMessageBox.question( self, _('Save changes'), text, buttons) if answer == QMessageBox.Yes: @@ -297,6 +294,27 @@ def save_as(self, name=None, reopen_after_save=True): self.close_client(save_before_close=False) self.create_new_client(filename=filename) + @staticmethod + def is_newly_created(client): + """ + Return whether client has a newly created notebook. + + This only looks at the file name of the notebook. If it has the form + of file names of newly created notebooks, the function returns True. + + Parameters + ---------- + client : NotebookClient + Client under consideration. + + Returns + ------- + True if notebook is newly created, False otherwise. + """ + path = client.get_filename() + dirname, basename = osp.split(path) + return dirname == NOTEBOOK_TMPDIR and basename.startswith('untitled') + def add_tab(self, widget): """ Add tab containing some notebook widget to the tabbed widget. diff --git a/spyder_notebook/widgets/tests/test_notebooktabwidget.py b/spyder_notebook/widgets/tests/test_notebooktabwidget.py new file mode 100755 index 00000000..db4b119e --- /dev/null +++ b/spyder_notebook/widgets/tests/test_notebooktabwidget.py @@ -0,0 +1,44 @@ +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License + +"""Tests for notebooktabwidget.py.""" + +# Standard library imports +import collections + +# Third party imports +import pytest + +# Local imports +from spyder_notebook.widgets.notebooktabwidget import NotebookTabWidget + + +@pytest.fixture +def tabwidget(mocker, qtbot): + """Create an empty NotebookTabWidget which does not start up servers.""" + mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', + return_value=collections.defaultdict(str)) + widget = NotebookTabWidget(None) + qtbot.addWidget(widget) + return widget + + +def test_is_newly_created_with_new_notebook(tabwidget): + """Test that .is_newly_created() returns True if passed a client that is + indeed newly created.""" + client = tabwidget.create_new_client() + assert tabwidget.is_newly_created(client) + + +def test_is_newly_created_with_opened_notebook(tabwidget): + """Test that .is_newly_created() returns False if passed a client that + contains a notebook opened from a file.""" + client = tabwidget.create_new_client('ham.ipynb') + assert not tabwidget.is_newly_created(client) + + +def test_is_newly_created_with_welcome_tab(tabwidget): + """Test that .is_newly_created() returns False if passed a welcome + client.""" + client = tabwidget.maybe_create_welcome_client() + assert not tabwidget.is_newly_created(client) From 086236de37f431d6c44a557d54c798bf77f2c8da Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Fri, 5 Jun 2020 10:40:05 +0100 Subject: [PATCH 05/10] Plugin: Add opened notebooks to "Open recent" menu This functionality was broken by mistake in commit 5e915fbe. --- spyder_notebook/notebookplugin.py | 5 ++++- spyder_notebook/widgets/notebooktabwidget.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index c33346a7..110e8ee9 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -260,7 +260,10 @@ def open_notebook(self, filenames=None): self.set_option('main/spyder_pythonpath', self.main.get_spyder_pythonpath()) - self.tabwidget.open_notebook(filenames) + filenames = self.tabwidget.open_notebook(filenames) + for filename in filenames: + self.add_to_recent(filename) + self.setup_menu_actions() def save_as(self): """Save current notebook to different file.""" diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index f73b2f86..2102f93f 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -93,6 +93,11 @@ def open_notebook(self, filenames=None): filenames : list of str or None, optional List of file names of notebooks to open. The default is None, meaning that the user should be asked. + + Returns + ------- + filenames : list of str + List of file names of notebooks that were opened. """ if not filenames: filenames, _selfilter = getopenfilenames( @@ -100,6 +105,7 @@ def open_notebook(self, filenames=None): if filenames: for filename in filenames: self.create_new_client(filename=filename) + return filenames def create_new_client(self, filename=None): """ From d13b90845a1ec28470e73f69d61c0afd4f367028 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Fri, 5 Jun 2020 10:45:03 +0100 Subject: [PATCH 06/10] Widgets: Add function identifying welcome clients Add a convenience function NotebookTabWidget.is_welcome_client(). Use this function in the code. Add tests. --- spyder_notebook/notebookplugin.py | 14 +++++------ spyder_notebook/widgets/notebooktabwidget.py | 23 +++++++++++++++---- .../widgets/tests/test_notebooktabwidget.py | 21 +++++++++++++++++ 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 110e8ee9..9e458bcd 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -31,7 +31,6 @@ FILTER_TITLE = _("Jupyter notebooks") FILES_FILTER = "{} (*.ipynb)".format(FILTER_TITLE) PACKAGE_PATH = osp.dirname(__file__) -WELCOME = osp.join(PACKAGE_PATH, 'utils', 'templates', 'welcome.html') class NotebookPlugin(SpyderPluginWidget): @@ -216,13 +215,12 @@ def update_notebook_actions(self): client = self.tabwidget.currentWidget() except AttributeError: # tabwidget is not yet constructed client = None - if client: - if client.get_filename() != WELCOME: - self.save_as_action.setEnabled(True) - self.open_console_action.setEnabled(True) - return - self.save_as_action.setEnabled(False) - self.open_console_action.setEnabled(False) + if client and not self.tabwidget.is_welcome_client(client): + self.save_as_action.setEnabled(True) + self.open_console_action.setEnabled(True) + else: + self.save_as_action.setEnabled(False) + self.open_console_action.setEnabled(False) def add_to_recent(self, notebook): """ diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index 2102f93f..45c6a74f 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -198,10 +198,9 @@ def close_client(self, index=None, save_before_close=True): index = self.currentIndex() client = self.widget(index) - is_welcome = client.get_filename() == WELCOME - if save_before_close and not is_welcome: - self.save_notebook(client) - if not is_welcome: + if not self.is_welcome_client(client): + if save_before_close: + self.save_notebook(client) client.shutdown_kernel() client.close() @@ -321,6 +320,22 @@ def is_newly_created(client): dirname, basename = osp.split(path) return dirname == NOTEBOOK_TMPDIR and basename.startswith('untitled') + @staticmethod + def is_welcome_client(client): + """ + Return whether some client is a newly created notebook. + + Parameters + ---------- + client : NotebookClient + Client under consideration. + + Returns + ------- + True if `client` is a welcome client, False otherwise. + """ + return client.get_filename() == WELCOME + def add_tab(self, widget): """ Add tab containing some notebook widget to the tabbed widget. diff --git a/spyder_notebook/widgets/tests/test_notebooktabwidget.py b/spyder_notebook/widgets/tests/test_notebooktabwidget.py index db4b119e..0d3f1fbf 100755 --- a/spyder_notebook/widgets/tests/test_notebooktabwidget.py +++ b/spyder_notebook/widgets/tests/test_notebooktabwidget.py @@ -42,3 +42,24 @@ def test_is_newly_created_with_welcome_tab(tabwidget): client.""" client = tabwidget.maybe_create_welcome_client() assert not tabwidget.is_newly_created(client) + + +def test_is_welcome_client_with_new_notebook(tabwidget): + """Test that .is_welcome_client() returns False if passed a client that is + indeed newly created.""" + client = tabwidget.create_new_client() + assert not tabwidget.is_welcome_client(client) + + +def test_is_welcome_client_with_opened_notebook(tabwidget): + """Test that .is_welcome_client() returns False if passed a client that + contains a notebook opened from a file.""" + client = tabwidget.create_new_client('ham.ipynb') + assert not tabwidget.is_welcome_client(client) + + +def test_is_welcome_client_with_welcome_tab(tabwidget): + """Test that .is_welcome_client() returns True if passed a welcome + client.""" + client = tabwidget.maybe_create_welcome_client() + assert tabwidget.is_welcome_client(client) From cc63acd103415e7ccd4ec56cf0480536303af5ec Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Fri, 5 Jun 2020 18:38:13 +0100 Subject: [PATCH 07/10] Widgets: Return (new) file name when closing or saving tabs --- spyder_notebook/widgets/notebooktabwidget.py | 72 ++++++++++++-------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/spyder_notebook/widgets/notebooktabwidget.py b/spyder_notebook/widgets/notebooktabwidget.py index 45c6a74f..ef2c1d0b 100755 --- a/spyder_notebook/widgets/notebooktabwidget.py +++ b/spyder_notebook/widgets/notebooktabwidget.py @@ -191,21 +191,25 @@ def close_client(self, index=None, save_before_close=True): save_before_close : bool, optional Whether to save the notebook before closing the tab. The default is True. + + Returns + ------- + The file name of the notebook, or None if no tab was closed. """ if not self.count(): - return + return None if index is None: index = self.currentIndex() client = self.widget(index) + filename = client.filename if not self.is_welcome_client(client): if save_before_close: - self.save_notebook(client) + filename = self.save_notebook(client) client.shutdown_kernel() client.close() # Delete notebook file if it is in temporary directory - filename = client.get_filename() if filename.startswith(get_temp_dir()): try: os.remove(filename) @@ -215,6 +219,7 @@ def close_client(self, index=None, save_before_close=True): # Note: notebook index may have changed after closing related widgets self.removeTab(self.indexOf(client)) self.maybe_create_welcome_client() + return filename def save_notebook(self, client): """ @@ -227,29 +232,35 @@ def save_notebook(self, client): ---------- client : NotebookClient Client of notebook to be saved. + + Returns + ------- + The file name of the notebook. """ client.save() + filename = client.filename if not self.is_newly_created(client): - return + return filename # Read file to see whether notebook is empty - path = client.get_filename() wait_save = QEventLoop() QTimer.singleShot(1000, wait_save.quit) wait_save.exec_() - nb_contents = nbformat.read(path, as_version=4) + nb_contents = nbformat.read(filename, as_version=4) if (len(nb_contents['cells']) == 0 or len(nb_contents['cells'][0]['source']) == 0): - return + return filename # Ask user to save notebook with new filename buttons = QMessageBox.Yes | QMessageBox.No text = _("{0} has been modified.
" - "Do you want to save changes?").format(osp.basename(path)) + "Do you want to save changes?").format(osp.basename(filename)) answer = QMessageBox.question( self, _('Save changes'), text, buttons) if answer == QMessageBox.Yes: - self.save_as(reopen_after_save=False) + return self.save_as(reopen_after_save=False) + else: + return filename def save_as(self, name=None, reopen_after_save=True): """ @@ -270,6 +281,10 @@ def save_as(self, name=None, reopen_after_save=True): reopen_after_save : bool, optional Whether to close the original tab and re-open it under the new file name after saving the notebook. The default is True. + + Returns + ------- + The file name of the notebook. """ current_client = self.currentWidget() current_client.save() @@ -280,24 +295,27 @@ def save_as(self, name=None, reopen_after_save=True): original_name = name filename, _selfilter = getsavefilename(self, _("Save notebook"), original_name, FILES_FILTER) - if filename: - try: - nb_contents = nbformat.read(original_path, as_version=4) - except EnvironmentError as error: - txt = (_("Error while reading {}

{}") - .format(original_path, str(error))) - QMessageBox.critical(self, _("File Error"), txt) - return - try: - nbformat.write(nb_contents, filename) - except EnvironmentError as error: - txt = (_("Error while writing {}

{}") - .format(filename, str(error))) - QMessageBox.critical(self, _("File Error"), txt) - return - if reopen_after_save: - self.close_client(save_before_close=False) - self.create_new_client(filename=filename) + if not filename: + return original_path + + try: + nb_contents = nbformat.read(original_path, as_version=4) + except EnvironmentError as error: + txt = (_("Error while reading {}

{}") + .format(original_path, str(error))) + QMessageBox.critical(self, _("File Error"), txt) + return original_path + try: + nbformat.write(nb_contents, filename) + except EnvironmentError as error: + txt = (_("Error while writing {}

{}") + .format(filename, str(error))) + QMessageBox.critical(self, _("File Error"), txt) + return original_path + if reopen_after_save: + self.close_client(save_before_close=False) + self.create_new_client(filename=filename) + return filename @staticmethod def is_newly_created(client): From ea608e4180f0c79f5d6305d78c99f28e5a44adc9 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Sat, 6 Jun 2020 14:16:03 +0100 Subject: [PATCH 08/10] Plugin: Store opened notebooks in conf when closing --- spyder_notebook/notebookplugin.py | 21 ++++++++++++++++++--- spyder_notebook/tests/test_plugin.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 9e458bcd..7e50fd23 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -37,7 +37,9 @@ class NotebookPlugin(SpyderPluginWidget): """IPython Notebook plugin.""" CONF_SECTION = 'notebook' - CONF_DEFAULTS = [(CONF_SECTION, {'recent_notebooks': []})] + CONF_DEFAULTS = [(CONF_SECTION, { + 'recent_notebooks': [], # Items in "Open recent" menu + 'opened_notebooks': []})] # Notebooks to open at start focus_changed = Signal() def __init__(self, parent, testing=False): @@ -104,10 +106,23 @@ def get_focus_widget(self): return client.notebookwidget def closing_plugin(self, cancelable=False): - """Perform actions before parent main window is closed.""" + """ + Perform actions before parent main window is closed. + + This function closes all tabs. It stores the file names of all opened + notebooks that are not temporary and all notebooks in the 'Open recent' + menu in the config. + """ + opened_notebooks = [] for client_index in range(self.tabwidget.count()): - self.tabwidget.widget(client_index).close() + client = self.tabwidget.widget(client_index) + if (not self.tabwidget.is_welcome_client(client) + and not self.tabwidget.is_newly_created(client)): + opened_notebooks.append(client.filename) + client.close() + self.set_option('recent_notebooks', self.recent_notebooks) + self.set_option('opened_notebooks', opened_notebooks) return True def refresh_plugin(self): diff --git a/spyder_notebook/tests/test_plugin.py b/spyder_notebook/tests/test_plugin.py index 2e0e6bc5..155f0028 100644 --- a/spyder_notebook/tests/test_plugin.py +++ b/spyder_notebook/tests/test_plugin.py @@ -7,6 +7,7 @@ """Tests for the plugin.""" # Standard library imports +import collections import json import os import os.path as osp @@ -278,5 +279,26 @@ def test_open_console_when_no_kernel(notebook, qtbot, mocker): notebook.ipyconsole._create_client_for_kernel.assert_not_called() +def test_closing_plugin(mocker, qtbot): + """Close a plugin with a welcome tab, a new notebooks and a notebook + opened from a file. Check that config variables `recent_notebooks` and + `opened_notebook` are correctly set.""" + def fake_nbopen(filename): + return collections.defaultdict(str, filename=filename) + mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', + fake_nbopen) + plugin = NotebookPlugin(None, testing=True) + mock_set_option = mocker.patch.object(plugin, 'set_option') + plugin.tabwidget.maybe_create_welcome_client() + plugin.create_new_client() + plugin.open_notebook(['ham.ipynb']) + + plugin.closing_plugin() + + expected = [mocker.call('recent_notebooks', ['ham.ipynb']), + mocker.call('opened_notebooks', ['ham.ipynb'])] + assert mock_set_option.call_args_list == expected + + if __name__ == "__main__": pytest.main() From 4991eddd5f61858473cca66065498160fd0a691b Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Sun, 7 Jun 2020 18:13:35 +0100 Subject: [PATCH 09/10] Plugin: On startup, open notebooks open in previous session --- spyder_notebook/notebookplugin.py | 12 +++++-- spyder_notebook/tests/test_plugin.py | 51 ++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/spyder_notebook/notebookplugin.py b/spyder_notebook/notebookplugin.py index 7e50fd23..3002a9af 100644 --- a/spyder_notebook/notebookplugin.py +++ b/spyder_notebook/notebookplugin.py @@ -172,9 +172,15 @@ def register_plugin(self): super().register_plugin() self.focus_changed.connect(self.main.plugin_focus_changed) self.ipyconsole = self.main.ipyconsole - self.tabwidget.maybe_create_welcome_client() - self.create_new_client() - self.tabwidget.setCurrentIndex(0) # bring welcome tab to top + + # Open initial tabs + filenames = self.get_option('opened_notebooks') + if filenames: + self.open_notebook(filenames) + else: + self.tabwidget.maybe_create_welcome_client() + self.create_new_client() + self.tabwidget.setCurrentIndex(0) # bring welcome tab to top # Connect to switcher self.switcher = self.main.switcher diff --git a/spyder_notebook/tests/test_plugin.py b/spyder_notebook/tests/test_plugin.py index 155f0028..529e8160 100644 --- a/spyder_notebook/tests/test_plugin.py +++ b/spyder_notebook/tests/test_plugin.py @@ -102,6 +102,19 @@ def notebook(qtbot): return notebook_plugin +@pytest.fixture +def plugin_no_server(mocker, qtbot): + """Set up the Notebook plugin with a fake nbopen which does not start + a notebook server.""" + def fake_nbopen(filename): + return collections.defaultdict(str, filename=filename) + mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', + fake_nbopen) + plugin = NotebookPlugin(None, testing=True) + qtbot.addWidget(plugin) + mocker.patch.object(plugin, 'main') + return plugin + # ============================================================================= # Tests # ============================================================================= @@ -279,15 +292,41 @@ def test_open_console_when_no_kernel(notebook, qtbot, mocker): notebook.ipyconsole._create_client_for_kernel.assert_not_called() -def test_closing_plugin(mocker, qtbot): +def test_register_plugin_with_opened_notebooks(mocker, plugin_no_server): + """Run .register_plugin() with the `opened_notebooks` conf option set to + a non-empty list. Check that plugin opens those notebooks.""" + plugin = plugin_no_server + plugin.set_option('opened_notebooks', ['ham.ipynb', 'spam.ipynb']) + + plugin.register_plugin() + + tabwidget = plugin.tabwidget + assert tabwidget.count() == 2 + assert tabwidget.widget(0).filename == 'ham.ipynb' + assert tabwidget.widget(1).filename == 'spam.ipynb' + + +def test_register_plugin_with_opened_notebooks_empty(mocker, plugin_no_server): + """Run .register_plugin() with the `opened_notebooks` conf option set to + an empty list. Check that plugin opens a welcome tab and a new notebook, + and that the welcome tab is on top.""" + plugin = plugin_no_server + plugin.set_option('opened_notebooks', []) + + plugin.register_plugin() + + tabwidget = plugin.tabwidget + assert tabwidget.count() == 2 + assert tabwidget.is_welcome_client(tabwidget.widget(0)) + assert tabwidget.is_newly_created(tabwidget.widget(1)) + assert tabwidget.currentIndex() == 0 + + +def test_closing_plugin(mocker, plugin_no_server): """Close a plugin with a welcome tab, a new notebooks and a notebook opened from a file. Check that config variables `recent_notebooks` and `opened_notebook` are correctly set.""" - def fake_nbopen(filename): - return collections.defaultdict(str, filename=filename) - mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', - fake_nbopen) - plugin = NotebookPlugin(None, testing=True) + plugin = plugin_no_server mock_set_option = mocker.patch.object(plugin, 'set_option') plugin.tabwidget.maybe_create_welcome_client() plugin.create_new_client() From b38b9ac0927772e8d1e018e5488d54a4708a1934 Mon Sep 17 00:00:00 2001 From: Jitse Niesen Date: Sun, 7 Jun 2020 19:19:31 +0100 Subject: [PATCH 10/10] Tests: Fix fake nbopen for Windows by specifying notebook_dir --- spyder_notebook/tests/test_plugin.py | 3 ++- spyder_notebook/widgets/tests/test_notebooktabwidget.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spyder_notebook/tests/test_plugin.py b/spyder_notebook/tests/test_plugin.py index 529e8160..02ab0781 100644 --- a/spyder_notebook/tests/test_plugin.py +++ b/spyder_notebook/tests/test_plugin.py @@ -107,7 +107,8 @@ def plugin_no_server(mocker, qtbot): """Set up the Notebook plugin with a fake nbopen which does not start a notebook server.""" def fake_nbopen(filename): - return collections.defaultdict(str, filename=filename) + return collections.defaultdict( + str, filename=filename, notebook_dir=osp.dirname(filename)) mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', fake_nbopen) plugin = NotebookPlugin(None, testing=True) diff --git a/spyder_notebook/widgets/tests/test_notebooktabwidget.py b/spyder_notebook/widgets/tests/test_notebooktabwidget.py index 0d3f1fbf..edb88566 100755 --- a/spyder_notebook/widgets/tests/test_notebooktabwidget.py +++ b/spyder_notebook/widgets/tests/test_notebooktabwidget.py @@ -5,6 +5,7 @@ # Standard library imports import collections +import os.path as osp # Third party imports import pytest @@ -16,8 +17,11 @@ @pytest.fixture def tabwidget(mocker, qtbot): """Create an empty NotebookTabWidget which does not start up servers.""" + def fake_nbopen(filename): + return collections.defaultdict( + str, filename=filename, notebook_dir=osp.dirname(filename)) mocker.patch('spyder_notebook.widgets.notebooktabwidget.nbopen', - return_value=collections.defaultdict(str)) + fake_nbopen) widget = NotebookTabWidget(None) qtbot.addWidget(widget) return widget