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: Update plugin to Spyder 6 #417

Merged
merged 6 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
matrix:
OS: ['ubuntu', 'macos', 'windows']
PYTHON_VERSION: ['3.8', '3.9', '3.10']
SPYDER_SOURCE: ['conda']
SPYDER_SOURCE: ['git']
name: ${{ matrix.OS }} py${{ matrix.PYTHON_VERSION }} spyder-from-${{ matrix.SPYDER_SOURCE }}
runs-on: ${{ matrix.OS }}-latest
env:
Expand Down
23 changes: 23 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
#

"""Configuration file for Pytest."""

# Standard library imports
import os

# To activate/deactivate certain things in Spyder when running tests.
# NOTE: Please leave this before any other import here!!
os.environ['SPYDER_PYTEST'] = 'True'

# Third-party imports
import pytest


@pytest.fixture(autouse=True)
def reset_conf_before_test():
from spyder.config.manager import CONF
CONF.reset_to_defaults(notification=False)
80 changes: 71 additions & 9 deletions spyder_notebook/notebookplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@

"""Notebook plugin."""

# Qt imports
from qtpy.QtCore import Signal
# Standard library imports
import os.path as osp

# Spyder imports
from spyder.api.plugins import Plugins, SpyderDockablePlugin
from spyder.api.plugin_registration.decorators import (
on_plugin_available, on_plugin_teardown)
from spyder.plugins.switcher.utils import shorten_paths

# Local imports
from spyder_notebook.config import CONF_DEFAULTS, CONF_VERSION
Expand All @@ -25,18 +26,14 @@ class NotebookPlugin(SpyderDockablePlugin):

NAME = 'notebook'
REQUIRES = [Plugins.Preferences]
OPTIONAL = [Plugins.IPythonConsole]
OPTIONAL = [Plugins.IPythonConsole, Plugins.Switcher]
TABIFY = [Plugins.Editor]
CONF_SECTION = NAME
CONF_DEFAULTS = CONF_DEFAULTS
CONF_VERSION = CONF_VERSION
WIDGET_CLASS = NotebookMainWidget
CONF_WIDGET_CLASS = NotebookConfigPage

# ---- Signals
# ------------------------------------------------------------------------
focus_changed = Signal()

# ---- SpyderDockablePlugin API
# ------------------------------------------------------------------------
@staticmethod
Expand All @@ -54,8 +51,8 @@ def get_icon(self):
return self.create_icon('notebook')

def on_initialize(self):
"""Register plugin in Spyder's main window."""
self.focus_changed.connect(self.main.plugin_focus_changed)
"""Set up the plugin; does nothing."""
pass

@on_plugin_available(plugin=Plugins.Preferences)
def on_preferences_available(self):
Expand All @@ -67,6 +64,12 @@ def on_ipyconsole_available(self):
self.get_widget().sig_open_console_requested.connect(
self._open_console)

@on_plugin_available(plugin=Plugins.Switcher)
def on_switcher_available(self):
switcher = self.get_plugin(Plugins.Switcher)
switcher.sig_mode_selected.connect(self._handle_switcher_modes)
switcher.sig_item_selected.connect(self._handle_switcher_selection)

@on_plugin_teardown(plugin=Plugins.Preferences)
def on_preferences_teardown(self):
preferences = self.get_plugin(Plugins.Preferences)
Expand All @@ -77,6 +80,12 @@ def on_ipyconsole_teardown(self):
self.get_widget().sig_open_console_requested.disconnect(
self._open_console)

@on_plugin_teardown(plugin=Plugins.Switcher)
def on_switcher_teardown(self):
switcher = self.get_plugin(Plugins.Switcher)
switcher.sig_mode_selected.disconnect(self._handle_switcher_modes)
switcher.sig_item_selected.disconnect(self._handle_switcher_selection)

def on_mainwindow_visible(self):
self.get_widget().open_previous_session()

Expand All @@ -92,3 +101,56 @@ def _open_console(self, kernel_id, tab_name):
ipyclient = ipyconsole.get_current_client()
ipyclient.allow_rename = False
ipyconsole.rename_client_tab(ipyclient, tab_name)

def _handle_switcher_modes(self, mode):
"""
Populate switcher with opened notebooks.

List the file names of the opened notebooks with their directories in
the switcher. Only handle file mode, where `mode` is empty string.
"""
if mode != '':
return

tabwidget = self.get_widget().tabwidget
clients = [tabwidget.widget(i) for i in range(tabwidget.count())]
paths = [client.get_filename() for client in clients]
is_unsaved = [False for client in clients]
short_paths = shorten_paths(paths, is_unsaved)
icon = self.create_icon('notebook')
section = self.get_name()
switcher = self.get_plugin(Plugins.Switcher)

for path, short_path, client in zip(paths, short_paths, clients):
title = osp.basename(path)
description = osp.dirname(path)
if len(path) > 75:
description = short_path
is_last_item = (client == clients[-1])

switcher.add_item(
title=title,
description=description,
icon=icon,
section=section,
data=client,
last_item=is_last_item
)

def _handle_switcher_selection(self, item, mode, search_text):
"""
Handle user selecting item in switcher.

If the selected item is not in the section of the switcher that
corresponds to this plugin, then ignore it. Otherwise, switch to
selected item in notebook plugin and hide the switcher.
"""
if item.get_section() != self.get_title():
return

client = item.get_data()
tabwidget = self.get_widget().tabwidget
tabwidget.setCurrentIndex(tabwidget.indexOf(client))
self.switch_to_plugin()
switcher = self.get_plugin(Plugins.Switcher)
switcher.hide()
1 change: 0 additions & 1 deletion spyder_notebook/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ def is_kernel_up(kernel_id, sessions_url):
class MainMock(QMainWindow):
def __init__(self):
super().__init__()
self.switcher = Mock()
self.main = self
self.resize(640, 480)

Expand Down
24 changes: 21 additions & 3 deletions spyder_notebook/utils/servermanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,22 @@ def _check_server_started(self, server_process):
self.sig_server_started.emit(server_process)

def shutdown_all_servers(self):
"""Shutdown all running servers."""
"""
Shutdown all servers.

Disconnect all signals of the server process and try to shutdown the
server nicely. However, if the server is still starting up, or if
shutting down nicely does not work, then kill the server process.
"""
for server in self.servers:
process = server.process
process.readyReadStandardOutput.disconnect()
process.errorOccurred.disconnect()
process.finished.disconnect()

if server.state == ServerState.RUNNING:
logger.debug('Shutting down notebook server for %s',
server.notebook_dir)
server.process.errorOccurred.disconnect()
server.process.finished.disconnect()

try:
serverapp.shutdown_server(server.server_info, log=logger)
Expand All @@ -308,6 +317,15 @@ def shutdown_all_servers(self):
logger.warning(f'Ignoring {err}')
server.state = ServerState.FINISHED

if server.state == ServerState.STARTING:
process.kill()
server.state = ServerState.FINISHED

if process.state() != QProcess.NotRunning:
# Should not be necessary, but make sure that process is killed
process.kill()


def read_server_output(self, server_process):
"""
Read the output of the notebook server process.
Expand Down
61 changes: 0 additions & 61 deletions spyder_notebook/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)

# Standard library imports
import os.path as osp

# Third-party imports
from qtpy.QtCore import Signal
from qtpy.QtWidgets import QMessageBox, QVBoxLayout

# Spyder imports
from spyder.api.widgets.main_widget import PluginMainWidget
from spyder.config.gui import is_dark_interface
from spyder.utils.switcher import shorten_paths

# Local imports
from spyder_notebook.utils.localization import _
Expand Down Expand Up @@ -85,12 +81,6 @@ def __init__(self, name, plugin, parent):
layout.addWidget(self.tabwidget)
self.setLayout(layout)

# Connect to switcher
self.switcher = plugin.main.switcher
self.switcher.sig_mode_selected.connect(self.handle_switcher_modes)
self.switcher.sig_item_selected.connect(
self.handle_switcher_selection)

# ---- PluginMainWidget API
# ------------------------------------------------------------------------
def get_focus_widget(self):
Expand Down Expand Up @@ -341,54 +331,3 @@ def clear_recent_notebooks(self):
"""Clear the list of recent notebooks."""
self.recent_notebooks = []
self.update_recent_notebooks_menu()

def handle_switcher_modes(self, mode):
"""
Populate switcher with opened notebooks.

List the file names of the opened notebooks with their directories in
the switcher. Only handle file mode, where `mode` is empty string.
"""
if mode != '':
return

clients = [self.tabwidget.widget(i)
for i in range(self.tabwidget.count())]
paths = [client.get_filename() for client in clients]
is_unsaved = [False for client in clients]
short_paths = shorten_paths(paths, is_unsaved)
icon = self.create_icon('notebook')
section = self.get_title()

for path, short_path, client in zip(paths, short_paths, clients):
title = osp.basename(path)
description = osp.dirname(path)
if len(path) > 75:
description = short_path
is_last_item = (client == clients[-1])

self.switcher.add_item(
title=title,
description=description,
icon=icon,
section=section,
data=client,
last_item=is_last_item
)

def handle_switcher_selection(self, item, mode, search_text):
"""
Handle user selecting item in switcher.

If the selected item is not in the section of the switcher that
corresponds to this plugin, then ignore it. Otherwise, switch to
selected item in notebook plugin and hide the switcher.
"""
if item.get_section() != self.get_title():
return

client = item.get_data()
index = self.tabwidget.indexOf(client)
self.tabwidget.setCurrentIndex(index)
self._plugin.switch_to_plugin()
self.switcher.hide()