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: Fix syncing IPython console cwd with the Working directory toolbar #19068

Merged
merged 10 commits into from
Aug 18, 2022
Merged
112 changes: 112 additions & 0 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5417,5 +5417,117 @@ def test_visible_plugins(main_window, qtbot):
assert set(selected) == set(visible_plugins)


@pytest.mark.slow
def test_cwd_is_synced_when_switching_consoles(main_window, qtbot, tmpdir):
"""
Test that the current working directory is synced between the IPython
console and other plugins when switching consoles.
"""
ipyconsole = main_window.ipyconsole
workdir = main_window.workingdirectory
files = main_window.get_plugin(Plugins.Explorer)

# Wait for the window to be fully up
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)

# Create two new clients and change their cwd's
for i in range(2):
sync_dir = tmpdir.mkdir(f'test_sync_{i}')
ipyconsole.create_new_client()
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
with qtbot.waitSignal(shell.executed):
shell.execute(f'cd {str(sync_dir)}')

# Switch between clients and check that the cwd is in sync with other
# plugins
for i in range(3):
ipyconsole.get_widget().tabwidget.setCurrentIndex(i)
shell_cwd = ipyconsole.get_current_shellwidget().get_cwd()
assert shell_cwd == workdir.get_workdir() == files.get_current_folder()


@pytest.mark.slow
def test_console_initial_cwd_is_synced(main_window, qtbot, tmpdir):
"""
Test that the initial current working directory for new consoles is synced
with other plugins.
"""
ipyconsole = main_window.ipyconsole
workdir = main_window.workingdirectory
files = main_window.get_plugin(Plugins.Explorer)

# Wait for the window to be fully up
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)

# Open console from Files in tmpdir
files.get_widget().treewidget.open_interpreter([str(tmpdir)])
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
assert shell.get_cwd() == str(tmpdir) == workdir.get_workdir() == \
files.get_current_folder()

# Check that a new client has the same initial cwd as the current one
ipyconsole.create_new_client()
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
qtbot.wait(500)
assert shell.get_cwd() == str(tmpdir) == workdir.get_workdir() == \
files.get_current_folder()

# Check new clients with a fixed directory
ipyconsole.set_conf('console/use_cwd', False, section='workingdir')
ipyconsole.set_conf(
'console/use_fixed_directory',
True,
section='workingdir'
)

fixed_dir = str(tmpdir.mkdir('fixed_dir'))
ipyconsole.set_conf(
'console/fixed_directory',
fixed_dir,
section='workingdir'
)

ipyconsole.create_new_client()
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
qtbot.wait(500)
assert shell.get_cwd() == fixed_dir == workdir.get_workdir() == \
files.get_current_folder()

# Check when opening projects
project_path = str(tmpdir.mkdir('test_project'))
main_window.projects.open_project(path=project_path)
qtbot.wait(500)

shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
qtbot.wait(500)
assert shell.get_cwd() == project_path == workdir.get_workdir() == \
files.get_current_folder()

# Check when closing projects
main_window.projects.close_project()
qtbot.wait(500)

shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None,
timeout=SHELL_TIMEOUT)
qtbot.wait(500)
assert shell.get_cwd() == get_home_dir() == workdir.get_workdir() == \
files.get_current_folder()


if __name__ == "__main__":
pytest.main()
12 changes: 8 additions & 4 deletions spyder/plugins/explorer/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class Explorer(SpyderDockablePlugin):
New path for renamed folder.
"""

sig_interpreter_opened = Signal(str)
sig_open_interpreter_requested = Signal(str)
"""
This signal is emitted to request opening an interpreter with the given
path as working directory.
Expand Down Expand Up @@ -177,7 +177,7 @@ def on_initialize(self):
widget.sig_file_created.connect(self.sig_file_created)
widget.sig_open_file_requested.connect(self.sig_open_file_requested)
widget.sig_open_interpreter_requested.connect(
self.sig_interpreter_opened)
self.sig_open_interpreter_requested)
widget.sig_module_created.connect(self.sig_module_created)
widget.sig_removed.connect(self.sig_file_removed)
widget.sig_renamed.connect(self.sig_file_renamed)
Expand Down Expand Up @@ -207,7 +207,7 @@ def on_preferences_available(self):
@on_plugin_available(plugin=Plugins.IPythonConsole)
def on_ipython_console_available(self):
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
self.sig_interpreter_opened.connect(
self.sig_open_interpreter_requested.connect(
ipyconsole.create_client_from_path)
self.sig_run_requested.connect(
lambda fname:
Expand Down Expand Up @@ -240,7 +240,7 @@ def on_preferences_teardown(self):
@on_plugin_teardown(plugin=Plugins.IPythonConsole)
def on_ipython_console_teardown(self):
ipyconsole = self.get_plugin(Plugins.IPythonConsole)
self.sig_interpreter_opened.disconnect(
self.sig_open_interpreter_requested.disconnect(
ipyconsole.create_client_from_path)
self.sig_run_requested.disconnect()

Expand All @@ -260,6 +260,10 @@ def chdir(self, directory, emit=True):
"""
self.get_widget().chdir(directory, emit=emit)

def get_current_folder(self):
"""Get folder displayed at the moment."""
return self.get_widget().get_current_folder()

def refresh(self, new_path=None, force_current=True):
"""
Refresh history.
Expand Down
1 change: 1 addition & 0 deletions spyder/plugins/explorer/widgets/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ def setup(self):
self.open_interpreter_action = self.create_action(
DirViewActions.OpenInterpreter,
text=_("Open IPython console here"),
icon=self.create_icon('ipython_console'),
triggered=lambda: self.open_interpreter(),
)

Expand Down
23 changes: 15 additions & 8 deletions spyder/plugins/ipythonconsole/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ def on_projects_available(self):
def on_working_directory_available(self):
working_directory = self.get_plugin(Plugins.WorkingDirectory)
working_directory.sig_current_directory_changed.connect(
self._set_working_directory)
self.save_working_directory)

@on_plugin_teardown(plugin=Plugins.Preferences)
def on_preferences_teardown(self):
Expand Down Expand Up @@ -379,7 +379,7 @@ def on_projects_teardown(self):
def on_working_directory_teardown(self):
working_directory = self.get_plugin(Plugins.WorkingDirectory)
working_directory.sig_current_directory_changed.disconnect(
self._set_working_directory)
self.save_working_directory)

def update_font(self):
"""Update font from Preferences"""
Expand Down Expand Up @@ -426,11 +426,6 @@ def _remove_old_std_files(self):
except Exception:
pass

@Slot(str)
def _set_working_directory(self, new_dir):
"""Set current working directory on the main widget."""
self.get_widget().set_working_directory(new_dir)

# ---- Public API
# -------------------------------------------------------------------------

Expand Down Expand Up @@ -807,7 +802,7 @@ def set_current_client_working_directory(self, directory):

def set_working_directory(self, dirname):
"""
Set current working directory for the `workingdirectory` and `explorer`
Set current working directory in the Working Directory and Files
plugins.

Parameters
Expand All @@ -821,6 +816,18 @@ def set_working_directory(self, dirname):
"""
self.get_widget().set_working_directory(dirname)

@Slot(str)
def save_working_directory(self, dirname):
"""
Save current working directory on the main widget to start new clients.

Parameters
----------
new_dir: str
Path to the new current working directory.
"""
self.get_widget().save_working_directory(dirname)

def update_working_directory(self):
"""Update working directory to console current working directory."""
self.get_widget().update_working_directory()
Expand Down
2 changes: 1 addition & 1 deletion spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -2282,7 +2282,7 @@ def get_cwd_of_new_client():

# Simulate a specific directory
cwd_dir = str(tmpdir.mkdir('ipyconsole_cwd_test'))
ipyconsole.get_widget().set_working_directory(cwd_dir)
ipyconsole.get_widget().save_working_directory(cwd_dir)

# Get cwd of new client and assert is the expected one
assert get_cwd_of_new_client() == cwd_dir
Expand Down
24 changes: 16 additions & 8 deletions spyder/plugins/ipythonconsole/widgets/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def __init__(self, parent, id_,
handlers={},
stderr_obj=None,
stdout_obj=None,
fault_obj=None):
fault_obj=None,
initial_cwd=None):
super(ClientWidget, self).__init__(parent)
SaveHistoryMixin.__init__(self, history_filename)

Expand All @@ -123,6 +124,7 @@ def __init__(self, parent, id_,
self.reset_warning = reset_warning
self.ask_before_restart = ask_before_restart
self.ask_before_closing = ask_before_closing
self.initial_cwd = initial_cwd

# --- Other attrs
self.context_menu_actions = context_menu_actions
Expand Down Expand Up @@ -220,8 +222,8 @@ def _when_prompt_is_ready(self):
# To show if special console is valid
self._check_special_console_error()

# Set the initial current working directory
self._set_initial_cwd()
# Set the initial current working directory in the kernel
self._set_initial_cwd_in_kernel()

self.shellwidget.sig_prompt_ready.disconnect(
self._when_prompt_is_ready)
Expand Down Expand Up @@ -351,11 +353,12 @@ def _connect_control_signals(self):
page_control.sig_show_find_widget_requested.connect(
self.container.find_widget.show)

def _set_initial_cwd(self):
"""Set initial cwd according to preferences."""
logger.debug("Setting initial working directory")
def _set_initial_cwd_in_kernel(self):
"""Set the initial cwd in the kernel."""
logger.debug("Setting initial working directory in the kernel")
cwd_path = get_home_dir()
project_path = self.container.get_active_project_path()
update_in_spyder = True

# This is for the first client
if self.id_['int_id'] == '1':
Expand All @@ -377,7 +380,9 @@ def _set_initial_cwd(self):
)
else:
# For new clients
if self.get_conf(
if self.initial_cwd is not None:
cwd_path = self.initial_cwd
elif self.get_conf(
'console/use_project_or_home_directory',
section='workingdir'
):
Expand All @@ -386,6 +391,7 @@ def _set_initial_cwd(self):
cwd_path = project_path
elif self.get_conf('console/use_cwd', section='workingdir'):
cwd_path = self.container.get_working_directory()
update_in_spyder = False
elif self.get_conf(
'console/use_fixed_directory',
section='workingdir'
Expand All @@ -397,7 +403,9 @@ def _set_initial_cwd(self):
)

if osp.isdir(cwd_path):
self.shellwidget.set_cwd(cwd_path)
self.shellwidget.set_cwd(
cwd_path, update_in_spyder=update_in_spyder
)

# ----- Public API --------------------------------------------------------
@property
Expand Down
Loading