QScintilla/TypingCompleters/CompleterPython.py

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

eric ide

mercurial