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 errors when displaying the Symbols switcher #21667

Merged
merged 9 commits into from
Jan 1, 2024
25 changes: 20 additions & 5 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2440,7 +2440,7 @@ def test_plot_from_collectioneditor(main_window, qtbot):
@flaky(max_runs=3)
@pytest.mark.use_introspection
@pytest.mark.order(after="test_debug_unsaved_function")
def test_switcher(main_window, qtbot, tmpdir):
def test_switcher(main_window, capsys, qtbot, tmpdir):
"""Test the use of shorten paths when necessary in the switcher."""
switcher = main_window.switcher
switcher_widget = switcher._switcher
Expand Down Expand Up @@ -2490,20 +2490,35 @@ def example_def_2():
main_window.editor.set_current_filename(str(file_a))

code_editor = main_window.editor.get_focus_widget()
qtbot.waitUntil(lambda: code_editor.completions_available,
timeout=COMPLETION_TIMEOUT)
qtbot.waitUntil(
lambda: code_editor.completions_available,
timeout=COMPLETION_TIMEOUT
)

with qtbot.waitSignal(
code_editor.completions_response_signal,
timeout=COMPLETION_TIMEOUT):
code_editor.completions_response_signal,
timeout=COMPLETION_TIMEOUT
):
code_editor.request_symbols()

qtbot.wait(9000)

switcher.open_switcher()
qtbot.keyClicks(switcher_widget.edit, '@')
qtbot.wait(500)

# Capture stderr and assert there are no errors
sys_stream = capsys.readouterr()
assert sys_stream.err == ''

# Check number of items
assert switcher_widget.count() == 2

# Check that selecting different items in the switcher jumps to the
# corresponding line in the editor
switcher.set_current_row(1)
code_editor.textCursor().blockNumber() == 5

switcher.on_close()


Expand Down
62 changes: 40 additions & 22 deletions spyder/plugins/editor/utils/switcher_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
import os.path as osp

# Local imports
from spyder.api.config.mixins import SpyderConfigurationAccessor
from spyder.config.base import _
from spyder.config.manager import CONF
from spyder.utils.icon_manager import ima
from spyder.plugins.switcher.utils import shorten_paths, get_file_icon
from spyder.plugins.completion.api import SymbolKind, SYMBOL_KIND_ICON


class EditorSwitcherManager(object):
class EditorSwitcherManager(SpyderConfigurationAccessor):
"""
Switcher instance manager to handle base modes for an Editor.

Expand Down Expand Up @@ -56,11 +56,12 @@ def setup_switcher(self):
self._switcher.add_mode(self.SYMBOL_MODE, _('Go to Symbol in File'))
self._switcher.sig_mode_selected.connect(self.handle_switcher_modes)
self._switcher.sig_item_selected.connect(
self.handle_switcher_selection)
self._switcher.sig_text_changed.connect(self.handle_switcher_text)
self.handle_switcher_selection
)
self._switcher.sig_rejected.connect(self.handle_switcher_rejection)
self._switcher.sig_item_changed.connect(
self.handle_switcher_item_change)
self.handle_switcher_item_change
)
self._switcher.sig_search_text_available.connect(
lambda text: self._switcher.setup()
)
Expand Down Expand Up @@ -136,50 +137,66 @@ def create_symbol_switcher(self):
editor = self._editor()
language = editor.language
editor.update_whitespace_count(0, 0)

self._current_line = editor.get_cursor_line_number()
self._switcher.clear()
self._switcher.set_placeholder_text(_('Select symbol'))

oe_symbols = editor.oe_proxy.info or []
display_variables = CONF.get('outline_explorer', 'display_variables')
display_variables = self.get_conf(
'display_variables',
section='outline_explorer'
)

idx = 0
total_symbols = len(oe_symbols)
oe_symbols = sorted(
oe_symbols, key=lambda x: x['location']['range']['start']['line'])
oe_symbols, key=lambda x: x['location']['range']['start']['line']
)

for symbol in oe_symbols:
symbol_name = symbol['name']
symbol_kind = symbol['kind']
if language.lower() == 'python':
if symbol_kind == SymbolKind.MODULE:
total_symbols -= 1
continue
if (symbol_kind == SymbolKind.VARIABLE and
not display_variables):

if (
symbol_kind == SymbolKind.VARIABLE and
not display_variables
):
total_symbols -= 1
continue

if symbol_kind == SymbolKind.FIELD and not display_variables:
total_symbols -= 1
continue

symbol_range = symbol['location']['range']
symbol_start = symbol_range['start']['line']

fold_level = editor.leading_whitespaces[symbol_start]

space = ' ' * fold_level
formated_title = '{space}{title}'.format(title=symbol_name,
space=space)
formated_title = f'{space}{symbol_name}'

icon = ima.icon(SYMBOL_KIND_ICON.get(symbol_kind, 'no_match'))
data = {'title': symbol_name,
'line_number': symbol_start + 1}
data = {
'title': symbol_name,
'line_number': symbol_start + 1
}
last_item = idx + 1 == total_symbols
self._switcher.add_item(title=formated_title,
icon=icon,
section=self._section,
data=data,
last_item=last_item)

self._switcher.add_item(
title=formated_title,
icon=icon,
section=self._section,
data=data,
last_item=last_item
)

idx += 1
# Needed to update fold spaces for items titles

# Needed to update fold spaces for item titles
self._switcher.setup()

def handle_switcher_selection(self, item, mode, search_text):
Expand Down Expand Up @@ -217,9 +234,9 @@ def handle_switcher_rejection(self):

def handle_switcher_item_change(self, current):
"""Handle item selection change."""
editorstack = self._editorstack()
mode = self._switcher.get_mode()
if mode == '@' and current is not None:
editorstack = self._editorstack()
line_number = int(current.get_data()['line_number'])
editorstack.go_to_line(line_number)

Expand All @@ -238,6 +255,7 @@ def line_switcher_handler(self, data, search_text, visible=False):
line_number = int(line_number)
editorstack.go_to_line(line_number)
self._switcher.set_visible(visible)

# Closing the switcher
if not visible:
self._current_line = None
Expand Down
4 changes: 4 additions & 0 deletions spyder/plugins/switcher/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
"""
Spyder Switcher API.
"""

class SwitcherActions:
FileSwitcherAction = 'file switcher'
SymbolFinderAction = 'symbol finder'
44 changes: 31 additions & 13 deletions spyder/plugins/switcher/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@
# Third-party imports
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt.utils import signals_blocked

# Spyder imports
from spyder.api.translations import _
from spyder.api.widgets.main_container import PluginMainContainer
from spyder.plugins.switcher.api import SwitcherActions
from spyder.plugins.switcher.widgets.switcher import Switcher
from spyder.utils.stylesheet import APP_TOOLBAR_STYLESHEET


class SwitcherContainer(PluginMainContainer):

# --- PluginMainContainer API
# ------------------------------------------------------------------------
# ---- PluginMainContainer API
# -------------------------------------------------------------------------
def setup(self):
self.switcher = Switcher(self._plugin.get_main())

# Switcher shortcuts
self.create_action(
'file switcher',
SwitcherActions.FileSwitcherAction,
_('File switcher...'),
icon=self._plugin.get_icon(),
tip=_('Fast switch between files'),
Expand All @@ -36,7 +39,7 @@ def setup(self):
)

self.create_action(
'symbol finder',
SwitcherActions.SymbolFinderAction,
_('Symbol finder...'),
icon=self.create_icon('symbol_find'),
tip=_('Fast symbol search in file'),
Expand All @@ -48,8 +51,8 @@ def setup(self):
def update_actions(self):
pass

# --- Public API
# ------------------------------------------------------------------------
# ---- Public API
# -------------------------------------------------------------------------
def open_switcher(self, symbol=False):
"""Open switcher dialog."""
switcher = self.switcher
Expand All @@ -60,7 +63,14 @@ def open_switcher(self, symbol=False):

# Set mode and setup
if symbol:
switcher.set_search_text('@')
# Avoid emitting sig_search_text_available
with signals_blocked(switcher.edit):
switcher.set_search_text('@')

# Manually set mode and emit sig_mode_selected so that symbols are
# shown instantly.
switcher._mode_on = "@"
switcher.sig_mode_selected.emit("@")
else:
switcher.set_search_text('')

Expand All @@ -70,18 +80,26 @@ def open_switcher(self, symbol=False):
# Set position
mainwindow = self._plugin.get_main()

# Note: The +8 pixel on the top makes it look better
default_top = (mainwindow.toolbar.toolbars_menu.geometry().height() +
mainwindow.menuBar().geometry().height() + 8)
# Note: The +3 pixels makes it vertically align with the main menu or
# main menu + toolbar
default_top_without_toolbar = (
mainwindow.menuBar().geometry().height()
+ 3
)

default_top_with_toolbar = (
int(APP_TOOLBAR_STYLESHEET.BUTTON_HEIGHT.split("px")[0])
+ default_top_without_toolbar
)

current_window = QApplication.activeWindow()
if current_window == mainwindow:
if self.get_conf('toolbars_visible', section='toolbar'):
delta_top = default_top
delta_top = default_top_with_toolbar
else:
delta_top = mainwindow.menuBar().geometry().height() + 8
delta_top = default_top_without_toolbar
else:
delta_top = default_top
delta_top = default_top_with_toolbar

switcher.set_position(delta_top, current_window)
switcher.show()
Expand Down
Loading