19 |
19 |
20 class CompleterPython(CompleterBase): |
20 class CompleterPython(CompleterBase): |
21 """ |
21 """ |
22 Class implementing typing completer for Python. |
22 Class implementing typing completer for Python. |
23 """ |
23 """ |
|
24 |
24 def __init__(self, editor, parent=None): |
25 def __init__(self, editor, parent=None): |
25 """ |
26 """ |
26 Constructor |
27 Constructor |
27 |
28 |
28 @param editor reference to the editor object (QScintilla.Editor) |
29 @param editor reference to the editor object (QScintilla.Editor) |
29 @param parent reference to the parent object (QObject) |
30 @param parent reference to the parent object (QObject) |
30 """ |
31 """ |
31 super().__init__(editor, parent) |
32 super().__init__(editor, parent) |
32 |
33 |
33 self.__defRX = re.compile( |
34 self.__defRX = re.compile(r"^[ \t]*(def|cdef|cpdef) \w+\(") |
34 r"^[ \t]*(def|cdef|cpdef) \w+\(") |
|
35 self.__defSelfRX = re.compile( |
35 self.__defSelfRX = re.compile( |
36 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*self[ \t]*[,)]") |
36 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*self[ \t]*[,)]" |
|
37 ) |
37 self.__defClsRX = re.compile( |
38 self.__defClsRX = re.compile( |
38 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*cls[ \t]*[,)]") |
39 r"^[ \t]*(def|cdef|cpdef) \w+\([ \t]*cls[ \t]*[,)]" |
39 self.__classRX = re.compile( |
40 ) |
40 r"^[ \t]*(cdef[ \t]+)?class \w+\(") |
41 self.__classRX = re.compile(r"^[ \t]*(cdef[ \t]+)?class \w+\(") |
41 self.__importRX = re.compile(r"^[ \t]*from [\w.]+ ") |
42 self.__importRX = re.compile(r"^[ \t]*from [\w.]+ ") |
42 self.__classmethodRX = re.compile(r"^[ \t]*@classmethod") |
43 self.__classmethodRX = re.compile(r"^[ \t]*@classmethod") |
43 self.__staticmethodRX = re.compile(r"^[ \t]*@staticmethod") |
44 self.__staticmethodRX = re.compile(r"^[ \t]*@staticmethod") |
44 |
45 |
45 self.__defOnlyRX = re.compile(r"^[ \t]*def ") |
46 self.__defOnlyRX = re.compile(r"^[ \t]*def ") |
46 |
47 |
47 self.__ifRX = re.compile(r"^[ \t]*if ") |
48 self.__ifRX = re.compile(r"^[ \t]*if ") |
48 self.__elifRX = re.compile(r"^[ \t]*elif ") |
49 self.__elifRX = re.compile(r"^[ \t]*elif ") |
49 self.__elseRX = re.compile(r"^[ \t]*else:") |
50 self.__elseRX = re.compile(r"^[ \t]*else:") |
50 |
51 |
51 self.__tryRX = re.compile(r"^[ \t]*try:") |
52 self.__tryRX = re.compile(r"^[ \t]*try:") |
52 self.__finallyRX = re.compile(r"^[ \t]*finally:") |
53 self.__finallyRX = re.compile(r"^[ \t]*finally:") |
53 self.__exceptRX = re.compile(r"^[ \t]*except ") |
54 self.__exceptRX = re.compile(r"^[ \t]*except ") |
54 self.__exceptcRX = re.compile(r"^[ \t]*except:") |
55 self.__exceptcRX = re.compile(r"^[ \t]*except:") |
55 |
56 |
56 self.__whileRX = re.compile(r"^[ \t]*while ") |
57 self.__whileRX = re.compile(r"^[ \t]*while ") |
57 self.__forRX = re.compile(r"^[ \t]*for ") |
58 self.__forRX = re.compile(r"^[ \t]*for ") |
58 |
59 |
59 self.readSettings() |
60 self.readSettings() |
60 |
61 |
61 def readSettings(self): |
62 def readSettings(self): |
62 """ |
63 """ |
63 Public slot called to reread the configuration parameters. |
64 Public slot called to reread the configuration parameters. |
64 """ |
65 """ |
65 self.setEnabled( |
66 self.setEnabled(Preferences.getEditorTyping("Python/EnabledTypingAids")) |
66 Preferences.getEditorTyping("Python/EnabledTypingAids")) |
|
67 self.__insertClosingBrace = Preferences.getEditorTyping( |
67 self.__insertClosingBrace = Preferences.getEditorTyping( |
68 "Python/InsertClosingBrace") |
68 "Python/InsertClosingBrace" |
69 self.__indentBrace = Preferences.getEditorTyping( |
69 ) |
70 "Python/IndentBrace") |
70 self.__indentBrace = Preferences.getEditorTyping("Python/IndentBrace") |
71 self.__skipBrace = Preferences.getEditorTyping( |
71 self.__skipBrace = Preferences.getEditorTyping("Python/SkipBrace") |
72 "Python/SkipBrace") |
72 self.__insertQuote = Preferences.getEditorTyping("Python/InsertQuote") |
73 self.__insertQuote = Preferences.getEditorTyping( |
73 self.__dedentElse = Preferences.getEditorTyping("Python/DedentElse") |
74 "Python/InsertQuote") |
74 self.__dedentExcept = Preferences.getEditorTyping("Python/DedentExcept") |
75 self.__dedentElse = Preferences.getEditorTyping( |
75 self.__insertImport = Preferences.getEditorTyping("Python/InsertImport") |
76 "Python/DedentElse") |
76 self.__importBraceType = Preferences.getEditorTyping("Python/ImportBraceType") |
77 self.__dedentExcept = Preferences.getEditorTyping( |
77 self.__insertSelf = Preferences.getEditorTyping("Python/InsertSelf") |
78 "Python/DedentExcept") |
78 self.__insertBlank = Preferences.getEditorTyping("Python/InsertBlank") |
79 self.__insertImport = Preferences.getEditorTyping( |
79 self.__colonDetection = Preferences.getEditorTyping("Python/ColonDetection") |
80 "Python/InsertImport") |
80 self.__dedentDef = Preferences.getEditorTyping("Python/DedentDef") |
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 |
81 |
92 def charAdded(self, charNumber): |
82 def charAdded(self, charNumber): |
93 """ |
83 """ |
94 Public slot called to handle the user entering a character. |
84 Public slot called to handle the user entering a character. |
95 |
85 |
96 @param charNumber value of the character entered (integer) |
86 @param charNumber value of the character entered (integer) |
97 """ |
87 """ |
98 char = chr(charNumber) |
88 char = chr(charNumber) |
99 if char not in ['(', ')', '{', '}', '[', ']', ' ', ',', "'", '"', |
89 if char not in ["(", ")", "{", "}", "[", "]", " ", ",", "'", '"', "\n", ":"]: |
100 '\n', ':']: |
|
101 return # take the short route |
90 return # take the short route |
102 |
91 |
103 line, col = self.editor.getCursorPosition() |
92 line, col = self.editor.getCursorPosition() |
104 |
93 |
105 if ( |
94 if ( |
106 self.__inComment(line, col) or |
95 self.__inComment(line, col) |
107 (char != '"' and self.__inDoubleQuotedString()) or |
96 or (char != '"' and self.__inDoubleQuotedString()) |
108 (char != '"' and self.__inTripleDoubleQuotedString()) or |
97 or (char != '"' and self.__inTripleDoubleQuotedString()) |
109 (char != "'" and self.__inSingleQuotedString()) or |
98 or (char != "'" and self.__inSingleQuotedString()) |
110 (char != "'" and self.__inTripleSingleQuotedString()) |
99 or (char != "'" and self.__inTripleSingleQuotedString()) |
111 ): |
100 ): |
112 return |
101 return |
113 |
102 |
114 # open parenthesis |
103 # open parenthesis |
115 # insert closing parenthesis and self |
104 # insert closing parenthesis and self |
116 if char == '(': |
105 if char == "(": |
117 txt = self.editor.text(line)[:col] |
106 txt = self.editor.text(line)[:col] |
118 self.editor.beginUndoAction() |
107 self.editor.beginUndoAction() |
119 if ( |
108 if self.__insertSelf and self.__defRX.fullmatch(txt) is not None: |
120 self.__insertSelf and |
|
121 self.__defRX.fullmatch(txt) is not None |
|
122 ): |
|
123 if self.__isClassMethodDef(): |
109 if self.__isClassMethodDef(): |
124 self.editor.insert('cls') |
110 self.editor.insert("cls") |
125 self.editor.setCursorPosition(line, col + 3) |
111 self.editor.setCursorPosition(line, col + 3) |
126 elif self.__isStaticMethodDef(): |
112 elif self.__isStaticMethodDef(): |
127 # nothing to insert |
113 # nothing to insert |
128 pass |
114 pass |
129 elif self.__isClassMethod(): |
115 elif self.__isClassMethod(): |
130 self.editor.insert('self') |
116 self.editor.insert("self") |
131 self.editor.setCursorPosition(line, col + 4) |
117 self.editor.setCursorPosition(line, col + 4) |
132 if self.__insertClosingBrace: |
118 if self.__insertClosingBrace: |
133 if ( |
119 if ( |
134 self.__defRX.fullmatch(txt) is not None or |
120 self.__defRX.fullmatch(txt) is not None |
135 self.__classRX.fullmatch(txt) is not None |
121 or self.__classRX.fullmatch(txt) is not None |
136 ): |
122 ): |
137 self.editor.insert('):') |
123 self.editor.insert("):") |
138 else: |
124 else: |
139 self.editor.insert(')') |
125 self.editor.insert(")") |
140 self.editor.endUndoAction() |
126 self.editor.endUndoAction() |
141 |
127 |
142 # closing parenthesis |
128 # closing parenthesis |
143 # skip matching closing parenthesis |
129 # skip matching closing parenthesis |
144 elif char in [')', '}', ']']: |
130 elif char in [")", "}", "]"]: |
145 txt = self.editor.text(line) |
131 txt = self.editor.text(line) |
146 if col < len(txt) and char == txt[col] and self.__skipBrace: |
132 if col < len(txt) and char == txt[col] and self.__skipBrace: |
147 self.editor.setSelection(line, col, line, col + 1) |
133 self.editor.setSelection(line, col, line, col + 1) |
148 self.editor.removeSelectedText() |
134 self.editor.removeSelectedText() |
149 |
135 |
150 # space |
136 # space |
151 # insert import, dedent to if for elif, dedent to try for except, |
137 # insert import, dedent to if for elif, dedent to try for except, |
152 # dedent def |
138 # dedent def |
153 elif char == ' ': |
139 elif char == " ": |
154 txt = self.editor.text(line)[:col] |
140 txt = self.editor.text(line)[:col] |
155 if self.__insertImport and self.__importRX.fullmatch(txt): |
141 if self.__insertImport and self.__importRX.fullmatch(txt): |
156 self.editor.beginUndoAction() |
142 self.editor.beginUndoAction() |
157 if self.__importBraceType: |
143 if self.__importBraceType: |
158 self.editor.insert('import ()') |
144 self.editor.insert("import ()") |
159 self.editor.setCursorPosition(line, col + 8) |
145 self.editor.setCursorPosition(line, col + 8) |
160 else: |
146 else: |
161 self.editor.insert('import ') |
147 self.editor.insert("import ") |
162 self.editor.setCursorPosition(line, col + 7) |
148 self.editor.setCursorPosition(line, col + 7) |
163 self.editor.endUndoAction() |
149 self.editor.endUndoAction() |
164 elif self.__dedentElse and self.__elifRX.fullmatch(txt): |
150 elif self.__dedentElse and self.__elifRX.fullmatch(txt): |
165 self.__dedentToIf() |
151 self.__dedentToIf() |
166 elif self.__dedentExcept and self.__exceptRX.fullmatch(txt): |
152 elif self.__dedentExcept and self.__exceptRX.fullmatch(txt): |
167 self.__dedentExceptToTry(False) |
153 self.__dedentExceptToTry(False) |
168 elif self.__dedentDef and self.__defOnlyRX.fullmatch(txt): |
154 elif self.__dedentDef and self.__defOnlyRX.fullmatch(txt): |
169 self.__dedentDefStatement() |
155 self.__dedentDefStatement() |
170 |
156 |
171 # comma |
157 # comma |
172 # insert blank |
158 # insert blank |
173 elif char == ',' and self.__insertBlank: |
159 elif char == "," and self.__insertBlank: |
174 self.editor.insert(' ') |
160 self.editor.insert(" ") |
175 self.editor.setCursorPosition(line, col + 1) |
161 self.editor.setCursorPosition(line, col + 1) |
176 |
162 |
177 # open curly brace |
163 # open curly brace |
178 # insert closing brace |
164 # insert closing brace |
179 elif char == '{' and self.__insertClosingBrace: |
165 elif char == "{" and self.__insertClosingBrace: |
180 self.editor.insert('}') |
166 self.editor.insert("}") |
181 |
167 |
182 # open bracket |
168 # open bracket |
183 # insert closing bracket |
169 # insert closing bracket |
184 elif char == '[' and self.__insertClosingBrace: |
170 elif char == "[" and self.__insertClosingBrace: |
185 self.editor.insert(']') |
171 self.editor.insert("]") |
186 |
172 |
187 # double quote |
173 # double quote |
188 # insert double quote |
174 # insert double quote |
189 elif char == '"' and self.__insertQuote: |
175 elif char == '"' and self.__insertQuote: |
190 self.editor.insert('"') |
176 self.editor.insert('"') |
191 |
177 |
192 # quote |
178 # quote |
193 # insert quote |
179 # insert quote |
194 elif char == '\'' and self.__insertQuote: |
180 elif char == "'" and self.__insertQuote: |
195 self.editor.insert('\'') |
181 self.editor.insert("'") |
196 |
182 |
197 # colon |
183 # colon |
198 # skip colon, dedent to if for else: |
184 # skip colon, dedent to if for else: |
199 elif char == ':': |
185 elif char == ":": |
200 text = self.editor.text(line) |
186 text = self.editor.text(line) |
201 if col < len(text) and char == text[col]: |
187 if col < len(text) and char == text[col]: |
202 if self.__colonDetection: |
188 if self.__colonDetection: |
203 self.editor.setSelection(line, col, line, col + 1) |
189 self.editor.setSelection(line, col, line, col + 1) |
204 self.editor.removeSelectedText() |
190 self.editor.removeSelectedText() |
275 prevInd = self.editor.indentation(line - 1) |
260 prevInd = self.editor.indentation(line - 1) |
276 ifLine = line - 1 |
261 ifLine = line - 1 |
277 while ifLine >= 0: |
262 while ifLine >= 0: |
278 txt = self.editor.text(ifLine) |
263 txt = self.editor.text(ifLine) |
279 edInd = self.editor.indentation(ifLine) |
264 edInd = self.editor.indentation(ifLine) |
280 if ( |
265 if (rxIndex(self.__elseRX, txt) == 0 and edInd <= indentation) or ( |
281 (rxIndex(self.__elseRX, txt) == 0 and |
266 rxIndex(self.__elifRX, txt) == 0 |
282 edInd <= indentation) or |
267 and edInd == indentation |
283 (rxIndex(self.__elifRX, txt) == 0 and |
268 and edInd == prevInd |
284 edInd == indentation and |
|
285 edInd == prevInd) |
|
286 ): |
269 ): |
287 indentation = edInd - 1 |
270 indentation = edInd - 1 |
288 elif ( |
271 elif ( |
289 (rxIndex(self.__ifRX, txt) == 0 or |
272 rxIndex(self.__ifRX, txt) == 0 |
290 rxIndex(self.__whileRX, txt) == 0 or |
273 or rxIndex(self.__whileRX, txt) == 0 |
291 rxIndex(self.__forRX, txt) == 0 or |
274 or rxIndex(self.__forRX, txt) == 0 |
292 rxIndex(self.__tryRX, txt) == 0) and |
275 or rxIndex(self.__tryRX, txt) == 0 |
293 edInd <= indentation |
276 ) and edInd <= indentation: |
294 ): |
|
295 self.editor.cancelList() |
277 self.editor.cancelList() |
296 self.editor.setIndentation(line, edInd) |
278 self.editor.setIndentation(line, edInd) |
297 break |
279 break |
298 ifLine -= 1 |
280 ifLine -= 1 |
299 |
281 |
300 def __dedentExceptToTry(self, hasColon): |
282 def __dedentExceptToTry(self, hasColon): |
301 """ |
283 """ |
302 Private method to dedent the line of the except statement to the last |
284 Private method to dedent the line of the except statement to the last |
303 try statement with less (or equal) indentation. |
285 try statement with less (or equal) indentation. |
304 |
286 |
305 @param hasColon flag indicating the except type (boolean) |
287 @param hasColon flag indicating the except type (boolean) |
306 """ |
288 """ |
307 line, col = self.editor.getCursorPosition() |
289 line, col = self.editor.getCursorPosition() |
308 indentation = self.editor.indentation(line) |
290 indentation = self.editor.indentation(line) |
309 tryLine = line - 1 |
291 tryLine = line - 1 |
310 while tryLine >= 0: |
292 while tryLine >= 0: |
311 txt = self.editor.text(tryLine) |
293 txt = self.editor.text(tryLine) |
312 edInd = self.editor.indentation(tryLine) |
294 edInd = self.editor.indentation(tryLine) |
313 if ( |
295 if ( |
314 (rxIndex(self.__exceptcRX, txt) == 0 or |
296 rxIndex(self.__exceptcRX, txt) == 0 |
315 rxIndex(self.__finallyRX, txt) == 0) and |
297 or rxIndex(self.__finallyRX, txt) == 0 |
316 edInd <= indentation |
298 ) and edInd <= indentation: |
317 ): |
|
318 indentation = edInd - 1 |
299 indentation = edInd - 1 |
319 elif (rxIndex(self.__exceptRX, txt) == 0 or |
300 elif ( |
320 rxIndex(self.__tryRX, txt) == 0) and edInd <= indentation: |
301 rxIndex(self.__exceptRX, txt) == 0 or rxIndex(self.__tryRX, txt) == 0 |
|
302 ) and edInd <= indentation: |
321 self.editor.cancelList() |
303 self.editor.cancelList() |
322 self.editor.setIndentation(line, edInd) |
304 self.editor.setIndentation(line, edInd) |
323 break |
305 break |
324 tryLine -= 1 |
306 tryLine -= 1 |
325 |
307 |
326 def __dedentFinallyToTry(self): |
308 def __dedentFinallyToTry(self): |
327 """ |
309 """ |
328 Private method to dedent the line of the except statement to the last |
310 Private method to dedent the line of the except statement to the last |
329 try statement with less (or equal) indentation. |
311 try statement with less (or equal) indentation. |
330 """ |
312 """ |
368 if newInd >= 0: |
349 if newInd >= 0: |
369 self.editor.cancelList() |
350 self.editor.cancelList() |
370 self.editor.setIndentation(line, newInd) |
351 self.editor.setIndentation(line, newInd) |
371 break |
352 break |
372 tryLine -= 1 |
353 tryLine -= 1 |
373 |
354 |
374 def __isClassMethod(self): |
355 def __isClassMethod(self): |
375 """ |
356 """ |
376 Private method to check, if the user is defining a class method. |
357 Private method to check, if the user is defining a class method. |
377 |
358 |
378 @return flag indicating the definition of a class method (boolean) |
359 @return flag indicating the definition of a class method (boolean) |
379 """ |
360 """ |
380 line, col = self.editor.getCursorPosition() |
361 line, col = self.editor.getCursorPosition() |
381 indentation = self.editor.indentation(line) |
362 indentation = self.editor.indentation(line) |
382 curLine = line - 1 |
363 curLine = line - 1 |
383 while curLine >= 0: |
364 while curLine >= 0: |
384 txt = self.editor.text(curLine) |
365 txt = self.editor.text(curLine) |
385 if ( |
366 if ( |
386 ((rxIndex(self.__defSelfRX, txt) == 0 or |
367 ( |
387 rxIndex(self.__defClsRX, txt) == 0) and |
368 rxIndex(self.__defSelfRX, txt) == 0 |
388 self.editor.indentation(curLine) == indentation) or |
369 or rxIndex(self.__defClsRX, txt) == 0 |
389 (rxIndex(self.__classRX, txt) == 0 and |
370 ) |
390 self.editor.indentation(curLine) < indentation) |
371 and self.editor.indentation(curLine) == indentation |
|
372 ) or ( |
|
373 rxIndex(self.__classRX, txt) == 0 |
|
374 and self.editor.indentation(curLine) < indentation |
391 ): |
375 ): |
392 return True |
376 return True |
393 elif ( |
377 elif ( |
394 rxIndex(self.__defRX, txt) == 0 and |
378 rxIndex(self.__defRX, txt) == 0 |
395 self.editor.indentation(curLine) <= indentation |
379 and self.editor.indentation(curLine) <= indentation |
396 ): |
380 ): |
397 return False |
381 return False |
398 curLine -= 1 |
382 curLine -= 1 |
399 return False |
383 return False |
400 |
384 |
401 def __isClassMethodDef(self): |
385 def __isClassMethodDef(self): |
402 """ |
386 """ |
403 Private method to check, if the user is defing a class method |
387 Private method to check, if the user is defing a class method |
404 (@classmethod). |
388 (@classmethod). |
405 |
389 |
406 @return flag indicating the definition of a class method (boolean) |
390 @return flag indicating the definition of a class method (boolean) |
407 """ |
391 """ |
408 line, col = self.editor.getCursorPosition() |
392 line, col = self.editor.getCursorPosition() |
409 indentation = self.editor.indentation(line) |
393 indentation = self.editor.indentation(line) |
410 curLine = line - 1 |
394 curLine = line - 1 |
411 if ( |
395 if ( |
412 rxIndex(self.__classmethodRX, self.editor.text(curLine)) == 0 and |
396 rxIndex(self.__classmethodRX, self.editor.text(curLine)) == 0 |
413 self.editor.indentation(curLine) == indentation |
397 and self.editor.indentation(curLine) == indentation |
414 ): |
398 ): |
415 return True |
399 return True |
416 return False |
400 return False |
417 |
401 |
418 def __isStaticMethodDef(self): |
402 def __isStaticMethodDef(self): |
419 """ |
403 """ |
420 Private method to check, if the user is defing a static method |
404 Private method to check, if the user is defing a static method |
421 (@staticmethod) method. |
405 (@staticmethod) method. |
422 |
406 |
423 @return flag indicating the definition of a static method (boolean) |
407 @return flag indicating the definition of a static method (boolean) |
424 """ |
408 """ |
425 line, col = self.editor.getCursorPosition() |
409 line, col = self.editor.getCursorPosition() |
426 indentation = self.editor.indentation(line) |
410 indentation = self.editor.indentation(line) |
427 curLine = line - 1 |
411 curLine = line - 1 |
428 if ( |
412 if ( |
429 rxIndex(self.__staticmethodRX, self.editor.text(curLine)) == 0 and |
413 rxIndex(self.__staticmethodRX, self.editor.text(curLine)) == 0 |
430 self.editor.indentation(curLine) == indentation |
414 and self.editor.indentation(curLine) == indentation |
431 ): |
415 ): |
432 return True |
416 return True |
433 return False |
417 return False |
434 |
418 |
435 def __inComment(self, line, col): |
419 def __inComment(self, line, col): |
436 """ |
420 """ |
437 Private method to check, if the cursor is inside a comment. |
421 Private method to check, if the cursor is inside a comment. |
438 |
422 |
439 @param line current line (integer) |
423 @param line current line (integer) |
440 @param col current position within line (integer) |
424 @param col current position within line (integer) |
441 @return flag indicating, if the cursor is inside a comment (boolean) |
425 @return flag indicating, if the cursor is inside a comment (boolean) |
442 """ |
426 """ |
443 txt = self.editor.text(line) |
427 txt = self.editor.text(line) |