eric6/QScintilla/TypingCompleters/CompleterPython.py

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

eric ide

mercurial