source: sasview/src/sas/qtgui/Utilities/PythonSyntax.py @ e4c45887

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since e4c45887 was 8b480d27, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Code cleanup and minor fixes

  • Property mode set to 100644
File size: 6.3 KB
Line 
1from PyQt5.QtCore import QRegExp
2from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter
3
4def format(color, style=''):
5    """Return a QTextCharFormat with the given attributes.
6    """
7    _color = QColor()
8    _color.setNamedColor(color)
9
10    _format = QTextCharFormat()
11    _format.setForeground(_color)
12    if 'bold' in style:
13        _format.setFontWeight(QFont.Bold)
14    if 'italic' in style:
15        _format.setFontItalic(True)
16
17    return _format
18
19
20# Syntax styles that can be shared by all languages
21STYLES = {
22    'keyword': format('blue'),
23    'operator': format('red'),
24    'brace': format('darkGray'),
25    'defclass': format('black', 'bold'),
26    'string': format('magenta'),
27    'string2': format('darkMagenta'),
28    'comment': format('darkGreen', 'italic'),
29    'self': format('black', 'italic'),
30    'numbers': format('brown'),
31}
32
33
34class PythonHighlighter (QSyntaxHighlighter):
35    """Syntax highlighter for the Python language.
36    """
37    # Python keywords
38    keywords = [
39        'and', 'assert', 'break', 'class', 'continue', 'def',
40        'del', 'elif', 'else', 'except', 'exec', 'finally',
41        'for', 'from', 'global', 'if', 'import', 'in',
42        'is', 'lambda', 'not', 'or', 'pass', 'print',
43        'raise', 'return', 'try', 'while', 'yield',
44        'None', 'True', 'False',
45    ]
46
47    # Python operators
48    operators = [
49        '=',
50        # Comparison
51        '==', '!=', '<', '<=', '>', '>=',
52        # Arithmetic
53        '\+', '-', '\*', '/', '//', '\%', '\*\*',
54        # In-place
55        '\+=', '-=', '\*=', '/=', '\%=',
56        # Bitwise
57        '\^', '\|', '\&', '\~', '>>', '<<',
58    ]
59
60    # Python braces
61    braces = [
62        '\{', '\}', '\(', '\)', '\[', '\]',
63    ]
64    def __init__(self, document):
65        QSyntaxHighlighter.__init__(self, document)
66
67        # Multi-line strings (expression, flag, style)
68        # FIXME: The triple-quotes in these two lines will mess up the
69        # syntax highlighting from this point onward
70        self.tri_single = (QRegExp("'''"), 1, STYLES['string2'])
71        self.tri_double = (QRegExp('"""'), 2, STYLES['string2'])
72
73        rules = []
74
75        # Keyword, operator, and brace rules
76        rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
77            for w in PythonHighlighter.keywords]
78        rules += [(r'%s' % o, 0, STYLES['operator'])
79            for o in PythonHighlighter.operators]
80        rules += [(r'%s' % b, 0, STYLES['brace'])
81            for b in PythonHighlighter.braces]
82
83        # All other rules
84        rules += [
85            # 'self'
86            (r'\bself\b', 0, STYLES['self']),
87
88            # Double-quoted string, possibly containing escape sequences
89            (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
90            # Single-quoted string, possibly containing escape sequences
91            (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
92
93            # 'def' followed by an identifier
94            (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
95            # 'class' followed by an identifier
96            (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
97
98            # From '#' until a newline
99            (r'#[^\n]*', 0, STYLES['comment']),
100
101            # Numeric literals
102            (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
103            (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
104            (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
105        ]
106
107        # Build a QRegExp for each pattern
108        self.rules = [(QRegExp(pat), index, fmt)
109            for (pat, index, fmt) in rules]
110
111
112    def highlightBlock(self, text):
113        """Apply syntax highlighting to the given block of text.
114        """
115        # Do other syntax formatting
116        for expression, nth, format in self.rules:
117            index = expression.indexIn(text, 0)
118
119            while index >= 0:
120                # We actually want the index of the nth match
121                index = expression.pos(nth)
122                length = len(expression.cap(nth))
123                self.setFormat(index, length, format)
124                index = expression.indexIn(text, index + length)
125
126        self.setCurrentBlockState(0)
127
128        # Do multi-line strings
129        in_multiline = self.match_multiline(text, *self.tri_single)
130        if not in_multiline:
131            in_multiline = self.match_multiline(text, *self.tri_double)
132
133
134    def match_multiline(self, text, delimiter, in_state, style):
135        """Do highlighting of multi-line strings. ``delimiter`` should be a
136        ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
137        ``in_state`` should be a unique integer to represent the corresponding
138        state changes when inside those strings. Returns True if we're still
139        inside a multi-line string when this function is finished.
140        """
141        # If inside triple-single quotes, start at 0
142        if self.previousBlockState() == in_state:
143            start = 0
144            add = 0
145        # Otherwise, look for the delimiter on this line
146        else:
147            start = delimiter.indexIn(text)
148            # Move past this match
149            add = delimiter.matchedLength()
150
151        # As long as there's a delimiter match on this line...
152        while start >= 0:
153            # Look for the ending delimiter
154            end = delimiter.indexIn(text, start + add)
155            # Ending delimiter on this line?
156            if end >= add:
157                length = end - start + add + delimiter.matchedLength()
158                self.setCurrentBlockState(0)
159            # No; multi-line string
160            else:
161                self.setCurrentBlockState(in_state)
162                length = len(text) - start + add
163            # Apply formatting
164            self.setFormat(start, length, style)
165            # Look for the next match
166            start = delimiter.indexIn(text, start + length)
167
168        # Return True if still inside a multi-line string, False otherwise
169        if self.currentBlockState() == in_state:
170            return True
171        else:
172            return False
173
174if __name__ == '__main__':
175    from PyQt5 import QtWidgets
176
177    app = QtWidgets.QApplication([])
178    editor = QtWidgets.QPlainTextEdit()
179    highlight = PythonHighlighter(editor.document())
180    editor.show()
181
182    # Load syntax.py into the editor for demo purposes
183    infile = open('PythonSyntax.py', 'r')
184    editor.setPlainText(infile.read())
185
186    app.exec_()
Note: See TracBrowser for help on using the repository browser.