def find_parent_scope(block): """ Find parent scope, if the block is not a fold trigger. :param block: block from which the research will start """ # if we moved up for more than n lines, just give up otherwise this # would take too much time. limit = 5000 counter = 0 original = block if not TextBlockHelper.is_fold_trigger(block): # search level of next non blank line while block.text().strip() == '' and block.isValid(): block = block.next() ref_lvl = TextBlockHelper.get_fold_lvl(block) - 1 block = original while (block.blockNumber() and counter < limit and (not TextBlockHelper.is_fold_trigger(block) or TextBlockHelper.get_fold_lvl(block) > ref_lvl)): counter += 1 block = block.previous() if counter < limit: return block return None
def process_block(self, current_block, previous_block, text): """ Processes a block and setup its folding info. This method call ``detect_fold_level`` and handles most of the tricky corner cases so that all you have to do is focus on getting the proper fold level foreach meaningful block, skipping the blank ones. :param current_block: current block to process :param previous_block: previous block :param text: current block text """ prev_fold_level = TextBlockHelper.get_fold_lvl(previous_block) if text.strip() == '' or self.editor.is_comment(current_block): # blank or comment line always have the same level # as the previous line fold_level = prev_fold_level else: fold_level = self.detect_fold_level( previous_block, current_block) if fold_level > self.limit: fold_level = self.limit prev_fold_level = TextBlockHelper.get_fold_lvl(previous_block) if fold_level > prev_fold_level: # apply on previous blank or comment lines block = current_block.previous() while block.isValid() and (block.text().strip() == '' or self.editor.is_comment(block)): TextBlockHelper.set_fold_lvl(block, fold_level) block = block.previous() TextBlockHelper.set_fold_trigger( block, True) # update block fold level if text.strip() and not self.editor.is_comment(previous_block): TextBlockHelper.set_fold_trigger( previous_block, fold_level > prev_fold_level) TextBlockHelper.set_fold_lvl(current_block, fold_level) # user pressed enter at the beginning of a fold trigger line # the previous blank or comment line will keep the trigger state # and the new line (which actually contains the trigger) must use # the prev state (and prev state must then be reset). prev = current_block.previous() # real prev block (may be blank) if (prev and prev.isValid() and (prev.text().strip() == '' or self.editor.is_comment(prev)) and TextBlockHelper.is_fold_trigger(prev)): # prev line has the correct trigger fold state TextBlockHelper.set_collapsed( current_block, TextBlockHelper.is_collapsed( prev)) # make empty or comment line not a trigger TextBlockHelper.set_fold_trigger(prev, False) TextBlockHelper.set_collapsed(prev, False)
def process_block(self, current_block, previous_block, text): """ Processes a block and setup its folding info. This method call ``detect_fold_level`` and handles most of the tricky corner cases so that all you have to do is focus on getting the proper fold level foreach meaningful block, skipping the blank ones. :param current_block: current block to process :param previous_block: previous block :param text: current block text """ prev_fold_level = TextBlockHelper.get_fold_lvl(previous_block) if text.strip() == '' or self.editor.is_comment(current_block): # blank or comment line always have the same level # as the previous line fold_level = prev_fold_level else: fold_level = self.detect_fold_level(previous_block, current_block) if fold_level > self.limit: fold_level = self.limit prev_fold_level = TextBlockHelper.get_fold_lvl(previous_block) if fold_level > prev_fold_level: # apply on previous blank or comment lines block = current_block.previous() while block.isValid() and (block.text().strip() == '' or self.editor.is_comment(block)): TextBlockHelper.set_fold_lvl(block, fold_level) block = block.previous() TextBlockHelper.set_fold_trigger(block, True) # update block fold level if text.strip() and not self.editor.is_comment(previous_block): TextBlockHelper.set_fold_trigger(previous_block, fold_level > prev_fold_level) TextBlockHelper.set_fold_lvl(current_block, fold_level) # user pressed enter at the beginning of a fold trigger line # the previous blank or comment line will keep the trigger state # and the new line (which actually contains the trigger) must use # the prev state (and prev state must then be reset). prev = current_block.previous() # real prev block (may be blank) if (prev and prev.isValid() and (prev.text().strip() == '' or self.editor.is_comment(prev)) and TextBlockHelper.is_fold_trigger(prev)): # prev line has the correct trigger fold state TextBlockHelper.set_collapsed(current_block, TextBlockHelper.is_collapsed(prev)) # make empty or comment line not a trigger TextBlockHelper.set_fold_trigger(prev, False) TextBlockHelper.set_collapsed(prev, False)
def find_parent_scope(block): """Find parent scope, if the block is not a fold trigger.""" original = block if not TextBlockHelper.is_fold_trigger(block): # search level of next non blank line while block.text().strip() == '' and block.isValid(): block = block.next() ref_lvl = TextBlockHelper.get_fold_lvl(block) - 1 block = original while (block.blockNumber() and (not TextBlockHelper.is_fold_trigger(block) or TextBlockHelper.get_fold_lvl(block) > ref_lvl)): block = block.previous() return block
def detect_fold_level(self, prev_block, block): if prev_block: prev_text = prev_block.text().strip() else: prev_text = '' text = block.text().strip() if text in self.open_chars: return TextBlockHelper.get_fold_lvl(prev_block) + 1 if prev_text.endswith(self.open_chars) and prev_text not in \ self.open_chars: return TextBlockHelper.get_fold_lvl(prev_block) + 1 if self.close_chars in prev_text: return TextBlockHelper.get_fold_lvl(prev_block) - 1 return TextBlockHelper.get_fold_lvl(prev_block)
def get_range(self, ignore_blank_lines=True): """ Gets the fold region range (start and end line). .. note:: Start line do no encompass the trigger line. :param ignore_blank_lines: True to ignore blank lines at the end of the scope (the method will rewind to find that last meaningful block that is part of the fold scope). :returns: tuple(int, int) """ ref_lvl = self.trigger_level first_line = self._trigger.blockNumber() block = self._trigger.next() last_line = block.blockNumber() lvl = self.scope_level if ref_lvl == lvl: # for zone set programmatically such as imports # in pyqode.python ref_lvl -= 1 while (block.isValid() and TextBlockHelper.get_fold_lvl(block) > ref_lvl): last_line = block.blockNumber() block = block.next() if ignore_blank_lines and last_line: block = block.document().findBlockByNumber(last_line) while block.blockNumber() and block.text().strip() == '': block = block.previous() last_line = block.blockNumber() return first_line, last_line
def collapse_all(self): """ Collapses all triggers and makes all blocks with fold level > 0 invisible. """ self._clear_block_deco() block = self.editor.document().firstBlock() last = self.editor.document().lastBlock() while block.isValid(): lvl = TextBlockHelper.get_fold_lvl(block) trigger = TextBlockHelper.is_fold_trigger(block) if trigger: if lvl == 0: self._show_previous_blank_lines(block) TextBlockHelper.set_collapsed(block, True) block.setVisible(lvl == 0) if block == last and block.text().strip() == '': block.setVisible(True) self._show_previous_blank_lines(block) block = block.next() self._refresh_editor_and_scrollbars() tc = self.editor.textCursor() tc.movePosition(tc.Start) self.editor.setTextCursor(tc) self.collapse_all_triggered.emit()
def scope_level(self): """ Returns the fold level of the first block of the foldable scope ( just after the trigger). :return: """ return TextBlockHelper.get_fold_lvl(self._trigger.next())
def parent(self): """ Return the parent scope. :return: FoldScope or None """ if TextBlockHelper.get_fold_lvl(self._trigger) > 0 and \ self._trigger.blockNumber(): block = self._trigger.previous() ref_lvl = self.trigger_level - 1 while (block.blockNumber() and (not TextBlockHelper.is_fold_trigger(block) or TextBlockHelper.get_fold_lvl(block) > ref_lvl)): block = block.previous() try: return FoldScope(block) except ValueError: return None return None
def child_regions(self): """This generator generates the list of direct child regions.""" start, end = self.get_range() block = self._trigger.next() ref_lvl = self.scope_level while block.blockNumber() <= end and block.isValid(): lvl = TextBlockHelper.get_fold_lvl(block) trigger = TextBlockHelper.is_fold_trigger(block) if lvl == ref_lvl and trigger: yield FoldScope(block) block = block.next()
def paintEvent(self, event): """Override Qt method.""" painter = QPainter(self) color = QColor(self.color) color.setAlphaF(.5) painter.setPen(color) for top, line_number, block in self.editor.visible_blocks: bottom = top + int(self.editor.blockBoundingRect(block).height()) indentation = TextBlockHelper.get_fold_lvl(block) for i in range(1, indentation): x = self.editor.fontMetrics().width(i * self.i_width * '9') painter.drawLine(x, top, x, bottom)
def trigger_level(self): """ Returns the fold level of the block trigger. :return: """ return TextBlockHelper.get_fold_lvl(self._trigger)