Thu, 18 Jul 2019 20:30:03 +0200
Continued implementing the MicroPython support.
--- a/eric6.e4p Wed Jul 17 20:31:47 2019 +0200 +++ b/eric6.e4p Thu Jul 18 20:30:03 2019 +0200 @@ -457,8 +457,10 @@ <Source>eric6/MicroPython/CircuitPythonDevices.py</Source> <Source>eric6/MicroPython/EspDevices.py</Source> <Source>eric6/MicroPython/MicroPythonDevices.py</Source> + <Source>eric6/MicroPython/MicroPythonFileSystem.py</Source> <Source>eric6/MicroPython/MicroPythonGraphWidget.py</Source> <Source>eric6/MicroPython/MicroPythonReplWidget.py</Source> + <Source>eric6/MicroPython/MicroPythonSerialPort.py</Source> <Source>eric6/MicroPython/MicrobitDevices.py</Source> <Source>eric6/MicroPython/__init__.py</Source> <Source>eric6/MultiProject/AddProjectDialog.py</Source>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/MicroPythonFileSystem.py Thu Jul 18 20:30:03 2019 +0200 @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing some file system commands for MicroPython. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QObject + + +class MicroPythonFileSystem(QObject): + """ + Class implementing some file system commands for MicroPython. + + Some FTP like commands are provided to perform operations on the file + system of a connected MicroPython device. Supported commands are: + <ul> + <li>ls: directory listing</li> + <li>lls: directory listing with meta data</li> + <li>cd: change directory</li> + <li>pwd: get the current directory</li> + <li>put: copy a file to the connected device</li> + <li>get: get a file from the connected device</li> + </ul> + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object + @type QObject + """ + super(MicroPythonFileSystem, self).__init__(parent) + + def ls(self): + """ + Public method to get a directory listing of the connected device. + + @return tuple containg the directory listing + @rtype tuple of str + """ + # TODO: not implemented yet + + def lls(self): + """ + Public method to get a long directory listing of the connected device + including meta data. + + @return tuple containg the the directory listing with tuple entries + containing the name, size, time and mode + @rtype tuple of str + """ + # TODO: not implemented yet + + def cd(self, path): + """ + Public method to change the current directory on the connected device. + + @param path directory to change to + @type str + """ + # TODO: not implemented yet + + def pwd(self): + """ + Public method to get the current directory of the connected device. + + @return current directory + @rtype str + """ + # TODO: not implemented yet + + def put(self, hostFileName, deviceFileName): + """ + Public method to copy a local file to the connected device. + + @param hostFileName name of the file to be copied + @type str + @param deviceFileName name of the file to copy to + @type str + @return flag indicating success + @rtype bool + """ + # TODO: not implemented yet + + def get(self, deviceFileName, hostFileName): + """ + Public method to copy a file from the connected device. + + @param deviceFileName name of the file to copy + @type str + @param hostFileName name of the file to copy to + @type str + @return flag indicating success + @rtype bool + """ + # TODO: not implemented yet
--- a/eric6/MicroPython/MicroPythonGraphWidget.py Wed Jul 17 20:31:47 2019 +0200 +++ b/eric6/MicroPython/MicroPythonGraphWidget.py Thu Jul 18 20:30:03 2019 +0200 @@ -154,7 +154,7 @@ for line in lines: if not line.endswith(b"\n"): # incomplete line (last line); skip it - continue + break line = line.strip() if line.startswith(b"(") and line.endswith(b")"):
--- a/eric6/MicroPython/MicroPythonReplWidget.py Wed Jul 17 20:31:47 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Thu Jul 18 20:30:03 2019 +0200 @@ -14,9 +14,12 @@ from PyQt5.QtCore import ( pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer ) -from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush +from PyQt5.QtGui import ( + QColor, QKeySequence, QTextCursor, QBrush, QTextCharFormat +) from PyQt5.QtWidgets import ( - QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy) + QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy +) try: from PyQt5.QtSerialPort import QSerialPort HAS_QTSERIALPORT = True @@ -38,6 +41,7 @@ import Globals import UI.PixmapCache +import Preferences class MicroPythonReplWidget(QWidget, Ui_MicroPythonReplWidget): @@ -57,22 +61,22 @@ # ANSI Colors AnsiColors = { - (1, 30): QBrush(Qt.darkGray), - (1, 31): QBrush(Qt.red), - (1, 32): QBrush(Qt.green), - (1, 33): QBrush(Qt.yellow), - (1, 34): QBrush(Qt.blue), - (1, 35): QBrush(Qt.magenta), - (1, 36): QBrush(Qt.cyan), - (1, 37): QBrush(Qt.white), - (2, 30): QBrush(Qt.black), - (2, 31): QBrush(Qt.darkRed), - (2, 32): QBrush(Qt.darkGreen), - (2, 33): QBrush(Qt.darkYellow), - (2, 34): QBrush(Qt.darkBlue), - (2, 35): QBrush(Qt.darkMagenta), - (2, 36): QBrush(Qt.darkCyan), - (2, 37): QBrush(Qt.lightGray), + 0: QBrush(Qt.black), + 1: QBrush(Qt.darkRed), + 2: QBrush(Qt.darkGreen), + 3: QBrush(Qt.darkYellow), + 4: QBrush(Qt.darkBlue), + 5: QBrush(Qt.darkMagenta), + 6: QBrush(Qt.darkCyan), + 7: QBrush(Qt.lightGray), + 10: QBrush(Qt.darkGray), + 11: QBrush(Qt.red), + 12: QBrush(Qt.green), + 13: QBrush(Qt.yellow), + 14: QBrush(Qt.blue), + 15: QBrush(Qt.magenta), + 16: QBrush(Qt.cyan), + 17: QBrush(Qt.white), } def __init__(self, parent=None): @@ -135,7 +139,7 @@ return self.__vt100Re = re.compile( - r'(?P<count>\d*);?(?P<color>\d*)(?P<action>[ABCDKm])') + r'(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])') self.__populateDeviceTypeComboBox() @@ -148,9 +152,12 @@ self.replEdit.customContextMenuRequested.connect( self.__showContextMenu) - defaultCharFormat = self.replEdit.textCursor().charFormat() - self.DefaultForeground = defaultCharFormat.foreground() - self.DefaultBackground = defaultCharFormat.background() + font = Preferences.getEditorOtherFonts("MonospacedFont") + self.replEdit.setFontFamily(font.family()) + self.replEdit.setFontPointSize(font.pointSize()) + self.DefaultCharFormat = self.replEdit.currentCharFormat() + self.DefaultForeground = self.DefaultCharFormat.foreground() + self.DefaultBackground = self.DefaultCharFormat.background() def __populateDeviceTypeComboBox(self): """ @@ -455,29 +462,13 @@ tc.removeSelectedText() self.replEdit.setTextCursor(tc) elif action == "m": - print(match.group("count"), match.group("color")) - charFormat = tc.charFormat() - if count == 0 and match.group("color") == "": - # reset color - charFormat.setForeground(self.DefaultForeground) - charFormat.setBackground(self.DefaultBackground) - elif count in (0, 1, 2): - if match.group("color") != "": - color = int(match.group("color")) - if count == 0: - count = 1 - if 30 <= color <= 37: - charFormat.setForeground( - self.AnsiColors[(count, color)]) - elif 40 <= color <= 47: - charFormat.setBackground( - self.AnsiColors[(count, color - 10)]) - tc.setCharFormat(charFormat) - self.replEdit.setTextCursor(tc) + self.__setCharFormat(match.group(0)[:-1].split(";"), + tc) elif data[index] == 10: # \n tc.movePosition(QTextCursor.End) self.replEdit.setTextCursor(tc) self.replEdit.insertPlainText(chr(data[index])) + self.__setCharFormat(["0"], tc) # reset format after a \n else: tc.deleteChar() self.replEdit.setTextCursor(tc) @@ -487,6 +478,118 @@ self.replEdit.ensureCursorVisible() + 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>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>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(self.AnsiColors[formatCode - 30]) + elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47): + charFormat.setBackground(self.AnsiColors[formatCode - 40]) + elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97): + charFormat.setForeground(self.AnsiColors[formatCode - 80]) + elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107): + charFormat.setBackground(self.AnsiColors[formatCode - 90]) + + textCursor.setCharFormat(charFormat) + def __doZoom(self, value): """ Private slot to zoom the REPL pane.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/MicroPythonSerialPort.py Thu Jul 18 20:30:03 2019 +0200 @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a QSerialPort with additional functionality. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QIODevice +from PyQt5.QtSerialPort import QSerialPort + + +class MicroPythonSerialPort(QSerialPort): + """ + Class implementing a QSerialPort with additional functionality + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object + @type QObject + """ + super(MicroPythonSerialPort, self).__init__(parent) + + self.__connected = False + + def openSerialLink(self, port): + """ + Public method to open a serial link to a given serial port. + + @param port port name to connect to + @type str + @return flag indicating success + @rtype bool + """ + self.setPortName(port) + if self.open(QIODevice.ReadWrite): + self.setDataTerminalReady(True) + # 115.200 baud, 8N1 + self.setBaudRate(115200) + self.setDataBits(QSerialPort.Data8) + self.setParity(QSerialPort.NoParity) + self.setStopBits(QSerialPort.OneStop) + + self.__connected = True + return True + else: + return False + + def closeSerialLink(self): + """ + Public method to close the open serial connection. + """ + if self.__connceted: + self.close() + + self.__connected = False + + def isConnected(self): + """ + Public method to get the connection state. + + @return flag indicating the connection state + @rtype bool + """ + return self.__connected + + def readUntil(self, expected=b"\n", size=None): + """ + Public method to read data until an expected sequence is found + (default: \n) or a specific size is exceeded. + + @param expected expected bytes sequence + @type bytes + @param size maximum data to be read + @type int + @return bytes read from the device including the expected sequence + @rtype bytes + """ + data = bytearray() + while True: + if self.waitForReadyRead(): + c = bytes(self.read(1)) + if c: + data += c + if data.endswith(expected): + break + if size is not None and len(data) >= size: + break + else: + break + else: + break + + return bytes(data)