def set_pythonshell_font(self, font=None): """Python Shell only""" if font is None: font = QFont() for style in self.font_styles: style.apply_style(font=font, light_background=self.light_background, is_default=style is self.default_style) self.ansi_handler.set_base_format(self.default_style.format)
def get_family(families): """Return the first installed font family in family list""" if not isinstance(families, list): families = [families] for family in families: if font_is_installed(family): return family else: print "Warning: None of the following fonts is installed: %r" % families return QFont().family()
def get_font(section, option=None): """Get console font properties depending on OS and user options""" font = FONT_CACHE.get((section, option)) if font is None: if option is None: option = 'font' else: option += '/font' families = CONF.get(section, option + "/family", None) if families is None: return QFont() family = get_family(families) weight = QFont.Normal italic = CONF.get(section, option + '/italic', False) if CONF.get(section, option + '/bold', False): weight = QFont.Bold size = CONF.get(section, option + '/size', 9) font = QFont(family, size, weight) font.setItalic(italic) FONT_CACHE[(section, option)] = font return font
def get_font(section, option=None): """Get console font properties depending on OS and user options""" font = FONT_CACHE.get((section, option)) if font is None: if option is None: option = 'font' else: option += '/font' families = CONF.get(section, option+"/family", None) if families is None: return QFont() family = get_family(families) weight = QFont.Normal italic = CONF.get(section, option+'/italic', False) if CONF.get(section, option+'/bold', False): weight = QFont.Bold size = CONF.get(section, option+'/size', 9) font = QFont(family, size, weight) font.setItalic(italic) FONT_CACHE[(section, option)] = font return font
def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) mixins.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_size = 600 self.calltip_font = QFont() self.completion_text = "" # 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) mixins.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_size = 600 self.calltip_font = QFont() self.completion_text = "" # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red)
class TextEditBaseWidget(QPlainTextEdit, mixins.BaseEditMixin): """ Text edit base widget """ BRACE_MATCHING_SCOPE = ('sof', 'eof') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) mixins.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_size = 600 self.calltip_font = QFont() self.completion_text = "" # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_calltips(self, size=None, font=None): self.calltip_size = size self.calltip_font = font 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) #------Line number area def get_linenumberarea_width(self): """Return line number area width""" # Implemented in CodeEditor, but needed here for completion widget return 0 #------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 self.extra_selections_dict.iteritems(): extra_selections.extend(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()) #------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 = unicode(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_single(self, state): """Show single completion""" self.codecompletion_single = state self.completion_widget.show_single = 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_executable_text(self): """Return selected text or current line as an processed text, to be executable in a Python/IPython interpreter""" no_selection = not self.has_selected_text() if no_selection: self.select_current_block() 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() 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, remove it min_indent = 999 for line in text.split(ls): if line.strip(): min_indent = min(_indent(line), min_indent) if min_indent: text = ls.join([line[min_indent:] for line in text.split(ls)]) # Add an EOL character if a block stars with various Python # reserved words, so that it gets evaluated automatically # by the console first_line = lines[0].lstrip() last_line = self.get_text_line(line_to).strip() words = ['def', 'for', 'if', 'while', 'try', 'with', 'class'] if any([first_line.startswith(w) for w in words]): text += ls if last_line != '': text += ls if no_selection: self.setFocus() self.clear_selection() return text def get_line_count(self): """Return document total line number""" return self.blockCount() def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() orig_sel = start_pos, end_pos = (cursor.selectionStart(), cursor.selectionEnd()) if unicode(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 unicode(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) orig_sel = (orig_sel[0]+len(text), orig_sel[1]+len(text)) cursor.insertText(text) cursor.endEditBlock() cursor.setPosition(orig_sel[0]) cursor.setPosition(orig_sel[1], QTextCursor.KeepAnchor) self.setTextCursor(cursor) 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() orig_sel = start_pos, end_pos = (cursor.selectionStart(), cursor.selectionEnd()) if unicode(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 unicode(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 = unicode(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = unicode(cursor.block().text()) orig_sel = (orig_sel[0]+len(text)+1, orig_sel[1]+len(text)+1) cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = unicode(cursor.block().text()) orig_sel = (orig_sel[0]-len(text)-1, orig_sel[1]-len(text)-1) cursor.insertText(sel_text) cursor.endEditBlock() cursor.setPosition(orig_sel[0]) cursor.setPosition(orig_sel[1], QTextCursor.KeepAnchor) self.setTextCursor(cursor) 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 show_calltip(self, title, text, color='#2D62FF', at_line=None, at_position=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: weight = 'bold' if self.calltip_font.bold() else 'normal' size = self.calltip_font.pointSize() family = self.calltip_font.family() format1 = '<div style=\'font-size: %spt; color: %s\'>' % (size, color) format2 = '<hr><div style=\'font-family: "%s"; font-size: %spt; font-weight: %s\'>' % (family, size, weight) if isinstance(text, list): text = "\n ".join(text) text = text.replace('\n', '<br>') if len(text) > self.calltip_size: text = text[:self.calltip_size] + " ..." tiptext = format1 + ('<b>%s</b></div>' % title) \ + format2 + text + "</div>" # Showing tooltip at cursor position: cx, cy = self.get_coordinates('cursor') 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.setX(point.x()+self.get_linenumberarea_width()) QToolTip.showText(point, tiptext) 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=string.lower)+\ sorted(underscore, key=string.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 #----Focus def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event)
class TextEditBaseWidget(QPlainTextEdit, mixins.BaseEditMixin): """ Text edit base widget """ BRACE_MATCHING_SCOPE = ('sof', 'eof') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) mixins.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_size = 600 self.calltip_font = QFont() self.completion_text = "" # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_calltips(self, size=None, font=None): self.calltip_size = size self.calltip_font = font 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) #------Line number area def get_linenumberarea_width(self): """Return line number area width""" # Implemented in CodeEditor, but needed here for completion widget return 0 #------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 self.extra_selections_dict.iteritems(): extra_selections.extend(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()) #------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 = unicode(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_single(self, state): """Show single completion""" self.codecompletion_single = state self.completion_widget.show_single = 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_executable_text(self): """Return selected text or current line as an processed text, to be executable in a Python/IPython interpreter""" no_selection = not self.has_selected_text() if no_selection: self.select_current_block() 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() 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, remove it min_indent = 999 for line in text.split(ls): if line.strip(): min_indent = min(_indent(line), min_indent) if min_indent: text = ls.join([line[min_indent:] for line in text.split(ls)]) # Add an EOL character if a block stars with various Python # reserved words, so that it gets evaluated automatically # by the console first_line = lines[0].lstrip() last_line = self.get_text_line(line_to).strip() words = ['def', 'for', 'if', 'while', 'try', 'with', 'class'] if any([first_line.startswith(w) for w in words]): text += ls if last_line != '': text += ls if no_selection: self.setFocus() self.clear_selection() return text def get_line_count(self): """Return document total line number""" return self.blockCount() def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() orig_sel = start_pos, end_pos = (cursor.selectionStart(), cursor.selectionEnd()) if unicode(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 unicode(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) orig_sel = (orig_sel[0] + len(text), orig_sel[1] + len(text)) cursor.insertText(text) cursor.endEditBlock() cursor.setPosition(orig_sel[0]) cursor.setPosition(orig_sel[1], QTextCursor.KeepAnchor) self.setTextCursor(cursor) 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() orig_sel = start_pos, end_pos = (cursor.selectionStart(), cursor.selectionEnd()) if unicode(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 unicode(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 = unicode(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = unicode(cursor.block().text()) orig_sel = (orig_sel[0] + len(text) + 1, orig_sel[1] + len(text) + 1) cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = unicode(cursor.block().text()) orig_sel = (orig_sel[0] - len(text) - 1, orig_sel[1] - len(text) - 1) cursor.insertText(sel_text) cursor.endEditBlock() cursor.setPosition(orig_sel[0]) cursor.setPosition(orig_sel[1], QTextCursor.KeepAnchor) self.setTextCursor(cursor) 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 show_calltip(self, title, text, color='#2D62FF', at_line=None, at_position=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: weight = 'bold' if self.calltip_font.bold() else 'normal' size = self.calltip_font.pointSize() family = self.calltip_font.family() format1 = '<div style=\'font-size: %spt; color: %s\'>' % (size, color) format2 = '<hr><div style=\'font-family: "%s"; font-size: %spt; font-weight: %s\'>' % ( family, size, weight) if isinstance(text, list): text = "\n ".join(text) text = text.replace('\n', '<br>') if len(text) > self.calltip_size: text = text[:self.calltip_size] + " ..." tiptext = format1 + ('<b>%s</b></div>' % title) \ + format2 + text + "</div>" # Showing tooltip at cursor position: cx, cy = self.get_coordinates('cursor') 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.setX(point.x() + self.get_linenumberarea_width()) QToolTip.showText(point, tiptext) 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=string.lower)+\ sorted(underscore, key=string.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 #----Focus def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event)