def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " " * 4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_enter = False self.completion_text = "" self.calltip_widget = CallTipWidget(self, hide_timer_on=True) self.calltips = True self.calltip_position = None self.has_cell_separators = False self.highlight_current_cell_enabled = False # The color values may be overridden by the syntax highlighter # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) self.currentcell_color = QColor(Qt.red).lighter(194) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red)
def __init__(self, parent=None): QTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) TracebackLinksMixin.__init__(self) InspectObjectMixin.__init__(self) self.found_results = [] self.calltip_widget = CallTipWidget(self, hide_timer_on=True) # To not use Spyder calltips obtained through the monitor self.calltips = False
def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " "*4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_enter = False self.completion_text = "" self.calltip_widget = CallTipWidget(self, hide_timer_on=True) self.calltips = True self.calltip_position = None self.has_cell_separators = False self.highlight_current_cell_enabled = False # The color values may be overridden by the syntax highlighter # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) self.currentcell_color = QColor(Qt.red).lighter(194) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red)
def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " "*4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_single = False self.codecompletion_enter = False self.calltips = True self.calltip_position = None self.calltip_font = QFont() self.completion_text = "" self.signature_widget = CallTipWidget(self) # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red)
class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin): """Text edit base widget""" BRACE_MATCHING_SCOPE = ('sof', 'eof') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " "*4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_enter = False self.calltips = True self.calltip_position = None self.has_cell_separators = False self.cell_separators = None self.completion_text = "" self.calltip_widget = CallTipWidget(self, hide_timer_on=True) # The color values may be overridden by the syntax highlighter # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) self.currentcell_color = QColor(Qt.red).lighter(194) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_completion(self, size=None, font=None): self.completion_widget.setup_appearance(size, font) def set_indent_chars(self, indent_chars): self.indent_chars = indent_chars def set_palette(self, background, foreground): """ Set text editor palette colors: background color and caret (text cursor) color """ palette = QPalette() palette.setColor(QPalette.Base, background) palette.setColor(QPalette.Text, foreground) self.setPalette(palette) #------Extra selections def get_extra_selections(self, key): return self.extra_selections_dict.get(key, []) def set_extra_selections(self, key, extra_selections): self.extra_selections_dict[key] = extra_selections def update_extra_selections(self): extra_selections = [] for key, extra in list(self.extra_selections_dict.items()): if key == 'current_line' or key == 'current_cell': # Python 3 compatibility (weird): current line has to be # highlighted first extra_selections = extra + extra_selections else: extra_selections += extra self.setExtraSelections(extra_selections) def clear_extra_selections(self, key): self.extra_selections_dict[key] = [] self.update_extra_selections() def changed(self): """Emit changed signal""" self.emit(SIGNAL('modificationChanged(bool)'), self.document().isModified()) #------Highlight current line def highlight_current_line(self): """Highlight current line""" selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, to_qvariant(True)) selection.format.setBackground(self.currentline_color) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.set_extra_selections('current_line', [selection]) self.update_extra_selections() def unhighlight_current_line(self): """Unhighlight current line""" self.clear_extra_selections('current_line') #------Highlight current cell def highlight_current_cell(self): """Highlight current cell""" if self.cell_separators is None: return selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, to_qvariant(True)) selection.format.setBackground(self.currentcell_color) selection.cursor, whole_file_selected, whole_screen_selected =\ self.select_current_cell_in_visible_portion() if whole_file_selected: self.clear_extra_selections('current_cell') elif whole_screen_selected: if self.has_cell_separators: self.set_extra_selections('current_cell', [selection]) self.update_extra_selections() else: self.clear_extra_selections('current_cell') else: self.set_extra_selections('current_cell', [selection]) self.update_extra_selections() def unhighlight_current_cell(self): """Unhighlight current cell""" self.clear_extra_selections('current_cell') #------Brace matching def find_brace_match(self, position, brace, forward): start_pos, end_pos = self.BRACE_MATCHING_SCOPE if forward: bracemap = {'(': ')', '[': ']', '{': '}'} text = self.get_text(position, end_pos) i_start_open = 1 i_start_close = 1 else: bracemap = {')': '(', ']': '[', '}': '{'} text = self.get_text(start_pos, position) i_start_open = len(text)-1 i_start_close = len(text)-1 while True: if forward: i_close = text.find(bracemap[brace], i_start_close) else: i_close = text.rfind(bracemap[brace], 0, i_start_close+1) if i_close > -1: if forward: i_start_close = i_close+1 i_open = text.find(brace, i_start_open, i_close) else: i_start_close = i_close-1 i_open = text.rfind(brace, i_close, i_start_open+1) if i_open > -1: if forward: i_start_open = i_open+1 else: i_start_open = i_open-1 else: # found matching brace if forward: return position+i_close else: return position-(len(text)-i_close) else: # no matching brace return def __highlight(self, positions, color=None, cancel=False): if cancel: self.clear_extra_selections('brace_matching') return extra_selections = [] for position in positions: if position > self.get_position('eof'): return selection = QTextEdit.ExtraSelection() selection.format.setBackground(color) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.setPosition(position) selection.cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('brace_matching', extra_selections) self.update_extra_selections() def cursor_position_changed(self): """Brace matching""" if self.bracepos is not None: self.__highlight(self.bracepos, cancel=True) self.bracepos = None cursor = self.textCursor() if cursor.position() == 0: return cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) text = to_text_string(cursor.selectedText()) pos1 = cursor.position() if text in (')', ']', '}'): pos2 = self.find_brace_match(pos1, text, forward=False) elif text in ('(', '[', '{'): pos2 = self.find_brace_match(pos1, text, forward=True) else: return if pos2 is not None: self.bracepos = (pos1, pos2) self.__highlight(self.bracepos, color=self.matched_p_color) else: self.bracepos = (pos1,) self.__highlight(self.bracepos, color=self.unmatched_p_color) #-----Widget setup and options def set_codecompletion_auto(self, state): """Set code completion state""" self.codecompletion_auto = state def set_codecompletion_case(self, state): """Case sensitive completion""" self.codecompletion_case = state self.completion_widget.case_sensitive = state def set_codecompletion_enter(self, state): """Enable Enter key to select completion""" self.codecompletion_enter = state self.completion_widget.enter_select = state def set_calltips(self, state): """Set calltips state""" self.calltips = state def set_wrap_mode(self, mode=None): """ Set wrap mode Valid *mode* values: None, 'word', 'character' """ if mode == 'word': wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere elif mode == 'character': wrap_mode = QTextOption.WrapAnywhere else: wrap_mode = QTextOption.NoWrap self.setWordWrapMode(wrap_mode) #------Reimplementing Qt methods def copy(self): """ Reimplement Qt method Copy text to clipboard with correct EOL chars """ QApplication.clipboard().setText(self.get_selected_text()) #------Text: get, set, ... def get_selection_as_executable_code(self): """Return selected text as a processed text, to be executable in a Python/IPython interpreter""" ls = self.get_line_separator() _indent = lambda line: len(line)-len(line.lstrip()) line_from, line_to = self.get_selection_bounds() text = self.get_selected_text() if not text: return lines = text.split(ls) if len(lines) > 1: # Multiline selection -> eventually fixing indentation original_indent = _indent(self.get_text_line(line_from)) text = (" "*(original_indent-_indent(lines[0])))+text # If there is a common indent to all lines, find it. # Moving from bottom line to top line ensures that blank # lines inherit the indent of the line *below* it, # which is the desired behavior. min_indent = 999 current_indent = 0 lines = text.split(ls) for i in range(len(lines)-1, -1, -1): line = lines[i] if line.strip(): current_indent = _indent(line) min_indent = min(current_indent, min_indent) else: lines[i] = ' ' * current_indent if min_indent: lines = [line[min_indent:] for line in lines] # Remove any leading whitespace or comment lines # since they confuse the reserved word detector that follows below while lines: first_line = lines[0].lstrip() if first_line == '' or first_line[0] == '#': lines.pop(0) else: break # Add an EOL character after indentation blocks that start with some # Python reserved words, so that it gets evaluated automatically # by the console varname = re.compile('[a-zA-Z0-9_]*') # matches valid variable names maybe = False nextexcept = () for n, line in enumerate(lines): if not _indent(line): word = varname.match(line).group() if maybe and word not in nextexcept: lines[n-1] += ls maybe = False if word: if word in ('def', 'for', 'while', 'with', 'class'): maybe = True nextexcept = () elif word == 'if': maybe = True nextexcept = ('elif', 'else') elif word == 'try': maybe = True nextexcept = ('except', 'finally') if maybe: if lines[-1].strip() == '': lines[-1] += ls else: lines.append(ls) return ls.join(lines) def get_cell_as_executable_code(self): """Return cell contents as executable code""" start_pos, end_pos = self.__save_selection() cursor, whole_file_selected = self.select_current_cell() if not whole_file_selected: self.setTextCursor(cursor) text = self.get_selection_as_executable_code() self.__restore_selection(start_pos, end_pos) return text 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 select_current_cell(self): """Select cell under cursor cell = group of lines separated by CELL_SEPARATORS returns the textCursor and a boolean indicating if the entire file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False else: break cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() return cursor, cell_at_file_start and cell_at_file_end def select_current_cell_in_visible_portion(self): """Select cell under cursor in the visible portion of the file cell = group of lines separated by CELL_SEPARATORS returns -the textCursor -a boolean indicating if the entire file is selected -a boolean indicating if the entire visible portion of the file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() beg_pos = self.cursorForPosition(QPoint(0, 0)).position() bottom_right = QPoint(self.viewport().width() - 1, self.viewport().height() - 1) end_pos = self.cursorForPosition(bottom_right).position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor)\ and cursor.position() >= beg_pos: cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False, False else: break cell_at_screen_start = cursor.position() <= beg_pos cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Selecting cell header if not cell_at_file_start: cursor.movePosition(QTextCursor.PreviousBlock) cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor)\ and cursor.position() <= end_pos: cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() cell_at_screen_end = cursor.position() >= end_pos return cursor,\ cell_at_file_start and cell_at_file_end,\ cell_at_screen_start and cell_at_screen_end def go_to_next_cell(self): """Go to the next cell of lines""" cursor = self.textCursor() cursor.movePosition(QTextCursor.NextBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Moving to the next code cell cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def get_line_count(self): """Return document total line number""" return self.blockCount() def __save_selection(self): """Save current cursor selection and return position bounds""" cursor = self.textCursor() return cursor.selectionStart(), cursor.selectionEnd() def __restore_selection(self, start_pos, end_pos): """Restore cursor selection from position bounds""" cursor = self.textCursor() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() 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 += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def duplicate_line(self): """ Duplicate current line or selected text Paste the duplicated text *after* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=True) def copy_line(self): """ Copy current line or selected text Paste the duplicated text *before* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=False) def __move_line_or_selection(self, after_current_line=True): """Move current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): # Check if start_pos is at the start of a block cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() 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 to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() else: cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) sel_text = to_text_string(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = to_text_string(cursor.block().text()) start_pos += len(text)+1 end_pos += len(text)+1 cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = to_text_string(cursor.block().text()) start_pos -= len(text)+1 end_pos -= len(text)+1 cursor.insertText(sel_text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def move_line_up(self): """Move up current line or selected text""" self.__move_line_or_selection(after_current_line=False) def move_line_down(self): """Move down current line or selected text""" self.__move_line_or_selection(after_current_line=True) def extend_selection_to_complete_lines(self): """Extend current selection to complete lines""" cursor = self.textCursor() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if cursor.atBlockStart(): cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def delete_line(self): """Delete current line""" cursor = self.textCursor() if self.has_selected_text(): self.extend_selection_to_complete_lines() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) else: start_pos = end_pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() self.ensureCursorVisible() #------Code completion / Calltips def hide_tooltip_if_necessary(self, key): """Hide calltip when necessary""" try: calltip_char = self.get_character(self.calltip_position) before = self.is_cursor_before(self.calltip_position, char_offset=1) other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab) if calltip_char not in ('?', '(') or before or other: QToolTip.hideText() except (IndexError, TypeError): QToolTip.hideText() def show_completion_widget(self, textlist, automatic=True): """Show completion widget""" self.completion_widget.show_list(textlist, automatic=automatic) def hide_completion_widget(self): """Hide completion widget""" self.completion_widget.hide() def show_completion_list(self, completions, completion_text="", automatic=True): """Display the possible completions""" if len(completions) == 0 or completions == [completion_text]: return self.completion_text = completion_text # Sorting completion list (entries starting with underscore are # put at the end of the list): underscore = set([comp for comp in completions if comp.startswith('_')]) completions = sorted(set(completions)-underscore, key=str_lower)+\ sorted(underscore, key=str_lower) self.show_completion_widget(completions, automatic=automatic) def select_completion_list(self): """Completion list is active, Enter was just pressed""" self.completion_widget.item_selected() def insert_completion(self, text): if text: cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, len(self.completion_text)) cursor.removeSelectedText() self.insert_text(text) def is_completion_widget_visible(self): """Return True is completion list widget is visible""" return self.completion_widget.isVisible() #------Standard keys def stdkey_clear(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def stdkey_backspace(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def __get_move_mode(self, shift): return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor def stdkey_up(self, shift): self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift)) def stdkey_down(self, shift): self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift)) def stdkey_tab(self): self.insert_text(self.indent_chars) def stdkey_home(self, shift, ctrl, prompt_pos=None): """Smart HOME feature: cursor is first moved at indentation position, then at the start of the line""" move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.Start, move_mode) else: cursor = self.textCursor() if prompt_pos is None: start_position = self.get_position('sol') else: start_position = self.get_position(prompt_pos) text = self.get_text(start_position, 'eol') indent_pos = start_position+len(text)-len(text.lstrip()) if cursor.position() != indent_pos: cursor.setPosition(indent_pos, move_mode) else: cursor.setPosition(start_position, move_mode) self.setTextCursor(cursor) def stdkey_end(self, shift, ctrl): move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.End, move_mode) else: self.moveCursor(QTextCursor.EndOfBlock, move_mode) def stdkey_pageup(self): pass def stdkey_pagedown(self): pass def stdkey_escape(self): pass #----Qt Events def mousePressEvent(self, event): """Reimplement Qt method""" if sys.platform.startswith('linux') and event.button() == Qt.MidButton: self.calltip_widget.hide() if self.has_selected_text(): self.remove_selected_text() self.setFocus() event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QPlainTextEdit.mousePressEvent(self, event) QPlainTextEdit.mouseReleaseEvent(self, event) # Send selection text to clipboard to be able to use # the paste method and avoid the strange Issue 1445 # # Note: This issue seems a focusing problem but it # seems really hard to track mode_clip = QClipboard.Clipboard mode_sel = QClipboard.Selection text_clip = QApplication.clipboard().text(mode=mode_clip) text_sel = QApplication.clipboard().text(mode=mode_sel) QApplication.clipboard().setText(text_sel, mode=mode_clip) self.paste() QApplication.clipboard().setText(text_clip, mode=mode_clip) else: self.calltip_widget.hide() QPlainTextEdit.mousePressEvent(self, event) def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) self.highlight_current_cell() QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event) def wheelEvent(self, event): """Reimplemented to emit zoom in/out signals when Ctrl is pressed""" # This feature is disabled on MacOS, see Issue 1510: # http://code.google.com/p/spyderlib/issues/detail?id=1510 if sys.platform != 'darwin': if event.modifiers() & Qt.ControlModifier: if event.delta() < 0: self.emit(SIGNAL("zoom_out()")) elif event.delta() > 0: self.emit(SIGNAL("zoom_in()")) return QPlainTextEdit.wheelEvent(self, event) self.highlight_current_cell()
class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin): """Text edit base widget""" BRACE_MATCHING_SCOPE = ('sof', 'eof') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " " * 4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_enter = False self.calltips = True self.calltip_position = None self.has_cell_separators = False self.cell_separators = None self.completion_text = "" self.calltip_widget = CallTipWidget(self, hide_timer_on=True) # The color values may be overridden by the syntax highlighter # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) self.currentcell_color = QColor(Qt.red).lighter(194) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_completion(self, size=None, font=None): self.completion_widget.setup_appearance(size, font) def set_indent_chars(self, indent_chars): self.indent_chars = indent_chars def set_palette(self, background, foreground): """ Set text editor palette colors: background color and caret (text cursor) color """ palette = QPalette() palette.setColor(QPalette.Base, background) palette.setColor(QPalette.Text, foreground) self.setPalette(palette) #------Extra selections def get_extra_selections(self, key): return self.extra_selections_dict.get(key, []) def set_extra_selections(self, key, extra_selections): self.extra_selections_dict[key] = extra_selections def update_extra_selections(self): extra_selections = [] for key, extra in list(self.extra_selections_dict.items()): if key == 'current_line' or key == 'current_cell': # Python 3 compatibility (weird): current line has to be # highlighted first extra_selections = extra + extra_selections else: extra_selections += extra self.setExtraSelections(extra_selections) def clear_extra_selections(self, key): self.extra_selections_dict[key] = [] self.update_extra_selections() def changed(self): """Emit changed signal""" self.emit(SIGNAL('modificationChanged(bool)'), self.document().isModified()) #------Highlight current line def highlight_current_line(self): """Highlight current line""" selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, to_qvariant(True)) selection.format.setBackground(self.currentline_color) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.set_extra_selections('current_line', [selection]) self.update_extra_selections() def unhighlight_current_line(self): """Unhighlight current line""" self.clear_extra_selections('current_line') #------Highlight current cell def highlight_current_cell(self): """Highlight current cell""" if self.cell_separators is None: return selection = QTextEdit.ExtraSelection() selection.format.setProperty(QTextFormat.FullWidthSelection, to_qvariant(True)) selection.format.setBackground(self.currentcell_color) selection.cursor, whole_file_selected, whole_screen_selected =\ self.select_current_cell_in_visible_portion() if whole_file_selected: self.clear_extra_selections('current_cell') elif whole_screen_selected: if self.has_cell_separators: self.set_extra_selections('current_cell', [selection]) self.update_extra_selections() else: self.clear_extra_selections('current_cell') else: self.set_extra_selections('current_cell', [selection]) self.update_extra_selections() def unhighlight_current_cell(self): """Unhighlight current cell""" self.clear_extra_selections('current_cell') #------Brace matching def find_brace_match(self, position, brace, forward): start_pos, end_pos = self.BRACE_MATCHING_SCOPE if forward: bracemap = {'(': ')', '[': ']', '{': '}'} text = self.get_text(position, end_pos) i_start_open = 1 i_start_close = 1 else: bracemap = {')': '(', ']': '[', '}': '{'} text = self.get_text(start_pos, position) i_start_open = len(text) - 1 i_start_close = len(text) - 1 while True: if forward: i_close = text.find(bracemap[brace], i_start_close) else: i_close = text.rfind(bracemap[brace], 0, i_start_close + 1) if i_close > -1: if forward: i_start_close = i_close + 1 i_open = text.find(brace, i_start_open, i_close) else: i_start_close = i_close - 1 i_open = text.rfind(brace, i_close, i_start_open + 1) if i_open > -1: if forward: i_start_open = i_open + 1 else: i_start_open = i_open - 1 else: # found matching brace if forward: return position + i_close else: return position - (len(text) - i_close) else: # no matching brace return def __highlight(self, positions, color=None, cancel=False): if cancel: self.clear_extra_selections('brace_matching') return extra_selections = [] for position in positions: if position > self.get_position('eof'): return selection = QTextEdit.ExtraSelection() selection.format.setBackground(color) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.setPosition(position) selection.cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('brace_matching', extra_selections) self.update_extra_selections() def cursor_position_changed(self): """Brace matching""" if self.bracepos is not None: self.__highlight(self.bracepos, cancel=True) self.bracepos = None cursor = self.textCursor() if cursor.position() == 0: return cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) text = to_text_string(cursor.selectedText()) pos1 = cursor.position() if text in (')', ']', '}'): pos2 = self.find_brace_match(pos1, text, forward=False) elif text in ('(', '[', '{'): pos2 = self.find_brace_match(pos1, text, forward=True) else: return if pos2 is not None: self.bracepos = (pos1, pos2) self.__highlight(self.bracepos, color=self.matched_p_color) else: self.bracepos = (pos1, ) self.__highlight(self.bracepos, color=self.unmatched_p_color) #-----Widget setup and options def set_codecompletion_auto(self, state): """Set code completion state""" self.codecompletion_auto = state def set_codecompletion_case(self, state): """Case sensitive completion""" self.codecompletion_case = state self.completion_widget.case_sensitive = state def set_codecompletion_enter(self, state): """Enable Enter key to select completion""" self.codecompletion_enter = state self.completion_widget.enter_select = state def set_calltips(self, state): """Set calltips state""" self.calltips = state def set_wrap_mode(self, mode=None): """ Set wrap mode Valid *mode* values: None, 'word', 'character' """ if mode == 'word': wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere elif mode == 'character': wrap_mode = QTextOption.WrapAnywhere else: wrap_mode = QTextOption.NoWrap self.setWordWrapMode(wrap_mode) #------Reimplementing Qt methods def copy(self): """ Reimplement Qt method Copy text to clipboard with correct EOL chars """ QApplication.clipboard().setText(self.get_selected_text()) #------Text: get, set, ... def get_selection_as_executable_code(self): """Return selected text as a processed text, to be executable in a Python/IPython interpreter""" ls = self.get_line_separator() _indent = lambda line: len(line) - len(line.lstrip()) line_from, line_to = self.get_selection_bounds() text = self.get_selected_text() if not text: return lines = text.split(ls) if len(lines) > 1: # Multiline selection -> eventually fixing indentation original_indent = _indent(self.get_text_line(line_from)) text = (" " * (original_indent - _indent(lines[0]))) + text # If there is a common indent to all lines, find it. # Moving from bottom line to top line ensures that blank # lines inherit the indent of the line *below* it, # which is the desired behavior. min_indent = 999 current_indent = 0 lines = text.split(ls) for i in range(len(lines) - 1, -1, -1): line = lines[i] if line.strip(): current_indent = _indent(line) min_indent = min(current_indent, min_indent) else: lines[i] = ' ' * current_indent if min_indent: lines = [line[min_indent:] for line in lines] # Remove any leading whitespace or comment lines # since they confuse the reserved word detector that follows below while lines: first_line = lines[0].lstrip() if first_line == '' or first_line[0] == '#': lines.pop(0) else: break # Add an EOL character after indentation blocks that start with some # Python reserved words, so that it gets evaluated automatically # by the console varname = re.compile('[a-zA-Z0-9_]*') # matches valid variable names maybe = False nextexcept = () for n, line in enumerate(lines): if not _indent(line): word = varname.match(line).group() if maybe and word not in nextexcept: lines[n - 1] += ls maybe = False if word: if word in ('def', 'for', 'while', 'with', 'class'): maybe = True nextexcept = () elif word == 'if': maybe = True nextexcept = ('elif', 'else') elif word == 'try': maybe = True nextexcept = ('except', 'finally') if maybe: if lines[-1].strip() == '': lines[-1] += ls else: lines.append(ls) return ls.join(lines) def get_cell_as_executable_code(self): """Return cell contents as executable code""" start_pos, end_pos = self.__save_selection() cursor, whole_file_selected = self.select_current_cell() if not whole_file_selected: self.setTextCursor(cursor) text = self.get_selection_as_executable_code() self.__restore_selection(start_pos, end_pos) return text 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 select_current_cell(self): """Select cell under cursor cell = group of lines separated by CELL_SEPARATORS returns the textCursor and a boolean indicating if the entire file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False else: break cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() return cursor, cell_at_file_start and cell_at_file_end def select_current_cell_in_visible_portion(self): """Select cell under cursor in the visible portion of the file cell = group of lines separated by CELL_SEPARATORS returns -the textCursor -a boolean indicating if the entire file is selected -a boolean indicating if the entire visible portion of the file is selected""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() beg_pos = self.cursorForPosition(QPoint(0, 0)).position() bottom_right = QPoint(self.viewport().width() - 1, self.viewport().height() - 1) end_pos = self.cursorForPosition(bottom_right).position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return cursor, False, False prev_pos = cur_pos # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor)\ and cursor.position() >= beg_pos: cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return cursor, False, False else: break cell_at_screen_start = cursor.position() <= beg_pos cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Selecting cell header if not cell_at_file_start: cursor.movePosition(QTextCursor.PreviousBlock) cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor)\ and cursor.position() <= end_pos: cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos cell_at_file_end = cursor.atEnd() cell_at_screen_end = cursor.position() >= end_pos return cursor,\ cell_at_file_start and cell_at_file_end,\ cell_at_screen_start and cell_at_screen_end def go_to_next_cell(self): """Go to the next cell of lines""" cursor = self.textCursor() cursor.movePosition(QTextCursor.NextBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Moving to the next code cell cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def get_line_count(self): """Return document total line number""" return self.blockCount() def __save_selection(self): """Save current cursor selection and return position bounds""" cursor = self.textCursor() return cursor.selectionStart(), cursor.selectionEnd() def __restore_selection(self, start_pos, end_pos): """Restore cursor selection from position bounds""" cursor = self.textCursor() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() 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 += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def duplicate_line(self): """ Duplicate current line or selected text Paste the duplicated text *after* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=True) def copy_line(self): """ Copy current line or selected text Paste the duplicated text *before* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=False) def __move_line_or_selection(self, after_current_line=True): """Move current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): # Check if start_pos is at the start of a block cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() 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 to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() else: cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) sel_text = to_text_string(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = to_text_string(cursor.block().text()) start_pos += len(text) + 1 end_pos += len(text) + 1 cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = to_text_string(cursor.block().text()) start_pos -= len(text) + 1 end_pos -= len(text) + 1 cursor.insertText(sel_text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def move_line_up(self): """Move up current line or selected text""" self.__move_line_or_selection(after_current_line=False) def move_line_down(self): """Move down current line or selected text""" self.__move_line_or_selection(after_current_line=True) def extend_selection_to_complete_lines(self): """Extend current selection to complete lines""" cursor = self.textCursor() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if cursor.atBlockStart(): cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def delete_line(self): """Delete current line""" cursor = self.textCursor() if self.has_selected_text(): self.extend_selection_to_complete_lines() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) else: start_pos = end_pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() self.ensureCursorVisible() #------Code completion / Calltips def hide_tooltip_if_necessary(self, key): """Hide calltip when necessary""" try: calltip_char = self.get_character(self.calltip_position) before = self.is_cursor_before(self.calltip_position, char_offset=1) other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab) if calltip_char not in ('?', '(') or before or other: QToolTip.hideText() except (IndexError, TypeError): QToolTip.hideText() def show_completion_widget(self, textlist, automatic=True): """Show completion widget""" self.completion_widget.show_list(textlist, automatic=automatic) def hide_completion_widget(self): """Hide completion widget""" self.completion_widget.hide() def show_completion_list(self, completions, completion_text="", automatic=True): """Display the possible completions""" if len(completions) == 0 or completions == [completion_text]: return self.completion_text = completion_text # Sorting completion list (entries starting with underscore are # put at the end of the list): underscore = set( [comp for comp in completions if comp.startswith('_')]) completions = sorted(set(completions)-underscore, key=str_lower)+\ sorted(underscore, key=str_lower) self.show_completion_widget(completions, automatic=automatic) def select_completion_list(self): """Completion list is active, Enter was just pressed""" self.completion_widget.item_selected() def insert_completion(self, text): if text: cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, len(self.completion_text)) cursor.removeSelectedText() self.insert_text(text) def is_completion_widget_visible(self): """Return True is completion list widget is visible""" return self.completion_widget.isVisible() #------Standard keys def stdkey_clear(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def stdkey_backspace(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def __get_move_mode(self, shift): return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor def stdkey_up(self, shift): self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift)) def stdkey_down(self, shift): self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift)) def stdkey_tab(self): self.insert_text(self.indent_chars) def stdkey_home(self, shift, ctrl, prompt_pos=None): """Smart HOME feature: cursor is first moved at indentation position, then at the start of the line""" move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.Start, move_mode) else: cursor = self.textCursor() if prompt_pos is None: start_position = self.get_position('sol') else: start_position = self.get_position(prompt_pos) text = self.get_text(start_position, 'eol') indent_pos = start_position + len(text) - len(text.lstrip()) if cursor.position() != indent_pos: cursor.setPosition(indent_pos, move_mode) else: cursor.setPosition(start_position, move_mode) self.setTextCursor(cursor) def stdkey_end(self, shift, ctrl): move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.End, move_mode) else: self.moveCursor(QTextCursor.EndOfBlock, move_mode) def stdkey_pageup(self): pass def stdkey_pagedown(self): pass def stdkey_escape(self): pass #----Qt Events def mousePressEvent(self, event): """Reimplement Qt method""" if sys.platform.startswith('linux') and event.button() == Qt.MidButton: self.calltip_widget.hide() if self.has_selected_text(): self.remove_selected_text() self.setFocus() event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QPlainTextEdit.mousePressEvent(self, event) QPlainTextEdit.mouseReleaseEvent(self, event) # Send selection text to clipboard to be able to use # the paste method and avoid the strange Issue 1445 # # Note: This issue seems a focusing problem but it # seems really hard to track mode_clip = QClipboard.Clipboard mode_sel = QClipboard.Selection text_clip = QApplication.clipboard().text(mode=mode_clip) text_sel = QApplication.clipboard().text(mode=mode_sel) QApplication.clipboard().setText(text_sel, mode=mode_clip) self.paste() QApplication.clipboard().setText(text_clip, mode=mode_clip) else: self.calltip_widget.hide() QPlainTextEdit.mousePressEvent(self, event) def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) self.highlight_current_cell() QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event) def wheelEvent(self, event): """Reimplemented to emit zoom in/out signals when Ctrl is pressed""" # This feature is disabled on MacOS, see Issue 1510: # http://code.google.com/p/spyderlib/issues/detail?id=1510 if sys.platform != 'darwin': if event.modifiers() & Qt.ControlModifier: if event.delta() < 0: self.emit(SIGNAL("zoom_out()")) elif event.delta() > 0: self.emit(SIGNAL("zoom_in()")) return QPlainTextEdit.wheelEvent(self, event) self.highlight_current_cell()