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_fold_trigger_state(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 _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 [QtCore.Qt.Key_Backspace, QtCore.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 _handle_docstrings(self, block, lvl, prev_block): if block.docstring: is_start = block.text().strip().startswith('"""') if is_start: TextBlockHelper.get_fold_lvl(prev_block) + 1 else: pblock = block.previous() while pblock.isValid() and pblock.text().strip() == '': pblock = pblock.previous() is_start = pblock.text().strip().startswith('"""') if is_start: return TextBlockHelper.get_fold_lvl(pblock) + 1 else: return TextBlockHelper.get_fold_lvl(pblock) # fix end of docstring elif prev_block and prev_block.text().strip().endswith('"""'): single_line = self._single_line_docstring.match( prev_block.text().strip()) if single_line: TextBlockHelper.set_fold_lvl(prev_block, lvl) else: TextBlockHelper.set_fold_lvl( prev_block, TextBlockHelper.get_fold_lvl( prev_block.previous())) return lvl
def _handle_docstrings(self, block, lvl, prev_block): if block.docstring: is_start = block.text().strip().startswith('"""') if is_start: TextBlockHelper.get_fold_lvl(prev_block) + 1 else: pblock = block.previous() while pblock.isValid() and pblock.text().strip() == '': pblock = pblock.previous() is_start = pblock.text().strip().startswith('"""') if is_start: return TextBlockHelper.get_fold_lvl(pblock) + 1 else: return TextBlockHelper.get_fold_lvl(pblock) # fix end of docstring elif prev_block and prev_block.text().strip().endswith('"""'): single_line = self._single_line_docstring.match( prev_block.text().strip()) if single_line: TextBlockHelper.set_fold_lvl(prev_block, lvl) else: TextBlockHelper.set_fold_lvl( prev_block, TextBlockHelper.get_fold_lvl(prev_block.previous())) return lvl
def detect_fold_level(self, prev_block, block): if prev_block: prev_text = prev_block.text().strip() else: prev_text = '' if '[]' not in prev_text and '{}' not in prev_text: if prev_text.endswith(('{', '[')): return TextBlockHelper.get_fold_lvl(prev_block) + 1 if prev_text.replace(',', '').endswith(('}', ']')): return TextBlockHelper.get_fold_lvl(prev_block) - 1 return TextBlockHelper.get_fold_lvl(prev_block)
def test_dynamic_folding_insert_section(): # test insert section under empty data division # data division (which is not a fold trigger initially) block will become a fold trigger editor = CobolCodeEdit() editor.setPlainText(section_code, '', '') th = TextHelper(editor) block = editor.document().findBlockByNumber(2) assert TextBlockHelper.is_fold_trigger(block) is False cursor = th.goto_line(2, column=len(' DATA DIVISION.')) cursor.insertText('\n WORKING-STORAGE SECTION.') assert TextBlockHelper.is_fold_trigger(block) is True
def expand_all(self): """ Expands all fold triggers. """ block = self.editor.document().firstBlock() while block.isValid(): TextBlockHelper.set_fold_trigger_state(block, False) block.setVisible(True) block = block.next() self._clear_block_deco() self._refresh_editor_and_scrollbars() self.expand_all_triggered.emit()
def test_expand_all(editor): panel = get_panel(editor) QTest.qWait(1000) panel.collapse_all() QTest.qWait(1000) panel.expand_all() block = editor.document().firstBlock() while block.blockNumber() < editor.document().blockCount() - 1: assert block.isVisible() if TextBlockHelper.is_fold_trigger(block): assert TextBlockHelper.is_collapsed(block) is False block = block.next()
def convert(name, editor, to_collapse): ti = QtWidgets.QTreeWidgetItem() ti.setText(0, name.name) if isinstance(name.icon, list): icon = QtGui.QIcon.fromTheme(name.icon[0], QtGui.QIcon(name.icon[1])) else: icon = QtGui.QIcon(name.icon) ti.setIcon(0, icon) name.block = editor.document().findBlockByNumber(name.line) ti.setData(0, QtCore.Qt.UserRole, name) ti.setToolTip(0, name.description) name.tree_item = ti block_data = name.block.userData() if block_data is None: block_data = TextBlockUserData() name.block.setUserData(block_data) block_data.tree_item = ti if to_collapse is not None and \ TextBlockHelper.is_collapsed(name.block): to_collapse.append(ti) for ch in name.children: ti_ch, to_collapse = convert(ch, editor, to_collapse) if ti_ch: ti.addChild(ti_ch) return ti, to_collapse
def convert(name, editor, to_collapse): ti = QtWidgets.QTreeWidgetItem() ti.setText(0, name.name) if isinstance(name.icon, list): icon = QtGui.QIcon.fromTheme( name.icon[0], QtGui.QIcon(name.icon[1])) else: icon = QtGui.QIcon(name.icon) ti.setIcon(0, icon) name.block = editor.document().findBlockByNumber(name.line) ti.setData(0, QtCore.Qt.UserRole, name) ti.setToolTip(0, name.description) block_data = name.block.userData() if block_data is None: block_data = TextBlockUserData() name.block.setUserData(block_data) block_data.tree_item = ti if to_collapse is not None and \ TextBlockHelper.is_collapsed(name.block): to_collapse.append(ti) for ch in name.children: ti_ch, to_collapse = convert(ch, editor, to_collapse) if ti_ch: ti.addChild(ti_ch) return ti, to_collapse
def test_collapse_all(editor): panel = get_panel(editor) QTest.qWait(1000) panel.collapse_all() QTest.qWait(1000) block = editor.document().firstBlock() while block.blockNumber() < editor.document().blockCount() - 1: blank_line = len(block.text().strip()) == 0 if TextBlockHelper.get_fold_lvl(block) > 0: if not blank_line: assert block.isVisible() is False else: assert block.isVisible() is True if TextBlockHelper.is_fold_trigger(block): assert TextBlockHelper.is_collapsed(block) is True block = block.next()
def detect_fold_level(self, prev_block, block): ctext, ptext = self.stripped_texts(block, prev_block) if not self.editor.free_format: ctext = self.normalize_text(ctext) ptext = self.normalize_text(ptext) if regex.DIVISION.indexIn( ctext) != -1 and not ctext.lstrip().startswith('*'): return OFFSET_DIVISION elif regex.SECTION.indexIn( ctext) != -1 and not ctext.lstrip().startswith('*'): return OFFSET_SECTION else: # anywhere else, folding is mostly based on the indentation level indent = self.get_indent(ctext) pindent = self.get_indent(ptext) if ctext.strip().upper().startswith('END-') and self.is_valid( prev_block) and pindent > indent: # find previous block with the same indent, use it's fold level + 1 to include # the end-branch statement in the fold scope pblock = prev_block while self.is_valid(pblock) and (pindent != indent or len(ptext.strip()) == 0): pblock = pblock.previous() ptext = self.normalize_text(pblock.text()) pindent = self.get_indent(ptext) lvl = TextBlockHelper.get_fold_lvl(pblock.next()) else: lvl = OFFSET_OTHER + indent # if not self.editor.free_format and (ctext.lstrip().startswith('-') or ctext.lstrip().startswith('*')): if not self.editor.free_format and ( ctext.lstrip().startswith('-')): # use previous fold level lvl = TextBlockHelper.get_fold_lvl(prev_block) if not self.editor.free_format and ctext.strip().startswith('*'): if regex.DIVISION.indexIn( ptext) != -1 and not ptext.lstrip().startswith('*'): lvl = OFFSET_SECTION elif regex.SECTION.indexIn( ptext) != -1 and not ptext.lstrip().startswith('*'): return OFFSET_SECTION + 2 else: lvl = TextBlockHelper.get_fold_lvl(prev_block) return lvl
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 test_fold_limit(editor): editor.syntax_highlighter.fold_detector.limit = 1 editor.file.open('test/test_api/folding_cases/foo.py') block = editor.document().firstBlock() while block.blockNumber() < editor.blockCount() - 1: assert TextBlockHelper.get_fold_lvl(block) <= 1 block = block.next() editor.syntax_highlighter.fold_detector.limit = sys.maxsize
def _on_item_state_changed(self, item): if self._updating: return block = item.data(0, QtCore.Qt.UserRole).block assert isinstance(item, QtWidgets.QTreeWidgetItem) item_state = not item.isExpanded() block_state = TextBlockHelper.is_collapsed(block) if item_state != block_state: self._updating = True self._folding_panel.toggle_fold_trigger(block) self._updating = False
def _on_item_state_changed(self, item): if self._updating: return block = item.data(0, QtCore.Qt.UserRole).block assert isinstance(item, QtWidgets.QTreeWidgetItem) item_state = not item.isExpanded() block_state = TextBlockHelper.get_fold_trigger_state(block) if item_state != block_state: self._updating = True self._folding_panel.toggle_fold_trigger(block) self._updating = False
def paintEvent(self, event): # Paints the fold indicators and the possible fold region background # on the folding panel. super(FoldingPanel, self).paintEvent(event) painter = QtGui.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.get_fold_trigger_state(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_parent_scopes(block): """ Gets the list of hierarchical parent scopes of the current block. :param block: current block :return: list of QTextBlock """ scopes = [block] scope = FoldScope.find_parent_scope(block) while scope is not None: ref = TextBlockHelper.get_fold_lvl(scopes[-1]) if TextBlockHelper.get_fold_lvl(scope) < ref: # ignore sibling scopes scopes.append(scope) if scope.blockNumber() == 0: # stop scope = None else: # next parent scope = FoldScope.find_parent_scope(scope.previous()) return reversed(scopes)
def detect_fold_level(self, prev_block, block): ctext, ptext = self.stripped_texts(block, prev_block) if not self.editor.free_format: ctext = self.normalize_text(ctext) ptext = self.normalize_text(ptext) if regex.DIVISION.indexIn(ctext) != -1 and not ctext.lstrip().startswith('*'): return OFFSET_DIVISION elif regex.SECTION.indexIn(ctext) != -1 and not ctext.lstrip().startswith('*'): return OFFSET_SECTION else: # anywhere else, folding is mostly based on the indentation level indent = self.get_indent(ctext) pindent = self.get_indent(ptext) if ctext.strip().upper().startswith('END-') and self.is_valid(prev_block) and pindent > indent: # find previous block with the same indent, use it's fold level + 1 to include # the end-branch statement in the fold scope pblock = prev_block while self.is_valid(pblock) and (pindent != indent or len(ptext.strip()) == 0): pblock = pblock.previous() ptext = self.normalize_text(pblock.text()) pindent = self.get_indent(ptext) lvl = TextBlockHelper.get_fold_lvl(pblock.next()) else: lvl = OFFSET_OTHER + indent # if not self.editor.free_format and (ctext.lstrip().startswith('-') or ctext.lstrip().startswith('*')): if not self.editor.free_format and (ctext.lstrip().startswith('-')): # use previous fold level lvl = TextBlockHelper.get_fold_lvl(prev_block) if not self.editor.free_format and ctext.strip().startswith('*'): if regex.DIVISION.indexIn(ptext) != -1 and not ptext.lstrip().startswith('*'): lvl = OFFSET_SECTION elif regex.SECTION.indexIn(ptext) != -1 and not ptext.lstrip().startswith('*'): return OFFSET_SECTION + 2 else: lvl = TextBlockHelper.get_fold_lvl(prev_block) return lvl
def _highlight_surrounding_scopes(self, block): """ Highlights the scopes surrounding 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 surrounding parent scopes with a darker color start, end = scope.get_range() if not TextBlockHelper.get_fold_trigger_state(block): self._add_scope_decorations(block, start, end)
def _on_item_state_changed(self, item): if self._updating: return if item == self._root_item: item_state = not item.isExpanded() if item_state: QtCore.QTimer.singleShot( 1, self._editor.folding_panel.collapse_all) else: QtCore.QTimer.singleShot( 1, self._editor.folding_panel.expand_all) else: block = item.data(0, QtCore.Qt.UserRole).block assert isinstance(item, QtWidgets.QTreeWidgetItem) item_state = not item.isExpanded() block_state = TextBlockHelper.get_fold_trigger_state(block) if item_state != block_state: self._editor.folding_panel.toggle_fold_trigger(block)
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._add_scope_decorations( region._trigger, *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 open(self, path, encoding=None, use_cached_encoding=True): encoding = self.detect_encoding(path) super(PyFileManager, self).open( path, encoding=encoding, use_cached_encoding=use_cached_encoding) try: folding_panel = self.editor.panels.get('FoldingPanel') except KeyError: pass else: # fold imports and/or docstrings blocks_to_fold = [] sh = self.editor.syntax_highlighter if self.fold_imports and sh.import_statements: blocks_to_fold += sh.import_statements if self.fold_docstrings and sh.docstrings: blocks_to_fold += sh.docstrings for block in blocks_to_fold: if TextBlockHelper.is_fold_trigger(block): folding_panel.toggle_fold_trigger(block)
def convert(name, editor, to_collapse): ti = QtWidgets.QTreeWidgetItem() ti.setText(0, name.name) ti.setIcon(0, QtGui.QIcon(name.icon)) name.block = editor.document().findBlockByNumber(name.line) ti.setData(0, QtCore.Qt.UserRole, name) block_data = name.block.userData() if block_data is None: block_data = TextBlockUserData() name.block.setUserData(block_data) block_data.tree_item = ti if to_collapse is not None and \ TextBlockHelper.get_fold_trigger_state(name.block): to_collapse.append(ti) for ch in name.children: ti_ch, to_collapse = convert(ch, editor, to_collapse) if ti_ch: ti.addChild(ti_ch) return ti, to_collapse
def _highlight_caret_scope(self): """ Highlight the scope surrounding the current caret position. This get called only if :attr:` pyqode.core.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_surrounding_scopes(block) self._block_nbr = block_nbr
def _convert_name(self, name, editor): ti = QtWidgets.QTreeWidgetItem() ti.setText(0, name.name) ti.setIcon(0, QtGui.QIcon(ICONS[name.node_type])) ti.setToolTip(0, name.description) name.block = editor.document().findBlockByNumber(name.line) ti.setData(0, QtCore.Qt.UserRole, name) block_data = name.block.userData() if block_data is None: block_data = TextBlockUserData() name.block.setUserData(block_data) block_data.tree_item = ti if TextBlockHelper.get_fold_trigger_state(name.block): self._to_collapse.append(ti) for child in name.children: ti_ch = self._convert_name(child, editor) ti.addChild(ti_ch) return ti
def _add_scope_decorations(self, block, start, end): """ Show a scope decoration on the editor widget :param start: Start line :param end: End line """ try: parent = FoldScope(block).parent() except ValueError: parent = None if TextBlockHelper.is_fold_trigger(block): base_color = self._get_scope_highlight_color() factor_step = 5 if base_color.lightness() < 128: factor_step = 10 factor = 70 else: factor = 100 while parent: # highlight parent scope parent_start, parent_end = parent.get_range() self._add_scope_deco( start, end + 1, parent_start, parent_end, base_color, factor) # next parent scope start = parent_start end = parent_end parent = parent.parent() factor += factor_step # global scope parent_start = 0 parent_end = self.editor.document().blockCount() self._add_scope_deco( start, end + 1, parent_start, parent_end, base_color, factor + factor_step) else: self._clear_scope_decos()
def detect_fold_level(self, prev_block, block): """ Perfoms fold level detection for current block (take previous block into account). :param prev_block: previous block, None if `block` is the first block. :param block: block to analyse. :return: block fold level """ # Python is an indent based language so use indentation for folding # makes sense but we restrict new regions to indentation after a ':', # that way only the real logical blocks are displayed. lvl = super(PythonFoldDetector, self).detect_fold_level(prev_block, block) # cancel false indentation, indentation can only happen if there is # ':' on the previous line prev_lvl = TextBlockHelper.get_fold_lvl(prev_block) if prev_block and lvl > prev_lvl and not ( self._strip_comments(prev_block).endswith(':')): lvl = prev_lvl lvl = self._handle_docstrings(block, lvl, prev_block) lvl = self._handle_imports(block, lvl, prev_block) return lvl
def detect_fold_level(self, prev_block, block): """ Perfoms fold level detection for current block (take previous block into account). :param prev_block: previous block, None if `block` is the first block. :param block: block to analyse. :return: block fold level """ # Python is an indent based language so use indentation for folding # makes sense but we restrict new regions to indentation after a ':', # that way only the real logical blocks are displayed. lvl = super(PythonFoldDetector, self).detect_fold_level( prev_block, block) # cancel false indentation, indentation can only happen if there is # ':' on the previous line prev_lvl = TextBlockHelper.get_fold_lvl(prev_block) if prev_block and lvl > prev_lvl and not ( self._strip_comments(prev_block).endswith(':')): lvl = prev_lvl lvl = self._handle_docstrings(block, lvl, prev_block) lvl = self._handle_imports(block, lvl, prev_block) return lvl
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)) if TextBlockHelper.is_fold_trigger(block): if self._mouse_over_line is None: # mouse enter fold scope QtWidgets.QApplication.setOverrideCursor( QtGui.QCursor(QtCore.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_surrounding_scopes(block) else: # same fold scope, request highlight self._mouse_over_line = block.blockNumber() self._highlight_runner.request_job( self._highlight_surrounding_scopes, 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 QtWidgets.QApplication.restoreOverrideCursor() self.repaint()
def test_mouse_press(editor): panel = get_panel(editor) panel.highlight_caret_scope = False # fold child block toggle_fold_trigger(editor, 15, panel) block = editor.document().findBlockByNumber(14) assert TextBlockHelper.is_fold_trigger(block) is True assert TextBlockHelper.is_collapsed(block) is True block = block.next() while block.blockNumber() < 21: assert block.isVisible() is False block = block.next() # fold top level block toggle_fold_trigger(editor, 9, panel) block = editor.document().findBlockByNumber(8) assert TextBlockHelper.is_fold_trigger(block) block = block.next() while block.blockNumber() < 27: if block.blockNumber() == 14: assert TextBlockHelper.is_fold_trigger(block) is True assert TextBlockHelper.is_collapsed(block) is True assert block.isVisible() is False block = block.next() # unfold it top level block toggle_fold_trigger(editor, 9, panel) block = editor.document().findBlockByNumber(8) assert TextBlockHelper.is_fold_trigger(block) block = block.next() while block.blockNumber() < 27: if 14 < block.blockNumber() < 22: assert block.isVisible() is False else: assert block.isVisible() is True block = block.next() # cleanup QTest.mouseMove(panel, QtCore.QPoint(0, 0)) panel.leaveEvent(None) editor.setFocus() panel.highlight_caret_scope = True
def highlight_block(self, text, block): """ Highlight a block of text. :param text: The text of the block to highlight :param block: The QTextBlock to highlight """ self._check_formats() prev_block = block.previous() prev_state = TextBlockHelper.get_state(prev_block) self.setFormat(0, len(text), self.formats["normal"]) no_formats = True match = self.PROG.search(text) state = self.NORMAL while match: for key, value in list(match.groupdict().items()): if value: no_formats = False start, end = match.span(key) if key == 'tag' and len(set(text)) != 1: # 2 different characters -> not a header, # probably a table continue self.setFormat(start, end - start, self.formats[key]) if key == 'comment': state = self.INSIDE_COMMENT if key == 'string' and not match.group(0).endswith('`'): state = self.INSIDE_STRING # make sure to highlight previous block if key == 'tag': state = self.INSIDE_HEADER pblock = block.previous() if pblock.isValid() and pblock.text() and \ prev_state != self.INSIDE_HEADER: self._block_to_rehighlight = pblock QtCore.QTimer.singleShot( 1, self._rehighlight_block) match = self.PROG.search(text, match.end()) if no_formats: nblock = block.next() indent = len(text) - len(text.lstrip()) if nblock.isValid() and self.PROG_HEADER.match(nblock.text()) and \ len(set(nblock.text())) == 1: self.setFormat(0, len(text), self.formats["tag"]) state = self.INSIDE_HEADER elif prev_state == self.INSIDE_COMMENT and ( indent > 0 or not len(text)): self.setFormat(0, len(text), self.formats["comment"]) state = self.INSIDE_COMMENT elif prev_state == self.INSIDE_STRING: # check if end string found -> highlight match only otherwise # highlight whole line match = self.PROG_END_STRING.match(text) if match: end = match.end() else: state = self.INSIDE_STRING end = len(text) self.setFormat(0, end, self.formats["string"]) TextBlockHelper.set_state(block, state)
def highlight_block(self, text, block): prev_block = block.previous() prev_state = TextBlockHelper.get_state(prev_block) if prev_state == self.INSIDE_DQ3STRING: offset = -4 text = r'""" ' + text elif prev_state == self.INSIDE_SQ3STRING: offset = -4 text = r"''' " + text elif prev_state == self.INSIDE_DQSTRING: offset = -2 text = r'" ' + text elif prev_state == self.INSIDE_SQSTRING: offset = -2 text = r"' " + text else: offset = 0 import_stmt = None # set docstring dynamic attribute, used by the fold detector. block.docstring = False self.setFormat(0, len(text), self.formats["normal"]) state = self.NORMAL match = self.PROG.search(text) while match: for key, value in list(match.groupdict().items()): if value: start, end = match.span(key) start = max([0, start + offset]) end = max([0, end + offset]) if key == "uf_sq3string": self.setFormat(start, end - start, self.formats["docstring"]) block.docstring = True state = self.INSIDE_SQ3STRING elif key == "uf_dq3string": self.setFormat(start, end - start, self.formats["docstring"]) block.docstring = True state = self.INSIDE_DQ3STRING elif key == "uf_sqstring": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_SQSTRING elif key == "uf_dqstring": self.setFormat(start, end - start, self.formats["string"]) state = self.INSIDE_DQSTRING elif key == 'builtin_fct': # trick to highlight __init__, __add__ and so on with # builtin color self.setFormat(start, end - start, self.formats["constant"]) else: if ('"""' in value or "'''" in value) and \ key != 'comment': # highlight docstring with a different color block.docstring = True self.setFormat(start, end - start, self.formats["docstring"]) elif key == 'decorator': # highlight decorators self.setFormat(start, end - start, self.formats["decorator"]) elif value in ['self', 'cls']: # highlight self attribute self.setFormat(start, end - start, self.formats["self"]) else: # highlight all other tokens self.setFormat(start, end - start, self.formats[key]) if key == "keyword": if value in ("def", "class"): match1 = self.IDPROG.match(text, end) if match1: start1, end1 = match1.span(1) fmt_key = ('definition' if value == 'class' else 'function') fmt = self.formats[fmt_key] self.setFormat(start1, end1 - start1, fmt) if key == 'namespace': import_stmt = text.strip() # color all the "as" words on same line, except # if in a comment; cheap approximation to the # truth if '#' in text: endpos = text.index('#') else: endpos = len(text) while True: match1 = self.ASPROG.match(text, end, endpos) if not match1: break start, end = match1.span(1) self.setFormat(start, end - start, self.formats["namespace"]) # next match match = self.PROG.search(text, match.end()) TextBlockHelper.set_state(block, state) # update import zone if import_stmt is not None: block.import_stmt = import_stmt self.import_statements.append(block) block.import_stmt = True elif block.docstring: self.docstrings.append(block)
def highlight_block(self, text, block): """ Highlight a block of text. :param text: The text of the block to highlight :param block: The QTextBlock to highlight """ self._check_formats() prev_block = block.previous() prev_state = TextBlockHelper.get_state(prev_block) self.setFormat(0, len(text), self.formats["normal"]) no_formats = True match = self.PROG.search(text) state = self.NORMAL while match: for key, value in list(match.groupdict().items()): if value: no_formats = False start, end = match.span(key) if key == 'tag' and len(set(text)) != 1: # 2 different characters -> not a header, # probably a table continue self.setFormat(start, end - start, self.formats[key]) if key == 'comment': state = self.INSIDE_COMMENT if key == 'string' and not match.group(0).endswith('`'): state = self.INSIDE_STRING # make sure to highlight previous block if key == 'tag': state = self.INSIDE_HEADER pblock = block.previous() if pblock.isValid() and pblock.text() and \ prev_state != self.INSIDE_HEADER: self._block_to_rehighlight = pblock QtCore.QTimer.singleShot(1, self._rehighlight_block) match = self.PROG.search(text, match.end()) if no_formats: nblock = block.next() indent = len(text) - len(text.lstrip()) if nblock.isValid() and self.PROG_HEADER.match(nblock.text()) and \ len(set(nblock.text())) == 1: self.setFormat(0, len(text), self.formats["tag"]) state = self.INSIDE_HEADER elif prev_state == self.INSIDE_COMMENT and (indent > 0 or not len(text)): self.setFormat(0, len(text), self.formats["comment"]) state = self.INSIDE_COMMENT elif prev_state == self.INSIDE_STRING: # check if end string found -> highlight match only otherwise # highlight whole line match = self.PROG_END_STRING.match(text) if match: end = match.end() else: state = self.INSIDE_STRING end = len(text) self.setFormat(0, end, self.formats["string"]) TextBlockHelper.set_state(block, state)
def detect_fold_level(self, prev_block, block): if not prev_block: return 0 ctext, ptext = self.stripped_texts(block, prev_block) if ctext.endswith('DIVISION.'): if 'DATA' in ctext: self.data_division = block self._data_div_txt = block.text() if 'PROCEDURE' in ctext: self.proc_division = block self._proc_div_txt = block.text() return 0 elif ctext.endswith('SECTION.'): return 1 elif ptext.endswith('DIVISION.'): return 1 elif ptext.endswith('SECTION.'): return 2 # in case of replace all or simply if the user deleted the data or # proc div. if (self.proc_division and self.proc_division.text() != self._proc_div_txt): self.proc_division = None if (self.data_division and self.data_division.text() != self._data_div_txt): self.data_division = None # inside PROCEDURE DIVISION if (self.proc_division and self.proc_division.isValid() and block.blockNumber() > self.proc_division.blockNumber()): # we only detect outline of paragraphes if regex.PARAGRAPH_PATTERN.indexIn(block.text()) != -1: # paragraph return 1 else: # content of a paragraph if regex.PARAGRAPH_PATTERN.indexIn(prev_block.text()) != -1: return 2 else: cstxt = ctext.lstrip() pstxt = ptext.lstrip() plvl = TextBlockHelper.get_fold_lvl(prev_block) if regex.LOOP_PATTERN.indexIn(pstxt) != -1: pstxt = '$L$O$OP$' if pstxt in ['END-IF', 'END-PERFORM', 'END-READ']: if cstxt in ['ELSE']: return plvl - 2 return plvl - 1 if cstxt in ['ELSE']: return plvl - 1 for token in ['IF', 'ELSE', '$L$O$OP$', 'READ']: if pstxt.startswith(token): return plvl + 1 return plvl # INSIDE DATA DIVISION elif (self.data_division and self.data_division.isValid() and block.blockNumber() > self.data_division.blockNumber() + 1): # here folding is based on the indentation level offset = 6 indent = ((len(ctext) - len(ctext.lstrip()) - offset) // self.editor.tab_length) return 2 + indent # other lines follow their previous fold level plvl = TextBlockHelper.get_fold_lvl(prev_block) return plvl