Skip to content

Commit

Permalink
Sync the current IPython console env with the one used for completions
Browse files Browse the repository at this point in the history
- Also, add logs to know when the console and completions interpreters
change.
- And make a signal in Completion plugin private because it doesn't need
to be exposed publicly.
  • Loading branch information
ccordoba12 committed Aug 21, 2024
1 parent c4f5779 commit 210faf0
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 18 deletions.
2 changes: 1 addition & 1 deletion spyder/api/shellconnect/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def set_shellwidget(self, shellwidget):
self.hide()
else:
self.show()
self.update_status(status)
self.current_shellwidget = shellwidget
self.update_status(status)

def add_shellwidget(self, shellwidget):
"""Actions to take when adding a shellwidget."""
Expand Down
44 changes: 33 additions & 11 deletions spyder/plugins/completion/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ class CompletionPlugin(SpyderPluginV2):
CONF_SECTION = 'completions'
REQUIRES = [Plugins.Preferences]
OPTIONAL = [
Plugins.MainInterpreter,
Plugins.MainMenu,
Plugins.IPythonConsole,
Plugins.PythonpathManager,
Plugins.StatusBar,
Plugins.MainInterpreter,
]

CONF_FILE = False
Expand Down Expand Up @@ -135,10 +136,10 @@ class CompletionPlugin(SpyderPluginV2):
New PythonPath settings.
"""

sig_interpreter_changed = Signal(str)
_sig_interpreter_changed = Signal(str)
"""
This signal is used to handle changes in the Python interpreter done by
other plugins.
This private signal is used to handle changes in the Python interpreter
done by other plugins.
Parameters
----------
Expand Down Expand Up @@ -281,9 +282,13 @@ def on_preferences_available(self):
@on_plugin_available(plugin=Plugins.MainInterpreter)
def on_maininterpreter_available(self):
maininterpreter = self.get_plugin(Plugins.MainInterpreter)
maininterpreter.sig_interpreter_changed.connect(
self.sig_interpreter_changed
)

# This will allow people to change the interpreter used for completions
# if they disable the IPython console.
if not self.is_plugin_enabled(Plugins.IPythonConsole):
maininterpreter.sig_interpreter_changed.connect(
self._sig_interpreter_changed
)

@on_plugin_available(plugin=Plugins.StatusBar)
def on_statusbar_available(self):
Expand All @@ -310,6 +315,13 @@ def on_pythonpath_manager_available(self):
pythonpath_manager.sig_pythonpath_changed.connect(
self.sig_pythonpath_changed)

@on_plugin_available(plugin=Plugins.IPythonConsole)
def on_ipython_console_available(self):
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
ipyconsole.sig_interpreter_changed.connect(
self._sig_interpreter_changed
)

@on_plugin_teardown(plugin=Plugins.Preferences)
def on_preferences_teardown(self):
preferences = self.get_plugin(Plugins.Preferences)
Expand All @@ -318,9 +330,12 @@ def on_preferences_teardown(self):
@on_plugin_teardown(plugin=Plugins.MainInterpreter)
def on_maininterpreter_teardown(self):
maininterpreter = self.get_plugin(Plugins.MainInterpreter)
maininterpreter.sig_interpreter_changed.disconnect(
self.sig_interpreter_changed
)

# We only connect to this signal if the IPython console is not enabled
if not self.is_plugin_enabled(Plugins.IPythonConsole):
maininterpreter.sig_interpreter_changed.disconnect(
self._sig_interpreter_changed
)

@on_plugin_teardown(plugin=Plugins.StatusBar)
def on_statusbar_teardown(self):
Expand Down Expand Up @@ -359,6 +374,13 @@ def on_pythonpath_manager_teardown(self):
pythonpath_manager.sig_pythonpath_changed.disconnect(
self.sig_pythonpath_changed)

@on_plugin_teardown(plugin=Plugins.IPythonConsole)
def on_ipython_console_teardowm(self):
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
ipyconsole.sig_interpreter_changed.disconnect(
self._sig_interpreter_changed
)

# ---- Public API
def stop_all_providers(self):
"""Stop all running completion providers."""
Expand Down Expand Up @@ -783,7 +805,7 @@ def connect_provider_signals(self, provider_instance):

self.sig_pythonpath_changed.connect(
provider_instance.python_path_update)
self.sig_interpreter_changed.connect(
self._sig_interpreter_changed.connect(
provider_instance.interpreter_changed
)

Expand Down
22 changes: 18 additions & 4 deletions spyder/plugins/completion/providers/languageserver/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qtpy.QtCore import Signal, Slot, QTimer
from qtpy.QtWidgets import QMessageBox
from qtpy import PYSIDE2, PYSIDE6
from superqt.utils import qdebounced

# Local imports
from spyder.api.config.decorators import on_conf_change
Expand Down Expand Up @@ -552,10 +553,23 @@ def python_path_update(self, path_dict, new_path_dict):
logger.debug("Update server's sys.path")
self.update_lsp_configuration(python_only=True)

@Slot(str)
def interpreter_changed(self, interpreter):
self._interpreter = interpreter
self.update_lsp_configuration(python_only=True)
@qdebounced(timeout=600)
def interpreter_changed(self, interpreter: str):
"""
Handle Python interperter changes from other plugins.
Notes
-----
- This method is debounced to prevent sending too many requests to the
server when switching IPython consoles for different envs in quick
succession.
- The timeout corresponds more or less to the time it takes to switch
back and forth between two consoles.
"""
if interpreter != self._interpreter:
logger.debug(f"LSP interpreter changed to {interpreter}")
self._interpreter = interpreter
self.update_lsp_configuration(python_only=True)

def file_opened_closed_or_updated(self, filename: str, language: str):
self.sig_call_statusbar.emit(
Expand Down
20 changes: 18 additions & 2 deletions spyder/plugins/ipythonconsole/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ class IPythonConsole(SpyderDockablePlugin, RunExecutor):
Plugins.History,
Plugins.MainInterpreter,
Plugins.MainMenu,
Plugins.Run,
Plugins.Projects,
Plugins.PythonpathManager,
Plugins.RemoteClient,
Plugins.WorkingDirectory,
Plugins.Run,
Plugins.StatusBar,
Plugins.WorkingDirectory,
]
TABIFY = [Plugins.History]
WIDGET_CLASS = IPythonConsoleWidget
Expand Down Expand Up @@ -209,6 +209,17 @@ class IPythonConsole(SpyderDockablePlugin, RunExecutor):
The new working directory path.
"""

sig_interpreter_changed = Signal(str)
"""
This signal is emitted when the interpreter of the active shell widget has
changed.
Parameters
----------
path: str
Path to the new interpreter.
"""

# ---- SpyderDockablePlugin API
# -------------------------------------------------------------------------
@staticmethod
Expand All @@ -227,6 +238,8 @@ def get_icon(cls):

def on_initialize(self):
widget = self.get_widget()

# Main widget signals
widget.sig_append_to_history_requested.connect(
self.sig_append_to_history_requested)
widget.sig_switch_to_plugin_requested.connect(self.switch_to_plugin)
Expand All @@ -244,6 +257,9 @@ def on_initialize(self):
widget.sig_help_requested.connect(self.sig_help_requested)
widget.sig_current_directory_changed.connect(
self.sig_current_directory_changed)
widget.sig_interpreter_changed.connect(
self.sig_interpreter_changed
)

# Run configurations
self.cython_editor_run_configuration = {
Expand Down
14 changes: 14 additions & 0 deletions spyder/plugins/ipythonconsole/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,17 @@ class IPythonConsoleWidget(PluginMainWidget, CachedKernelMixin):
The new working directory path.
"""

sig_interpreter_changed = Signal(str)
"""
This signal is emitted when the interpreter of the active shell widget has
changed.
Parameters
----------
path: str
Path to the new interpreter.
"""

def __init__(self, name=None, plugin=None, parent=None):
super().__init__(name, plugin, parent)

Expand Down Expand Up @@ -373,6 +384,9 @@ def __init__(self, name=None, plugin=None, parent=None):
# Create status widgets
self.matplotlib_status = MatplotlibStatus(self)
self.pythonenv_status = PythonEnvironmentStatus(self)
self.pythonenv_status.sig_interpreter_changed.connect(
self.sig_interpreter_changed
)

# Initial value for the current working directory
self._current_working_directory = get_home_dir()
Expand Down
17 changes: 17 additions & 0 deletions spyder/plugins/ipythonconsole/widgets/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@

# Standard library imports
import functools
import logging
import sys
import textwrap

# Third-party imports
from IPython.core import release as ipython_release
from qtpy.QtCore import Signal
from spyder_kernels.comms.frontendcomm import CommError
from spyder_kernels.utils.pythonenv import PythonEnvInfo, PythonEnvType

Expand All @@ -22,6 +24,9 @@
from spyder.config.base import running_in_ci


logger = logging.getLogger(__name__)


class MatplotlibStatus(ShellConnectStatusBarWidget):
"""Status bar widget for current Matplotlib backend."""

Expand Down Expand Up @@ -170,6 +175,8 @@ class PythonEnvironmentStatus(ShellConnectStatusBarWidget):
ID = 'pythonenv_status'
CONF_SECTION = 'ipython_console'

sig_interpreter_changed = Signal(str)

def __init__(self, parent):
self._current_env_info: PythonEnvInfo | None = None
super().__init__(parent)
Expand All @@ -183,6 +190,16 @@ def get_tooltip(self):
# -------------------------------------------------------------------------
def update_status(self, env_info: dict):
"""Update env info."""
if (
# There's no need to emit this signal for remote consoles because
# other plugins can only react to local interpreter changes.
not self.current_shellwidget.is_remote()
and env_info != self._current_env_info
):
new_interpreter = env_info["path"]
logger.debug(f"Console interpreter changed to {new_interpreter}")
self.sig_interpreter_changed.emit(new_interpreter)

self._current_env_info = env_info

if env_info["env_type"] == PythonEnvType.Conda:
Expand Down

0 comments on commit 210faf0

Please sign in to comment.