src/eric7/MicroPython/MicroPythonReplWidget.py

branch
mpy_network
changeset 10011
26a7d607b8f6
child 10012
d649d500a9a1
diff -r 8a68a7a7ab88 -r 26a7d607b8f6 src/eric7/MicroPython/MicroPythonReplWidget.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonReplWidget.py	Thu May 04 11:22:11 2023 +0200
@@ -0,0 +1,703 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the MicroPython REPL widget.
+"""
+
+import re
+
+from PyQt6.QtCore import QPoint, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import (
+    QBrush,
+    QClipboard,
+    QColor,
+    QGuiApplication,
+    QKeySequence,
+    QTextCursor,
+)
+from PyQt6.QtWidgets import (
+    QHBoxLayout,
+    QLabel,
+    QMenu,
+    QSizePolicy,
+    QTextEdit,
+    QVBoxLayout,
+    QWidget,
+)
+
+from eric7 import Preferences
+from eric7.EricGui import EricPixmapCache
+from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
+from eric7.SystemUtilities import OSUtilities
+
+AnsiColorSchemes = {
+    "Windows 7": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(128, 0, 0)),
+        2: QBrush(QColor(0, 128, 0)),
+        3: QBrush(QColor(128, 128, 0)),
+        4: QBrush(QColor(0, 0, 128)),
+        5: QBrush(QColor(128, 0, 128)),
+        6: QBrush(QColor(0, 128, 128)),
+        7: QBrush(QColor(192, 192, 192)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Windows 10": {
+        0: QBrush(QColor(12, 12, 12)),
+        1: QBrush(QColor(197, 15, 31)),
+        2: QBrush(QColor(19, 161, 14)),
+        3: QBrush(QColor(193, 156, 0)),
+        4: QBrush(QColor(0, 55, 218)),
+        5: QBrush(QColor(136, 23, 152)),
+        6: QBrush(QColor(58, 150, 221)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(118, 118, 118)),
+        11: QBrush(QColor(231, 72, 86)),
+        12: QBrush(QColor(22, 198, 12)),
+        13: QBrush(QColor(249, 241, 165)),
+        14: QBrush(QColor(59, 12, 255)),
+        15: QBrush(QColor(180, 0, 158)),
+        16: QBrush(QColor(97, 214, 214)),
+        17: QBrush(QColor(242, 242, 242)),
+    },
+    "PuTTY": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(187, 0, 0)),
+        2: QBrush(QColor(0, 187, 0)),
+        3: QBrush(QColor(187, 187, 0)),
+        4: QBrush(QColor(0, 0, 187)),
+        5: QBrush(QColor(187, 0, 187)),
+        6: QBrush(QColor(0, 187, 187)),
+        7: QBrush(QColor(187, 187, 187)),
+        10: QBrush(QColor(85, 85, 85)),
+        11: QBrush(QColor(255, 85, 85)),
+        12: QBrush(QColor(85, 255, 85)),
+        13: QBrush(QColor(255, 255, 85)),
+        14: QBrush(QColor(85, 85, 255)),
+        15: QBrush(QColor(255, 85, 255)),
+        16: QBrush(QColor(85, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "xterm": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(205, 0, 0)),
+        2: QBrush(QColor(0, 205, 0)),
+        3: QBrush(QColor(205, 205, 0)),
+        4: QBrush(QColor(0, 0, 238)),
+        5: QBrush(QColor(205, 0, 205)),
+        6: QBrush(QColor(0, 205, 205)),
+        7: QBrush(QColor(229, 229, 229)),
+        10: QBrush(QColor(127, 127, 127)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Ubuntu": {
+        0: QBrush(QColor(1, 1, 1)),
+        1: QBrush(QColor(222, 56, 43)),
+        2: QBrush(QColor(57, 181, 74)),
+        3: QBrush(QColor(255, 199, 6)),
+        4: QBrush(QColor(0, 11, 184)),
+        5: QBrush(QColor(118, 38, 113)),
+        6: QBrush(QColor(44, 181, 233)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Ubuntu (dark)": {
+        0: QBrush(QColor(96, 96, 96)),
+        1: QBrush(QColor(235, 58, 45)),
+        2: QBrush(QColor(57, 181, 74)),
+        3: QBrush(QColor(255, 199, 29)),
+        4: QBrush(QColor(25, 56, 230)),
+        5: QBrush(QColor(200, 64, 193)),
+        6: QBrush(QColor(48, 200, 255)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Breeze (dark)": {
+        0: QBrush(QColor(35, 38, 39)),
+        1: QBrush(QColor(237, 21, 21)),
+        2: QBrush(QColor(17, 209, 22)),
+        3: QBrush(QColor(246, 116, 0)),
+        4: QBrush(QColor(29, 153, 243)),
+        5: QBrush(QColor(155, 89, 182)),
+        6: QBrush(QColor(26, 188, 156)),
+        7: QBrush(QColor(252, 252, 252)),
+        10: QBrush(QColor(127, 140, 141)),
+        11: QBrush(QColor(192, 57, 43)),
+        12: QBrush(QColor(28, 220, 154)),
+        13: QBrush(QColor(253, 188, 75)),
+        14: QBrush(QColor(61, 174, 233)),
+        15: QBrush(QColor(142, 68, 173)),
+        16: QBrush(QColor(22, 160, 133)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+}
+
+
+class MicroPythonReplWidget(QWidget):
+    """
+    Class implementing the MicroPython REPL widget.
+    """
+
+    ZoomMin = -10
+    ZoomMax = 20
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent=parent)
+
+        self.__layout = QVBoxLayout(self)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
+
+        self.__zoomLayout = QHBoxLayout()
+        self.__osdLabel = QLabel()
+        self.__osdLabel.setSizePolicy(
+            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
+        )
+        self.__zoomLayout.addWidget(self.__osdLabel)
+
+        self.__zoomWidget = EricZoomWidget(
+            EricPixmapCache.getPixmap("zoomOut"),
+            EricPixmapCache.getPixmap("zoomIn"),
+            EricPixmapCache.getPixmap("zoomReset"),
+            self,
+        )
+        self.__zoomWidget.setMinimum(self.ZoomMin)
+        self.__zoomWidget.setMaximum(self.ZoomMax)
+        self.__zoomLayout.addWidget(self.__zoomWidget)
+        self.__layout.addLayout(self.__zoomLayout)
+
+        self.__replEdit = MicroPythonReplEdit(self)
+        self.__layout.addWidget(self.__replEdit)
+
+        self.setLayout(self.__layout)
+
+        self.__zoomWidget.valueChanged.connect(self.__replEdit.doZoom)
+        self.__replEdit.osdInfo.connect(self.setOSDInfo)
+
+    @pyqtSlot(str)
+    def setOSDInfo(self, infoStr):
+        """
+        Public slot to set the OSD information.
+
+        @param infoStr string to be shown
+        @type str
+        """
+        self.__osdLabel.setText(infoStr)
+
+    @pyqtSlot()
+    def clearOSD(self):
+        """
+        Public slot to clear the OSD info.
+        """
+        self.__osdLabel.clear()
+
+    def replEdit(self):
+        """
+        Public method to get a reference to the REPL edit.
+
+        @return reference to the REPL edit
+        @rtype MicroPythonReplEdit
+        """
+        return self.__replEdit
+
+
+class MicroPythonReplEdit(QTextEdit):
+    """
+    Class implementing the REPL edit pane.
+
+    @signal osdInfo(str) emitted when some OSD data was received from the device
+    """
+
+    osdInfo = pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent=parent)
+
+        self.setAcceptRichText(False)
+        self.setUndoRedoEnabled(False)
+        self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
+        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
+
+        self.__currentZoom = 0
+
+        self.__replBuffer = b""
+
+        self.__vt100Re = re.compile(
+            r"(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])"
+        )
+
+        self.customContextMenuRequested.connect(self.__showContextMenu)
+
+        charFormat = self.currentCharFormat()
+        self.DefaultForeground = charFormat.foreground()
+        self.DefaultBackground = charFormat.background()
+
+        self.__interface = None
+
+    def setInterface(self, deviceInterface):
+        """
+        Public method to set the reference to the device interface object.
+
+        @param deviceInterface reference to the device interface object
+        @type MicroPythonDeviceInterface
+        """
+        self.__interface = deviceInterface
+
+    @pyqtSlot(int)
+    def doZoom(self, value):
+        """
+        Public slot to zoom in or out.
+
+        @param value zoom value
+        @type int
+        """
+        if value < self.__currentZoom:
+            self.zoomOut(self.__currentZoom - value)
+        elif value > self.__currentZoom:
+            self.zoomIn(value - self.__currentZoom)
+        self.__currentZoom = value
+
+    @pyqtSlot(QPoint)
+    def __showContextMenu(self, pos):
+        """
+        Private slot to show the REPL context menu.
+
+        @param pos position to show the menu at
+        @type QPoint
+        """
+        connected = bool(self.__interface) and self.__interface.isConnected()
+
+        if OSUtilities.isMacPlatform():
+            copyKeys = QKeySequence("Ctrl+C")
+            pasteKeys = QKeySequence("Ctrl+V")
+            selectAllKeys = QKeySequence("Ctrl+A")
+        else:
+            copyKeys = QKeySequence("Ctrl+Shift+C")
+            pasteKeys = QKeySequence("Ctrl+Shift+V")
+            selectAllKeys = QKeySequence("Ctrl+Shift+A")
+
+        menu = QMenu(self)
+        menu.addAction(
+            EricPixmapCache.getIcon("editDelete"), self.tr("Clear"), self.__clear
+        ).setEnabled(bool(self.toPlainText()))
+        menu.addSeparator()
+        menu.addAction(
+            EricPixmapCache.getIcon("editCopy"),
+            self.tr("Copy"),
+            copyKeys,
+            self.copy,
+        ).setEnabled(self.textCursor().hasSelection())
+        menu.addAction(
+            EricPixmapCache.getIcon("editPaste"),
+            self.tr("Paste"),
+            pasteKeys,
+            self.__paste,
+        ).setEnabled(self.canPaste() and connected)
+        menu.addSeparator()
+        menu.addAction(
+            EricPixmapCache.getIcon("editSelectAll"),
+            self.tr("Select All"),
+            selectAllKeys,
+            self.selectAll,
+        ).setEnabled(bool(self.toPlainText()))
+
+        menu.exec(self.mapToGlobal(pos))
+
+    @pyqtSlot()
+    def handlePreferencesChanged(self):
+        """
+        Public slot to handle a change in preferences.
+        """
+        self.__colorScheme = Preferences.getMicroPython("ColorScheme")
+
+        self.__font = Preferences.getEditorOtherFonts("MonospacedFont")
+        self.setFontFamily(self.__font.family())
+        self.setFontPointSize(self.__font.pointSize())
+
+        if Preferences.getMicroPython("ReplLineWrap"):
+            self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
+        else:
+            self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
+
+    @pyqtSlot()
+    def __clear(self):
+        """
+        Private slot to clear the REPL pane.
+        """
+        self.clear()
+        if bool(self.__interface) and self.__interface.isConnected():
+            self.__interface.write(b"\r")
+
+    @pyqtSlot()
+    def __paste(self, mode=QClipboard.Mode.Clipboard):
+        """
+        Private slot to perform a paste operation.
+
+        @param mode paste mode (defaults to QClipboard.Mode.Clipboard)
+        @type QClipboard.Mode (optional)
+        """
+        # add support for paste by mouse middle button
+        clipboard = QGuiApplication.clipboard()
+        if clipboard:
+            pasteText = clipboard.text(mode=mode)
+            if pasteText:
+                pasteText = pasteText.replace("\n\r", "\r")
+                pasteText = pasteText.replace("\n", "\r")
+                if bool(self.__interface) and self.__interface.isConnected():
+                    self.__interface.write(b"\x05")
+                    self.__interface.write(pasteText.encode("utf-8"))
+                    self.__interface.write(b"\x04")
+
+    def keyPressEvent(self, evt):
+        """
+        Protected method to handle key press events.
+
+        @param evt reference to the key press event
+        @type QKeyEvent
+        """
+        key = evt.key()
+        msg = bytes(evt.text(), "utf8")
+        if key == Qt.Key.Key_Backspace:
+            msg = b"\b"
+        elif key == Qt.Key.Key_Delete:
+            msg = b"\x1B[\x33\x7E"
+        elif key == Qt.Key.Key_Up:
+            msg = b"\x1B[A"
+        elif key == Qt.Key.Key_Down:
+            msg = b"\x1B[B"
+        elif key == Qt.Key.Key_Right:
+            msg = b"\x1B[C"
+        elif key == Qt.Key.Key_Left:
+            msg = b"\x1B[D"
+        elif key == Qt.Key.Key_Home:
+            msg = b"\x1B[H"
+        elif key == Qt.Key.Key_End:
+            msg = b"\x1B[F"
+        elif (
+            OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.MetaModifier
+        ) or (
+            not OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
+        ):
+            if Qt.Key.Key_A <= key <= Qt.Key.Key_Z:
+                # devices treat an input of \x01 as Ctrl+A, etc.
+                msg = bytes([1 + key - Qt.Key.Key_A])
+        elif evt.modifiers() == (
+            Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
+        ) or (
+            OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
+        ):
+            if key == Qt.Key.Key_C:
+                self.copy()
+                msg = b""
+            elif key == Qt.Key.Key_V:
+                self.__paste()
+                msg = b""
+            elif key == Qt.Key.Key_A:
+                self.selectAll()
+                msg = b""
+        elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
+            tc = self.textCursor()
+            tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
+            self.setTextCursor(tc)
+        if bool(self.__interface) and self.__interface.isConnected():
+            self.__interface.write(msg)
+
+        evt.accept()
+
+    def mouseReleaseEvent(self, evt):
+        """
+        Protected method to handle mouse release events.
+
+        @param evt reference to the event object
+        @type QMouseEvent
+        """
+        if evt.button() == Qt.MouseButton.MiddleButton:
+            self.__paste(mode=QClipboard.Mode.Selection)
+            msg = b""
+            if bool(self.__interface) and self.__interface.isConnected():
+                self.__interface.write(msg)
+            evt.accept()
+        else:
+            super().mouseReleaseEvent(evt)
+
+    @pyqtSlot(bytes)
+    def processData(self, data):
+        """
+        Public slot to process the data received from the device.
+
+        @param data data received from the device
+        @type bytes
+        """
+        tc = self.textCursor()
+        # the text cursor must be on the last line
+        while tc.movePosition(QTextCursor.MoveOperation.Down):
+            pass
+
+        # reset the font
+        charFormat = tc.charFormat()
+        self.__setCharFormat(None, tc)
+        tc.setCharFormat(charFormat)
+
+        # add received data to the buffered one
+        data = self.__replBuffer + data
+
+        index = 0
+        while index < len(data):
+            if data[index] == 8:  # \b
+                tc.movePosition(QTextCursor.MoveOperation.Left)
+                self.setTextCursor(tc)
+            elif data[index] in (4, 13):  # EOT, \r
+                pass
+            elif len(data) > index + 1 and data[index] == 27 and data[index + 1] == 91:
+                # VT100 cursor command detected: <Esc>[
+                index += 2  # move index to after the [
+                match = self.__vt100Re.search(
+                    data[index:].decode("utf-8", errors="replace")
+                )
+                if match:
+                    # move to last position in control sequence
+                    # ++ will be done at end of loop
+                    index += match.end() - 1
+
+                    action = match.group("action")
+                    if action in "ABCD":
+                        if match.group("count") == "":
+                            count = 1
+                        else:
+                            count = int(match.group("count"))
+
+                        if action == "A":  # up
+                            tc.movePosition(QTextCursor.MoveOperation.Up, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "B":  # down
+                            tc.movePosition(QTextCursor.MoveOperation.Down, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "C":  # right
+                            tc.movePosition(QTextCursor.MoveOperation.Right, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "D":  # left
+                            tc.movePosition(QTextCursor.MoveOperation.Left, n=count)
+                            self.setTextCursor(tc)
+                    elif action == "K":  # delete things
+                        if match.group("count") in ("", "0"):
+                            # delete to end of line
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.EndOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                        elif match.group("count") == "1":
+                            # delete to beginning of line
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.StartOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                        elif match.group("count") == "2":
+                            # delete whole line
+                            tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.StartOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                    elif action == "m":
+                        self.__setCharFormat(match.group(0)[:-1].split(";"), tc)
+            elif (
+                len(data) > index + 1
+                and data[index] == 27
+                and data[index + 1 : index + 4] == b"]0;"
+            ):
+                if b"\x1b\\" in data[index + 4 :]:
+                    # 'set window title' command detected: <Esc>]0;...<Esc>\
+                    # __IGNORE_WARNING_M891__
+                    titleData = data[index + 4 :].split(b"\x1b\\")[0]
+                    title = titleData.decode()
+                    index += len(titleData) + 5  # one more is done at the end
+                    self.osdInfo.emit(title)
+                else:
+                    # data is incomplete; buffer and stop processing
+                    self.__replBuffer = data[index:]
+                    return
+            else:
+                tc.deleteChar()
+                self.setTextCursor(tc)
+                self.insertPlainText(chr(data[index]))
+
+            index += 1
+
+        self.ensureCursorVisible()
+        self.__replBuffer = b""
+
+    def __setCharFormat(self, formatCodes, textCursor):
+        """
+        Private method setting the current text format of the REPL pane based
+        on the passed ANSI codes.
+
+        Following codes are used:
+        <ul>
+        <li>0: Reset</li>
+        <li>1: Bold font (weight 75)</li>
+        <li>2: Light font (weight 25)</li>
+        <li>3: Italic font</li>
+        <li>4: Underlined font</li>
+        <li>9: Strikeout font</li>
+        <li>21: Bold off (weight 50)</li>
+        <li>22: Light off (weight 50)</li>
+        <li>23: Italic off</li>
+        <li>24: Underline off</li>
+        <li>29: Strikeout off</li>
+        <li>30: foreground Black</li>
+        <li>31: foreground Dark Red</li>
+        <li>32: foreground Dark Green</li>
+        <li>33: foreground Dark Yellow</li>
+        <li>34: foreground Dark Blue</li>
+        <li>35: foreground Dark Magenta</li>
+        <li>36: foreground Dark Cyan</li>
+        <li>37: foreground Light Gray</li>
+        <li>39: reset foreground to default</li>
+        <li>40: background Black</li>
+        <li>41: background Dark Red</li>
+        <li>42: background Dark Green</li>
+        <li>43: background Dark Yellow</li>
+        <li>44: background Dark Blue</li>
+        <li>45: background Dark Magenta</li>
+        <li>46: background Dark Cyan</li>
+        <li>47: background Light Gray</li>
+        <li>49: reset background to default</li>
+        <li>53: Overlined font</li>
+        <li>55: Overline off</li>
+        <li>90: bright foreground Dark Gray</li>
+        <li>91: bright foreground Red</li>
+        <li>92: bright foreground Green</li>
+        <li>93: bright foreground Yellow</li>
+        <li>94: bright foreground Blue</li>
+        <li>95: bright foreground Magenta</li>
+        <li>96: bright foreground Cyan</li>
+        <li>97: bright foreground White</li>
+        <li>100: bright background Dark Gray</li>
+        <li>101: bright background Red</li>
+        <li>102: bright background Green</li>
+        <li>103: bright background Yellow</li>
+        <li>104: bright background Blue</li>
+        <li>105: bright background Magenta</li>
+        <li>106: bright background Cyan</li>
+        <li>107: bright background White</li>
+        </ul>
+
+        @param formatCodes list of format codes
+        @type list of str
+        @param textCursor reference to the text cursor
+        @type QTextCursor
+        """
+        if not formatCodes:
+            # empty format codes list is treated as a reset
+            formatCodes = ["0"]
+
+        charFormat = textCursor.charFormat()
+        for formatCode in formatCodes:
+            try:
+                formatCode = int(formatCode)
+            except ValueError:
+                # ignore non digit values
+                continue
+
+            if formatCode == 0:
+                charFormat.setFontWeight(50)
+                charFormat.setFontItalic(False)
+                charFormat.setFontUnderline(False)
+                charFormat.setFontStrikeOut(False)
+                charFormat.setFontOverline(False)
+                charFormat.setForeground(self.DefaultForeground)
+                charFormat.setBackground(self.DefaultBackground)
+            elif formatCode == 1:
+                charFormat.setFontWeight(75)
+            elif formatCode == 2:
+                charFormat.setFontWeight(25)
+            elif formatCode == 3:
+                charFormat.setFontItalic(True)
+            elif formatCode == 4:
+                charFormat.setFontUnderline(True)
+            elif formatCode == 9:
+                charFormat.setFontStrikeOut(True)
+            elif formatCode in (21, 22):
+                charFormat.setFontWeight(50)
+            elif formatCode == 23:
+                charFormat.setFontItalic(False)
+            elif formatCode == 24:
+                charFormat.setFontUnderline(False)
+            elif formatCode == 29:
+                charFormat.setFontStrikeOut(False)
+            elif formatCode == 53:
+                charFormat.setFontOverline(True)
+            elif formatCode == 55:
+                charFormat.setFontOverline(False)
+            elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 30]
+                )
+            elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 40]
+                )
+            elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 80]
+                )
+            elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 90]
+                )
+            elif formatCode == 39:
+                charFormat.setForeground(self.DefaultForeground)
+            elif formatCode == 49:
+                charFormat.setBackground(self.DefaultBackground)
+
+        textCursor.setCharFormat(charFormat)

eric ide

mercurial