def _on_key_pressed(self, event): """ Override key press to select the current scope if the user wants to deleted a folded scope (without selecting it). """ delete_request = event.key() in [Qt.Key_Backspace, Qt.Key_Delete] if event.text() or delete_request: cursor = self.editor.textCursor() if cursor.hasSelection(): # change selection to encompass the whole scope. positions_to_check = cursor.selectionStart( ), cursor.selectionEnd() else: positions_to_check = (cursor.position(), ) for pos in positions_to_check: block = self.editor.document().findBlock(pos) th = TextBlockHelper() if th.is_fold_trigger(block) and th.is_collapsed(block): self.toggle_fold_trigger(block) if delete_request and cursor.hasSelection(): scope = FoldScope(self.find_parent_scope(block)) tc = TextHelper( self.editor).select_lines(*scope.get_range()) if tc.selectionStart() > cursor.selectionStart(): start = cursor.selectionStart() else: start = tc.selectionStart() if tc.selectionEnd() < cursor.selectionEnd(): end = cursor.selectionEnd() else: end = tc.selectionEnd() tc.setPosition(start) tc.setPosition(end, tc.KeepAnchor) self.editor.setTextCursor(tc)
def _highlight_block(self, block): """ Highlights the current fold scope. :param block: Block that starts the current fold scope. """ scope = FoldScope(block) if (self._current_scope is None or self._current_scope.get_range() != scope.get_range()): self._current_scope = scope self._clear_scope_decos() # highlight current scope with darker or lighter color start, end = scope.get_range() if not TextBlockHelper.is_collapsed(block): self._decorate_block(start, end)
def _get_fold_levels(editor): """ Return a list of all the class/function definition ranges. Parameters ---------- editor : :class:`spyder.plugins.editor.widgets.codeeditor.CodeEditor` Returns ------- folds : list of :class:`FoldScopeHelper` A list of all the class or function defintion fold points. """ folds = [] parents = [] prev = None for oedata in editor.outlineexplorer_data_list(): if TextBlockHelper.is_fold_trigger(oedata.block): try: if oedata.def_type in (OED.CLASS, OED.FUNCTION): fsh = FoldScopeHelper(FoldScope(oedata.block), oedata) # Determine the parents of the item using a stack. _adjust_parent_stack(fsh, prev, parents) # Update the parents of this FoldScopeHelper item fsh.parents = copy.copy(parents) folds.append(fsh) prev = fsh except KeyError: pass return folds
def _highlight_caret_scope(self): """ Highlight the scope of the current caret position. This get called only if :attr:` spyder.widgets.panels.FoldingPanel.highlight_care_scope` is True. """ cursor = self.editor.textCursor() block_nbr = cursor.blockNumber() if self._block_nbr != block_nbr: block = FoldScope.find_parent_scope( self.editor.textCursor().block()) try: s = FoldScope(block) except ValueError: self._clear_scope_decos() else: self._mouse_over_line = block.blockNumber() if TextBlockHelper.is_fold_trigger(block): self._highlight_block(block) self._block_nbr = block_nbr
def _draw_fold_region_background(self, block, painter): """ Draw the fold region when the mouse is over and non collapsed indicator. :param top: Top position :param block: Current block. :param painter: QPainter """ r = FoldScope(block) th = TextHelper(self.editor) start, end = r.get_range(ignore_blank_lines=True) if start > 0: top = th.line_pos_from_number(start) else: top = 0 bottom = th.line_pos_from_number(end + 1) h = bottom - top if h == 0: h = self.sizeHint().height() w = self.sizeHint().width() self._draw_rect(QRectF(0, top, w, h), painter)
def unfold_if_colapsed(self, block): """Unfold parent fold trigger if the block is collapsed. :param block: Block to unfold. """ try: folding_panel = self._editor.panels.get('FoldingPanel') except KeyError: pass else: from spyder.plugins.editor.utils.folding import FoldScope if not block.isVisible(): block = FoldScope.find_parent_scope(block) if TextBlockHelper.is_collapsed(block): folding_panel.toggle_fold_trigger(block)
class TestFoldScopeHelper(object): test_case = """# -*- coding: utf-8 -*- def my_add(): a = 1 b = 2 return a + b """ doc = QTextDocument(test_case) sh = PythonSH(doc, color_scheme='Spyder') sh.fold_detector = IndentFoldDetector() sh.rehighlightBlock(doc.firstBlock()) block = doc.firstBlock() block = block.next() TextBlockHelper.set_fold_trigger(block, True) fold_scope = FoldScope(block) oed = block.userData().oedata def test_fold_scope_helper(self): fsh = cfd.FoldScopeHelper(None, None) assert isinstance(fsh, cfd.FoldScopeHelper) def test_fold_scope_helper_str(self): fsh = cfd.FoldScopeHelper(self.fold_scope, self.oed) assert "my_add" in str(fsh) def test_fold_scope_helper_str_with_parents(self): fsh = cfd.FoldScopeHelper(self.fold_scope, self.oed) fsh.parents = ["fake parent list!"] assert "parents:" in str(fsh) def test_fold_scope_helper_repr(self): fsh = cfd.FoldScopeHelper(self.fold_scope, self.oed) assert "(at 0x" in repr(fsh) def test_fold_scope_helper_properties(self): fsh = cfd.FoldScopeHelper(self.fold_scope, self.oed) assert fsh.range == (1, 4) assert fsh.start_line == 1 assert fsh.end_line == 4 assert fsh.name == "my_add" assert fsh.line == 1 assert fsh.def_type == OED.FUNCTION_TOKEN
def toggle_fold_trigger(self, block): """ Toggle a fold trigger block (expand or collapse it). :param block: The QTextBlock to expand/collapse """ if not TextBlockHelper.is_fold_trigger(block): return region = FoldScope(block) if region.collapsed: region.unfold() if self._mouse_over_line is not None: self._decorate_block(*region.get_range()) else: region.fold() self._clear_scope_decos() self._refresh_editor_and_scrollbars() self.trigger_state_changed.emit(region._trigger, region.collapsed)
def paintEvent(self, event): # Paints the fold indicators and the possible fold region background # on the folding panel. super(FoldingPanel, self).paintEvent(event) painter = QPainter(self) # Draw background over the selected non collapsed fold region if self._mouse_over_line is not None: block = self.editor.document().findBlockByNumber( self._mouse_over_line) try: self._draw_fold_region_background(block, painter) except ValueError: pass # Draw fold triggers for top_position, line_number, block in self.editor.visible_blocks: if TextBlockHelper.is_fold_trigger(block): collapsed = TextBlockHelper.is_collapsed(block) mouse_over = self._mouse_over_line == line_number self._draw_fold_indicator(top_position, mouse_over, collapsed, painter) if collapsed: # check if the block already has a decoration, it might # have been folded by the parent editor/document in the # case of cloned editor for deco in self._block_decos: if deco.block == block: # no need to add a deco, just go to the next block break else: self._add_fold_decoration(block, FoldScope(block)) else: for deco in self._block_decos: # check if the block decoration has been removed, it # might have been unfolded by the parent # editor/document in the case of cloned editor if deco.block == block: # remove it and self._block_decos.remove(deco) self.editor.decorations.remove(deco) del deco break
def _get_fold_levels(editor): """ Return a list of all the class/function definition ranges. Parameters ---------- editor : :class:`spyder.plugins.editor.widgets.codeeditor.CodeEditor` Returns ------- folds : list of :class:`FoldScopeHelper` A list of all the class or function defintion fold points. """ block = editor.document().firstBlock() oed = editor.get_outlineexplorer_data() folds = [] parents = [] prev = None while block.isValid(): if TextBlockHelper.is_fold_trigger(block): try: data = oed[block.firstLineNumber()] if data.def_type in (OED.CLASS, OED.FUNCTION): fsh = FoldScopeHelper(FoldScope(block), data) # Determine the parents of the item using a stack. _adjust_parent_stack(fsh, prev, parents) # Update the parents of this FoldScopeHelper item fsh.parents = copy.copy(parents) folds.append(fsh) prev = fsh except KeyError: pass block = block.next() return folds
def mouseMoveEvent(self, event): """ Detect mouser over indicator and highlight the current scope in the editor (up and down decoration arround the foldable text when the mouse is over an indicator). :param event: event """ super(FoldingPanel, self).mouseMoveEvent(event) th = TextHelper(self.editor) line = th.line_nbr_from_position(event.pos().y()) if line >= 0: block = FoldScope.find_parent_scope( self.editor.document().findBlockByNumber(line - 1)) if TextBlockHelper.is_fold_trigger(block): if self._mouse_over_line is None: # mouse enter fold scope QApplication.setOverrideCursor( QCursor(Qt.PointingHandCursor)) if self._mouse_over_line != block.blockNumber() and \ self._mouse_over_line is not None: # fold scope changed, a previous block was highlighter so # we quickly update our highlighting self._mouse_over_line = block.blockNumber() self._highlight_block(block) else: # same fold scope, request highlight self._mouse_over_line = block.blockNumber() self._highlight_runner.request_job(self._highlight_block, block) self._highight_block = block else: # no fold scope to highlight, cancel any pending requests self._highlight_runner.cancel_requests() self._mouse_over_line = None QApplication.restoreOverrideCursor() self.repaint()
def _on_action_toggle(self): """Toggle the current fold trigger.""" block = FoldScope.find_parent_scope(self.editor.textCursor().block()) self.toggle_fold_trigger(block)