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: Restore notebooks that were open at end of last session #292

Merged
merged 10 commits into from
Jun 14, 2020
56 changes: 39 additions & 17 deletions spyder_notebook/notebookplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,18 +28,18 @@
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__)
WELCOME = osp.join(PACKAGE_PATH, 'utils', 'templates', 'welcome.html')


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):
Expand Down Expand Up @@ -107,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):
Expand Down Expand Up @@ -160,7 +172,15 @@ def register_plugin(self):
super().register_plugin()
self.focus_changed.connect(self.main.plugin_focus_changed)
self.ipyconsole = self.main.ipyconsole
self.create_new_client()

# 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
Expand Down Expand Up @@ -216,13 +236,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):
"""
Expand All @@ -247,8 +266,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)
if NOTEBOOK_TMPDIR not in filename:
client = self.tabwidget.create_new_client(filename)
if not self.tabwidget.is_newly_created(client):
self.add_to_recent(filename)
self.setup_menu_actions()

Expand All @@ -260,7 +279,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."""
Expand Down
66 changes: 63 additions & 3 deletions spyder_notebook/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
"""Tests for the plugin."""

# Standard library imports
import collections
import json
import os
import os.path as osp
import shutil
import sys
import tempfile

# Third-party library imports
from flaky import flaky
Expand All @@ -21,7 +21,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
Expand Down Expand Up @@ -98,11 +97,25 @@ 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


@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, notebook_dir=osp.dirname(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
# =============================================================================
Expand Down Expand Up @@ -280,5 +293,52 @@ def test_open_console_when_no_kernel(notebook, qtbot, mocker):
notebook.ipyconsole._create_client_for_kernel.assert_not_called()


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."""
plugin = plugin_no_server
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()
2 changes: 1 addition & 1 deletion spyder_notebook/widgets/example_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading