|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a Python lexer with some additional methods. |
|
8 """ |
|
9 |
|
10 import re |
|
11 import contextlib |
|
12 |
|
13 from PyQt6.Qsci import QsciLexerPython, QsciScintilla |
|
14 |
|
15 from .SubstyledLexer import SubstyledLexer |
|
16 import Preferences |
|
17 |
|
18 |
|
19 class LexerPython(SubstyledLexer, QsciLexerPython): |
|
20 """ |
|
21 Subclass to implement some additional lexer dependant methods. |
|
22 """ |
|
23 def __init__(self, variant="", parent=None): |
|
24 """ |
|
25 Constructor |
|
26 |
|
27 @param variant name of the language variant (string) |
|
28 @param parent parent widget of this lexer |
|
29 """ |
|
30 QsciLexerPython.__init__(self, parent) |
|
31 SubstyledLexer.__init__(self) |
|
32 |
|
33 self.variant = variant |
|
34 self.commentString = "#" |
|
35 |
|
36 self.keywordSetDescriptions = [ |
|
37 self.tr("Keywords"), |
|
38 self.tr("Highlighted identifiers"), |
|
39 ] |
|
40 |
|
41 ############################################################## |
|
42 ## default sub-style definitions |
|
43 ############################################################## |
|
44 |
|
45 # list of style numbers, that support sub-styling |
|
46 self.baseStyles = [11] |
|
47 |
|
48 self.defaultSubStyles = { |
|
49 11: { |
|
50 0: { |
|
51 "Description": self.tr("Standard Library Modules"), |
|
52 "Words": """ |
|
53 __main__ _dummy_thread _thread abc aifc argparse array ascii ast asynchat |
|
54 asyncio asyncore atexit audioop base64 bdb binascii binhex bisect builtins bz2 |
|
55 calendar cgi cgitb chunk cmath cmd code codecs codeop collections colorsys |
|
56 compileall concurrent configparser contextlib contextvars copy copyreg |
|
57 cProfile crypt csv ctypes curses dataclasses datetime dbm decimal difflib dis |
|
58 distutils doctest email encodings ensurepip enum errno faulthandler fcntl |
|
59 filecmp fileinput fnmatch formatter fractions ftplib functools gc getopt |
|
60 getpass gettext glob graphlib grp gzip hashlib heapq hmac html http imaplib |
|
61 imghdr imp importlib inspect io ipaddress itertools json keyword lib2to3 |
|
62 linecache locale logging lzma mailbox mailcap marshal math mimetypes mmap |
|
63 modulefinder msilib msvcrt multiprocessing netrc nis nntplib numbers opcode |
|
64 operator optparse os ossaudiodev parser path pathlib pdb pickle pickletools |
|
65 pipes pkgutil platform plistlib poplib posix pprint profile pstats pty pwd |
|
66 py_compile pyclbr pydoc queue quopri random re readline reprlib resource |
|
67 rlcompleter runpy sched secrets select selectors shelve shlex shutil signal |
|
68 site smtpd smtplib sndhdr socket socketserver spwd sqlite3 ssl stat statistics |
|
69 string stringprep struct subprocess sunau symbol symtable sys sysconfig syslog |
|
70 tabnanny tarfile telnetlib tempfile termios test textwrap threading time |
|
71 timeit tkinter token tokenize trace traceback tracemalloc tty turtle |
|
72 turtledemo types typing unicodedata unittest urllib uu uuid venv warnings wave |
|
73 weakref webbrowser winreg winsound wsgiref xdrlib xml xmlrpc zipapp zipfile |
|
74 zipimport zlib zoneinfo""", |
|
75 "Style": { |
|
76 "fore": 0xDD9900, |
|
77 "font_bold": True, |
|
78 } |
|
79 }, |
|
80 1: { |
|
81 "Description": self.tr("__future__ Imports"), |
|
82 "Words": """ |
|
83 __future__ absolute_import annotations division generators generator_stop |
|
84 nested_scopes print_function unicode_literals with_statement""", |
|
85 "Style": { |
|
86 "fore": 0xEE00AA, |
|
87 "font_italic": True, |
|
88 } |
|
89 }, |
|
90 2: { |
|
91 "Description": self.tr("PyQt5/6 Modules"), |
|
92 "Words": """ |
|
93 PyQt5 PyQt6 Qsci Qt Qt3DAnimation Qt3DCore Qt3DExtras Qt3DInput Qt3DLogic |
|
94 Qt3DRender QtBluetooth QtChart QtCharts QtCore QtDataVisualization QtDBus |
|
95 QtDesigner QtGui QtHelp QtLocation QtMacExtras QtMultimedia |
|
96 QtMultimediaWidgets QtNetwork QtNetworkAuth QtNfc QtOpenGL QtOpenGLWidgets |
|
97 QtPositioning QtPrintSupport QtPurchasing QtQml QtQuick QtQuick3D |
|
98 QtQuickWidgets QtRemoteObjects QtSensors QtSerialPort QtSql QtSvg QtSvgWidgets |
|
99 QtTest QtTextToSpeech QtWebChannel QtWebEngine QtWebEngineCore |
|
100 QtWebEngineWidgets QtWebSockets QtWidgets QtWinExtras QtX11Extras QtXml |
|
101 QtXmlPatterns sip""", |
|
102 "Style": { |
|
103 "fore": 0x44AADD, |
|
104 "font_bold": True, |
|
105 } |
|
106 }, |
|
107 3: { |
|
108 "Description": self.tr("Cython Specifics"), |
|
109 "Words": "cython pyximport Cython __cinit__ __dealloc__", |
|
110 "Style": { |
|
111 "fore": 0xdd0000, |
|
112 "font_bold": True, |
|
113 } |
|
114 }, |
|
115 }, |
|
116 } |
|
117 |
|
118 def language(self): |
|
119 """ |
|
120 Public method to get the lexer language. |
|
121 |
|
122 @return lexer language (string) |
|
123 """ |
|
124 if not self.variant: |
|
125 return QsciLexerPython.language(self) |
|
126 else: |
|
127 return self.variant |
|
128 |
|
129 def initProperties(self): |
|
130 """ |
|
131 Public slot to initialize the properties. |
|
132 """ |
|
133 self.setIndentationWarning( |
|
134 Preferences.getEditor("PythonBadIndentation")) |
|
135 self.setFoldComments(Preferences.getEditor("PythonFoldComment")) |
|
136 self.setFoldQuotes(Preferences.getEditor("PythonFoldString")) |
|
137 if not Preferences.getEditor("PythonAutoIndent"): |
|
138 self.setAutoIndentStyle(QsciScintilla.AiMaintain) |
|
139 with contextlib.suppress(AttributeError): |
|
140 self.setV2UnicodeAllowed( |
|
141 Preferences.getEditor("PythonAllowV2Unicode")) |
|
142 self.setV3BinaryOctalAllowed( |
|
143 Preferences.getEditor("PythonAllowV3Binary")) |
|
144 self.setV3BytesAllowed(Preferences.getEditor("PythonAllowV3Bytes")) |
|
145 with contextlib.suppress(AttributeError): |
|
146 self.setFoldQuotes(Preferences.getEditor("PythonFoldQuotes")) |
|
147 self.setStringsOverNewlineAllowed( |
|
148 Preferences.getEditor("PythonStringsOverNewLineAllowed")) |
|
149 with contextlib.suppress(AttributeError): |
|
150 self.setHighlightSubidentifiers( |
|
151 Preferences.getEditor("PythonHighlightSubidentifier")) |
|
152 |
|
153 def getIndentationDifference(self, line, editor): |
|
154 """ |
|
155 Public method to determine the difference for the new indentation. |
|
156 |
|
157 @param line line to perform the calculation for (integer) |
|
158 @param editor QScintilla editor |
|
159 @return amount of difference in indentation (integer) |
|
160 """ |
|
161 indent_width = editor.getEditorConfig('IndentWidth') |
|
162 |
|
163 lead_spaces = editor.indentation(line) |
|
164 |
|
165 pline = line - 1 |
|
166 while pline >= 0 and re.match(r'^\s*(#.*)?$', editor.text(pline)): |
|
167 pline -= 1 |
|
168 |
|
169 if pline < 0: |
|
170 last = 0 |
|
171 else: |
|
172 previous_lead_spaces = editor.indentation(pline) |
|
173 # trailing spaces |
|
174 m = re.search(r':\s*(#.*)?$', editor.text(pline)) |
|
175 last = previous_lead_spaces |
|
176 if m: |
|
177 last += indent_width |
|
178 else: |
|
179 # special cases, like pass (unindent) or return (also unindent) |
|
180 m = re.search(r'(pass\s*(#.*)?$)|(^[^#]return)', |
|
181 editor.text(pline)) |
|
182 if m: |
|
183 last -= indent_width |
|
184 |
|
185 indentDifference = ( |
|
186 last - lead_spaces |
|
187 if (lead_spaces % indent_width != 0 or |
|
188 lead_spaces == 0 or |
|
189 self.lastIndented != line) else |
|
190 -indent_width # __IGNORE_WARNING_W503__ |
|
191 ) |
|
192 |
|
193 return indentDifference |
|
194 |
|
195 def autoCompletionWordSeparators(self): |
|
196 """ |
|
197 Public method to return the list of separators for autocompletion. |
|
198 |
|
199 @return list of separators (list of strings) |
|
200 """ |
|
201 return ['.'] |
|
202 |
|
203 def isCommentStyle(self, style): |
|
204 """ |
|
205 Public method to check, if a style is a comment style. |
|
206 |
|
207 @param style style to check (integer) |
|
208 @return flag indicating a comment style (boolean) |
|
209 """ |
|
210 return style in [QsciLexerPython.Comment, |
|
211 QsciLexerPython.CommentBlock] |
|
212 |
|
213 def isStringStyle(self, style): |
|
214 """ |
|
215 Public method to check, if a style is a string style. |
|
216 |
|
217 @param style style to check (integer) |
|
218 @return flag indicating a string style (boolean) |
|
219 """ |
|
220 return style in [QsciLexerPython.DoubleQuotedString, |
|
221 QsciLexerPython.SingleQuotedString, |
|
222 QsciLexerPython.TripleDoubleQuotedString, |
|
223 QsciLexerPython.TripleSingleQuotedString, |
|
224 QsciLexerPython.UnclosedString] |
|
225 |
|
226 def defaultKeywords(self, kwSet): |
|
227 """ |
|
228 Public method to get the default keywords. |
|
229 |
|
230 @param kwSet number of the keyword set (integer) |
|
231 @return string giving the keywords (string) or None |
|
232 """ |
|
233 if kwSet == 1: |
|
234 if self.language() == "Python3": |
|
235 import keyword |
|
236 keywords = " ".join(keyword.kwlist) |
|
237 elif self.language() == "MicroPython": |
|
238 keywords = ("False None True and as assert break class " |
|
239 "continue def del elif else except finally for " |
|
240 "from global if import in is lambda nonlocal not " |
|
241 "or pass raise return try while with yield") |
|
242 elif self.language() == "Cython": |
|
243 keywords = ("False None True and as assert break class " |
|
244 "continue def del elif else except finally for " |
|
245 "from global if import in is lambda nonlocal not " |
|
246 "or pass raise return try while with yield " |
|
247 "cdef cimport cpdef ctypedef") |
|
248 else: |
|
249 keywords = QsciLexerPython.keywords(self, kwSet) |
|
250 else: |
|
251 keywords = QsciLexerPython.keywords(self, kwSet) |
|
252 |
|
253 return keywords |
|
254 |
|
255 def maximumKeywordSet(self): |
|
256 """ |
|
257 Public method to get the maximum keyword set. |
|
258 |
|
259 @return maximum keyword set (integer) |
|
260 """ |
|
261 return 2 |