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: Compute Projects switcher results in a worker to avoid freezes #21275

Merged
merged 26 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0cf2757
Utils: Don't use threads when starting ProcessWorkers in WorkerManager
ccordoba12 Aug 15, 2023
6bfa0e0
Utils: Add some logging to WorkerManager
ccordoba12 Aug 15, 2023
47a9d37
Projects: Remove unnecessary method
ccordoba12 Aug 15, 2023
071e163
Projects: Call fzf in a worker to not block the interface
ccordoba12 Aug 15, 2023
79bbc04
Projects: Detect if fzf is present in the system before trying to use it
ccordoba12 Aug 15, 2023
a81d5b2
Projects: Call switcher setup method after adding project items
ccordoba12 Aug 18, 2023
7a9d7b1
Projects: Remove repeated code in several places
ccordoba12 Aug 18, 2023
3aa2b35
Switcher: Remove items_data of sig_search_text_available signal
ccordoba12 Aug 18, 2023
ed7d6fa
Remove lower case path names in the Switcher
ccordoba12 Aug 18, 2023
df972f4
Projects: Remove binary files from the results returned by fzf
ccordoba12 Aug 18, 2023
4840e17
Switcher: Detect when a project is active
ccordoba12 Aug 19, 2023
d96203f
Switcher: Fix searching when no project is active
ccordoba12 Aug 19, 2023
6ee0ef3
Switcher: Fix showing sections when a project is active
ccordoba12 Aug 19, 2023
5099dfe
Switcher: Remove hard-coded projects section
ccordoba12 Aug 20, 2023
db685ef
Projects: Update default switcher paths on files creation, deletion a…
ccordoba12 Aug 20, 2023
e932b7f
Testing: Fix test_switcher_project_files
ccordoba12 Aug 20, 2023
843535f
App: Set app and monospace interface fonts when running single tests
ccordoba12 Aug 20, 2023
3843986
Projects: Filter switcher files according to their extension
ccordoba12 Aug 22, 2023
e5483ae
Projects: Prevent showing project path when fzf results are empty
ccordoba12 Aug 23, 2023
b3624fe
Switcher: Prevent to populate the switcher when closing it
ccordoba12 Aug 23, 2023
36a39c7
Switcher: Add a new public method to remove all items in a section
ccordoba12 Aug 25, 2023
60466aa
Editor: Connect to the Switcher's sig_search_text_available signal
ccordoba12 Aug 25, 2023
5480966
Switcher: Add attribute to items to check if their score can be changed
ccordoba12 Aug 25, 2023
e6140b1
Switcher: Remove direct dependency on Projects
ccordoba12 Aug 25, 2023
a36cc6f
Projects: Fix clearing the switcher when fzf can't find any match
ccordoba12 Aug 26, 2023
d51c98d
Testing: Expand test that checks Switcher/Projects integration
ccordoba12 Aug 26, 2023
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
69 changes: 51 additions & 18 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2535,9 +2535,14 @@ def example_def_2():


@flaky(max_runs=3)
def test_switcher_project_files(main_window, qtbot, tmpdir):
def test_switcher_project_files(main_window, pytestconfig, qtbot, tmp_path):
"""Test the number of items in the switcher when a project is active."""
# Wait until the window is fully up
# Disable pytest stdin capture to make calls to fzf work. Idea taken from:
# https://github.com/pytest-dev/pytest/issues/2189#issuecomment-449512764
capmanager = pytestconfig.pluginmanager.getplugin('capturemanager')
capmanager.suspend_global_capture(in_=True)

# Wait until the console is fully up
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(
lambda: shell.spyder_kernel_ready and shell._prompt_html is not None,
Expand All @@ -2550,47 +2555,75 @@ def test_switcher_project_files(main_window, qtbot, tmpdir):
editorstack = main_window.editor.get_current_editorstack()

# Create a temp project directory
project_dir = to_text_string(tmpdir.mkdir('test'))
project_dir = tmp_path / 'test-projects-switcher'
project_dir.mkdir()

# Create project
with qtbot.waitSignal(projects.sig_project_loaded):
projects.create_project(project_dir)
projects.create_project(str(project_dir))

# Create four empty files in the project dir
for i in range(3):
main_window.editor.new("test_file"+str(i)+".py")
# Create some empty files in the project dir
n_files_project = 3
for i in range(n_files_project):
fpath = project_dir / f"test_file{i}.py"
fpath.touch()

# Check that the switcher has been populated in Projects
qtbot.waitUntil(
lambda: projects.get_widget()._default_switcher_paths != [],
timeout=1000
)

# Assert that the number of items in the switcher is correct
switcher.open_switcher()
n_files_project = len(projects.get_project_filenames())
n_files_open = editorstack.get_stack_count()
assert switcher.count() == n_files_open + n_files_project
switcher.on_close()

# Assert that the number of items in the switcher is correct
assert switcher_widget.model.rowCount() == n_files_open + n_files_project
# Assert only two items have visible sections
switcher.open_switcher()

sections = []
for row in range(switcher.count()):
item = switcher_widget.model.item(row)
if item._section_visible:
sections.append(item.get_section())

assert set(sections) == {"Editor", "Project"}
switcher.on_close()

# Close all files opened in editorstack
main_window.editor.close_all_files()
# Assert searching text in the switcher works as expected
switcher.open_switcher()
switcher.set_search_text('0')
qtbot.wait(500)
assert switcher.count() == 1
switcher.on_close()

# Remove project file and check the switcher is updated
n_files_project -= 1
os.remove(osp.join(str(project_dir), 'test_file1.py'))
switcher.open_switcher()
n_files_project = len(projects.get_project_filenames())
n_files_open = editorstack.get_stack_count()
assert switcher_widget.model.rowCount() == n_files_open + n_files_project
qtbot.wait(500)
assert switcher.count() == n_files_open + n_files_project
switcher.on_close()

# Select file in the project explorer
idx = projects.get_widget().treewidget.get_index(
osp.join(project_dir, 'test_file0.py'))
osp.join(str(project_dir), 'test_file0.py')
)
projects.get_widget().treewidget.setCurrentIndex(idx)

# Press Enter there
qtbot.keyClick(projects.get_widget().treewidget, Qt.Key_Enter)

switcher.open_switcher()
n_files_project = len(projects.get_project_filenames())
n_files_open = editorstack.get_stack_count()
assert switcher_widget.model.rowCount() == n_files_open + n_files_project
assert switcher.count() == n_files_open + n_files_project - 1
switcher.on_close()

# Resume capturing
capmanager.resume_global_capture()


@flaky(max_runs=3)
@pytest.mark.skipif(sys.platform == 'darwin',
Expand Down
16 changes: 14 additions & 2 deletions spyder/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,20 @@ def create_application():
# The try/except is necessary to run the main window tests on their own.
try:
app.set_font()
except AttributeError:
pass
except AttributeError as error:
if running_under_pytest():
# Set font options to avoid a ton of Qt warnings when running tests
app_family = app.font().family()
app_size = app.font().pointSize()
CONF.set('appearance', 'app_font/family', app_family)
CONF.set('appearance', 'app_font/size', app_size)

from spyder.config.fonts import MEDIUM, MONOSPACE
CONF.set('appearance', 'monospace_app_font/family', MONOSPACE[0])
CONF.set('appearance', 'monospace_app_font/size', MEDIUM)
else:
# Raise in case the error is valid
raise error

# Required for correct icon on GNOME/Wayland:
if hasattr(app, 'setDesktopFileName'):
Expand Down
30 changes: 13 additions & 17 deletions spyder/plugins/editor/utils/switcher_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,9 @@ def create_editor_switcher(self):
_('Start typing the name of an open file'))

editorstack = self._editorstack()

# Since editor open files are inserted at position 0, the
# list needs to be reversed so they're shown in order.
editor_list = editorstack.data.copy()
editor_list.reverse()

paths = [data.filename.lower()
for data in editor_list]
save_statuses = [data.newly_created
for data in editor_list]
paths = [data.filename for data in editor_list]
save_statuses = [data.newly_created for data in editor_list]
short_paths = shorten_paths(paths, save_statuses)

for idx, data in enumerate(editor_list):
Expand All @@ -99,15 +92,18 @@ def create_editor_switcher(self):
if len(paths[idx]) > 75:
path = short_paths[idx]
else:
path = osp.dirname(data.filename.lower())
path = osp.dirname(data.filename)
last_item = (idx + 1 == len(editor_list))
self._switcher.add_item(title=title,
description=path,
icon=icon,
section=self._section,
data=data,
last_item=last_item)
self._switcher.set_current_row(0)

self._switcher.add_item(
title=title,
description=path,
icon=icon,
section=self._section,
data=data,
last_item=last_item,
score=0 # To make these items appear above those from Projects
)

def create_line_switcher(self):
"""Populate switcher with line info."""
Expand Down
36 changes: 16 additions & 20 deletions spyder/plugins/projects/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def on_switcher_available(self):
self._switcher.sig_item_selected.connect(
self._handle_switcher_selection)
self._switcher.sig_search_text_available.connect(
self._handle_switcher_results)
self._handle_switcher_search)

@on_plugin_teardown(plugin=Plugins.Editor)
def on_editor_teardown(self):
Expand Down Expand Up @@ -281,7 +281,7 @@ def on_switcher_teardown(self):
self._switcher.sig_item_selected.disconnect(
self._handle_switcher_selection)
self._switcher.sig_search_text_available.disconnect(
self._handle_switcher_results)
self._handle_switcher_search)
self._switcher = None

def on_close(self, cancelable=False):
Expand Down Expand Up @@ -508,17 +508,11 @@ def _handle_switcher_modes(self, mode):
mode: str
The selected mode (open files "", symbol "@" or line ":").
"""
items = self.get_widget().handle_switcher_modes()
for (title, description, icon, section, path, is_last_item) in items:
self._switcher.add_item(
title=title,
description=description,
icon=icon,
section=section,
data=path,
last_item=is_last_item
)
self._switcher.set_current_row(0)
# Don't compute anything if we're not in files mode
if mode != "":
return

self.get_widget().display_default_switcher_items()

def _handle_switcher_selection(self, item, mode, search_text):
"""
Expand All @@ -540,20 +534,19 @@ def _handle_switcher_selection(self, item, mode, search_text):
self.get_widget().handle_switcher_selection(item, mode, search_text)
self._switcher.hide()

def _handle_switcher_results(self, search_text, items_data):
def _handle_switcher_search(self, search_text):
"""
Handle user typing in switcher to filter results.

Load switcher results when a search text is typed for projects.
Parameters
----------
text: str
The current search text in the switcher dialog box.
items_data: list
List of items shown in the switcher.
"""
items = self.get_widget().handle_switcher_results(search_text,
items_data)
self.get_widget().handle_switcher_search(search_text)

def _display_items_in_switcher(self, items, setup=True):
"""Display a list of items in the switcher."""
for (title, description, icon, section, path, is_last_item) in items:
self._switcher.add_item(
title=title,
Expand All @@ -562,5 +555,8 @@ def _handle_switcher_results(self, search_text, items_data):
section=section,
data=path,
last_item=is_last_item,
score=100
score=1e10 # To make the editor results appear first
)

if setup:
self._switcher.setup()
2 changes: 1 addition & 1 deletion spyder/plugins/projects/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def test_project_explorer_tree_root(projects, tmpdir, qtbot):
# Open the projects.
for ppath in [ppath1, ppath2]:
projects.open_project(path=ppath)
projects.get_widget()._update_explorer(None)
projects.get_widget()._setup_project(ppath)

# Check that the root path of the project explorer tree widget is
# set correctly.
Expand Down
Loading