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

ESS_GUI
Last change on this file was f2e199e, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Allow editing models with corresponding C-files. SASVIEW-1208

  • Property mode set to 100644
File size: 6.9 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    python_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    # C keywords
48    c_keywords = [
49        'auto', 'break', 'case', 'char',
50        'const', 'continue', 'default', 'do',
51        'double', 'else', 'enum', 'extern',
52        'float', 'for', 'goto', 'if',
53        'int', 'long', 'register', 'return',
54        'short', 'signed', 'sizeof', 'static',
55        'struct', 'switch', 'typedef', 'union',
56        'unsigned', 'void', 'volatile', 'while'
57    ]
58
59    # Python operators
60    operators = [
61        '=',
62        # Comparison
63        '==', '!=', '<', '<=', '>', '>=',
64        # Arithmetic
65        '\+', '-', '\*', '/', '//', '\%', '\*\*',
66        # In-place
67        '\+=', '-=', '\*=', '/=', '\%=',
68        # Bitwise
69        '\^', '\|', '\&', '\~', '>>', '<<',
70    ]
71
72    # Python braces
73    braces = [
74        '\{', '\}', '\(', '\)', '\[', '\]',
75    ]
76    def __init__(self, document, is_python=True):
77        QSyntaxHighlighter.__init__(self, document)
78
79        # Multi-line strings (expression, flag, style)
80        # FIXME: The triple-quotes in these two lines will mess up the
81        # syntax highlighting from this point onward
82        self.tri_single = (QRegExp("'''"), 1, STYLES['string2'])
83        self.tri_double = (QRegExp('"""'), 2, STYLES['string2'])
84
85        rules = []
86
87        # Keyword, operator, and brace rules
88        keywords = PythonHighlighter.python_keywords if is_python \
89            else PythonHighlighter.c_keywords
90        rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
91            for w in keywords]
92        rules += [(r'%s' % o, 0, STYLES['operator'])
93            for o in PythonHighlighter.operators]
94        rules += [(r'%s' % b, 0, STYLES['brace'])
95            for b in PythonHighlighter.braces]
96
97        # All other rules
98        rules += [
99            # 'self'
100            (r'\bself\b', 0, STYLES['self']),
101
102            # Double-quoted string, possibly containing escape sequences
103            (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
104            # Single-quoted string, possibly containing escape sequences
105            (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
106
107            # 'def' followed by an identifier
108            (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
109            # 'class' followed by an identifier
110            (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
111
112            # From '#' until a newline
113            (r'#[^\n]*', 0, STYLES['comment']),
114
115            # Numeric literals
116            (r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
117            (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
118            (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
119        ]
120        # Add "//" to comments for C
121        if not is_python:
122            rules.append((r'//[^\n]*', 0, STYLES['comment']),)
123
124        # Build a QRegExp for each pattern
125        self.rules = [(QRegExp(pat), index, fmt)
126            for (pat, index, fmt) in rules]
127
128
129    def highlightBlock(self, text):
130        """Apply syntax highlighting to the given block of text.
131        """
132        # Do other syntax formatting
133        for expression, nth, format in self.rules:
134            index = expression.indexIn(text, 0)
135
136            while index >= 0:
137                # We actually want the index of the nth match
138                index = expression.pos(nth)
139                length = len(expression.cap(nth))
140                self.setFormat(index, length, format)
141                index = expression.indexIn(text, index + length)
142
143        self.setCurrentBlockState(0)
144
145        # Do multi-line strings
146        in_multiline = self.match_multiline(text, *self.tri_single)
147        if not in_multiline:
148            in_multiline = self.match_multiline(text, *self.tri_double)
149
150
151    def match_multiline(self, text, delimiter, in_state, style):
152        """Do highlighting of multi-line strings. ``delimiter`` should be a
153        ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
154        ``in_state`` should be a unique integer to represent the corresponding
155        state changes when inside those strings. Returns True if we're still
156        inside a multi-line string when this function is finished.
157        """
158        # If inside triple-single quotes, start at 0
159        if self.previousBlockState() == in_state:
160            start = 0
161            add = 0
162        # Otherwise, look for the delimiter on this line
163        else:
164            start = delimiter.indexIn(text)
165            # Move past this match
166            add = delimiter.matchedLength()
167
168        # As long as there's a delimiter match on this line...
169        while start >= 0:
170            # Look for the ending delimiter
171            end = delimiter.indexIn(text, start + add)
172            # Ending delimiter on this line?
173            if end >= add:
174                length = end - start + add + delimiter.matchedLength()
175                self.setCurrentBlockState(0)
176            # No; multi-line string
177            else:
178                self.setCurrentBlockState(in_state)
179                length = len(text) - start + add
180            # Apply formatting
181            self.setFormat(start, length, style)
182            # Look for the next match
183            start = delimiter.indexIn(text, start + length)
184
185        # Return True if still inside a multi-line string, False otherwise
186        if self.currentBlockState() == in_state:
187            return True
188        else:
189            return False
190
191if __name__ == '__main__':
192    from PyQt5 import QtWidgets
193
194    app = QtWidgets.QApplication([])
195    editor = QtWidgets.QPlainTextEdit()
196    highlight = PythonHighlighter(editor.document())
197    editor.show()
198
199    # Load syntax.py into the editor for demo purposes
200    infile = open('PythonSyntax.py', 'r')
201    editor.setPlainText(infile.read())
202
203    app.exec_()
Note: See TracBrowser for help on using the repository browser.