diff --git a/spyder/plugins/editor/panels/scrollflag.py b/spyder/plugins/editor/panels/scrollflag.py index 5a761f24935..1ae496f039d 100644 --- a/spyder/plugins/editor/panels/scrollflag.py +++ b/spyder/plugins/editor/panels/scrollflag.py @@ -20,6 +20,7 @@ # Local imports from spyder.api.panel import Panel from spyder.plugins.completion.api import DiagnosticSeverity +from spyder.plugins.editor.utils.editor import is_block_safe REFRESH_RATE = 1000 @@ -221,7 +222,7 @@ def paintEvent(self, event): if editor.verticalScrollBar().maximum() == 0: # No scroll for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not is_block_safe(block): continue geometry = editor.blockBoundingGeometry(block) rect_y = ceil( @@ -233,7 +234,7 @@ def paintEvent(self, event): elif last_line == 0: # Only one line for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not is_block_safe(block): continue rect_y = ceil(first_y_pos) painter.drawRect(rect_x, rect_y, rect_w, rect_h) @@ -243,7 +244,7 @@ def paintEvent(self, event): # If the file is too long, do not freeze the editor next_line = 0 for block in dict_flag_lists[flag_type]: - if not block.isValid(): + if not is_block_safe(block): continue block_line = block.firstLineNumber() # block_line = -1 if invalid diff --git a/spyder/plugins/editor/utils/editor.py b/spyder/plugins/editor/utils/editor.py index 1b6b127d54d..28096710399 100644 --- a/spyder/plugins/editor/utils/editor.py +++ b/spyder/plugins/editor/utils/editor.py @@ -51,6 +51,24 @@ def drift_color(base_color, factor=110): return base_color.lighter(factor + 10) +def is_block_safe(block): + """ + Check if the block is safe to work with. + + A BlockUserData must have been set on the block while it was known to be + safe. + + If an editor is cleared by editor.clear() or editor.set_text() for example, + all old blocks will continue to report block.isValid() == True, but will + raise a segmentation fault on almost all methods. + + One way to check if a block is valid is that the userData is reset to None + or QTextBlockUserData. So if a block is known to have setUserData to + BlockUserData, this fact can be used to check the block. + """ + return block.isValid() and isinstance(block.userData(), BlockUserData) + + class BlockUserData(QTextBlockUserData): def __init__(self, editor, color=None, selection_start=None, selection_end=None): @@ -67,15 +85,6 @@ def __init__(self, editor, color=None, selection_start=None, self.selection_start = selection_start self.selection_end = selection_end - # Add a reference to the user data in the editor as the block won't. - # The list should /not/ be used to list BlockUserData as the blocks - # they refer to might not exist anymore. - # This prevents a segmentation fault. - if editor is None: - # Won't be destroyed - self.refloop = self - return - def _selection(self): """ Function to compute the selection. diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index d18c33548ea..73c3b23d486 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -2579,7 +2579,12 @@ def __mark_occurrences(self): extra_selections = self.get_extra_selections('occurrences') first_occurrence = None while cursor: - self.occurrences.append(cursor.block()) + block = cursor.block() + if not block.userData(): + # Add user data to check block validity + block.setUserData(BlockUserData(self)) + self.occurrences.append(block) + selection = self.get_selection(cursor) if len(selection.cursor.selectedText()) > 0: extra_selections.append(selection) @@ -2623,7 +2628,13 @@ def highlight_found_results(self, pattern, word=False, regexp=False, selection = TextDecoration(self.textCursor()) selection.format.setBackground(self.found_results_color) selection.cursor.setPosition(pos1) - self.found_results.append(selection.cursor.block()) + + block = selection.cursor.block() + if not block.userData(): + # Add user data to check block validity + block.setUserData(BlockUserData(self)) + self.found_results.append(block) + selection.cursor.setPosition(pos2, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('find', extra_selections)