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

ESS_GUIESS_GUI_DocsESS_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 3b3b40b was 3b3b40b, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Merging feature branches

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