Aluode/PerceptionLabPortable
0
1# based on https://github.com/art1415926535/PyQt5-syntax-highlighting2 3import pyqtgraph as pg4from pyqtgraph.Qt import QtCore, QtGui, QtWidgets5 6QRegExp = QtCore.QRegExp7 8QFont = QtGui.QFont9QColor = QtGui.QColor10QTextCharFormat = QtGui.QTextCharFormat11QSyntaxHighlighter = QtGui.QSyntaxHighlighter12 13 14def format(color, style=''):15 """16 Return a QTextCharFormat with the given attributes.17 """18 _color = pg.functions.mkColor(color)19 20 _format = QTextCharFormat()21 _format.setForeground(_color)22 if 'bold' in style:23 _format.setFontWeight(QFont.Weight.Bold)24 if 'italic' in style:25 _format.setFontItalic(True)26 27 return _format28 29 30class LightThemeColors:31 32 Red = "#B71C1C"33 Pink = "#FCE4EC"34 Purple = "#4A148C"35 DeepPurple = "#311B92"36 Indigo = "#1A237E"37 Blue = "#0D47A1"38 LightBlue = "#01579B"39 Cyan = "#006064"40 Teal = "#004D40"41 Green = "#1B5E20"42 LightGreen = "#33691E"43 Lime = "#827717"44 Yellow = "#F57F17"45 Amber = "#FF6F00"46 Orange = "#E65100"47 DeepOrange = "#BF360C"48 Brown = "#3E2723"49 Grey = "#212121"50 BlueGrey = "#263238"51 52 53class DarkThemeColors:54 55 Red = "#F44336"56 Pink = "#F48FB1"57 Purple = "#CE93D8"58 DeepPurple = "#B39DDB"59 Indigo = "#9FA8DA"60 Blue = "#90CAF9"61 LightBlue = "#81D4FA"62 Cyan = "#80DEEA"63 Teal = "#80CBC4"64 Green = "#A5D6A7"65 LightGreen = "#C5E1A5"66 Lime = "#E6EE9C"67 Yellow = "#FFF59D"68 Amber = "#FFE082"69 Orange = "#FFCC80"70 DeepOrange = "#FFAB91"71 Brown = "#BCAAA4"72 Grey = "#EEEEEE"73 BlueGrey = "#B0BEC5"74 75 76LIGHT_STYLES = {77 'keyword': format(LightThemeColors.Blue, 'bold'),78 'operator': format(LightThemeColors.Red, 'bold'),79 'brace': format(LightThemeColors.Purple),80 'defclass': format(LightThemeColors.Indigo, 'bold'),81 'string': format(LightThemeColors.Amber),82 'string2': format(LightThemeColors.DeepPurple),83 'comment': format(LightThemeColors.Green, 'italic'),84 'self': format(LightThemeColors.Blue, 'bold'),85 'numbers': format(LightThemeColors.Teal),86}87 88 89DARK_STYLES = {90 'keyword': format(DarkThemeColors.Blue, 'bold'),91 'operator': format(DarkThemeColors.Red, 'bold'),92 'brace': format(DarkThemeColors.Purple),93 'defclass': format(DarkThemeColors.Indigo, 'bold'),94 'string': format(DarkThemeColors.Amber),95 'string2': format(DarkThemeColors.DeepPurple),96 'comment': format(DarkThemeColors.Green, 'italic'),97 'self': format(DarkThemeColors.Blue, 'bold'),98 'numbers': format(DarkThemeColors.Teal),99}100 101 102class PythonHighlighter(QSyntaxHighlighter):103 """Syntax highlighter for the Python language.104 """105 # Python keywords106 keywords = [107 'and', 'assert', 'break', 'class', 'continue', 'def',108 'del', 'elif', 'else', 'except', 'exec', 'finally',109 'for', 'from', 'global', 'if', 'import', 'in',110 'is', 'lambda', 'not', 'or', 'pass', 'print',111 'raise', 'return', 'try', 'while', 'yield',112 'None', 'True', 'False', 'async', 'await',113 ]114 115 # Python operators116 operators = [117 r'=',118 # Comparison119 r'==', r'!=', r'<', r'<=', r'>', r'>=',120 # Arithmetic121 r'\+', r'-', r'\*', r'/', r'//', r'\%', r'\*\*',122 # In-place123 r'\+=', r'-=', r'\*=', r'/=', r'\%=',124 # Bitwise125 r'\^', r'\|', r'\&', r'\~', r'>>', r'<<',126 ]127 128 # Python braces129 braces = [130 r'\{', r'\}', r'\(', r'\)', r'\[', r'\]',131 ]132 133 def __init__(self, document):134 QSyntaxHighlighter.__init__(self, document)135 136 # Multi-line strings (expression, flag, style)137 # FIXME: The triple-quotes in these two lines will mess up the138 # syntax highlighting from this point onward139 self.tri_single = (QRegExp("'''"), 1, 'string2')140 self.tri_double = (QRegExp('"""'), 2, 'string2')141 142 rules = []143 144 # Keyword, operator, and brace rules145 rules += [(r'\b%s\b' % w, 0, 'keyword')146 for w in PythonHighlighter.keywords]147 rules += [(r'%s' % o, 0, 'operator')148 for o in PythonHighlighter.operators]149 rules += [(r'%s' % b, 0, 'brace')150 for b in PythonHighlighter.braces]151 152 # All other rules153 rules += [154 155 # 'self'156 (r'\bself\b', 0, 'self'),157 158 # 'def' followed by an identifier159 (r'\bdef\b\s*(\w+)', 1, 'defclass'),160 # 'class' followed by an identifier161 (r'\bclass\b\s*(\w+)', 1, 'defclass'),162 163 # Numeric literals164 (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'),165 (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'),166 (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'),167 168 # Double-quoted string, possibly containing escape sequences169 (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'),170 # Single-quoted string, possibly containing escape sequences171 (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'),172 173 # From '#' until a newline174 (r'#[^\n]*', 0, 'comment'),175 176 ]177 178 # Build a QRegExp for each pattern179 self.rules = [(QRegExp(pat), index, fmt)180 for (pat, index, fmt) in rules]181 182 @property183 def styles(self):184 app = QtWidgets.QApplication.instance()185 return DARK_STYLES if app.property('darkMode') else LIGHT_STYLES186 187 def highlightBlock(self, text):188 """Apply syntax highlighting to the given block of text."""189 190 rules = self.rules.copy()191 string_spans = []192 193 # First: apply string rules and record spans194 for expression, nth, format in rules:195 if format not in ('string', 'string2'):196 continue197 format = self.styles[format]198 for n, match in enumerate(re.finditer(expression, text)):199 if n < nth:200 continue201 start, end = match.span()202 self.setFormat(start, end - start, format)203 string_spans.append((start, end))204 205 # Then: apply other rules only if not in a string206 for expression, nth, format in rules:207 if format in ('string', 'string2'):208 continue209 format = self.styles[format]210 for n, match in enumerate(re.finditer(expression, text)):211 if n < nth:212 continue213 start, end = match.span()214 if any(start < e and end > s for s, e in string_spans):215 continue # Skip overlapping with string216 self.setFormat(start, end - start, format)217 218 self.applySearchHighlight(text)219 self.setCurrentBlockState(0)220 221 # Do multi-line strings222 in_multiline = self.match_multiline(text, *self.tri_single)223 if not in_multiline:224 in_multiline = self.match_multiline(text, *self.tri_double)225 226 def match_multiline(self, text, delimiter, in_state, style):227 """Do highlighting of multi-line strings. ``delimiter`` should be a228 ``QRegExp`` for triple-single-quotes or triple-double-quotes, and229 ``in_state`` should be a unique integer to represent the corresponding230 state changes when inside those strings. Returns True if we're still231 inside a multi-line string when this function is finished.232 """233 # If inside triple-single quotes, start at 0234 if self.previousBlockState() == in_state:235 start = 0236 add = 0237 # Otherwise, look for the delimiter on this line238 else:239 start = delimiter.indexIn(text)240 # Move past this match241 add = delimiter.matchedLength()242 243 # As long as there's a delimiter match on this line...244 while start >= 0:245 # Look for the ending delimiter246 end = delimiter.indexIn(text, start + add)247 # Ending delimiter on this line?248 if end >= add:249 length = end - start + add + delimiter.matchedLength()250 self.setCurrentBlockState(0)251 # No; multi-line string252 else:253 self.setCurrentBlockState(in_state)254 length = len(text) - start + add255 # Apply formatting256 self.setFormat(start, length, self.styles[style])257 # Look for the next match258 start = delimiter.indexIn(text, start + length)259 260 # Return True if still inside a multi-line string, False otherwise261 if self.currentBlockState() == in_state:262 return True263 else:264 return False265 