def __init__(self, parent, should_highlight_current_line=True, font=None, lexer=None): super(CodeWidget, self).__init__(parent) self.highlighter = PygmentsHighlighter(self.document(), lexer) self.line_number_widget = LineNumberWidget(self) self.status_widget = StatusGutterWidget(self) if font is None: # Set a decent fixed width font for this platform. font = QtGui.QFont() if sys.platform == 'win32': # Prefer Consolas, but fall back to Courier if necessary. font.setFamily('Consolas') if not font.exactMatch(): font.setFamily('Courier') elif sys.platform == 'darwin': font.setFamily('Monaco') else: font.setFamily('Monospace') font.setStyleHint(QtGui.QFont.TypeWriter) self.set_font(font) # Whether we should highlight the current line or not. self.should_highlight_current_line = should_highlight_current_line # What that highlight color should be. self.line_highlight_color = QtGui.QColor(QtCore.Qt.yellow).lighter(160) # Auto-indentation behavior self.auto_indent = True self.smart_backspace = True # Tab settings self.tabs_as_spaces = True self.tab_width = 4 self.indent_character = ':' self.comment_character = '#' # Set up gutter widget and current line highlighting self.blockCountChanged.connect(self.update_line_number_width) self.updateRequest.connect(self.update_line_numbers) self.cursorPositionChanged.connect(self.highlight_current_line) self.update_line_number_width() self.highlight_current_line() # Don't wrap text self.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # Key bindings self.indent_key = QtGui.QKeySequence(QtCore.Qt.Key_Tab) self.unindent_key = QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Backtab) self.comment_key = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Slash) self.backspace_key = QtGui.QKeySequence(QtCore.Qt.Key_Backspace)
class CodeWidget(QtGui.QPlainTextEdit): """ A widget for viewing and editing code. """ ########################################################################### # CodeWidget interface ########################################################################### def __init__(self, parent, should_highlight_current_line=True, font=None, lexer=None): super(CodeWidget, self).__init__(parent) self.highlighter = PygmentsHighlighter(self.document(), lexer) self.line_number_widget = LineNumberWidget(self) self.status_widget = StatusGutterWidget(self) if font is None: # Set a decent fixed width font for this platform. font = QtGui.QFont() if sys.platform == 'win32': # Prefer Consolas, but fall back to Courier if necessary. font.setFamily('Consolas') if not font.exactMatch(): font.setFamily('Courier') elif sys.platform == 'darwin': font.setFamily('Monaco') else: font.setFamily('Monospace') font.setStyleHint(QtGui.QFont.TypeWriter) self.set_font(font) # Whether we should highlight the current line or not. self.should_highlight_current_line = should_highlight_current_line # What that highlight color should be. self.line_highlight_color = QtGui.QColor(QtCore.Qt.yellow).lighter(160) # Auto-indentation behavior self.auto_indent = True self.smart_backspace = True # Tab settings self.tabs_as_spaces = True self.tab_width = 4 self.indent_character = ':' self.comment_character = '#' # Set up gutter widget and current line highlighting self.blockCountChanged.connect(self.update_line_number_width) self.updateRequest.connect(self.update_line_numbers) self.cursorPositionChanged.connect(self.highlight_current_line) self.update_line_number_width() self.highlight_current_line() # Don't wrap text self.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # Key bindings self.indent_key = QtGui.QKeySequence(QtCore.Qt.Key_Tab) self.unindent_key = QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Backtab) self.comment_key = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Slash) self.backspace_key = QtGui.QKeySequence(QtCore.Qt.Key_Backspace) def lines(self): """ Return the number of lines. """ return self.blockCount() def set_line_column(self, line, column): """ Move the cursor to a particular line/column number. These line and column numbers are 1-indexed. """ # Allow the caller to ignore either line or column by passing None. line0, col0 = self.get_line_column() if line is None: line = line0 if column is None: column = col0 line -= 1 column -= 1 block = self.document().findBlockByLineNumber(line) line_start = block.position() position = line_start + column cursor = self.textCursor() cursor.setPosition(position) self.setTextCursor(cursor) def get_line_column(self): """ Get the current line and column numbers. These line and column numbers are 1-indexed. """ cursor = self.textCursor() pos = cursor.position() line = cursor.blockNumber() + 1 line_start = cursor.block().position() column = pos - line_start + 1 return line, column def get_selected_text(self): """ Return the currently selected text. """ return unicode(self.textCursor().selectedText()) def set_font(self, font): """ Set the new QFont. """ self.document().setDefaultFont(font) self.line_number_widget.set_font(font) self.update_line_number_width() def update_line_number_width(self, nblocks=0): """ Update the width of the line number widget. """ left = 0 if not self.line_number_widget.isHidden(): left = self.line_number_widget.digits_width() self.setViewportMargins(left, 0, 0, 0) def update_line_numbers(self, rect, dy): """ Update the line numbers. """ if dy: self.line_number_widget.scroll(0, dy) self.line_number_widget.update(0, rect.y(), self.line_number_widget.width(), rect.height()) if rect.contains(self.viewport().rect()): self.update_line_number_width() def set_info_lines(self, info_lines): self.status_widget.info_lines = info_lines self.status_widget.update() def set_warn_lines(self, warn_lines): self.status_widget.warn_lines = warn_lines self.status_widget.update() def set_error_lines(self, error_lines): self.status_widget.error_lines = error_lines self.status_widget.update() def highlight_current_line(self): """ Highlight the line with the cursor. """ if self.should_highlight_current_line: selection = QtGui.QTextEdit.ExtraSelection() selection.format.setBackground(self.line_highlight_color) selection.format.setProperty(QtGui.QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.setExtraSelections([selection]) def autoindent_newline(self): tab = '\t' if self.tabs_as_spaces: tab = ' ' * self.tab_width cursor = self.textCursor() text = cursor.block().text() trimmed = text.rstrip() current_indent_pos = self._get_indent_position(text) cursor.beginEditBlock() # Create the new line. There is no need to move to the new block, as # the insertBlock will do that automatically cursor.insertBlock() # Remove any leading whitespace from the current line after = cursor.block().text() trimmed_after = after.rstrip() pos = after.index(trimmed_after) for i in range(pos): cursor.deleteChar() if self.indent_character and trimmed.endswith(self.indent_character): # indent one level indent = text[:current_indent_pos] + tab else: # indent to the same level indent = text[:current_indent_pos] cursor.insertText(indent) cursor.endEditBlock() self.ensureCursorVisible() def block_indent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Insert a tabulator self.line_indent(cursor) else: # Indent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_indent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_unindent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Unindent current line position = cursor.position() cursor.beginEditBlock() removed = self.line_unindent(cursor) position = max(position - removed, 0) cursor.endEditBlock() cursor.setPosition(position) self.setTextCursor(cursor) else: # Unindent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_unindent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_comment(self): """the comment char will be placed at the first non-whitespace char of the first line. For example: if foo: bar will be commented as: #if foo: # bar """ cursor = self.textCursor() if not cursor.hasSelection(): text = cursor.block().text() current_indent_pos = self._get_indent_position(text) if text[current_indent_pos] == self.comment_character: self.line_uncomment(cursor, current_indent_pos) else: self.line_comment(cursor, current_indent_pos) else: sel_blocks = self._get_selected_blocks() text = sel_blocks[0].text() indent_pos = self._get_indent_position(text) comment = True for block in sel_blocks: text = block.text() if len(text) > indent_pos and \ text[indent_pos] == self.comment_character: # Already commented. comment = False break cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) if comment: if block.length() < indent_pos: cursor.insertText(' ' * indent_pos) self.line_comment(cursor, indent_pos) else: self.line_uncomment(cursor, indent_pos) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def line_comment(self, cursor, position): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.MoveAnchor, position) cursor.insertText(self.comment_character) def line_uncomment(self, cursor, position=0): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) text = cursor.block().text() new_text = text[:position] + text[position + 1:] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) def line_indent(self, cursor): tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.insertText(tab) def line_unindent(self, cursor): """ Unindents the cursor's line. Returns the number of characters removed. """ tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.movePosition(QtGui.QTextCursor.StartOfBlock) if cursor.block().text().startswith(tab): new_text = cursor.block().text()[len(tab):] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) return len(tab) else: return 0 def word_under_cursor(self): """ Return the word under the cursor. """ cursor = self.textCursor() cursor.select(QtGui.QTextCursor.WordUnderCursor) return unicode(cursor.selectedText()) ########################################################################### # QWidget interface ########################################################################### # FIXME: This is a quick hack to be able to access the keyPressEvent # from the rest editor. This should be changed to work within the traits # framework. def keyPressEvent_action(self, event): pass def keyPressEvent(self, event): key_sequence = QtGui.QKeySequence(event.key() + int(event.modifiers())) self.keyPressEvent_action(event) # FIXME: see above # If the cursor is in the middle of the first line, pressing the "up" # key causes the cursor to go to the start of the first line, i.e. the # beginning of the document. Likewise, if the cursor is somewhere in the # last line, the "down" key causes it to go to the end. cursor = self.textCursor() if key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Up)): cursor.movePosition(QtGui.QTextCursor.StartOfLine) if cursor.atStart(): self.setTextCursor(cursor) event.accept() elif key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Down)): cursor.movePosition(QtGui.QTextCursor.EndOfLine) if cursor.atEnd(): self.setTextCursor(cursor) event.accept() elif self.auto_indent and \ key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Return)): event.accept() return self.autoindent_newline() elif key_sequence.matches(self.indent_key): event.accept() return self.block_indent() elif key_sequence.matches(self.unindent_key): event.accept() return self.block_unindent() elif key_sequence.matches(self.comment_key): event.accept() return self.block_comment() elif self.auto_indent and self.smart_backspace and \ key_sequence.matches(self.backspace_key) and \ self._backspace_should_unindent(): event.accept() return self.block_unindent() return super(CodeWidget, self).keyPressEvent(event) def resizeEvent(self, event): QtGui.QPlainTextEdit.resizeEvent(self, event) contents = self.contentsRect() self.line_number_widget.setGeometry( QtCore.QRect(contents.left(), contents.top(), self.line_number_widget.digits_width(), contents.height())) # use the viewport width to determine the right edge. This allows for # the propper placement w/ and w/o the scrollbar right_pos = self.viewport().width() + self.line_number_widget.width() + 1\ - self.status_widget.sizeHint().width() self.status_widget.setGeometry( QtCore.QRect(right_pos, contents.top(), self.status_widget.sizeHint().width(), contents.height())) def sizeHint(self): # Suggest a size that is 80 characters wide and 40 lines tall. style = self.style() opt = QtGui.QStyleOptionHeader() font_metrics = QtGui.QFontMetrics(self.document().defaultFont()) width = font_metrics.width(' ') * 80 width += self.line_number_widget.sizeHint().width() width += self.status_widget.sizeHint().width() width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self) height = font_metrics.height() * 40 return QtCore.QSize(width, height) ########################################################################### # Private methods ########################################################################### def _get_indent_position(self, line): trimmed = line.rstrip() if len(trimmed) != 0: return line.index(trimmed) else: # if line is all spaces, treat it as the indent position return len(line) def _show_selected_blocks(self, selected_blocks): """ Assumes contiguous blocks """ cursor = self.textCursor() cursor.clearSelection() cursor.setPosition(selected_blocks[0].position()) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor, len(selected_blocks)) cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) self.setTextCursor(cursor) def _get_selected_blocks(self): cursor = self.textCursor() if cursor.position() > cursor.anchor(): move_op = QtGui.QTextCursor.PreviousBlock start_pos = cursor.anchor() end_pos = cursor.position() else: move_op = QtGui.QTextCursor.NextBlock start_pos = cursor.position() end_pos = cursor.anchor() cursor.setPosition(start_pos) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) blocks = [cursor.block()] while cursor.movePosition(QtGui.QTextCursor.NextBlock): block = cursor.block() if block.position() < end_pos: blocks.append(block) return blocks def _backspace_should_unindent(self): cursor = self.textCursor() # Don't unindent if we have a selection. if cursor.hasSelection(): return False column = cursor.columnNumber() # Don't unindent if we are at the beggining of the line if column < self.tab_width: return False else: # Unindent if we are at the indent position return column == self._get_indent_position(cursor.block().text())
class CodeWidget(QtGui.QPlainTextEdit): """ A widget for viewing and editing code. """ breakpointClicked = QtCore.Signal(int) ########################################################################### # CodeWidget interface ########################################################################### def __init__(self, parent, font=None, lexer=None): super(CodeWidget, self).__init__(parent) self.highlighter = PygmentsHighlighter(self.document(), lexer) self.line_number_widget = LineNumberWidget(self) self.breakpoints_widget = BreakpointsWidget(self) self.status_widget = StatusGutterWidget(self) if font is None: # Set a decent fixed width font for this platform. font = QtGui.QFont() if sys.platform == 'win32': # Prefer Consolas, but fall back to Courier if necessary. font.setFamily('Consolas') if not font.exactMatch(): font.setFamily('Courier') elif sys.platform == 'darwin': font.setFamily('Monaco') else: font.setFamily('Monospace') font.setStyleHint(QtGui.QFont.TypeWriter) self.set_font(font) # What that highlight color should be. self.line_highlight_color = QtGui.QColor(QtCore.Qt.yellow).lighter(160) # Auto-indentation behavior self.auto_indent = True self.smart_backspace = True # Tab settings self.tabs_as_spaces = True self.tab_width = 4 self.indent_character = ':' self.comment_character = '#' # Set up gutter widget and current line highlighting self.blockCountChanged.connect(self.update_line_number_width) self.updateRequest.connect(self.update_line_numbers) # Set up breakpoint signals self.breakpoints_widget.gutterClicked.connect(self.breakpointClicked) self.update_line_number_width() # Don't wrap text self.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # Key bindings self.indent_key = QtGui.QKeySequence(QtCore.Qt.Key_Tab) self.unindent_key = QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Backtab) self.comment_key = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Slash) self.backspace_key = QtGui.QKeySequence(QtCore.Qt.Key_Backspace) def lines(self): """ Return the number of lines. """ return self.blockCount() def set_line_column(self, line, column): """ Move the cursor to a particular line/column number. These line and column numbers are 1-indexed. """ # Allow the caller to ignore either line or column by passing None. line0, col0 = self.get_line_column() if line is None: line = line0 if column is None: column = col0 line -= 1 #column -= 1 block = self.document().findBlockByLineNumber(line) line_start = block.position() position = line_start + column cursor = self.textCursor() cursor.setPosition(position) self.setTextCursor(cursor) def get_line_column(self): """ Get the current line and column numbers. These line and column numbers are 1-indexed. """ cursor = self.textCursor() pos = cursor.position() line = cursor.blockNumber() + 1 line_start = cursor.block().position() column = pos - line_start + 1 return line, column def get_selected_text(self): """ Return the currently selected text. """ return unicode(self.textCursor().selectedText()) def set_font(self, font): """ Set the new QFont. """ self.document().setDefaultFont(font) self.line_number_widget.set_font(font) self.update_line_number_width() def update_line_number_width(self, nblocks=0): """ Update the width of the line number widget. """ left = 0 if not self.breakpoints_widget.isHidden(): left += self.breakpoints_widget.gutter_width() if not self.line_number_widget.isHidden(): left += self.line_number_widget.gutter_width() self.setViewportMargins(left, 0, 0, 0) def update_line_numbers(self, rect, dy): """ Update the line numbers. """ if dy: self.line_number_widget.scroll(0, dy) self.breakpoints_widget.scroll(0, dy) self.line_number_widget.update( 0, rect.y(), self.line_number_widget.width(), rect.height()) self.breakpoints_widget.update( 0, rect.y(), self.breakpoints_widget.width(), rect.height()) if rect.contains(self.viewport().rect()): self.update_line_number_width() def set_info_lines(self, info_lines): self.status_widget.info_lines = info_lines self.status_widget.update() def set_warn_lines(self, warn_lines): self.status_widget.warn_lines = warn_lines self.status_widget.update() def set_error_lines(self, error_lines): self.status_widget.error_lines = error_lines self.status_widget.update() def autoindent_newline(self): tab = '\t' if self.tabs_as_spaces: tab = ' '*self.tab_width cursor = self.textCursor() text = cursor.block().text() trimmed = text.rstrip() current_indent_pos = self._get_indent_position(text) cursor.beginEditBlock() # Create the new line. There is no need to move to the new block, as # the insertBlock will do that automatically cursor.insertBlock() # Remove any leading whitespace from the current line after = cursor.block().text() trimmed_after = after.rstrip() pos = after.index(trimmed_after) for i in range(pos): cursor.deleteChar() if self.indent_character and trimmed.endswith(self.indent_character): # indent one level indent = text[:current_indent_pos] + tab else: # indent to the same level indent = text[:current_indent_pos] cursor.insertText(indent) cursor.endEditBlock() self.ensureCursorVisible() def block_indent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Insert a tabulator self.line_indent(cursor) else: # Indent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_indent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_unindent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Unindent current line position = cursor.position() cursor.beginEditBlock() removed = self.line_unindent(cursor) position = max(position-removed, 0) cursor.endEditBlock() cursor.setPosition(position) self.setTextCursor(cursor) else: # Unindent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_unindent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_comment(self): """the comment char will be placed at the first non-whitespace char of the first line. For example: if foo: bar will be commented as: #if foo: # bar """ cursor = self.textCursor() if not cursor.hasSelection(): text = cursor.block().text() current_indent_pos = self._get_indent_position(text) if text[current_indent_pos] == self.comment_character: self.line_uncomment(cursor, current_indent_pos) else: self.line_comment(cursor, current_indent_pos) else: sel_blocks = self._get_selected_blocks() text = sel_blocks[0].text() indent_pos = self._get_indent_position(text) comment = True for block in sel_blocks: text = block.text() if len(text) > indent_pos and \ text[indent_pos] == self.comment_character: # Already commented. comment = False break cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) if comment: if block.length() < indent_pos: cursor.insertText(' ' * indent_pos) self.line_comment(cursor, indent_pos) else: self.line_uncomment(cursor, indent_pos) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def line_comment(self, cursor, position): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.MoveAnchor, position) cursor.insertText(self.comment_character) def line_uncomment(self, cursor, position=0): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) text = cursor.block().text() new_text = text[:position] + text[position+1:] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) def line_indent(self, cursor): tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.insertText(tab) def line_unindent(self, cursor): """ Unindents the cursor's line. Returns the number of characters removed. """ tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.movePosition(QtGui.QTextCursor.StartOfBlock) if cursor.block().text().startswith(tab): new_text = cursor.block().text()[len(tab):] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) return len(tab) else: return 0 def word_under_cursor(self): """ Return the word under the cursor. """ cursor = self.textCursor() cursor.select(QtGui.QTextCursor.WordUnderCursor) return unicode(cursor.selectedText()) ########################################################################### # QWidget interface ########################################################################### # FIXME: This is a quick hack to be able to access the keyPressEvent # from the rest editor. This should be changed to work within the traits # framework. def keyPressEvent_action(self, event): pass def keyPressEvent(self, event): key_sequence = QtGui.QKeySequence(event.key() + int(event.modifiers())) self.keyPressEvent_action(event) # FIXME: see above # If the cursor is in the middle of the first line, pressing the "up" # key causes the cursor to go to the start of the first line, i.e. the # beginning of the document. Likewise, if the cursor is somewhere in the # last line, the "down" key causes it to go to the end. cursor = self.textCursor() if key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Up)): cursor.movePosition(QtGui.QTextCursor.StartOfLine) if cursor.atStart(): self.setTextCursor(cursor) event.accept() elif key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Down)): cursor.movePosition(QtGui.QTextCursor.EndOfLine) if cursor.atEnd(): self.setTextCursor(cursor) event.accept() elif self.auto_indent and \ key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Return)): event.accept() return self.autoindent_newline() elif key_sequence.matches(self.indent_key): event.accept() return self.block_indent() elif key_sequence.matches(self.unindent_key): event.accept() return self.block_unindent() elif key_sequence.matches(self.comment_key): event.accept() return self.block_comment() elif self.auto_indent and self.smart_backspace and \ key_sequence.matches(self.backspace_key) and \ self._backspace_should_unindent(): event.accept() return self.block_unindent() return super(CodeWidget, self).keyPressEvent(event) def resizeEvent(self, event): QtGui.QPlainTextEdit.resizeEvent(self, event) contents = self.contentsRect() left = contents.left() self.breakpoints_widget.setGeometry(QtCore.QRect(left, contents.top(), self.breakpoints_widget.gutter_width(), contents.height())) left += self.breakpoints_widget.gutter_width() self.line_number_widget.setGeometry(QtCore.QRect(left, contents.top(), self.line_number_widget.gutter_width(), contents.height())) # use the viewport width to determine the right edge. This allows for # the propper placement w/ and w/o the scrollbar right_pos = self.viewport().width() + self.line_number_widget.width() + \ self.breakpoints_widget.width() + 1\ - self.status_widget.sizeHint().width() self.status_widget.setGeometry(QtCore.QRect(right_pos, contents.top(), self.status_widget.sizeHint().width(), contents.height())) def sizeHint(self): # Suggest a size that is 80 characters wide and 40 lines tall. style = self.style() opt = QtGui.QStyleOptionHeader() font_metrics = QtGui.QFontMetrics(self.document().defaultFont()) width = font_metrics.width(' ') * 80 width += self.breakpoints_widget.sizeHint().width() width += self.line_number_widget.sizeHint().width() width += self.status_widget.sizeHint().width() width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self) height = font_metrics.height() * 40 return QtCore.QSize(width, height) def setTextCursor(self, cursor): cursor.setVisualNavigation(True) super(CodeWidget, self).setTextCursor(cursor) ########################################################################### # Private methods ########################################################################### def _get_indent_position(self, line): trimmed = line.rstrip() if len(trimmed) != 0: return line.index(trimmed) else: # if line is all spaces, treat it as the indent position return len(line) def _show_selected_blocks(self, selected_blocks): """ Assumes contiguous blocks """ cursor = self.textCursor() cursor.clearSelection() cursor.setPosition(selected_blocks[0].position()) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor, len(selected_blocks)) cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) self.setTextCursor(cursor) def _get_selected_blocks(self): cursor = self.textCursor() if cursor.position() > cursor.anchor(): move_op = QtGui.QTextCursor.PreviousBlock start_pos = cursor.anchor() end_pos = cursor.position() else: move_op = QtGui.QTextCursor.NextBlock start_pos = cursor.position() end_pos = cursor.anchor() cursor.setPosition(start_pos) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) blocks = [cursor.block()] while cursor.movePosition(QtGui.QTextCursor.NextBlock): block = cursor.block() if block.position() < end_pos: blocks.append(block) return blocks def _backspace_should_unindent(self): cursor = self.textCursor() # Don't unindent if we have a selection. if cursor.hasSelection(): return False column = cursor.columnNumber() # Don't unindent if we are at the beggining of the line if column < self.tab_width: return False else: # Unindent if we are at the indent position return column == self._get_indent_position(cursor.block().text())