eric7/QScintilla/TypingCompleters/CompleterPython.py

branch
eric7
changeset 8312
800c432b34c8
parent 8228
772103b14c18
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a typing completer for Python.
8 """
9
10 import re
11
12 from PyQt5.Qsci import QsciLexerPython, QsciScintilla
13
14 from .CompleterBase import CompleterBase
15
16 import Preferences
17 from Utilities import rxIndex
18
19
20 class CompleterPython(CompleterBase):
21 """
22 Class implementing typing completer for Python.
23 """
24 def __init__(self, editor, parent=None):
25 """
26 Constructor
27
28 @param editor reference to the editor object (QScintilla.Editor)
29 @param parent reference to the parent object (QObject)
30 """
31 super().__init__(editor, parent)
32
33 self.__defRX = re.compile(
34 r"^[ \t]*(def|cdef|cpdef) \w+\(")
35 self.__defSelfRX = re.compile(
36 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*self[ \t]*[,)]")
37 self.__defClsRX = re.compile(
38 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*cls[ \t]*[,)]")
39 self.__classRX = re.compile(
40 r"^[ \t]*(cdef[ \t]+)?class \w+\(")
41 self.__importRX = re.compile(r"^[ \t]*from [\w.]+ ")
42 self.__classmethodRX = re.compile(r"^[ \t]*@classmethod")
43 self.__staticmethodRX = re.compile(r"^[ \t]*@staticmethod")
44
45 self.__defOnlyRX = re.compile(r"^[ \t]*def ")
46
47 self.__ifRX = re.compile(r"^[ \t]*if ")
48 self.__elifRX = re.compile(r"^[ \t]*elif ")
49 self.__elseRX = re.compile(r"^[ \t]*else:")
50
51 self.__tryRX = re.compile(r"^[ \t]*try:")
52 self.__finallyRX = re.compile(r"^[ \t]*finally:")
53 self.__exceptRX = re.compile(r"^[ \t]*except ")
54 self.__exceptcRX = re.compile(r"^[ \t]*except:")
55
56 self.__whileRX = re.compile(r"^[ \t]*while ")
57 self.__forRX = re.compile(r"^[ \t]*for ")
58
59 self.readSettings()
60
61 def readSettings(self):
62 """
63 Public slot called to reread the configuration parameters.
64 """
65 self.setEnabled(
66 Preferences.getEditorTyping("Python/EnabledTypingAids"))
67 self.__insertClosingBrace = Preferences.getEditorTyping(
68 "Python/InsertClosingBrace")
69 self.__indentBrace = Preferences.getEditorTyping(
70 "Python/IndentBrace")
71 self.__skipBrace = Preferences.getEditorTyping(
72 "Python/SkipBrace")
73 self.__insertQuote = Preferences.getEditorTyping(
74 "Python/InsertQuote")
75 self.__dedentElse = Preferences.getEditorTyping(
76 "Python/DedentElse")
77 self.__dedentExcept = Preferences.getEditorTyping(
78 "Python/DedentExcept")
79 self.__insertImport = Preferences.getEditorTyping(
80 "Python/InsertImport")
81 self.__importBraceType = Preferences.getEditorTyping(
82 "Python/ImportBraceType")
83 self.__insertSelf = Preferences.getEditorTyping(
84 "Python/InsertSelf")
85 self.__insertBlank = Preferences.getEditorTyping(
86 "Python/InsertBlank")
87 self.__colonDetection = Preferences.getEditorTyping(
88 "Python/ColonDetection")
89 self.__dedentDef = Preferences.getEditorTyping(
90 "Python/DedentDef")
91
92 def charAdded(self, charNumber):
93 """
94 Public slot called to handle the user entering a character.
95
96 @param charNumber value of the character entered (integer)
97 """
98 char = chr(charNumber)
99 if char not in ['(', ')', '{', '}', '[', ']', ' ', ',', "'", '"',
100 '\n', ':']:
101 return # take the short route
102
103 line, col = self.editor.getCursorPosition()
104
105 if (
106 self.__inComment(line, col) or
107 (char != '"' and self.__inDoubleQuotedString()) or
108 (char != '"' and self.__inTripleDoubleQuotedString()) or
109 (char != "'" and self.__inSingleQuotedString()) or
110 (char != "'" and self.__inTripleSingleQuotedString())
111 ):
112 return
113
114 # open parenthesis
115 # insert closing parenthesis and self
116 if char == '(':
117 txt = self.editor.text(line)[:col]
118 self.editor.beginUndoAction()
119 if (
120 self.__insertSelf and
121 self.__defRX.fullmatch(txt) is not None
122 ):
123 if self.__isClassMethodDef():
124 self.editor.insert('cls')
125 self.editor.setCursorPosition(line, col + 3)
126 elif self.__isStaticMethodDef():
127 # nothing to insert
128 pass
129 elif self.__isClassMethod():
130 self.editor.insert('self')
131 self.editor.setCursorPosition(line, col + 4)
132 if self.__insertClosingBrace:
133 if (
134 self.__defRX.fullmatch(txt) is not None or
135 self.__classRX.fullmatch(txt) is not None
136 ):
137 self.editor.insert('):')
138 else:
139 self.editor.insert(')')
140 self.editor.endUndoAction()
141
142 # closing parenthesis
143 # skip matching closing parenthesis
144 elif char in [')', '}', ']']:
145 txt = self.editor.text(line)
146 if col < len(txt) and char == txt[col] and self.__skipBrace:
147 self.editor.setSelection(line, col, line, col + 1)
148 self.editor.removeSelectedText()
149
150 # space
151 # insert import, dedent to if for elif, dedent to try for except,
152 # dedent def
153 elif char == ' ':
154 txt = self.editor.text(line)[:col]
155 if self.__insertImport and self.__importRX.fullmatch(txt):
156 self.editor.beginUndoAction()
157 if self.__importBraceType:
158 self.editor.insert('import ()')
159 self.editor.setCursorPosition(line, col + 8)
160 else:
161 self.editor.insert('import ')
162 self.editor.setCursorPosition(line, col + 7)
163 self.editor.endUndoAction()
164 elif self.__dedentElse and self.__elifRX.fullmatch(txt):
165 self.__dedentToIf()
166 elif self.__dedentExcept and self.__exceptRX.fullmatch(txt):
167 self.__dedentExceptToTry(False)
168 elif self.__dedentDef and self.__defOnlyRX.fullmatch(txt):
169 self.__dedentDefStatement()
170
171 # comma
172 # insert blank
173 elif char == ',' and self.__insertBlank:
174 self.editor.insert(' ')
175 self.editor.setCursorPosition(line, col + 1)
176
177 # open curly brace
178 # insert closing brace
179 elif char == '{' and self.__insertClosingBrace:
180 self.editor.insert('}')
181
182 # open bracket
183 # insert closing bracket
184 elif char == '[' and self.__insertClosingBrace:
185 self.editor.insert(']')
186
187 # double quote
188 # insert double quote
189 elif char == '"' and self.__insertQuote:
190 self.editor.insert('"')
191
192 # quote
193 # insert quote
194 elif char == '\'' and self.__insertQuote:
195 self.editor.insert('\'')
196
197 # colon
198 # skip colon, dedent to if for else:
199 elif char == ':':
200 text = self.editor.text(line)
201 if col < len(text) and char == text[col]:
202 if self.__colonDetection:
203 self.editor.setSelection(line, col, line, col + 1)
204 self.editor.removeSelectedText()
205 else:
206 txt = text[:col]
207 if self.__dedentElse and self.__elseRX.fullmatch(txt):
208 self.__dedentElseToIfWhileForTry()
209 elif self.__dedentExcept and self.__exceptcRX.fullmatch(txt):
210 self.__dedentExceptToTry(True)
211 elif self.__dedentExcept and self.__finallyRX.fullmatch(txt):
212 self.__dedentFinallyToTry()
213
214 # new line
215 # indent to opening brace
216 elif char == '\n' and self.__indentBrace:
217 txt = self.editor.text(line - 1)
218 if re.search(":\r?\n", txt) is None:
219 self.editor.beginUndoAction()
220 stxt = txt.strip()
221 if stxt and stxt[-1] in ("(", "[", "{"):
222 # indent one more level
223 self.editor.indent(line)
224 self.editor.editorCommand(QsciScintilla.SCI_VCHOME)
225 else:
226 # indent to the level of the opening brace
227 openCount = len(re.findall("[({[]", txt))
228 closeCount = len(re.findall(r"[)}\]]", txt))
229 if openCount > closeCount:
230 openCount = 0
231 closeCount = 0
232 openList = list(re.finditer("[({[]", txt))
233 index = len(openList) - 1
234 while index > -1 and openCount == closeCount:
235 lastOpenIndex = openList[index].start()
236 txt2 = txt[lastOpenIndex:]
237 openCount = len(re.findall("[({[]", txt2))
238 closeCount = len(re.findall(r"[)}\]]", txt2))
239 index -= 1
240 if openCount > closeCount and lastOpenIndex > col:
241 self.editor.insert(
242 ' ' * (lastOpenIndex - col + 1))
243 self.editor.setCursorPosition(
244 line, lastOpenIndex + 1)
245 self.editor.endUndoAction()
246
247 def __dedentToIf(self):
248 """
249 Private method to dedent the last line to the last if statement with
250 less (or equal) indentation.
251 """
252 line, col = self.editor.getCursorPosition()
253 indentation = self.editor.indentation(line)
254 ifLine = line - 1
255 while ifLine >= 0:
256 txt = self.editor.text(ifLine)
257 edInd = self.editor.indentation(ifLine)
258 if rxIndex(self.__elseRX, txt) == 0 and edInd <= indentation:
259 indentation = edInd - 1
260 elif (rxIndex(self.__ifRX, txt) == 0 or
261 rxIndex(self.__elifRX, txt) == 0) and edInd <= indentation:
262 self.editor.cancelList()
263 self.editor.setIndentation(line, edInd)
264 break
265 ifLine -= 1
266
267 def __dedentElseToIfWhileForTry(self):
268 """
269 Private method to dedent the line of the else statement to the last
270 if, while, for or try statement with less (or equal) indentation.
271 """
272 line, col = self.editor.getCursorPosition()
273 indentation = self.editor.indentation(line)
274 if line > 0:
275 prevInd = self.editor.indentation(line - 1)
276 ifLine = line - 1
277 while ifLine >= 0:
278 txt = self.editor.text(ifLine)
279 edInd = self.editor.indentation(ifLine)
280 if (
281 (rxIndex(self.__elseRX, txt) == 0 and
282 edInd <= indentation) or
283 (rxIndex(self.__elifRX, txt) == 0 and
284 edInd == indentation and
285 edInd == prevInd)
286 ):
287 indentation = edInd - 1
288 elif (
289 (rxIndex(self.__ifRX, txt) == 0 or
290 rxIndex(self.__whileRX, txt) == 0 or
291 rxIndex(self.__forRX, txt) == 0 or
292 rxIndex(self.__tryRX, txt) == 0) and
293 edInd <= indentation
294 ):
295 self.editor.cancelList()
296 self.editor.setIndentation(line, edInd)
297 break
298 ifLine -= 1
299
300 def __dedentExceptToTry(self, hasColon):
301 """
302 Private method to dedent the line of the except statement to the last
303 try statement with less (or equal) indentation.
304
305 @param hasColon flag indicating the except type (boolean)
306 """
307 line, col = self.editor.getCursorPosition()
308 indentation = self.editor.indentation(line)
309 tryLine = line - 1
310 while tryLine >= 0:
311 txt = self.editor.text(tryLine)
312 edInd = self.editor.indentation(tryLine)
313 if (
314 (rxIndex(self.__exceptcRX, txt) == 0 or
315 rxIndex(self.__finallyRX, txt) == 0) and
316 edInd <= indentation
317 ):
318 indentation = edInd - 1
319 elif (rxIndex(self.__exceptRX, txt) == 0 or
320 rxIndex(self.__tryRX, txt) == 0) and edInd <= indentation:
321 self.editor.cancelList()
322 self.editor.setIndentation(line, edInd)
323 break
324 tryLine -= 1
325
326 def __dedentFinallyToTry(self):
327 """
328 Private method to dedent the line of the except statement to the last
329 try statement with less (or equal) indentation.
330 """
331 line, col = self.editor.getCursorPosition()
332 indentation = self.editor.indentation(line)
333 tryLine = line - 1
334 while tryLine >= 0:
335 txt = self.editor.text(tryLine)
336 edInd = self.editor.indentation(tryLine)
337 if rxIndex(self.__finallyRX, txt) == 0 and edInd <= indentation:
338 indentation = edInd - 1
339 elif (
340 (rxIndex(self.__tryRX, txt) == 0 or
341 rxIndex(self.__exceptcRX, txt) == 0 or
342 rxIndex(self.__exceptRX, txt) == 0) and
343 edInd <= indentation
344 ):
345 self.editor.cancelList()
346 self.editor.setIndentation(line, edInd)
347 break
348 tryLine -= 1
349
350 def __dedentDefStatement(self):
351 """
352 Private method to dedent the line of the def statement to a previous
353 def statement or class statement.
354 """
355 line, col = self.editor.getCursorPosition()
356 indentation = self.editor.indentation(line)
357 tryLine = line - 1
358 while tryLine >= 0:
359 txt = self.editor.text(tryLine)
360 edInd = self.editor.indentation(tryLine)
361 newInd = -1
362 if rxIndex(self.__defRX, txt) == 0 and edInd < indentation:
363 newInd = edInd
364 elif rxIndex(self.__classRX, txt) == 0 and edInd < indentation:
365 newInd = edInd + (
366 self.editor.indentationWidth() or self.editor.tabWidth()
367 )
368 if newInd >= 0:
369 self.editor.cancelList()
370 self.editor.setIndentation(line, newInd)
371 break
372 tryLine -= 1
373
374 def __isClassMethod(self):
375 """
376 Private method to check, if the user is defining a class method.
377
378 @return flag indicating the definition of a class method (boolean)
379 """
380 line, col = self.editor.getCursorPosition()
381 indentation = self.editor.indentation(line)
382 curLine = line - 1
383 while curLine >= 0:
384 txt = self.editor.text(curLine)
385 if (
386 ((rxIndex(self.__defSelfRX, txt) == 0 or
387 rxIndex(self.__defClsRX, txt) == 0) and
388 self.editor.indentation(curLine) == indentation) or
389 (rxIndex(self.__classRX, txt) == 0 and
390 self.editor.indentation(curLine) < indentation)
391 ):
392 return True
393 elif (
394 rxIndex(self.__defRX, txt) == 0 and
395 self.editor.indentation(curLine) <= indentation
396 ):
397 return False
398 curLine -= 1
399 return False
400
401 def __isClassMethodDef(self):
402 """
403 Private method to check, if the user is defing a class method
404 (@classmethod).
405
406 @return flag indicating the definition of a class method (boolean)
407 """
408 line, col = self.editor.getCursorPosition()
409 indentation = self.editor.indentation(line)
410 curLine = line - 1
411 if (
412 rxIndex(self.__classmethodRX, self.editor.text(curLine)) == 0 and
413 self.editor.indentation(curLine) == indentation
414 ):
415 return True
416 return False
417
418 def __isStaticMethodDef(self):
419 """
420 Private method to check, if the user is defing a static method
421 (@staticmethod) method.
422
423 @return flag indicating the definition of a static method (boolean)
424 """
425 line, col = self.editor.getCursorPosition()
426 indentation = self.editor.indentation(line)
427 curLine = line - 1
428 if (
429 rxIndex(self.__staticmethodRX, self.editor.text(curLine)) == 0 and
430 self.editor.indentation(curLine) == indentation
431 ):
432 return True
433 return False
434
435 def __inComment(self, line, col):
436 """
437 Private method to check, if the cursor is inside a comment.
438
439 @param line current line (integer)
440 @param col current position within line (integer)
441 @return flag indicating, if the cursor is inside a comment (boolean)
442 """
443 txt = self.editor.text(line)
444 if col == len(txt):
445 col -= 1
446 while col >= 0:
447 if txt[col] == "#":
448 return True
449 col -= 1
450 return False
451
452 def __inDoubleQuotedString(self):
453 """
454 Private method to check, if the cursor is within a double quoted
455 string.
456
457 @return flag indicating, if the cursor is inside a double
458 quoted string (boolean)
459 """
460 return self.editor.currentStyle() == QsciLexerPython.DoubleQuotedString
461
462 def __inTripleDoubleQuotedString(self):
463 """
464 Private method to check, if the cursor is within a triple double
465 quoted string.
466
467 @return flag indicating, if the cursor is inside a triple double
468 quoted string (boolean)
469 """
470 return (
471 self.editor.currentStyle() ==
472 QsciLexerPython.TripleDoubleQuotedString
473 )
474
475 def __inSingleQuotedString(self):
476 """
477 Private method to check, if the cursor is within a single quoted
478 string.
479
480 @return flag indicating, if the cursor is inside a single
481 quoted string (boolean)
482 """
483 return self.editor.currentStyle() == QsciLexerPython.SingleQuotedString
484
485 def __inTripleSingleQuotedString(self):
486 """
487 Private method to check, if the cursor is within a triple single
488 quoted string.
489
490 @return flag indicating, if the cursor is inside a triple single
491 quoted string (boolean)
492 """
493 return (
494 self.editor.currentStyle() ==
495 QsciLexerPython.TripleSingleQuotedString
496 )

eric ide

mercurial