diff --git a/spyder/plugins/editor/widgets/tests/test_save.py b/spyder/plugins/editor/widgets/tests/test_save.py index f9f73150c0d..02cc7acd87c 100644 --- a/spyder/plugins/editor/widgets/tests/test_save.py +++ b/spyder/plugins/editor/widgets/tests/test_save.py @@ -10,6 +10,7 @@ # Standard library imports import os.path as osp +from textwrap import dedent from unittest.mock import Mock # Third party imports @@ -298,38 +299,86 @@ def test_save_as(editor_bot, mocker): @pytest.mark.show_save_dialog -def test_save_as_with_outline(editor_bot, mocker, tmpdir): +def test_save_as_with_outline(completions_editor, mocker, qtbot, tmpdir): """ - Test EditorStack.save_as() when the outline explorer is not None. + Test EditorStack.save_as() when the outline explorer is active. - Regression test for spyder-ide/spyder#7754. + Regression test for issues spyder-ide/spyder#7754 and + spyder-ide/spyder#15517. """ - editorstack, qtbot = editor_bot - - # Set and assert the initial state. - editorstack.tabs.setCurrentIndex(1) - assert editorstack.get_current_filename() == 'secondtab.py' - - # Add an outline explorer to the editor stack and refresh it. - editorstack.set_outlineexplorer(OutlineExplorerWidget(None, None, None)) + file_path, editorstack, code_editor, completion_plugin = completions_editor + proxy = code_editor.oe_proxy + + # Set outline explorer to editor stack and refresh it. + outline_explorer = OutlineExplorerWidget(None, None, None) + treewidget = outline_explorer.treewidget + outline_explorer.show() + editorstack.set_outlineexplorer(outline_explorer) qtbot.addWidget(editorstack.outlineexplorer) - for finfo in editorstack.data: - editorstack.outlineexplorer.register_editor(finfo.editor.oe_proxy) + editorstack.outlineexplorer.register_editor(proxy) + + outline_explorer.start_symbol_services('python') editorstack.refresh() + # Add some code to the test file and save it + code = dedent(""" + def foo(x): + return x + + def bar(y): + return y + """) + + code_editor.set_text(code) + editorstack.save(force=True) + + # Notify changes + with qtbot.waitSignal( + code_editor.completions_response_signal, timeout=30000 + ): + code_editor.document_did_change() + + # Wait until the outline is filled + qtbot.waitUntil( + lambda: len(treewidget.editor_tree_cache[proxy.get_id()]) > 0, + timeout=5000 + ) + # No save name. mocker.patch.object(editorstack, 'select_savename', return_value=None) assert editorstack.save_as() is False - assert editorstack.get_filenames() == ['foo.py', 'secondtab.py', __file__] - # Save secondtab.py as foo2.py in a tmpdir + # Save file as foo2.py in tmpdir new_filename = osp.join(tmpdir.strpath, 'foo2.py') editorstack.select_savename.return_value = new_filename assert not osp.exists(new_filename) - assert editorstack.save_as() is True - assert editorstack.get_filenames() == ['foo.py', new_filename, __file__] + + # Symbols should have been requested for the renamed file, which emits the + # signals below. + with qtbot.waitSignals( + [proxy.sig_start_outline_spinner, + proxy.sig_outline_explorer_data_changed], + timeout=30000 + ): + assert editorstack.save_as() is True + + assert editorstack.get_filenames() == [new_filename] assert osp.exists(new_filename) + # Wait until the outline is filled + qtbot.waitUntil( + lambda: len(treewidget.editor_tree_cache[proxy.get_id()]) > 0, + timeout=5000 + ) + + # Assert root and symbol items have the right path. + items = treewidget.editor_items[proxy.get_id()] + root_item = items.node + + assert root_item.path == new_filename + assert items.path == new_filename + assert all([item.path == new_filename for item in items.children]) + def test_save_copy_as(editor_bot, mocker): """Test EditorStack.save_copy as().""" diff --git a/spyder/plugins/outlineexplorer/editor.py b/spyder/plugins/outlineexplorer/editor.py index be3c2152473..aeb20c3b991 100644 --- a/spyder/plugins/outlineexplorer/editor.py +++ b/spyder/plugins/outlineexplorer/editor.py @@ -6,9 +6,6 @@ """Outline explorer editor server""" -# Third-party imports -from intervaltree import IntervalTree - # Local imports from spyder.plugins.outlineexplorer.api import OutlineExplorerProxy diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json b/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json index c1c4260149a..855e4fbb9e5 100644 --- a/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json +++ b/spyder/plugins/outlineexplorer/tests/assets/text_exp_test_widgets.json @@ -1,50 +1,106 @@ { "text_test_widgets.py": [ { - "d": [ + "functions": [ { - "inner": [] - } - ] - }, - { - "func1": [ + "d": [ + { + "inner": [], + "kind": 12 + } + ], + "kind": 12 + }, { - "i": [] - } - ] - }, - { - "func2": [] - }, - { - "a": [] - }, - { - "b": [] - }, - { - "c": [] - }, - { - "Class1": [ + "func 1 and 2": [], + "kind": 224 + }, { - "__init__": [ + "func1": [ { - "x": [] + "i": [], + "kind": 13 } - ] + ], + "kind": 12 }, { - "method3": [] + "func2": [], + "kind": 12 }, { - "method2": [] + "other functions": [], + "kind": 224 }, { - "method1": [] + "cell level 2": [ + { + "a": [], + "kind": 12 + }, + { + "cell level 3": [ + { + "b": [], + "kind": 12 + }, + { + "cell level 4": [ + { + "c": [], + "kind": 12 + } + ], + "kind": 225 + } + ], + "kind": 225 + }, + { + "cell level 3-1": [ + { + "func3": [], + "kind": 12 + } + ], + "kind": 225 + } + ], + "kind": 225 + } + ], + "kind": 225 + }, + { + "classes": [ + { + "Class1": [ + { + "__init__": [ + { + "x": [], + "kind": 8 + } + ], + "kind": 6 + }, + { + "method3": [], + "kind": 6 + }, + { + "method2": [], + "kind": 6 + }, + { + "method1": [], + "kind": 6 + } + ], + "kind": 5 } - ] + ], + "kind": 225 } ] } \ No newline at end of file diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json index d583f276573..7ffc0362ac3 100644 --- a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json +++ b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.json @@ -1,7 +1,7 @@ [ { "name": "d", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { @@ -37,7 +37,7 @@ }, { "name": "func1", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { @@ -73,7 +73,7 @@ }, { "name": "func2", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { @@ -91,16 +91,16 @@ }, { "name": "a", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 32, + "line": 33, "character": 0 }, "end": { - "line": 34, + "line": 35, "character": 0 } } @@ -109,16 +109,16 @@ }, { "name": "b", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 35, + "line": 37, "character": 0 }, "end": { - "line": 37, + "line": 39, "character": 0 } } @@ -127,16 +127,34 @@ }, { "name": "c", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 38, + "line": 41, "character": 0 }, "end": { - "line": 40, + "line": 43, + "character": 0 + } + } + }, + "kind": 12 + }, + { + "name": "func3", + "containerName": "None", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 45, + "character": 0 + }, + "end": { + "line": 47, "character": 0 } } @@ -145,16 +163,16 @@ }, { "name": "Class1", - "containerName": null, + "containerName": "None", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 42, + "line": 49, "character": 0 }, "end": { - "line": 55, + "line": 62, "character": 0 } } @@ -168,87 +186,231 @@ "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 43, + "line": 50, "character": 4 }, "end": { - "line": 46, + "line": 53, "character": 0 } } }, - "kind": 12 + "kind": 6 }, { - "name": "x", - "containerName": "__init__", + "name": "method3", + "containerName": "Class1", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 45, - "character": 8 + "line": 54, + "character": 4 }, "end": { - "line": 45, - "character": 18 + "line": 56, + "character": 0 } } }, - "kind": 13 + "kind": 6 }, { - "name": "method3", + "name": "method2", "containerName": "Class1", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 47, + "line": 57, "character": 4 }, "end": { - "line": 49, + "line": 59, "character": 0 } } }, - "kind": 12 + "kind": 6 }, { - "name": "method2", + "name": "method1", "containerName": "Class1", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 50, + "line": 60, "character": 4 }, + "end": { + "line": 62, + "character": 0 + } + } + }, + "kind": 6 + }, + { + "name": "x", + "containerName": "__init__", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 52, + "character": 8 + }, "end": { "line": 52, + "character": 18 + } + } + }, + "kind": 8 + }, + { + "name": "functions", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 12, + "character": 0 + }, + "end": { + "line": 47, "character": 0 } } }, - "kind": 12 + "kind": 225 }, { - "name": "method1", - "containerName": "Class1", + "name": "func 1 and 2", + "containerName": "", + "location": { + "uri":"file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 19, + "character": 0 + }, + "end": { + "line": 19, + "character": 0 + } + } + }, + "kind": 224 + }, + { + "name": "other functions", + "containerName": "", "location": { "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", "range": { "start": { - "line": 53, - "character": 4 + "line": 30, + "character": 0 }, "end": { - "line": 55, + "line": 30, "character": 0 } } }, - "kind": 12 + "kind": 224 + }, + { + "name": "cell level 2", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 32, + "character": 0 + }, + "end": { + "line": 47, + "character": 0 + } + } + }, + "kind": 225 + }, + { + "name": "cell level 3", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 36, + "character": 0 + }, + "end": { + "line": 43, + "character": 0 + } + } + }, + "kind": 225 + }, + { + "name": "cell level 4", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 40, + "character": 0 + }, + "end": { + "line": 43, + "character": 0 + } + } + }, + "kind": 225 + }, + { + "name": "cell level 3-1", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 44, + "character": 0 + }, + "end": { + "line": 47, + "character": 0 + } + } + }, + "kind": 225 + }, + { + "name": "classes", + "containerName": "", + "location": { + "uri": "file:///home/andfoy/.config/spyder-py3-dev/temp.py", + "range": { + "start": { + "line": 48, + "character": 0 + }, + "end": { + "line": 61, + "character": 0 + } + } + }, + "kind": 225 } ] \ No newline at end of file diff --git a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py index 844cf582438..70157cde7c8 100644 --- a/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py +++ b/spyder/plugins/outlineexplorer/tests/assets/text_test_widgets.py @@ -30,15 +30,22 @@ def func2(): # ---- other functions +# %%% cell level 2 def a(): pass +# %%%% cell level 3 def b(): pass +# %%%%% cell level 4 def c(): pass +# %%%% cell level 3-1 +def func3(): + pass + # %% classes class Class1: def __init__(self): diff --git a/spyder/plugins/outlineexplorer/tests/test_widgets.py b/spyder/plugins/outlineexplorer/tests/test_widgets.py index 67e3f06502d..c17cd2cab64 100644 --- a/spyder/plugins/outlineexplorer/tests/test_widgets.py +++ b/spyder/plugins/outlineexplorer/tests/test_widgets.py @@ -5,13 +5,13 @@ # """ -Tests for editortool.py +Tests for the Outline explorer widgets. """ + # Standard Libray Imports import json import os.path as osp import sys -from textwrap import dedent from unittest.mock import MagicMock # Third party imports @@ -23,10 +23,10 @@ from spyder.plugins.outlineexplorer.editor import OutlineExplorerProxyEditor from spyder.plugins.outlineexplorer.main_widget import ( OutlineExplorerWidget, OutlineExplorerToolbuttons) -from spyder.plugins.outlineexplorer.widgets import ( - FileRootItem, SymbolStatus, TreeItem) from spyder.plugins.editor.widgets.codeeditor import CodeEditor + +# ---- Constants HERE = osp.abspath(osp.dirname(__file__)) ASSETS = osp.join(HERE, 'assets') SUFFIX = 'test_widgets' @@ -41,97 +41,8 @@ for case in AVAILABLE_CASES } -CODE = """# -*- coding: utf-8 -*- - - def function0(x): - return x - - # %% Top level 1 - def function1(x): - return x - - x = function1(x) - - # %%% Cell Level 1-1 - q = 3 - w = 'word' - - # %%% Cell Level 1-2 - def function2(x): - def inside(x): - return x - return x - - y = function2(x) - - # %%%% Cell Level 2 - class Class2(x): - def __init__(x): - return x - async def medthod1(x): - if x: - return x - - # %%%%%% Cell level 4 - def function4(x): - return x - - # %%%%% Cell Level 3 - def function5(x): - return x - - # %%%%%%%% Cell Level 6 - class Class3(x): - def __init__(x): - return x - def medthod1(x): - if x: - return x - - # %% Top level 2 - class Class4(x): - def __init__(x): - return x - def medthod1(x): - if x: - return x - - z = Class1(x) - - if __name__ == "__main__": - - # %% MGroup3 - def function6(x): - return x - # %%% MGroup4 - x = 'test' - # %% Unnamed Cell - - # %%% - - # %% Unnamed Cell - - # %% Unnamed Cell, #1 - - # %%% Unnamed Cell, #1 - - # %% - - # %% a - def a(): - pass - - # %% a - - # %% b - - def b(): - pass -""" - - -# ---- Qt Test Fixtures +# ---- Fixtures @pytest.fixture def create_outlineexplorer(qtbot): def _create_outlineexplorer(case, follow_cursor=False): @@ -145,6 +56,7 @@ def _create_outlineexplorer(case, follow_cursor=False): code_editor = CodeEditor(None) code_editor.set_language('py', filename) + code_editor.show() code_editor.set_text(text) editor = OutlineExplorerProxyEditor(code_editor, filename) @@ -168,11 +80,12 @@ def _create_outlineexplorer(case, follow_cursor=False): editor.update_outline_info(symbol_info) qtbot.addWidget(outlineexplorer) + qtbot.addWidget(code_editor) return outlineexplorer, expected_tree return _create_outlineexplorer -# ---- Test OutlineExplorerWidget +# ---- Tests @pytest.mark.parametrize('case', AVAILABLE_CASES) def test_outline_explorer(case, create_outlineexplorer): """ @@ -185,7 +98,6 @@ def test_outline_explorer(case, create_outlineexplorer): outlineexplorer.treewidget.expandAll() tree_widget = outlineexplorer.treewidget - editor = tree_widget.current_editor root_item = tree_widget.get_top_level_items()[0] root_ref = root_item.ref @@ -195,7 +107,7 @@ def test_outline_explorer(case, create_outlineexplorer): while len(stack) > 0: parent_tree, node = stack.pop(0) - this_tree = {node.name: []} + this_tree = {node.name: [], 'kind': node.kind} parent_tree.append(this_tree) this_stack = [(this_tree[node.name], child) for child in node.children] stack = this_stack + stack @@ -212,10 +124,11 @@ def test_go_to_cursor_position(create_outlineexplorer, qtbot): Regression test for spyder-ide/spyder#7729. """ outlineexplorer, _ = create_outlineexplorer('text') - # Move the mouse cursor in the editor to line 31 : + + # Move the mouse cursor in the editor to line 15 editor = outlineexplorer.treewidget.current_editor editor._editor.go_to_line(15) - assert editor._editor.get_text_line(15) == " return 2" + assert editor._editor.get_text_line(14) == " def inner():" # Click on the 'Go to cursor position' button of the outline explorer's # toolbar : @@ -232,10 +145,13 @@ def test_follow_cursor(create_outlineexplorer, qtbot): Test that the cursor is followed. """ outlineexplorer, _ = create_outlineexplorer('text', follow_cursor=True) - # Move the mouse cursor in the editor to line 45 : + + # Move the mouse cursor in the editor to line 52 editor = outlineexplorer.treewidget.current_editor - editor._editor.go_to_line(45) - assert editor._editor.get_text_line(45) == " self.x = 2" + editor._editor.go_to_line(52) + assert editor._editor.get_text_line(51) == \ + " super(Class1, self).__init__()" + # __init__ is collapsed assert outlineexplorer.treewidget.currentItem().text(0) == '__init__' @@ -248,8 +164,10 @@ def test_follow_cursor(create_outlineexplorer, qtbot): editor._editor.go_to_line(1) text = outlineexplorer.treewidget.currentItem().text(0) assert text == CASES['text']['file'] + editor._editor.go_to_line(37) - assert outlineexplorer.treewidget.currentItem().text(0) == 'b' + assert editor._editor.get_text_line(36) == "# %%%% cell level 3" + assert outlineexplorer.treewidget.currentItem().text(0) == 'cell level 3' @flaky(max_runs=10) @@ -298,120 +216,6 @@ def test_go_to_last_item(create_outlineexplorer, qtbot): assert outlineexplorer.treewidget.currentItem().text(0) == 'method1' -@pytest.mark.skip(reason='Cell support is disabled temporarily') -def test_code_cell_grouping(create_outlineexplorer): - """ - Test to assert the outline explorer is initializing correctly and - is showing the expected number of items, the expected type of items, and - the expected text for each item. In addition this tests ancestry, code - cells comments, code cell grouping and disabling this feature. - """ - outlineexplorer = create_outlineexplorer(dedent(CODE), 'test_file.py') - assert outlineexplorer - - expected_results = [ - ('test_file.py', FileRootItem), - ('function0', FunctionItem, 'test_file.py', 'test_file.py', False), - ('Top level 1', CellItem, 'test_file.py', 'test_file.py'), - ('function1', FunctionItem, 'Top level 1', 'test_file.py', False), - ('Cell Level 1-1', CellItem, 'Top level 1', 'test_file.py'), - ('Cell Level 1-2', CellItem, 'Top level 1', 'test_file.py'), - ('function2', FunctionItem, 'Cell Level 1-2', 'test_file.py', False), - ('inside', FunctionItem, 'function2', 'function2', False), - ('Cell Level 2', CellItem, 'Cell Level 1-2', 'test_file.py'), - ('Class2', ClassItem, 'Cell Level 2', 'test_file.py'), - ('__init__', FunctionItem, 'Class2', 'Class2', True), - ('medthod1', FunctionItem, 'Class2', 'Class2', True), - ('Cell level 4', CellItem, 'Cell Level 2', 'test_file.py'), - ('function4', FunctionItem, 'Cell level 4', 'test_file.py', False), - ('Cell Level 3', CellItem, 'Cell Level 1-2', 'test_file.py'), - ('function5', FunctionItem, 'Cell Level 3', 'test_file.py', False), - ('Cell Level 6', CellItem, 'Cell Level 3', 'test_file.py'), - ('Class3', ClassItem, 'Cell Level 6', 'test_file.py'), - ('__init__', FunctionItem, 'Class3', 'Class3', True), - ('medthod1', FunctionItem, 'Class3', 'Class3', True), - ('Top level 2', CellItem, 'test_file.py', 'test_file.py'), - ('Class4', ClassItem, 'Top level 2', 'test_file.py'), - ('__init__', FunctionItem, 'Class4', 'Class4', True), - ('medthod1', FunctionItem, 'Class4', 'Class4', True), - ('MGroup3', CellItem, 'test_file.py', 'test_file.py'), - ('function6', FunctionItem, 'MGroup3', 'MGroup3', False), - ('MGroup4', CellItem, 'MGroup3', 'test_file.py'), - ('Unnamed Cell, #2', CellItem, 'test_file.py', 'test_file.py'), - ('Unnamed Cell, #3', CellItem, 'Unnamed Cell, #2', 'test_file.py'), - ('Unnamed Cell, #4', CellItem, 'test_file.py', 'test_file.py'), - ('Unnamed Cell, #1, #1', CellItem, 'test_file.py', 'test_file.py'), - ('Unnamed Cell, #1, #2', CellItem, 'Unnamed Cell, #1, #1', - 'test_file.py'), - ('Unnamed Cell, #5', CellItem, 'test_file.py', 'test_file.py'), - ('a, #1', CellItem, 'test_file.py', 'test_file.py'), - ('a', FunctionItem, 'a, #1', 'test_file.py', False), - ('a, #2', CellItem, 'test_file.py', 'test_file.py'), - ('b', CellItem, 'test_file.py', 'test_file.py'), - ('b', FunctionItem, 'b', 'test_file.py', False), - ] - - outlineexplorer.treewidget.expandAll() - tree_widget = outlineexplorer.treewidget - cell_items = tree_widget.get_top_level_items() + tree_widget.get_items() - - # Assert that the expected number, text, ancestry and type of cell items is - # displayed in the tree. - assert len(cell_items) == len(expected_results) - for item, expected_result in zip(cell_items, expected_results): - assert item.text(0) == expected_result[0] - assert type(item) == expected_result[1] - if type(item) != FileRootItem: - assert item.parent().text(0) == expected_result[2] - if type(item) == FunctionItem: - assert item.is_method() == expected_result[4] - - # Disable cell groups - tree_widget.toggle_group_cells(False) - tree_widget.expandAll() - flat_items = tree_widget.get_top_level_items() + tree_widget.get_items() - - # Assert that the expected number, text, ancestry and type of flat items is - # displayed in the tree. - assert len(flat_items) == len(expected_results) - for item, expected_result in zip(flat_items, expected_results): - assert item.text(0) == expected_result[0] - assert type(item) == expected_result[1] - if type(item) != FileRootItem: - assert item.parent().text(0) == expected_result[3] - if type(item) == FunctionItem: - assert item.is_method() == expected_result[4] - - # Change back to cell groups - tree_widget.toggle_group_cells(True) - tree_widget.expandAll() - cell_items2 = tree_widget.get_top_level_items() + tree_widget.get_items() - - # Assert that the expected number, text, ancestry and type of flat items is - # displayed in the tree. - assert len(cell_items2) == len(expected_results) - for item, expected_result in zip(cell_items2, expected_results): - assert item.text(0) == expected_result[0] - assert type(item) == expected_result[1] - if type(item) != FileRootItem: - assert item.parent().text(0) == expected_result[2] - if type(item) == FunctionItem: - assert item.is_method() == expected_result[4] - -# Code used to create expected_results -# ============================================================================= -# for item in cell_items2: -# if type(item) == FunctionItem: -# print(f"('{item.text(0)}', {type(item).__name__}, " -# f"'{item.parent().text(0)}', {item.is_method()}),") -# elif type(item) == FileRootItem: -# print(f"('{item.text(0)}', {type(item).__name__}),") -# else: -# print(f"('{item.text(0)}', {type(item).__name__}, " -# f"'{item.parent().text(0)}'),") -# ============================================================================= - - if __name__ == "__main__": import os pytest.main(['-x', os.path.basename(__file__), '-v', '-rw']) diff --git a/spyder/plugins/outlineexplorer/widgets.py b/spyder/plugins/outlineexplorer/widgets.py index bce7680e4d8..5364cb9199a 100644 --- a/spyder/plugins/outlineexplorer/widgets.py +++ b/spyder/plugins/outlineexplorer/widgets.py @@ -15,14 +15,12 @@ from intervaltree import IntervalTree from pkg_resources import parse_version from qtpy import PYSIDE2 -from qtpy.compat import from_qvariant from qtpy.QtCore import Qt, QTimer, Signal, Slot from qtpy.QtWidgets import QTreeWidgetItem, QTreeWidgetItemIterator # Local imports from spyder.api.config.decorators import on_conf_change from spyder.config.base import _ -from spyder.py3compat import to_text_string from spyder.utils.icon_manager import ima from spyder.plugins.completion.api import SymbolKind, SYMBOL_KIND_ICON from spyder.utils.qthelpers import set_item_user_text @@ -93,7 +91,10 @@ def delete(self): self.parent.remove_node(self) self.parent = None - if self.node.parent is not None: + if ( + self.node.parent is not None + and hasattr(self.node.parent, 'remove_children') + ): self.node.parent.remove_children(self.node) def add_node(self, node): @@ -153,6 +154,10 @@ def create_node(self): self.position[0] + 1, self.status, self.selected) + def set_path(self, new_path): + self.name = new_path + self.path = new_path + def __repr__(self): return str(self) @@ -215,61 +220,6 @@ def update_info(self, name, kind, position, status, selected): self.setSelected(selected) -class TreeItem(QTreeWidgetItem): - """Class browser item base class.""" - def __init__(self, oedata, parent, preceding): - if preceding is None: - QTreeWidgetItem.__init__(self, parent, QTreeWidgetItem.Type) - else: - if preceding is not parent: - # Preceding must be either the same as item's parent - # or have the same parent as item - while preceding.parent() is not parent: - preceding = preceding.parent() - if preceding is None: - break - if preceding is None: - QTreeWidgetItem.__init__(self, parent, QTreeWidgetItem.Type) - else: - QTreeWidgetItem.__init__(self, parent, preceding, - QTreeWidgetItem.Type) - self.parent_item = parent - self.oedata = oedata - oedata.sig_update.connect(self.update) - self.update() - - def level(self): - """Get fold level.""" - return self.oedata.fold_level - - def get_name(self): - """Get the item name.""" - return self.oedata.def_name - - def set_icon(self, icon): - self.setIcon(0, icon) - - def setup(self): - self.setToolTip(0, _("Line %s") % str(self.line)) - - @property - def line(self): - """Get line number.""" - block_number = self.oedata.get_block_number() - if block_number is not None: - return block_number + 1 - return None - - def update(self): - """Update the tree element.""" - name = self.get_name() - self.setText(0, name) - parent_text = from_qvariant(self.parent_item.data(0, Qt.UserRole), - to_text_string) - set_item_user_text(self, parent_text + '/' + name) - self.setup() - - # ---- Treewidget # ----------------------------------------------------------------------------- class OutlineExplorerTreeWidget(OneColumnTree): @@ -513,12 +463,26 @@ def file_renamed(self, editor, new_filename): if editor is None: # This is needed when we can't find an editor to attach # the outline explorer to. - # Fix spyder-ide/spyder#8813. + # Fixes spyder-ide/spyder#8813. return + editor_id = editor.get_id() if editor_id in list(self.editor_ids.values()): - root_item = self.editor_items[editor_id].node + items = self.editor_items[editor_id] + + # Set path for items + items.set_path(new_filename) + + # Change path of root item (i.e. the file name) + root_item = items.node root_item.set_path(new_filename, fullpath=self.show_fullpath) + + # Clear and re-populate the tree again. + # Fixes spyder-ide/spyder#15517 + items.delete() + editor.request_symbols() + + # Resort root items self.__sort_toplevel_items() def update_editors(self, language):