def draw_snippets(self): document = self.editor.document() for snippet_number in self.snippets_map: snippet_nodes = self.snippets_map[snippet_number] for node in snippet_nodes: cursor = QTextCursor(self.editor.textCursor()) position = node.position if isinstance(node.position, tuple): position = [list(node.position)] for path in position: start_line, start_column = path[0] end_line, end_column = path[-1] if path[0] == path[-1]: end_column += 1 start_block = document.findBlockByNumber(start_line) cursor.setPosition(start_block.position()) cursor.movePosition(QTextCursor.StartOfBlock) cursor.movePosition( QTextCursor.NextCharacter, n=start_column) end_block = document.findBlockByNumber(end_line) cursor.setPosition( end_block.position(), QTextCursor.KeepAnchor) cursor.movePosition( QTextCursor.StartOfBlock, mode=QTextCursor.KeepAnchor) cursor.movePosition( QTextCursor.NextCharacter, n=end_column, mode=QTextCursor.KeepAnchor) color = QColor(self.editor.comment_color) self.editor.highlight_selection('code_snippets', QTextCursor(cursor), outline_color=color) self.editor.update_extra_selections()
def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False, multiline_replace_check=True): """Call the find function""" # When several lines are selected in the editor and replace box is # activated, dynamic search is deactivated to prevent changing the # selection. Otherwise we show matching items. if multiline_replace_check and self.replace_widgets[0].isVisible(): sel_text = self.editor.get_selected_text() if len(to_text_string(sel_text).splitlines()) > 1: return None text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") if not self.is_code_editor: # Clears the selection for WebEngine self.editor.find_text('') self.change_number_matches() return None else: case = self.case_button.isChecked() word = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, word=word, regexp=regexp) stylesheet = self.STYLE[found] tooltip = self.TOOLTIP[found] if not found and regexp: error_msg = regexp_error_msg(text) if error_msg: # special styling for regexp errors stylesheet = self.STYLE['regexp_error'] tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg self.search_text.lineEdit().setStyleSheet(stylesheet) self.search_text.setToolTip(tooltip) if self.is_code_editor and found: cursor = QTextCursor(self.editor.textCursor()) TextHelper(self.editor).unfold_if_colapsed(cursor) if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() number_matches = self.editor.get_number_matches(text, case=case, regexp=regexp, word=word) if hasattr(self.editor, 'get_match_number'): match_number = self.editor.get_match_number(text, case=case, regexp=regexp, word=word) else: match_number = 0 self.change_number_matches(current_match=match_number, total_matches=number_matches) return found
def is_end_of_function_definition(self, text, line_number): """Return True if text is the end of the function definition.""" text_without_whitespace = "".join(text.split()) if (text_without_whitespace.endswith("):") or text_without_whitespace.endswith("]:") or (text_without_whitespace.endswith(":") and "->" in text_without_whitespace)): return True elif text_without_whitespace.endswith(":") and line_number > 1: complete_text = text_without_whitespace document = self.code_editor.document() cursor = QTextCursor( document.findBlockByNumber(line_number - 2)) # previous line for i in range(line_number - 2, -1, -1): txt = "".join(str(cursor.block().text()).split()) if txt.endswith("\\") or is_in_scope_backward(complete_text): if txt.endswith("\\"): txt = txt[:-1] complete_text = txt + complete_text else: break if i != 0: cursor.movePosition(QTextCursor.PreviousBlock) if is_start_of_function(complete_text): return (complete_text.endswith("):") or complete_text.endswith("]:") or (complete_text.endswith(":") and "->" in complete_text)) else: return False else: return False
def update_appearance(self): if not self.editing and not self.highlighting: return self.setUpdatesEnabled(False) # speed up, doesnt really seem to help though cursor_pos = self.textCursor().position() scroll_pos = (self.horizontalScrollBar().sliderPosition(), self.verticalScrollBar().sliderPosition()) self.block_change_signal = True highlighted = """ <style> * { font-family: Consolas; } </style> """ + highlight(self.toPlainText(), self.lexer, self.formatter) self.setHtml(highlighted) self.block_change_signal = False if self.hasFocus(): c = QTextCursor(self.document()) c.setPosition(cursor_pos) self.setTextCursor(c) self.horizontalScrollBar().setSliderPosition(scroll_pos[0]) self.verticalScrollBar().setSliderPosition(scroll_pos[1]) else: self.textCursor().setPosition(0) self.setUpdatesEnabled(True)
def __init__(self, cursor_or_bloc_or_doc, start_pos=None, end_pos=None, start_line=None, end_line=None, draw_order=0, tooltip=None, full_width=False, font=None, kind=None): """ Creates a text decoration. .. note:: start_pos/end_pos and start_line/end_line pairs let you easily specify the selected text. You should use one pair or the other or they will conflict between each others. If you don't specify any values, the selection will be based on the cursor. :param cursor_or_bloc_or_doc: Reference to a valid QTextCursor/QTextBlock/QTextDocument :param start_pos: Selection start position :param end_pos: Selection end position :param start_line: Selection start line. :param end_line: Selection end line. :param draw_order: The draw order of the selection, highest values will appear on top of the lowest values. :param tooltip: An optional tooltips that will be automatically shown when the mouse cursor hover the decoration. :param full_width: True to select the full line width. :param font: Decoration font. :param kind: Decoration kind, e.g. 'current_cell'. .. note:: Use the cursor selection if startPos and endPos are none. """ super(TextDecoration, self).__init__() self.signals = self.Signals() self.draw_order = draw_order self.tooltip = tooltip self.cursor = QTextCursor(cursor_or_bloc_or_doc) self.kind = kind if full_width: self.set_full_width(full_width) if start_pos is not None: self.cursor.setPosition(start_pos) if end_pos is not None: self.cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if start_line is not None: self.cursor.movePosition(self.cursor.Start, self.cursor.MoveAnchor) self.cursor.movePosition(self.cursor.Down, self.cursor.MoveAnchor, start_line) if end_line is not None: self.cursor.movePosition(self.cursor.Down, self.cursor.KeepAnchor, end_line - start_line) if font is not None: self.format.setFont(font)
def show_calltip(self, title, text, signature=False, color='#2D62FF', at_line=None, at_position=None, at_point=None): """Show calltip""" if text is None or len(text) == 0: return # Saving cursor position: if at_position is None: at_position = self.get_position('cursor') self.calltip_position = at_position # Preparing text: if signature: text, wrapped_textlines = self._format_signature(text) else: if isinstance(text, list): text = "\n ".join(text) text = text.replace('\n', '<br>') if len(text) > self.calltip_size: text = text[:self.calltip_size] + " ..." # Formatting text font = self.font() size = font.pointSize() family = font.family() format1 = '<div style=\'font-family: "%s"; font-size: %spt; color: %s\'>'\ % (family, size, color) format2 = '<div style=\'font-family: "%s"; font-size: %spt\'>'\ % (family, size-1 if size > 9 else size) tiptext = format1 + ('<b>%s</b></div>' % title) + '<hr>' + \ format2 + text + "</div>" # Showing tooltip at cursor position: cx, cy = self.get_coordinates('cursor') if at_point is not None: cx, cy = at_point.x(), at_point.y() if at_line is not None: cx = 5 cursor = QTextCursor(self.document().findBlockByNumber(at_line - 1)) cy = self.cursorRect(cursor).top() point = self.mapToGlobal(QPoint(cx, cy)) point = self.calculate_real_position(point) point.setY(point.y() + font.pointSize() + 5) if signature: self.calltip_widget.show_tip(point, tiptext, wrapped_textlines) else: QToolTip.showText(point, tiptext)
def _get_line(self, editor_cursor, lines=1): """Return the line at cursor position.""" try: cursor = QTextCursor(editor_cursor) except TypeError: print("ERROR: editor_cursor must be an instance of QTextCursor") else: cursor.movePosition(QTextCursor.StartOfLine) cursor.movePosition(QTextCursor.Down, QTextCursor.KeepAnchor, n=lines) line = cursor.selectedText() return line
def create_selection(self, start, end): """Create selection.""" end_document = self.get_end_position() if end > end_document: end = end_document sel = self.selection sel.cursor = QTextCursor(self.get_editor().document()) sel.cursor.setPosition(start) sel.cursor.setPosition(end, QTextCursor.KeepAnchor) self.set_extra_selections("vim_selection", [sel]) self.draw_vim_cursor()
def set_editor_cursor(self, editor, cursor): """Set the cursor of an editor.""" pos = cursor.position() anchor = cursor.anchor() new_cursor = QTextCursor() if pos == anchor: new_cursor.movePosition(pos) else: new_cursor.movePosition(anchor) new_cursor.movePosition(pos, QTextCursor.KeepAnchor) editor.setTextCursor(cursor)
def __exec_cell(self): ls = self.get_line_separator() init_cursor = QTextCursor(self.textCursor()) start_pos, end_pos = self.__save_selection() cursor, whole_file_selected = self.select_current_cell() self.setTextCursor(cursor) line_from, line_to = self.get_selection_bounds() text = self.get_selection_as_executable_code() self.last_cursor_cell = init_cursor self.__restore_selection(start_pos, end_pos) if text is not None: text = ls * line_from + text return text, line_from
def is_cell_separator(self, cursor=None, block=None): """Return True if cursor (or text block) is on a block separator""" assert cursor is not None or block is not None if cursor is not None: cursor0 = QTextCursor(cursor) cursor0.select(QTextCursor.BlockUnderCursor) text = to_text_string(cursor0.selectedText()) else: text = to_text_string(block.text()) if self.cell_separators is None: return False else: return text.lstrip().startswith(self.cell_separators)
def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() cur_pos = cursor.position() start_pos, end_pos = self.__save_selection() end_pos_orig = end_pos if to_text_string(cursor.selectedText()): cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if not to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.PreviousBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): cursor_temp = QTextCursor(cursor) cursor_temp.clearSelection() cursor_temp.insertText(self.get_line_separator()) break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() cursor.clearSelection() if not after_current_line: # Moving cursor before current line/selected text cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos += len(text) end_pos_orig += len(text) cur_pos += len(text) # We save the end and start position of the selection, so that it # can be restored within the paint event that is triggered by the # text insertion. This is done to prevent a graphical glitch that # occurs when text gets inserted at the current position of the cursor. # See spyder-ide/spyder#11089 for more info. if cur_pos == start_pos: self._restore_selection_pos = (end_pos_orig, start_pos) else: self._restore_selection_pos = (start_pos, end_pos_orig) cursor.insertText(text) cursor.endEditBlock() self.document_did_change()
def search(self, txt): """Search regular expressions key inside document(from spyder_vim).""" editor = self.get_editor() cursor = QTextCursor(editor.document()) cursor.movePosition(QTextCursor.Start) # Apply the option for search is_ignorecase = CONF.get(CONF_SECTION, 'ignorecase') is_smartcase = CONF.get(CONF_SECTION, 'smartcase') if is_ignorecase is True: option = None self.vim_status.search.ignorecase = True if is_smartcase and txt.lower() != txt: option = QTextDocument.FindCaseSensitively self.vim_status.search.ignorecase = False else: option = QTextDocument.FindCaseSensitively self.vim_status.search.ignorecase = False # Find key in document forward search_stack = [] if option: cursor = editor.document().find(QRegularExpression(txt), options=option) else: cursor = editor.document().find(QRegularExpression(txt)) back = self.vim_status.search.color_bg fore = self.vim_status.search.color_fg while not cursor.isNull(): selection = QTextEdit.ExtraSelection() selection.format.setBackground(back) selection.format.setForeground(fore) selection.cursor = cursor search_stack.append(selection) if option: cursor = editor.document().find(QRegularExpression(txt), cursor, options=option) else: cursor = editor.document().find(QRegularExpression(txt), cursor) self.vim_status.cursor.set_extra_selections('vim_search', [i for i in search_stack]) editor.update_extra_selections() self.vim_status.search.selection_list = search_stack self.vim_status.search.txt_searched = txt
def get_function_definition_from_first_line(self): """Get func def when the cursor is located on the first def line.""" document = self.code_editor.document() cursor = QTextCursor( document.findBlockByNumber(self.line_number_cursor - 1)) func_text = '' func_indent = '' is_first_line = True line_number = cursor.blockNumber() + 1 number_of_lines = self.code_editor.blockCount() remain_lines = number_of_lines - line_number + 1 number_of_lines_of_function = 0 for __ in range(min(remain_lines, 20)): cur_text = to_text_string(cursor.block().text()).rstrip() if is_first_line: if not is_start_of_function(cur_text): return None func_indent = get_indent(cur_text) is_first_line = False else: cur_indent = get_indent(cur_text) if cur_indent <= func_indent and cur_text.strip() != '': return None if is_start_of_function(cur_text): return None if (cur_text.strip() == '' and not is_in_scope_forward(func_text)): return None if len(cur_text) > 0 and cur_text[-1] == '\\': cur_text = cur_text[:-1] func_text += cur_text number_of_lines_of_function += 1 if self.is_end_of_function_definition( cur_text, line_number + number_of_lines_of_function - 1): return func_text, number_of_lines_of_function cursor.movePosition(QTextCursor.NextBlock) return None
def selection_range(self): """ Returns the selected lines boundaries (start line, end line) :return: tuple(int, int) """ editor = self._editor doc = editor.document() start = doc.findBlock( editor.textCursor().selectionStart()).blockNumber() end = doc.findBlock(editor.textCursor().selectionEnd()).blockNumber() text_cursor = QTextCursor(editor.textCursor()) text_cursor.setPosition(editor.textCursor().selectionEnd()) if text_cursor.columnNumber() == 0 and start != end: end -= 1 return start, end
def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() cur_pos = cursor.position() start_pos, end_pos = self.__save_selection() end_pos_orig = end_pos if to_text_string(cursor.selectedText()): cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if not to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.PreviousBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): cursor_temp = QTextCursor(cursor) cursor_temp.clearSelection() cursor_temp.insertText(self.get_line_separator()) break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() cursor.clearSelection() if not after_current_line: # Moving cursor before current line/selected text cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos += len(text) end_pos_orig += len(text) cur_pos += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) if cur_pos == start_pos: self.__restore_selection(end_pos_orig, start_pos) else: self.__restore_selection(start_pos, end_pos_orig)
def _calculate_position(self, at_line=None, at_position=None, at_point=None): """ Calculate a global point position `QPoint(x, y)`, for a given line, local cursor position, or local point. """ # Check that no option or only one option is given: if [at_line, at_position, at_point].count(None) < 2: raise Exception('Provide no argument or only one argument!') # Saving cursor position: if at_position is None: at_position = self.get_position('cursor') # FIXME: What id this used for? self.calltip_position = at_position if at_point is not None: # Showing tooltip at point position cx, cy = at_point.x(), at_point.y() elif at_line is not None: # Showing tooltip at line cx = 5 cursor = QTextCursor(self.document().findBlockByNumber(at_line-1)) cy = self.cursorRect(cursor).top() else: # Showing tooltip at cursor position # FIXME: why is at_position not being used to calculate the # coordinates? cx, cy = self.get_coordinates('cursor') # Calculate vertical delta font = self.font() delta = font.pointSize() + 5 # Map to global coordinates point = self.mapToGlobal(QPoint(cx, cy)) point = self.calculate_real_position(point) point.setY(point.y() + delta) return point
def _selection(self): """ Function to compute the selection. This is slow to call so it is only called when needed. """ if self.selection_start is None or self.selection_end is None: return None document = self.editor.document() cursor = self.editor.textCursor() block = document.findBlockByNumber(self.selection_start['line']) cursor.setPosition(block.position()) cursor.movePosition(QTextCursor.StartOfBlock) cursor.movePosition(QTextCursor.NextCharacter, n=self.selection_start['character']) block2 = document.findBlockByNumber(self.selection_end['line']) cursor.setPosition(block2.position(), QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.StartOfBlock, mode=QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.NextCharacter, n=self.selection_end['character'], mode=QTextCursor.KeepAnchor) return QTextCursor(cursor)
def _calculate_position(self, at_line=None, at_point=None): """ Calculate a global point position `QPoint(x, y)`, for a given line, local cursor position, or local point. """ font = self.font() if at_point is not None: # Showing tooltip at point position cx, cy = at_point.x(), at_point.y() elif at_line is not None: # Showing tooltip at line cx = 5 line = at_line - 1 cursor = QTextCursor(self.document().findBlockByNumber(line)) cy = self.cursorRect(cursor).top() else: # Showing tooltip at cursor position cx, cy = self.get_coordinates('cursor') cy = cy - font.pointSize() / 2 # Calculate vertical delta # The needed delta changes with font size, so we use a power law if sys.platform == 'darwin': delta = int((font.pointSize() * 1.20) ** 0.98) + 4.5 elif os.name == 'nt': delta = int((font.pointSize() * 1.20) ** 1.05) + 7 else: delta = int((font.pointSize() * 1.20) ** 0.98) + 7 # delta = font.pointSize() + 5 # Map to global coordinates point = self.mapToGlobal(QPoint(cx, cy)) point = self.calculate_real_position(point) point.setY(point.y() + delta) return point
def list_symbols(editor, block, character): """ Retuns a list of symbols found in the block text :param editor: code editor instance :param block: block to parse :param character: character to look for. """ text = block.text() symbols = [] cursor = QTextCursor(block) cursor.movePosition(cursor.StartOfBlock) pos = text.find(character, 0) cursor.movePosition(cursor.Right, cursor.MoveAnchor, pos) while pos != -1: if not TextHelper(editor).is_comment_or_string(cursor): # skips symbols in string literal or comment info = ParenthesisInfo(pos, character) symbols.append(info) pos = text.find(character, pos + 1) cursor.movePosition(cursor.StartOfBlock) cursor.movePosition(cursor.Right, cursor.MoveAnchor, pos) return symbols
def _update_cursor(self, value: int) -> None: if not self._view.textCursor().hasSelection(): block = self._view.document().findBlockByLineNumber(value) cursor = QTextCursor(block) self._view.setTextCursor(cursor)
def setCurrentLine(self, line): cursor = QTextCursor(self.document().findBlockByLineNumber(line - 1)) self.setTextCursor(cursor) self.centerCursor()