1# -*- coding: utf-8 -*-
2#
3# Copyright © Spyder Project Contributors
4# Licensed under the terms of the MIT License
5# (see spyder/__init__.py for details)
6
7"""QPlainTextEdit base class"""
8
9# pylint: disable=C0103
10# pylint: disable=R0903
11# pylint: disable=R0911
12# pylint: disable=R0201
13
14# Standard library imports
15import os
16import re
17import sys
18from collections import OrderedDict
19
20# Third party imports
21from qtpy.compat import to_qvariant
22from qtpy.QtCore import QEvent, QEventLoop, QPoint, Qt, Signal, Slot
23from qtpy.QtGui import (QClipboard, QColor, QFont, QMouseEvent, QPalette,
24                        QTextCharFormat, QTextFormat, QTextOption, QTextCursor)
25from qtpy.QtWidgets import (QAbstractItemView, QApplication, QListWidget,
26                            QListWidgetItem, QMainWindow, QPlainTextEdit,
27                            QTextEdit, QToolTip)
28
29# Local imports
30from spyder.config.gui import get_font
31from spyder.config.main import CONF
32from spyder.py3compat import PY3, str_lower, to_text_string
33from spyder.utils import icon_manager as ima
34from spyder.widgets.calltip import CallTipWidget
35from spyder.widgets.mixins import BaseEditMixin
36from spyder.widgets.sourcecode.terminal import ANSIEscapeCodeHandler
37
38
39def insert_text_to(cursor, text, fmt):
40    """Helper to print text, taking into account backspaces"""
41    while True:
42        index = text.find(chr(8))  # backspace
43        if index == -1:
44            break
45        cursor.insertText(text[:index], fmt)
46        if cursor.positionInBlock() > 0:
47            cursor.deletePreviousChar()
48        text = text[index+1:]
49    cursor.insertText(text, fmt)
50
51
52class CompletionWidget(QListWidget):
53    """Completion list widget"""
54
55    sig_show_completions = Signal(object)
56
57    def __init__(self, parent, ancestor):
58        QListWidget.__init__(self, ancestor)
59        self.setWindowFlags(Qt.SubWindow | Qt.FramelessWindowHint)
60        self.textedit = parent
61        self.completion_list = None
62        self.case_sensitive = False
63        self.enter_select = None
64        self.hide()
65        self.itemActivated.connect(self.item_selected)
66
67    def setup_appearance(self, size, font):
68        self.resize(*size)
69        self.setFont(font)
70
71    def show_list(self, completion_list, automatic=True):
72        types = [c[1] for c in completion_list]
73        completion_list = [c[0] for c in completion_list]
74        if len(completion_list) == 1 and not automatic:
75            self.textedit.insert_completion(completion_list[0])
76            return
77
78        self.completion_list = completion_list
79        self.clear()
80
81        icons_map = {'instance': 'attribute',
82                     'statement': 'attribute',
83                     'method': 'method',
84                     'function': 'function',
85                     'class': 'class',
86                     'module': 'module'}
87
88        self.type_list = types
89        if any(types):
90            for (c, t) in zip(completion_list, types):
91                icon = icons_map.get(t, 'no_match')
92                self.addItem(QListWidgetItem(ima.icon(icon), c))
93        else:
94            self.addItems(completion_list)
95
96        self.setCurrentRow(0)
97
98        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
99        self.show()
100        self.setFocus()
101        self.raise_()
102
103        # Retrieving current screen height
104        desktop = QApplication.desktop()
105        srect = desktop.availableGeometry(desktop.screenNumber(self))
106        screen_right = srect.right()
107        screen_bottom = srect.bottom()
108
109        point = self.textedit.cursorRect().bottomRight()
110        point.setX(point.x()+self.textedit.get_linenumberarea_width())
111        point = self.textedit.mapToGlobal(point)
112
113        # Computing completion widget and its parent right positions
114        comp_right = point.x()+self.width()
115        ancestor = self.parent()
116        if ancestor is None:
117            anc_right = screen_right
118        else:
119            anc_right = min([ancestor.x()+ancestor.width(), screen_right])
120
121        # Moving completion widget to the left
122        # if there is not enough space to the right
123        if comp_right > anc_right:
124            point.setX(point.x()-self.width())
125
126        # Computing completion widget and its parent bottom positions
127        comp_bottom = point.y()+self.height()
128        ancestor = self.parent()
129        if ancestor is None:
130            anc_bottom = screen_bottom
131        else:
132            anc_bottom = min([ancestor.y()+ancestor.height(), screen_bottom])
133
134        # Moving completion widget above if there is not enough space below
135        x_position = point.x()
136        if comp_bottom > anc_bottom:
137            point = self.textedit.cursorRect().topRight()
138            point = self.textedit.mapToGlobal(point)
139            point.setX(x_position)
140            point.setY(point.y()-self.height())
141
142        if ancestor is not None:
143            # Useful only if we set parent to 'ancestor' in __init__
144            point = ancestor.mapFromGlobal(point)
145        self.move(point)
146
147        if to_text_string(self.textedit.completion_text):
148            # When initialized, if completion text is not empty, we need
149            # to update the displayed list:
150            self.update_current()
151
152        # signal used for testing
153        self.sig_show_completions.emit(completion_list)
154
155    def hide(self):
156        QListWidget.hide(self)
157        self.textedit.setFocus()
158
159    def keyPressEvent(self, event):
160        text, key = event.text(), event.key()
161        alt = event.modifiers() & Qt.AltModifier
162        shift = event.modifiers() & Qt.ShiftModifier
163        ctrl = event.modifiers() & Qt.ControlModifier
164        modifier = shift or ctrl or alt
165        if (key in (Qt.Key_Return, Qt.Key_Enter) and self.enter_select) \
166           or key == Qt.Key_Tab:
167            self.item_selected()
168        elif key in (Qt.Key_Return, Qt.Key_Enter,
169                     Qt.Key_Left, Qt.Key_Right) or text in ('.', ':'):
170            self.hide()
171            self.textedit.keyPressEvent(event)
172        elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown,
173                     Qt.Key_Home, Qt.Key_End,
174                     Qt.Key_CapsLock) and not modifier:
175            QListWidget.keyPressEvent(self, event)
176        elif len(text) or key == Qt.Key_Backspace:
177            self.textedit.keyPressEvent(event)
178            self.update_current()
179        elif modifier:
180            self.textedit.keyPressEvent(event)
181        else:
182            self.hide()
183            QListWidget.keyPressEvent(self, event)
184
185    def update_current(self):
186        completion_text = to_text_string(self.textedit.completion_text)
187
188        if completion_text:
189            for row, completion in enumerate(self.completion_list):
190                if not self.case_sensitive:
191                    print(completion_text)  # spyder: test-skip
192                    completion = completion.lower()
193                    completion_text = completion_text.lower()
194                if completion.startswith(completion_text):
195                    self.setCurrentRow(row)
196                    self.scrollTo(self.currentIndex(),
197                                  QAbstractItemView.PositionAtTop)
198                    break
199            else:
200                self.hide()
201        else:
202            self.hide()
203
204
205    def focusOutEvent(self, event):
206        event.ignore()
207        # Don't hide it on Mac when main window loses focus because
208        # keyboard input is lost
209        # Fixes Issue 1318
210        if sys.platform == "darwin":
211            if event.reason() != Qt.ActiveWindowFocusReason:
212                self.hide()
213        else:
214            self.hide()
215
216    def item_selected(self, item=None):
217        if item is None:
218            item = self.currentItem()
219        self.textedit.insert_completion( to_text_string(item.text()) )
220        self.hide()
221
222
223class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin):
224    """Text edit base widget"""
225    BRACE_MATCHING_SCOPE = ('sof', 'eof')
226    cell_separators = None
227    focus_in = Signal()
228    zoom_in = Signal()
229    zoom_out = Signal()
230    zoom_reset = Signal()
231    focus_changed = Signal()
232    sig_eol_chars_changed = Signal(str)
233
234    def __init__(self, parent=None):
235        QPlainTextEdit.__init__(self, parent)
236        BaseEditMixin.__init__(self)
237        self.setAttribute(Qt.WA_DeleteOnClose)
238
239        self.extra_selections_dict = OrderedDict()
240
241        self.textChanged.connect(self.changed)
242        self.cursorPositionChanged.connect(self.cursor_position_changed)
243
244        self.indent_chars = " "*4
245        self.tab_stop_width_spaces = 4
246
247        # Code completion / calltips
248        if parent is not None:
249            mainwin = parent
250            while not isinstance(mainwin, QMainWindow):
251                mainwin = mainwin.parent()
252                if mainwin is None:
253                    break
254            if mainwin is not None:
255                parent = mainwin
256
257        self.completion_widget = CompletionWidget(self, parent)
258        self.codecompletion_auto = False
259        self.codecompletion_case = True
260        self.codecompletion_enter = False
261        self.completion_text = ""
262        self.setup_completion()
263
264        self.calltip_widget = CallTipWidget(self, hide_timer_on=False)
265        self.calltips = True
266        self.calltip_position = None
267
268        self.has_cell_separators = False
269        self.highlight_current_cell_enabled = False
270
271        # The color values may be overridden by the syntax highlighter
272        # Highlight current line color
273        self.currentline_color = QColor(Qt.red).lighter(190)
274        self.currentcell_color = QColor(Qt.red).lighter(194)
275
276        # Brace matching
277        self.bracepos = None
278        self.matched_p_color = QColor(Qt.green)
279        self.unmatched_p_color = QColor(Qt.red)
280
281        self.last_cursor_cell = None
282
283    def setup_completion(self):
284        size = CONF.get('main', 'completion/size')
285        font = get_font()
286        self.completion_widget.setup_appearance(size, font)
287
288    def set_indent_chars(self, indent_chars):
289        self.indent_chars = indent_chars
290
291    def set_tab_stop_width_spaces(self, tab_stop_width_spaces):
292        self.tab_stop_width_spaces = tab_stop_width_spaces
293        self.update_tab_stop_width_spaces()
294
295    def update_tab_stop_width_spaces(self):
296        self.setTabStopWidth(self.fontMetrics().width(
297                '9' * self.tab_stop_width_spaces))
298
299    def set_palette(self, background, foreground):
300        """
301        Set text editor palette colors:
302        background color and caret (text cursor) color
303        """
304        palette = QPalette()
305        palette.setColor(QPalette.Base, background)
306        palette.setColor(QPalette.Text, foreground)
307        self.setPalette(palette)
308
309        # Set the right background color when changing color schemes
310        # or creating new Editor windows. This seems to be a Qt bug.
311        # Fixes Issue 2028
312        if sys.platform == 'darwin':
313            if self.objectName():
314                style = "QPlainTextEdit#%s {background: %s; color: %s;}" % \
315                        (self.objectName(), background.name(), foreground.name())
316                self.setStyleSheet(style)
317
318
319    #------Extra selections
320    def extra_selection_length(self, key):
321        selection = self.get_extra_selections(key)
322        if selection:
323            cursor = self.extra_selections_dict[key][0].cursor
324            selection_length = cursor.selectionEnd() - cursor.selectionStart()
325            return selection_length
326        else:
327            return 0
328
329    def get_extra_selections(self, key):
330        return self.extra_selections_dict.get(key, [])
331
332    def set_extra_selections(self, key, extra_selections):
333        self.extra_selections_dict[key] = extra_selections
334        self.extra_selections_dict = \
335            OrderedDict(sorted(self.extra_selections_dict.items(),
336                               key=lambda s: self.extra_selection_length(s[0]),
337                               reverse=True))
338
339    def update_extra_selections(self):
340        extra_selections = []
341
342        # Python 3 compatibility (weird): current line has to be
343        # highlighted first
344        if 'current_cell' in self.extra_selections_dict:
345            extra_selections.extend(self.extra_selections_dict['current_cell'])
346        if 'current_line' in self.extra_selections_dict:
347            extra_selections.extend(self.extra_selections_dict['current_line'])
348
349        for key, extra in list(self.extra_selections_dict.items()):
350            if not (key == 'current_line' or key == 'current_cell'):
351                extra_selections.extend(extra)
352        self.setExtraSelections(extra_selections)
353
354    def clear_extra_selections(self, key):
355        self.extra_selections_dict[key] = []
356        self.update_extra_selections()
357
358    def changed(self):
359        """Emit changed signal"""
360        self.modificationChanged.emit(self.document().isModified())
361
362
363    #------Highlight current line
364    def highlight_current_line(self):
365        """Highlight current line"""
366        selection = QTextEdit.ExtraSelection()
367        selection.format.setProperty(QTextFormat.FullWidthSelection,
368                                     to_qvariant(True))
369        selection.format.setBackground(self.currentline_color)
370        selection.cursor = self.textCursor()
371        selection.cursor.clearSelection()
372        self.set_extra_selections('current_line', [selection])
373        self.update_extra_selections()
374
375    def unhighlight_current_line(self):
376        """Unhighlight current line"""
377        self.clear_extra_selections('current_line')
378
379    #------Highlight current cell
380    def highlight_current_cell(self):
381        """Highlight current cell"""
382        if self.cell_separators is None or \
383          not self.highlight_current_cell_enabled:
384            return
385        selection = QTextEdit.ExtraSelection()
386        selection.format.setProperty(QTextFormat.FullWidthSelection,
387                                     to_qvariant(True))
388        selection.format.setBackground(self.currentcell_color)
389        selection.cursor, whole_file_selected, whole_screen_selected =\
390            self.select_current_cell_in_visible_portion()
391        if whole_file_selected:
392            self.clear_extra_selections('current_cell')
393        elif whole_screen_selected:
394            if self.has_cell_separators:
395                self.set_extra_selections('current_cell', [selection])
396                self.update_extra_selections()
397            else:
398                self.clear_extra_selections('current_cell')
399        else:
400            self.set_extra_selections('current_cell', [selection])
401            self.update_extra_selections()
402
403    def unhighlight_current_cell(self):
404        """Unhighlight current cell"""
405        self.clear_extra_selections('current_cell')
406
407    #------Brace matching
408    def find_brace_match(self, position, brace, forward):
409        start_pos, end_pos = self.BRACE_MATCHING_SCOPE
410        if forward:
411            bracemap = {'(': ')', '[': ']', '{': '}'}
412            text = self.get_text(position, end_pos)
413            i_start_open = 1
414            i_start_close = 1
415        else:
416            bracemap = {')': '(', ']': '[', '}': '{'}
417            text = self.get_text(start_pos, position)
418            i_start_open = len(text)-1
419            i_start_close = len(text)-1
420
421        while True:
422            if forward:
423                i_close = text.find(bracemap[brace], i_start_close)
424            else:
425                i_close = text.rfind(bracemap[brace], 0, i_start_close+1)
426            if i_close > -1:
427                if forward:
428                    i_start_close = i_close+1
429                    i_open = text.find(brace, i_start_open, i_close)
430                else:
431                    i_start_close = i_close-1
432                    i_open = text.rfind(brace, i_close, i_start_open+1)
433                if i_open > -1:
434                    if forward:
435                        i_start_open = i_open+1
436                    else:
437                        i_start_open = i_open-1
438                else:
439                    # found matching brace
440                    if forward:
441                        return position+i_close
442                    else:
443                        return position-(len(text)-i_close)
444            else:
445                # no matching brace
446                return
447
448    def __highlight(self, positions, color=None, cancel=False):
449        if cancel:
450            self.clear_extra_selections('brace_matching')
451            return
452        extra_selections = []
453        for position in positions:
454            if position > self.get_position('eof'):
455                return
456            selection = QTextEdit.ExtraSelection()
457            selection.format.setBackground(color)
458            selection.cursor = self.textCursor()
459            selection.cursor.clearSelection()
460            selection.cursor.setPosition(position)
461            selection.cursor.movePosition(QTextCursor.NextCharacter,
462                                          QTextCursor.KeepAnchor)
463            extra_selections.append(selection)
464        self.set_extra_selections('brace_matching', extra_selections)
465        self.update_extra_selections()
466
467    def cursor_position_changed(self):
468        """Brace matching"""
469        if self.bracepos is not None:
470            self.__highlight(self.bracepos, cancel=True)
471            self.bracepos = None
472        cursor = self.textCursor()
473        if cursor.position() == 0:
474            return
475        cursor.movePosition(QTextCursor.PreviousCharacter,
476                            QTextCursor.KeepAnchor)
477        text = to_text_string(cursor.selectedText())
478        pos1 = cursor.position()
479        if text in (')', ']', '}'):
480            pos2 = self.find_brace_match(pos1, text, forward=False)
481        elif text in ('(', '[', '{'):
482            pos2 = self.find_brace_match(pos1, text, forward=True)
483        else:
484            return
485        if pos2 is not None:
486            self.bracepos = (pos1, pos2)
487            self.__highlight(self.bracepos, color=self.matched_p_color)
488        else:
489            self.bracepos = (pos1,)
490            self.__highlight(self.bracepos, color=self.unmatched_p_color)
491
492
493    #-----Widget setup and options
494    def set_codecompletion_auto(self, state):
495        """Set code completion state"""
496        self.codecompletion_auto = state
497
498    def set_codecompletion_case(self, state):
499        """Case sensitive completion"""
500        self.codecompletion_case = state
501        self.completion_widget.case_sensitive = state
502
503    def set_codecompletion_enter(self, state):
504        """Enable Enter key to select completion"""
505        self.codecompletion_enter = state
506        self.completion_widget.enter_select = state
507
508    def set_calltips(self, state):
509        """Set calltips state"""
510        self.calltips = state
511
512    def set_wrap_mode(self, mode=None):
513        """
514        Set wrap mode
515        Valid *mode* values: None, 'word', 'character'
516        """
517        if mode == 'word':
518            wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere
519        elif mode == 'character':
520            wrap_mode = QTextOption.WrapAnywhere
521        else:
522            wrap_mode = QTextOption.NoWrap
523        self.setWordWrapMode(wrap_mode)
524
525
526    #------Reimplementing Qt methods
527    @Slot()
528    def copy(self):
529        """
530        Reimplement Qt method
531        Copy text to clipboard with correct EOL chars
532        """
533        if self.get_selected_text():
534            QApplication.clipboard().setText(self.get_selected_text())
535
536    def toPlainText(self):
537        """
538        Reimplement Qt method
539        Fix PyQt4 bug on Windows and Python 3
540        """
541        # Fix what appears to be a PyQt4 bug when getting file
542        # contents under Windows and PY3. This bug leads to
543        # corruptions when saving files with certain combinations
544        # of unicode chars on them (like the one attached on
545        # Issue 1546)
546        if os.name == 'nt' and PY3:
547            text = self.get_text('sof', 'eof')
548            return text.replace('\u2028', '\n').replace('\u2029', '\n')\
549                       .replace('\u0085', '\n')
550        else:
551            return super(TextEditBaseWidget, self).toPlainText()
552
553    def keyPressEvent(self, event):
554        text, key = event.text(), event.key()
555        ctrl = event.modifiers() & Qt.ControlModifier
556        meta = event.modifiers() & Qt.MetaModifier
557        # Use our own copy method for {Ctrl,Cmd}+C to avoid Qt
558        # copying text in HTML (See Issue 2285)
559        if (ctrl or meta) and key == Qt.Key_C:
560            self.copy()
561        else:
562            super(TextEditBaseWidget, self).keyPressEvent(event)
563
564    #------Text: get, set, ...
565    def get_selection_as_executable_code(self):
566        """Return selected text as a processed text,
567        to be executable in a Python/IPython interpreter"""
568        ls = self.get_line_separator()
569
570        _indent = lambda line: len(line)-len(line.lstrip())
571
572        line_from, line_to = self.get_selection_bounds()
573        text = self.get_selected_text()
574        if not text:
575            return
576
577        lines = text.split(ls)
578        if len(lines) > 1:
579            # Multiline selection -> eventually fixing indentation
580            original_indent = _indent(self.get_text_line(line_from))
581            text = (" "*(original_indent-_indent(lines[0])))+text
582
583        # If there is a common indent to all lines, find it.
584        # Moving from bottom line to top line ensures that blank
585        # lines inherit the indent of the line *below* it,
586        # which is the desired behavior.
587        min_indent = 999
588        current_indent = 0
589        lines = text.split(ls)
590        for i in range(len(lines)-1, -1, -1):
591            line = lines[i]
592            if line.strip():
593                current_indent = _indent(line)
594                min_indent = min(current_indent, min_indent)
595            else:
596                lines[i] = ' ' * current_indent
597        if min_indent:
598            lines = [line[min_indent:] for line in lines]
599
600        # Remove any leading whitespace or comment lines
601        # since they confuse the reserved word detector that follows below
602        while lines:
603            first_line = lines[0].lstrip()
604            if first_line == '' or first_line[0] == '#':
605                lines.pop(0)
606            else:
607                break
608
609        # Add an EOL character after indentation blocks that start with some
610        # Python reserved words, so that it gets evaluated automatically
611        # by the console
612        varname = re.compile(r'[a-zA-Z0-9_]*')  # Matches valid variable names.
613        maybe = False
614        nextexcept = ()
615        for n, line in enumerate(lines):
616            if not _indent(line):
617                word = varname.match(line).group()
618                if maybe and word not in nextexcept:
619                    lines[n-1] += ls
620                    maybe = False
621                if word:
622                    if word in ('def', 'for', 'while', 'with', 'class'):
623                        maybe = True
624                        nextexcept = ()
625                    elif word == 'if':
626                        maybe = True
627                        nextexcept = ('elif', 'else')
628                    elif word == 'try':
629                        maybe = True
630                        nextexcept = ('except', 'finally')
631        if maybe:
632            if lines[-1].strip() == '':
633                lines[-1] += ls
634            else:
635                lines.append(ls)
636
637        return ls.join(lines)
638
639    def __exec_cell(self):
640        init_cursor = QTextCursor(self.textCursor())
641        start_pos, end_pos = self.__save_selection()
642        cursor, whole_file_selected = self.select_current_cell()
643        if not whole_file_selected:
644            self.setTextCursor(cursor)
645        text = self.get_selection_as_executable_code()
646        self.last_cursor_cell = init_cursor
647        self.__restore_selection(start_pos, end_pos)
648        if text is not None:
649            text = text.rstrip()
650        return text
651
652    def get_cell_as_executable_code(self):
653        """Return cell contents as executable code"""
654        return self.__exec_cell()
655
656    def get_last_cell_as_executable_code(self):
657        text = None
658        if self.last_cursor_cell:
659            self.setTextCursor(self.last_cursor_cell)
660            self.highlight_current_cell()
661            text = self.__exec_cell()
662        return text
663
664    def is_cell_separator(self, cursor=None, block=None):
665        """Return True if cursor (or text block) is on a block separator"""
666        assert cursor is not None or block is not None
667        if cursor is not None:
668            cursor0 = QTextCursor(cursor)
669            cursor0.select(QTextCursor.BlockUnderCursor)
670            text = to_text_string(cursor0.selectedText())
671        else:
672            text = to_text_string(block.text())
673        if self.cell_separators is None:
674            return False
675        else:
676            return text.lstrip().startswith(self.cell_separators)
677
678    def select_current_cell(self):
679        """Select cell under cursor
680        cell = group of lines separated by CELL_SEPARATORS
681        returns the textCursor and a boolean indicating if the
682        entire file is selected"""
683        cursor = self.textCursor()
684        cursor.movePosition(QTextCursor.StartOfBlock)
685        cur_pos = prev_pos = cursor.position()
686
687        # Moving to the next line that is not a separator, if we are
688        # exactly at one of them
689        while self.is_cell_separator(cursor):
690            cursor.movePosition(QTextCursor.NextBlock)
691            prev_pos = cur_pos
692            cur_pos = cursor.position()
693            if cur_pos == prev_pos:
694                return cursor, False
695        prev_pos = cur_pos
696        # If not, move backwards to find the previous separator
697        while not self.is_cell_separator(cursor):
698            cursor.movePosition(QTextCursor.PreviousBlock)
699            prev_pos = cur_pos
700            cur_pos = cursor.position()
701            if cur_pos == prev_pos:
702                if self.is_cell_separator(cursor):
703                    return cursor, False
704                else:
705                    break
706        cursor.setPosition(prev_pos)
707        cell_at_file_start = cursor.atStart()
708        # Once we find it (or reach the beginning of the file)
709        # move to the next separator (or the end of the file)
710        # so we can grab the cell contents
711        while not self.is_cell_separator(cursor):
712            cursor.movePosition(QTextCursor.NextBlock,
713                                QTextCursor.KeepAnchor)
714            cur_pos = cursor.position()
715            if cur_pos == prev_pos:
716                cursor.movePosition(QTextCursor.EndOfBlock,
717                                    QTextCursor.KeepAnchor)
718                break
719            prev_pos = cur_pos
720        cell_at_file_end = cursor.atEnd()
721        return cursor, cell_at_file_start and cell_at_file_end
722
723    def select_current_cell_in_visible_portion(self):
724        """Select cell under cursor in the visible portion of the file
725        cell = group of lines separated by CELL_SEPARATORS
726        returns
727         -the textCursor
728         -a boolean indicating if the entire file is selected
729         -a boolean indicating if the entire visible portion of the file is selected"""
730        cursor = self.textCursor()
731        cursor.movePosition(QTextCursor.StartOfBlock)
732        cur_pos = prev_pos = cursor.position()
733
734        beg_pos = self.cursorForPosition(QPoint(0, 0)).position()
735        bottom_right = QPoint(self.viewport().width() - 1,
736                              self.viewport().height() - 1)
737        end_pos = self.cursorForPosition(bottom_right).position()
738
739        # Moving to the next line that is not a separator, if we are
740        # exactly at one of them
741        while self.is_cell_separator(cursor):
742            cursor.movePosition(QTextCursor.NextBlock)
743            prev_pos = cur_pos
744            cur_pos = cursor.position()
745            if cur_pos == prev_pos:
746                return cursor, False, False
747        prev_pos = cur_pos
748        # If not, move backwards to find the previous separator
749        while not self.is_cell_separator(cursor)\
750          and cursor.position() >= beg_pos:
751            cursor.movePosition(QTextCursor.PreviousBlock)
752            prev_pos = cur_pos
753            cur_pos = cursor.position()
754            if cur_pos == prev_pos:
755                if self.is_cell_separator(cursor):
756                    return cursor, False, False
757                else:
758                    break
759        cell_at_screen_start = cursor.position() <= beg_pos
760        cursor.setPosition(prev_pos)
761        cell_at_file_start = cursor.atStart()
762        # Selecting cell header
763        if not cell_at_file_start:
764            cursor.movePosition(QTextCursor.PreviousBlock)
765            cursor.movePosition(QTextCursor.NextBlock,
766                                QTextCursor.KeepAnchor)
767        # Once we find it (or reach the beginning of the file)
768        # move to the next separator (or the end of the file)
769        # so we can grab the cell contents
770        while not self.is_cell_separator(cursor)\
771          and cursor.position() <= end_pos:
772            cursor.movePosition(QTextCursor.NextBlock,
773                                QTextCursor.KeepAnchor)
774            cur_pos = cursor.position()
775            if cur_pos == prev_pos:
776                cursor.movePosition(QTextCursor.EndOfBlock,
777                                    QTextCursor.KeepAnchor)
778                break
779            prev_pos = cur_pos
780        cell_at_file_end = cursor.atEnd()
781        cell_at_screen_end = cursor.position() >= end_pos
782        return cursor,\
783               cell_at_file_start and cell_at_file_end,\
784               cell_at_screen_start and cell_at_screen_end
785
786    def go_to_next_cell(self):
787        """Go to the next cell of lines"""
788        cursor = self.textCursor()
789        cursor.movePosition(QTextCursor.NextBlock)
790        cur_pos = prev_pos = cursor.position()
791        while not self.is_cell_separator(cursor):
792            # Moving to the next code cell
793            cursor.movePosition(QTextCursor.NextBlock)
794            prev_pos = cur_pos
795            cur_pos = cursor.position()
796            if cur_pos == prev_pos:
797                return
798        self.setTextCursor(cursor)
799
800    def go_to_previous_cell(self):
801        """Go to the previous cell of lines"""
802        cursor = self.textCursor()
803        cur_pos = prev_pos = cursor.position()
804
805        if self.is_cell_separator(cursor):
806            # Move to the previous cell
807            cursor.movePosition(QTextCursor.PreviousBlock)
808            cur_pos = prev_pos = cursor.position()
809
810        while not self.is_cell_separator(cursor):
811            # Move to the previous cell or the beginning of the current cell
812            cursor.movePosition(QTextCursor.PreviousBlock)
813            prev_pos = cur_pos
814            cur_pos = cursor.position()
815            if cur_pos == prev_pos:
816                return
817
818        self.setTextCursor(cursor)
819
820    def get_line_count(self):
821        """Return document total line number"""
822        return self.blockCount()
823
824    def __save_selection(self):
825        """Save current cursor selection and return position bounds"""
826        cursor = self.textCursor()
827        return cursor.selectionStart(), cursor.selectionEnd()
828
829    def __restore_selection(self, start_pos, end_pos):
830        """Restore cursor selection from position bounds"""
831        cursor = self.textCursor()
832        cursor.setPosition(start_pos)
833        cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
834        self.setTextCursor(cursor)
835
836    def __duplicate_line_or_selection(self, after_current_line=True):
837        """Duplicate current line or selected text"""
838        cursor = self.textCursor()
839        cursor.beginEditBlock()
840        start_pos, end_pos = self.__save_selection()
841        if to_text_string(cursor.selectedText()):
842            cursor.setPosition(end_pos)
843            # Check if end_pos is at the start of a block: if so, starting
844            # changes from the previous block
845            cursor.movePosition(QTextCursor.StartOfBlock,
846                                QTextCursor.KeepAnchor)
847            if not to_text_string(cursor.selectedText()):
848                cursor.movePosition(QTextCursor.PreviousBlock)
849                end_pos = cursor.position()
850
851        cursor.setPosition(start_pos)
852        cursor.movePosition(QTextCursor.StartOfBlock)
853        while cursor.position() <= end_pos:
854            cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
855            if cursor.atEnd():
856                cursor_temp = QTextCursor(cursor)
857                cursor_temp.clearSelection()
858                cursor_temp.insertText(self.get_line_separator())
859                break
860            cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
861        text = cursor.selectedText()
862        cursor.clearSelection()
863
864        if not after_current_line:
865            # Moving cursor before current line/selected text
866            cursor.setPosition(start_pos)
867            cursor.movePosition(QTextCursor.StartOfBlock)
868            start_pos += len(text)
869            end_pos += len(text)
870
871        cursor.insertText(text)
872        cursor.endEditBlock()
873        self.setTextCursor(cursor)
874        self.__restore_selection(start_pos, end_pos)
875
876    def duplicate_line(self):
877        """
878        Duplicate current line or selected text
879        Paste the duplicated text *after* the current line/selected text
880        """
881        self.__duplicate_line_or_selection(after_current_line=True)
882
883    def copy_line(self):
884        """
885        Copy current line or selected text
886        Paste the duplicated text *before* the current line/selected text
887        """
888        self.__duplicate_line_or_selection(after_current_line=False)
889
890    def __move_line_or_selection(self, after_current_line=True):
891        """Move current line or selected text"""
892        cursor = self.textCursor()
893        cursor.beginEditBlock()
894        start_pos, end_pos = self.__save_selection()
895        last_line = False
896
897        # ------ Select text
898
899        # Get selection start location
900        cursor.setPosition(start_pos)
901        cursor.movePosition(QTextCursor.StartOfBlock)
902        start_pos = cursor.position()
903
904        # Get selection end location
905        cursor.setPosition(end_pos)
906        if not cursor.atBlockStart() or end_pos == start_pos:
907            cursor.movePosition(QTextCursor.EndOfBlock)
908            cursor.movePosition(QTextCursor.NextBlock)
909        end_pos = cursor.position()
910
911        # Check if selection ends on the last line of the document
912        if cursor.atEnd():
913            if not cursor.atBlockStart() or end_pos == start_pos:
914                last_line = True
915
916        # ------ Stop if at document boundary
917
918        cursor.setPosition(start_pos)
919        if cursor.atStart() and not after_current_line:
920            # Stop if selection is already at top of the file while moving up
921            cursor.endEditBlock()
922            self.setTextCursor(cursor)
923            self.__restore_selection(start_pos, end_pos)
924            return
925
926        cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
927        if last_line and after_current_line:
928            # Stop if selection is already at end of the file while moving down
929            cursor.endEditBlock()
930            self.setTextCursor(cursor)
931            self.__restore_selection(start_pos, end_pos)
932            return
933
934        # ------ Move text
935
936        sel_text = to_text_string(cursor.selectedText())
937        cursor.removeSelectedText()
938
939
940        if after_current_line:
941            # Shift selection down
942            text = to_text_string(cursor.block().text())
943            sel_text = os.linesep + sel_text[0:-1]  # Move linesep at the start
944            cursor.movePosition(QTextCursor.EndOfBlock)
945            start_pos += len(text)+1
946            end_pos += len(text)
947            if not cursor.atEnd():
948                end_pos += 1
949        else:
950            # Shift selection up
951            if last_line:
952                # Remove the last linesep and add it to the selected text
953                cursor.deletePreviousChar()
954                sel_text = sel_text + os.linesep
955                cursor.movePosition(QTextCursor.StartOfBlock)
956                end_pos += 1
957            else:
958                cursor.movePosition(QTextCursor.PreviousBlock)
959            text = to_text_string(cursor.block().text())
960            start_pos -= len(text)+1
961            end_pos -= len(text)+1
962
963        cursor.insertText(sel_text)
964
965        cursor.endEditBlock()
966        self.setTextCursor(cursor)
967        self.__restore_selection(start_pos, end_pos)
968
969    def move_line_up(self):
970        """Move up current line or selected text"""
971        self.__move_line_or_selection(after_current_line=False)
972
973    def move_line_down(self):
974        """Move down current line or selected text"""
975        self.__move_line_or_selection(after_current_line=True)
976
977    def extend_selection_to_complete_lines(self):
978        """Extend current selection to complete lines"""
979        cursor = self.textCursor()
980        start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd()
981        cursor.setPosition(start_pos)
982        cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
983        if cursor.atBlockStart():
984            cursor.movePosition(QTextCursor.PreviousBlock,
985                                QTextCursor.KeepAnchor)
986            cursor.movePosition(QTextCursor.EndOfBlock,
987                                QTextCursor.KeepAnchor)
988        self.setTextCursor(cursor)
989
990    def delete_line(self):
991        """Delete current line"""
992        cursor = self.textCursor()
993        if self.has_selected_text():
994            self.extend_selection_to_complete_lines()
995            start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd()
996            cursor.setPosition(start_pos)
997        else:
998            start_pos = end_pos = cursor.position()
999        cursor.beginEditBlock()
1000        cursor.setPosition(start_pos)
1001        cursor.movePosition(QTextCursor.StartOfBlock)
1002        while cursor.position() <= end_pos:
1003            cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
1004            if cursor.atEnd():
1005                break
1006            cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor)
1007        cursor.removeSelectedText()
1008        cursor.endEditBlock()
1009        self.ensureCursorVisible()
1010
1011    def set_selection(self, start, end):
1012        cursor = self.textCursor()
1013        cursor.setPosition(start)
1014        cursor.setPosition(end, QTextCursor.KeepAnchor)
1015        self.setTextCursor(cursor)
1016
1017    def truncate_selection(self, position_from):
1018        """Unselect read-only parts in shell, like prompt"""
1019        position_from = self.get_position(position_from)
1020        cursor = self.textCursor()
1021        start, end = cursor.selectionStart(), cursor.selectionEnd()
1022        if start < end:
1023            start = max([position_from, start])
1024        else:
1025            end = max([position_from, end])
1026        self.set_selection(start, end)
1027
1028    def restrict_cursor_position(self, position_from, position_to):
1029        """In shell, avoid editing text except between prompt and EOF"""
1030        position_from = self.get_position(position_from)
1031        position_to = self.get_position(position_to)
1032        cursor = self.textCursor()
1033        cursor_position = cursor.position()
1034        if cursor_position < position_from or cursor_position > position_to:
1035            self.set_cursor_position(position_to)
1036
1037    #------Code completion / Calltips
1038    def hide_tooltip_if_necessary(self, key):
1039        """Hide calltip when necessary"""
1040        try:
1041            calltip_char = self.get_character(self.calltip_position)
1042            before = self.is_cursor_before(self.calltip_position,
1043                                           char_offset=1)
1044            other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab)
1045            if calltip_char not in ('?', '(') or before or other:
1046                QToolTip.hideText()
1047        except (IndexError, TypeError):
1048            QToolTip.hideText()
1049
1050    def show_completion_widget(self, textlist, automatic=True):
1051        """Show completion widget"""
1052        self.completion_widget.show_list(textlist, automatic=automatic)
1053
1054    def hide_completion_widget(self):
1055        """Hide completion widget"""
1056        self.completion_widget.hide()
1057
1058    def show_completion_list(self, completions, completion_text="",
1059                             automatic=True):
1060        """Display the possible completions"""
1061        if not completions:
1062            return
1063        if not isinstance(completions[0], tuple):
1064            completions = [(c, '') for c in completions]
1065        if len(completions) == 1 and completions[0][0] == completion_text:
1066            return
1067        self.completion_text = completion_text
1068        # Sorting completion list (entries starting with underscore are
1069        # put at the end of the list):
1070        underscore = set([(comp, t) for (comp, t) in completions
1071                          if comp.startswith('_')])
1072
1073        completions = sorted(set(completions) - underscore,
1074                             key=lambda x: str_lower(x[0]))
1075        completions += sorted(underscore, key=lambda x: str_lower(x[0]))
1076        self.show_completion_widget(completions, automatic=automatic)
1077
1078    def select_completion_list(self):
1079        """Completion list is active, Enter was just pressed"""
1080        self.completion_widget.item_selected()
1081
1082    def insert_completion(self, text):
1083        if text:
1084            cursor = self.textCursor()
1085            cursor.movePosition(QTextCursor.PreviousCharacter,
1086                                QTextCursor.KeepAnchor,
1087                                len(self.completion_text))
1088            cursor.removeSelectedText()
1089            self.insert_text(text)
1090
1091    def is_completion_widget_visible(self):
1092        """Return True is completion list widget is visible"""
1093        return self.completion_widget.isVisible()
1094
1095
1096    #------Standard keys
1097    def stdkey_clear(self):
1098        if not self.has_selected_text():
1099            self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor)
1100        self.remove_selected_text()
1101
1102    def stdkey_backspace(self):
1103        if not self.has_selected_text():
1104            self.moveCursor(QTextCursor.PreviousCharacter,
1105                            QTextCursor.KeepAnchor)
1106        self.remove_selected_text()
1107
1108    def __get_move_mode(self, shift):
1109        return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor
1110
1111    def stdkey_up(self, shift):
1112        self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift))
1113
1114    def stdkey_down(self, shift):
1115        self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift))
1116
1117    def stdkey_tab(self):
1118        self.insert_text(self.indent_chars)
1119
1120    def stdkey_home(self, shift, ctrl, prompt_pos=None):
1121        """Smart HOME feature: cursor is first moved at
1122        indentation position, then at the start of the line"""
1123        move_mode = self.__get_move_mode(shift)
1124        if ctrl:
1125            self.moveCursor(QTextCursor.Start, move_mode)
1126        else:
1127            cursor = self.textCursor()
1128            if prompt_pos is None:
1129                start_position = self.get_position('sol')
1130            else:
1131                start_position = self.get_position(prompt_pos)
1132            text = self.get_text(start_position, 'eol')
1133            indent_pos = start_position+len(text)-len(text.lstrip())
1134            if cursor.position() != indent_pos:
1135                cursor.setPosition(indent_pos, move_mode)
1136            else:
1137                cursor.setPosition(start_position, move_mode)
1138            self.setTextCursor(cursor)
1139
1140    def stdkey_end(self, shift, ctrl):
1141        move_mode = self.__get_move_mode(shift)
1142        if ctrl:
1143            self.moveCursor(QTextCursor.End, move_mode)
1144        else:
1145            self.moveCursor(QTextCursor.EndOfBlock, move_mode)
1146
1147    def stdkey_pageup(self):
1148        pass
1149
1150    def stdkey_pagedown(self):
1151        pass
1152
1153    def stdkey_escape(self):
1154        pass
1155
1156
1157    #----Qt Events
1158    def mousePressEvent(self, event):
1159        """Reimplement Qt method"""
1160        if sys.platform.startswith('linux') and event.button() == Qt.MidButton:
1161            self.calltip_widget.hide()
1162            self.setFocus()
1163            event = QMouseEvent(QEvent.MouseButtonPress, event.pos(),
1164                                Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
1165            QPlainTextEdit.mousePressEvent(self, event)
1166            QPlainTextEdit.mouseReleaseEvent(self, event)
1167            # Send selection text to clipboard to be able to use
1168            # the paste method and avoid the strange Issue 1445
1169            # NOTE: This issue seems a focusing problem but it
1170            # seems really hard to track
1171            mode_clip = QClipboard.Clipboard
1172            mode_sel = QClipboard.Selection
1173            text_clip = QApplication.clipboard().text(mode=mode_clip)
1174            text_sel = QApplication.clipboard().text(mode=mode_sel)
1175            QApplication.clipboard().setText(text_sel, mode=mode_clip)
1176            self.paste()
1177            QApplication.clipboard().setText(text_clip, mode=mode_clip)
1178        else:
1179            self.calltip_widget.hide()
1180            QPlainTextEdit.mousePressEvent(self, event)
1181
1182    def focusInEvent(self, event):
1183        """Reimplemented to handle focus"""
1184        self.focus_changed.emit()
1185        self.focus_in.emit()
1186        self.highlight_current_cell()
1187        QPlainTextEdit.focusInEvent(self, event)
1188
1189    def focusOutEvent(self, event):
1190        """Reimplemented to handle focus"""
1191        self.focus_changed.emit()
1192        QPlainTextEdit.focusOutEvent(self, event)
1193
1194    def wheelEvent(self, event):
1195        """Reimplemented to emit zoom in/out signals when Ctrl is pressed"""
1196        # This feature is disabled on MacOS, see Issue 1510
1197        if sys.platform != 'darwin':
1198            if event.modifiers() & Qt.ControlModifier:
1199                if hasattr(event, 'angleDelta'):
1200                    if event.angleDelta().y() < 0:
1201                        self.zoom_out.emit()
1202                    elif event.angleDelta().y() > 0:
1203                        self.zoom_in.emit()
1204                elif hasattr(event, 'delta'):
1205                    if event.delta() < 0:
1206                        self.zoom_out.emit()
1207                    elif event.delta() > 0:
1208                        self.zoom_in.emit()
1209                return
1210        QPlainTextEdit.wheelEvent(self, event)
1211        self.highlight_current_cell()
1212
1213
1214class QtANSIEscapeCodeHandler(ANSIEscapeCodeHandler):
1215    def __init__(self):
1216        ANSIEscapeCodeHandler.__init__(self)
1217        self.base_format = None
1218        self.current_format = None
1219
1220    def set_light_background(self, state):
1221        if state:
1222            self.default_foreground_color = 30
1223            self.default_background_color = 47
1224        else:
1225            self.default_foreground_color = 37
1226            self.default_background_color = 40
1227
1228    def set_base_format(self, base_format):
1229        self.base_format = base_format
1230
1231    def get_format(self):
1232        return self.current_format
1233
1234    def set_style(self):
1235        """
1236        Set font style with the following attributes:
1237        'foreground_color', 'background_color', 'italic',
1238        'bold' and 'underline'
1239        """
1240        if self.current_format is None:
1241            assert self.base_format is not None
1242            self.current_format = QTextCharFormat(self.base_format)
1243        # Foreground color
1244        if self.foreground_color is None:
1245            qcolor = self.base_format.foreground()
1246        else:
1247            cstr = self.ANSI_COLORS[self.foreground_color-30][self.intensity]
1248            qcolor = QColor(cstr)
1249        self.current_format.setForeground(qcolor)
1250        # Background color
1251        if self.background_color is None:
1252            qcolor = self.base_format.background()
1253        else:
1254            cstr = self.ANSI_COLORS[self.background_color-40][self.intensity]
1255            qcolor = QColor(cstr)
1256        self.current_format.setBackground(qcolor)
1257
1258        font = self.current_format.font()
1259        # Italic
1260        if self.italic is None:
1261            italic = self.base_format.fontItalic()
1262        else:
1263            italic = self.italic
1264        font.setItalic(italic)
1265        # Bold
1266        if self.bold is None:
1267            bold = self.base_format.font().bold()
1268        else:
1269            bold = self.bold
1270        font.setBold(bold)
1271        # Underline
1272        if self.underline is None:
1273            underline = self.base_format.font().underline()
1274        else:
1275            underline = self.underline
1276        font.setUnderline(underline)
1277        self.current_format.setFont(font)
1278
1279
1280def inverse_color(color):
1281    color.setHsv(color.hue(), color.saturation(), 255-color.value())
1282
1283
1284class ConsoleFontStyle(object):
1285    def __init__(self, foregroundcolor, backgroundcolor,
1286                 bold, italic, underline):
1287        self.foregroundcolor = foregroundcolor
1288        self.backgroundcolor = backgroundcolor
1289        self.bold = bold
1290        self.italic = italic
1291        self.underline = underline
1292        self.format = None
1293
1294    def apply_style(self, font, light_background, is_default):
1295        self.format = QTextCharFormat()
1296        self.format.setFont(font)
1297        foreground = QColor(self.foregroundcolor)
1298        if not light_background and is_default:
1299            inverse_color(foreground)
1300        self.format.setForeground(foreground)
1301        background = QColor(self.backgroundcolor)
1302        if not light_background:
1303            inverse_color(background)
1304        self.format.setBackground(background)
1305        font = self.format.font()
1306        font.setBold(self.bold)
1307        font.setItalic(self.italic)
1308        font.setUnderline(self.underline)
1309        self.format.setFont(font)
1310
1311
1312class ConsoleBaseWidget(TextEditBaseWidget):
1313    """Console base widget"""
1314    BRACE_MATCHING_SCOPE = ('sol', 'eol')
1315    COLOR_PATTERN = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
1316    exception_occurred = Signal(str, bool)
1317    userListActivated = Signal(int, str)
1318    completion_widget_activated = Signal(str)
1319
1320    def __init__(self, parent=None):
1321        TextEditBaseWidget.__init__(self, parent)
1322
1323        self.light_background = True
1324
1325        self.setMaximumBlockCount(300)
1326
1327        # ANSI escape code handler
1328        self.ansi_handler = QtANSIEscapeCodeHandler()
1329
1330        # Disable undo/redo (nonsense for a console widget...):
1331        self.setUndoRedoEnabled(False)
1332
1333        self.userListActivated.connect(lambda user_id, text:
1334                                   self.completion_widget_activated.emit(text))
1335
1336        self.default_style = ConsoleFontStyle(
1337                            foregroundcolor=0x000000, backgroundcolor=0xFFFFFF,
1338                            bold=False, italic=False, underline=False)
1339        self.error_style  = ConsoleFontStyle(
1340                            foregroundcolor=0xFF0000, backgroundcolor=0xFFFFFF,
1341                            bold=False, italic=False, underline=False)
1342        self.traceback_link_style  = ConsoleFontStyle(
1343                            foregroundcolor=0x0000FF, backgroundcolor=0xFFFFFF,
1344                            bold=True, italic=False, underline=True)
1345        self.prompt_style  = ConsoleFontStyle(
1346                            foregroundcolor=0x00AA00, backgroundcolor=0xFFFFFF,
1347                            bold=True, italic=False, underline=False)
1348        self.font_styles = (self.default_style, self.error_style,
1349                            self.traceback_link_style, self.prompt_style)
1350        self.set_pythonshell_font()
1351        self.setMouseTracking(True)
1352
1353    def set_light_background(self, state):
1354        self.light_background = state
1355        if state:
1356            self.set_palette(background=QColor(Qt.white),
1357                             foreground=QColor(Qt.darkGray))
1358        else:
1359            self.set_palette(background=QColor(Qt.black),
1360                             foreground=QColor(Qt.lightGray))
1361        self.ansi_handler.set_light_background(state)
1362        self.set_pythonshell_font()
1363
1364    #------Python shell
1365    def insert_text(self, text):
1366        """Reimplement TextEditBaseWidget method"""
1367        # Eventually this maybe should wrap to insert_text_to if
1368        # backspace-handling is required
1369        self.textCursor().insertText(text, self.default_style.format)
1370
1371    def paste(self):
1372        """Reimplement Qt method"""
1373        if self.has_selected_text():
1374            self.remove_selected_text()
1375        self.insert_text(QApplication.clipboard().text())
1376
1377    def append_text_to_shell(self, text, error, prompt):
1378        """
1379        Append text to Python shell
1380        In a way, this method overrides the method 'insert_text' when text is
1381        inserted at the end of the text widget for a Python shell
1382
1383        Handles error messages and show blue underlined links
1384        Handles ANSI color sequences
1385        Handles ANSI FF sequence
1386        """
1387        cursor = self.textCursor()
1388        cursor.movePosition(QTextCursor.End)
1389        if '\r' in text:    # replace \r\n with \n
1390            text = text.replace('\r\n', '\n')
1391            text = text.replace('\r', '\n')
1392        while True:
1393            index = text.find(chr(12))
1394            if index == -1:
1395                break
1396            text = text[index+1:]
1397            self.clear()
1398        if error:
1399            is_traceback = False
1400            for text in text.splitlines(True):
1401                if text.startswith('  File') \
1402                and not text.startswith('  File "<'):
1403                    is_traceback = True
1404                    # Show error links in blue underlined text
1405                    cursor.insertText('  ', self.default_style.format)
1406                    cursor.insertText(text[2:],
1407                                      self.traceback_link_style.format)
1408                else:
1409                    # Show error/warning messages in red
1410                    cursor.insertText(text, self.error_style.format)
1411            self.exception_occurred.emit(text, is_traceback)
1412        elif prompt:
1413            # Show prompt in green
1414            insert_text_to(cursor, text, self.prompt_style.format)
1415        else:
1416            # Show other outputs in black
1417            last_end = 0
1418            for match in self.COLOR_PATTERN.finditer(text):
1419                insert_text_to(cursor, text[last_end:match.start()],
1420                               self.default_style.format)
1421                last_end = match.end()
1422                try:
1423                    for code in [int(_c) for _c in match.group(1).split(';')]:
1424                        self.ansi_handler.set_code(code)
1425                except ValueError:
1426                    pass
1427                self.default_style.format = self.ansi_handler.get_format()
1428            insert_text_to(cursor, text[last_end:], self.default_style.format)
1429#            # Slower alternative:
1430#            segments = self.COLOR_PATTERN.split(text)
1431#            cursor.insertText(segments.pop(0), self.default_style.format)
1432#            if segments:
1433#                for ansi_tags, text in zip(segments[::2], segments[1::2]):
1434#                    for ansi_tag in ansi_tags.split(';'):
1435#                        self.ansi_handler.set_code(int(ansi_tag))
1436#                    self.default_style.format = self.ansi_handler.get_format()
1437#                    cursor.insertText(text, self.default_style.format)
1438        self.set_cursor_position('eof')
1439        self.setCurrentCharFormat(self.default_style.format)
1440
1441    def set_pythonshell_font(self, font=None):
1442        """Python Shell only"""
1443        if font is None:
1444            font = QFont()
1445        for style in self.font_styles:
1446            style.apply_style(font=font,
1447                              light_background=self.light_background,
1448                              is_default=style is self.default_style)
1449        self.ansi_handler.set_base_format(self.default_style.format)
1450