| 1 | # peppy Copyright (c) 2006-2008 Rob McMullen |
|---|
| 2 | # Licenced under the GPLv2; see http://peppy.flipturn.org for more info |
|---|
| 3 | |
|---|
| 4 | # Transcribed from KDE's kateautoindent.cpp which was licensed under the |
|---|
| 5 | # LGPLv2. LGPL code can be relicensed under the GPL according to LGPL section |
|---|
| 6 | # 3, which is what I've done here. The original kate code contained the |
|---|
| 7 | # following copyright: |
|---|
| 8 | # Copyright (C) 2003 Jesse Yurkovich <yurkjes@iit.edu> |
|---|
| 9 | # Copyright (C) 2004 >Anders Lund <anders@alweb.dk> (KateVarIndent class) |
|---|
| 10 | # Copyright (C) 2005 Dominik Haumann <dhdev@gmx.de> (basic support for config page) |
|---|
| 11 | |
|---|
| 12 | """Autoindent code for peppy |
|---|
| 13 | |
|---|
| 14 | This is a collection of autoindent code designed for use with peppy's |
|---|
| 15 | enhancements to the wx.StyledTextCtrl. |
|---|
| 16 | |
|---|
| 17 | If you wanted to use this without peppy, you'd need to provide several |
|---|
| 18 | methods, including C{isStyleComment}, C{isStyleString}, C{getLinesep}, |
|---|
| 19 | (and others) to the stc of interest. See the implementation of them in |
|---|
| 20 | L{peppy.editra.stcmixin} and L{peppy.stcbase}. |
|---|
| 21 | """ |
|---|
| 22 | |
|---|
| 23 | import os, re |
|---|
| 24 | from cStringIO import StringIO |
|---|
| 25 | |
|---|
| 26 | import wx.stc |
|---|
| 27 | from peppy.debug import * |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | class BasicAutoindent(debugmixin): |
|---|
| 31 | """Simple autoindent that indents the line to the level of the line above it. |
|---|
| 32 | |
|---|
| 33 | This is about the bare minimum indenter. It just looks at the line above |
|---|
| 34 | it and reports the indent level of that line. No effort is made to look |
|---|
| 35 | at syntax or anything. Simple. |
|---|
| 36 | """ |
|---|
| 37 | |
|---|
| 38 | # Scintilla always reports a fold of zero on the last line, so if |
|---|
| 39 | # subclasses use folding to determine the indent, this should be set to |
|---|
| 40 | # True so that processReturn will add an extra newline character if it's |
|---|
| 41 | # at the end of the file. |
|---|
| 42 | folding_last_line_bug = False |
|---|
| 43 | |
|---|
| 44 | def __init__(self): |
|---|
| 45 | pass |
|---|
| 46 | |
|---|
| 47 | def findIndent(self, stc, linenum): |
|---|
| 48 | """Find proper indention of current line based on the previous line |
|---|
| 49 | |
|---|
| 50 | This is designed to be overridden in subclasses. Given the current |
|---|
| 51 | line and assuming the current line is indented correctly, figure out |
|---|
| 52 | what the indention should be for the next line. |
|---|
| 53 | |
|---|
| 54 | @param linenum: line number |
|---|
| 55 | @return: integer indicating number of columns to indent the following |
|---|
| 56 | line |
|---|
| 57 | """ |
|---|
| 58 | # look at indention of previous line |
|---|
| 59 | prevind, prevline = stc.GetPrevLineIndentation(linenum) |
|---|
| 60 | #if (prevind < indcol and prevline < linenum-1) or prevline < linenum-2: |
|---|
| 61 | # # if there's blank lines before this and the previous |
|---|
| 62 | # # non-blank line is indented less than this one, ignore |
|---|
| 63 | # # it. Make the user manually unindent lines. |
|---|
| 64 | # return None |
|---|
| 65 | |
|---|
| 66 | # previous line is not blank, so indent line to previous |
|---|
| 67 | # line's level |
|---|
| 68 | return prevind |
|---|
| 69 | |
|---|
| 70 | def reindentLine(self, stc, linenum=None, dedent_only=False): |
|---|
| 71 | """Reindent the specified line to the correct level. |
|---|
| 72 | |
|---|
| 73 | Changes the indentation of the given line by inserting or deleting |
|---|
| 74 | whitespace as required. This operation is typically bound to the tab |
|---|
| 75 | key, but regardless to the actual keypress to which it is bound is |
|---|
| 76 | *only* called in response to a user keypress. |
|---|
| 77 | |
|---|
| 78 | @param stc: the stc of interest |
|---|
| 79 | @param linenum: the line number, or None to use the current line |
|---|
| 80 | @param dedent_only: flag to indicate that indentation should only be |
|---|
| 81 | removed, not added |
|---|
| 82 | @return: the new cursor position, in case the cursor has moved as a |
|---|
| 83 | result of the indention. |
|---|
| 84 | """ |
|---|
| 85 | if linenum is None: |
|---|
| 86 | linenum = stc.GetCurrentLine() |
|---|
| 87 | if linenum == 0: |
|---|
| 88 | # first line is always indented correctly |
|---|
| 89 | return stc.GetCurrentPos() |
|---|
| 90 | |
|---|
| 91 | linestart = stc.PositionFromLine(linenum) |
|---|
| 92 | |
|---|
| 93 | # actual indention of current line |
|---|
| 94 | indcol = stc.GetLineIndentation(linenum) # columns |
|---|
| 95 | pos = stc.GetCurrentPos() |
|---|
| 96 | indpos = stc.GetLineIndentPosition(linenum) # absolute character position |
|---|
| 97 | col = stc.GetColumn(pos) |
|---|
| 98 | self.dprint("linestart=%d indpos=%d pos=%d col=%d indcol=%d" % (linestart, indpos, pos, col, indcol)) |
|---|
| 99 | |
|---|
| 100 | newind = self.findIndent(stc, linenum) |
|---|
| 101 | if newind is None: |
|---|
| 102 | return pos |
|---|
| 103 | if dedent_only and newind > indcol: |
|---|
| 104 | return pos |
|---|
| 105 | |
|---|
| 106 | # the target to be replaced is the leading indention of the |
|---|
| 107 | # current line |
|---|
| 108 | indstr = stc.GetIndentString(newind) |
|---|
| 109 | self.dprint("linenum=%d indstr='%s'" % (linenum, indstr)) |
|---|
| 110 | stc.SetTargetStart(linestart) |
|---|
| 111 | stc.SetTargetEnd(indpos) |
|---|
| 112 | stc.ReplaceTarget(indstr) |
|---|
| 113 | |
|---|
| 114 | # recalculate cursor position, because it may have moved if it |
|---|
| 115 | # was within the target |
|---|
| 116 | after = stc.GetLineIndentPosition(linenum) |
|---|
| 117 | self.dprint("after: indent=%d cursor=%d" % (after, stc.GetCurrentPos())) |
|---|
| 118 | if pos < linestart: |
|---|
| 119 | return pos |
|---|
| 120 | newpos = pos - indpos + after |
|---|
| 121 | if newpos < linestart: |
|---|
| 122 | # we were in the indent region, but the region was made smaller |
|---|
| 123 | return after |
|---|
| 124 | elif pos < indpos: |
|---|
| 125 | # in the indent region |
|---|
| 126 | return after |
|---|
| 127 | return newpos |
|---|
| 128 | |
|---|
| 129 | def processReturn(self, stc): |
|---|
| 130 | """Add a newline and indent to the proper tab level. |
|---|
| 131 | |
|---|
| 132 | Indent to the level of the line above. This uses the findIndent method |
|---|
| 133 | to determine the proper indentation of the line about to be added, |
|---|
| 134 | inserts the appropriate end-of-line characters, and indents the new |
|---|
| 135 | line to that indentation level. |
|---|
| 136 | |
|---|
| 137 | @param stc: stc of interest |
|---|
| 138 | """ |
|---|
| 139 | linesep = stc.getLinesep() |
|---|
| 140 | |
|---|
| 141 | stc.BeginUndoAction() |
|---|
| 142 | # reindent current line (if necessary), then process the return |
|---|
| 143 | #pos = stc.reindentLine() |
|---|
| 144 | |
|---|
| 145 | linenum = stc.GetCurrentLine() |
|---|
| 146 | pos = stc.GetCurrentPos() |
|---|
| 147 | col = stc.GetColumn(pos) |
|---|
| 148 | #linestart = stc.PositionFromLine(linenum) |
|---|
| 149 | #line = stc.GetLine(linenum)[:pos-linestart] |
|---|
| 150 | |
|---|
| 151 | #get info about the current line's indentation |
|---|
| 152 | ind = stc.GetLineIndentation(linenum) |
|---|
| 153 | |
|---|
| 154 | self.dprint("format = %s col=%d ind = %d" % (repr(linesep), col, ind)) |
|---|
| 155 | |
|---|
| 156 | stc.SetTargetStart(pos) |
|---|
| 157 | stc.SetTargetEnd(pos) |
|---|
| 158 | if col <= ind: |
|---|
| 159 | newline = linesep + stc.GetIndentString(col) |
|---|
| 160 | elif not pos: |
|---|
| 161 | newline = linesep |
|---|
| 162 | else: |
|---|
| 163 | stc.ReplaceTarget(linesep) |
|---|
| 164 | pos += len(linesep) |
|---|
| 165 | end = min(pos + 1, stc.GetTextLength()) |
|---|
| 166 | |
|---|
| 167 | # Scintilla always returns a fold level of zero on the last line, |
|---|
| 168 | # so when trying to indent the last line, must add a newline |
|---|
| 169 | # character. |
|---|
| 170 | if pos == end and self.folding_last_line_bug: |
|---|
| 171 | stc.AddText("\n") |
|---|
| 172 | end = stc.GetTextLength() |
|---|
| 173 | |
|---|
| 174 | # When we insert a new line, the colorization isn't always |
|---|
| 175 | # immediately updated, so we have to force that here before |
|---|
| 176 | # calling findIndent to guarantee that the new line will have the |
|---|
| 177 | # correct fold property set. |
|---|
| 178 | stc.Colourise(stc.PositionFromLine(linenum), end) |
|---|
| 179 | stc.SetTargetStart(pos) |
|---|
| 180 | stc.SetTargetEnd(pos) |
|---|
| 181 | ind = self.findIndent(stc, linenum + 1) |
|---|
| 182 | self.dprint("pos=%d ind=%d fold=%d" % (pos, ind, (stc.GetFoldLevel(linenum+1)&wx.stc.STC_FOLDLEVELNUMBERMASK) - wx.stc.STC_FOLDLEVELBASE)) |
|---|
| 183 | newline = stc.GetIndentString(ind) |
|---|
| 184 | stc.ReplaceTarget(newline) |
|---|
| 185 | stc.GotoPos(pos + len(newline)) |
|---|
| 186 | stc.EndUndoAction() |
|---|
| 187 | |
|---|
| 188 | def processTab(self, stc): |
|---|
| 189 | stc.BeginUndoAction() |
|---|
| 190 | self.dprint() |
|---|
| 191 | pos = self.reindentLine(stc) |
|---|
| 192 | stc.GotoPos(pos) |
|---|
| 193 | stc.EndUndoAction() |
|---|
| 194 | |
|---|
| 195 | def electricChar(self, stc, uchar): |
|---|
| 196 | """Autoindent in response to a special character |
|---|
| 197 | |
|---|
| 198 | This is a hook to cause an autoindent on a particular character. |
|---|
| 199 | Note that the hook can do more than that -- it can insert or delete |
|---|
| 200 | characters as well. |
|---|
| 201 | |
|---|
| 202 | This takes its name from emacs, where "electric" meant that something |
|---|
| 203 | else happened other than simply inserting the char. |
|---|
| 204 | |
|---|
| 205 | @param stc: stc instance |
|---|
| 206 | |
|---|
| 207 | @param uchar: unicode character that was just typed by the user (note |
|---|
| 208 | that it hasn't been inserted into the document yet.) |
|---|
| 209 | |
|---|
| 210 | @return: True if this method handled the character and the text |
|---|
| 211 | was modified; False if the calling event handler should handle the |
|---|
| 212 | character. |
|---|
| 213 | """ |
|---|
| 214 | return False |
|---|
| 215 | |
|---|
| 216 | def electricDelete(self, stc): |
|---|
| 217 | """Delete the next character |
|---|
| 218 | |
|---|
| 219 | This hook allows more complex processing of the Delete key than the |
|---|
| 220 | default which is to delete the character after the cursor. |
|---|
| 221 | """ |
|---|
| 222 | stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR) |
|---|
| 223 | |
|---|
| 224 | def electricBackspace(self, stc): |
|---|
| 225 | """Delete the previous character |
|---|
| 226 | |
|---|
| 227 | This hook allows more complex processing of the Backspace key than the |
|---|
| 228 | default which is to delete the character before the cursor. |
|---|
| 229 | """ |
|---|
| 230 | stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK) |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | class FoldingAutoindent(BasicAutoindent): |
|---|
| 234 | """Experimental class to use STC Folding to reindent a line. |
|---|
| 235 | """ |
|---|
| 236 | folding_last_line_bug = True |
|---|
| 237 | |
|---|
| 238 | def getFold(self, stc, linenum): |
|---|
| 239 | return stc.GetFoldLevel(linenum)&wx.stc.STC_FOLDLEVELNUMBERMASK - wx.stc.STC_FOLDLEVELBASE |
|---|
| 240 | |
|---|
| 241 | def getPreviousText(self, stc, linenum): |
|---|
| 242 | """Find the text above the line with the same fold level. |
|---|
| 243 | |
|---|
| 244 | """ |
|---|
| 245 | fold = self.getFold(stc, linenum) |
|---|
| 246 | ln = linenum - 1 |
|---|
| 247 | fc = lc = stc.GetLineEndPosition(ln) |
|---|
| 248 | above = '' |
|---|
| 249 | while ln > 0: |
|---|
| 250 | f = self.getFold(stc, ln) |
|---|
| 251 | if f != fold: |
|---|
| 252 | above = stc.GetTextRange(fc, lc) |
|---|
| 253 | break |
|---|
| 254 | fc = stc.PositionFromLine(ln) |
|---|
| 255 | ln -= 1 |
|---|
| 256 | return above |
|---|
| 257 | |
|---|
| 258 | def getFoldSectionStart(self, stc, linenum): |
|---|
| 259 | """Find the line number of the text above the given line that has the |
|---|
| 260 | same fold level. |
|---|
| 261 | |
|---|
| 262 | Searching from the given line number toward the start of the file, |
|---|
| 263 | find the set of lines that have the same fold level as the given |
|---|
| 264 | line. Once the fold level changes, we know that we're done searching |
|---|
| 265 | because there's no way that any indentation for the given line can be |
|---|
| 266 | affected by a separate block of code |
|---|
| 267 | |
|---|
| 268 | @return: the line number of the start of the fold section |
|---|
| 269 | """ |
|---|
| 270 | fold = self.getFold(stc, linenum) |
|---|
| 271 | ln = linenum |
|---|
| 272 | while ln > 0: |
|---|
| 273 | f = self.getFold(stc, ln - 1) |
|---|
| 274 | if f != fold: |
|---|
| 275 | break |
|---|
| 276 | ln -= 1 |
|---|
| 277 | return ln |
|---|
| 278 | |
|---|
| 279 | def getNonCodeStyles(self, stc): |
|---|
| 280 | """Return a list of styling integers that indicate characters that are |
|---|
| 281 | outside the normal code flow. |
|---|
| 282 | |
|---|
| 283 | Character styles that are outside code flow are strings, comments, |
|---|
| 284 | preprocessor statements, and stuff that should be ignored when |
|---|
| 285 | indenting. |
|---|
| 286 | """ |
|---|
| 287 | styles = [] |
|---|
| 288 | styles.extend(stc.getStringStyles()) |
|---|
| 289 | styles.extend(stc.getCommentStyles()) |
|---|
| 290 | #self.dprint(styles) |
|---|
| 291 | return styles |
|---|
| 292 | |
|---|
| 293 | def getCodeChars(self, stc, ln, lc=-1): |
|---|
| 294 | """Get a version of the given line with all non code chars blanked out. |
|---|
| 295 | |
|---|
| 296 | This function blanks out all non-code characters (comments, strings, |
|---|
| 297 | etc) from the line and returns a copy of the interesting stuff. |
|---|
| 298 | |
|---|
| 299 | @param stc: stc of interest |
|---|
| 300 | @param ln: line number |
|---|
| 301 | @param lc: optional integer specifying the last position on the |
|---|
| 302 | line to consider |
|---|
| 303 | """ |
|---|
| 304 | fc = stc.PositionFromLine(ln) |
|---|
| 305 | if lc < 0: |
|---|
| 306 | lc = stc.GetLineEndPosition(ln) |
|---|
| 307 | if lc < fc: |
|---|
| 308 | # FIXME: fail hard during development |
|---|
| 309 | raise IndexError("bad line specification for line %d: fc=%d lc=%d" % (ln, fc, lc)) |
|---|
| 310 | |
|---|
| 311 | mask = (2 ** stc.GetStyleBits()) - 1 |
|---|
| 312 | |
|---|
| 313 | out = [] |
|---|
| 314 | |
|---|
| 315 | line = stc.GetStyledText(fc, lc) |
|---|
| 316 | self.dprint(repr(line)) |
|---|
| 317 | i = len(line) |
|---|
| 318 | |
|---|
| 319 | # replace all uninteresting chars with blanks |
|---|
| 320 | skip = self.getNonCodeStyles(stc) |
|---|
| 321 | while i > 0: |
|---|
| 322 | i -= 1 |
|---|
| 323 | s = ord(line[i]) & mask |
|---|
| 324 | i -= 1 |
|---|
| 325 | if s in skip: |
|---|
| 326 | c = ' ' |
|---|
| 327 | else: |
|---|
| 328 | c = line[i] |
|---|
| 329 | out.append(c) |
|---|
| 330 | |
|---|
| 331 | # Note that we assembled the string in reverse, so flip it around |
|---|
| 332 | out = ''.join(reversed(out)) |
|---|
| 333 | return out |
|---|
| 334 | |
|---|
| 335 | def getLastNonWhitespaceChar(self, stc, pos): |
|---|
| 336 | """Working backward, find the closest non-whitespace character |
|---|
| 337 | |
|---|
| 338 | @param stc: stc of interest |
|---|
| 339 | @param pos: boundary position from which to start looking backwards |
|---|
| 340 | @return: tuple of the matching char and the position |
|---|
| 341 | """ |
|---|
| 342 | found = '' |
|---|
| 343 | skip = self.getNonCodeStyles(stc) |
|---|
| 344 | while pos > 0: |
|---|
| 345 | check = pos - 1 |
|---|
| 346 | c = unichr(stc.GetCharAt(check)) |
|---|
| 347 | s = stc.GetStyleAt(check) |
|---|
| 348 | #dprint("check=%d char='%s'" % (check, c)) |
|---|
| 349 | |
|---|
| 350 | # Comment or string terminates the search and will return with the |
|---|
| 351 | # character after the last comment/string char. |
|---|
| 352 | if s in skip: |
|---|
| 353 | break |
|---|
| 354 | |
|---|
| 355 | found = c |
|---|
| 356 | pos = check |
|---|
| 357 | if not c.isspace(): |
|---|
| 358 | break |
|---|
| 359 | return (found, pos) |
|---|
| 360 | |
|---|
| 361 | def getBraceMatch(self, text, open=u'(', close=u')'): |
|---|
| 362 | """Search the text to see if there are unmatched braces |
|---|
| 363 | |
|---|
| 364 | Search the line for unmatched braces given the open and close matching |
|---|
| 365 | pair. This does not look at styling information; it assumes that |
|---|
| 366 | L{getCodeChars} has been called before this. |
|---|
| 367 | |
|---|
| 368 | @param text: line number |
|---|
| 369 | @param open: the opening brace character, e.g. "(" |
|---|
| 370 | @param close: the complimentary closing brace character, e.g. ")" |
|---|
| 371 | |
|---|
| 372 | @return: brace mismatch count: 0 for matching braces, positive for a |
|---|
| 373 | surplus of opening braces, and negative for a surplus of closing braces |
|---|
| 374 | """ |
|---|
| 375 | r = 0 |
|---|
| 376 | for c in text: |
|---|
| 377 | if c == open: |
|---|
| 378 | r += 1 |
|---|
| 379 | elif c == close: |
|---|
| 380 | r -= 1 |
|---|
| 381 | return r |
|---|
| 382 | |
|---|
| 383 | def findIndent(self, stc, linenum=None): |
|---|
| 384 | """Reindent the specified line to the correct level. |
|---|
| 385 | |
|---|
| 386 | Given a line, use Scintilla's built-in folding to determine |
|---|
| 387 | the indention level of the current line. |
|---|
| 388 | """ |
|---|
| 389 | if linenum is None: |
|---|
| 390 | linenum = stc.GetCurrentLine() |
|---|
| 391 | linestart = stc.PositionFromLine(linenum) |
|---|
| 392 | |
|---|
| 393 | # actual indention of current line |
|---|
| 394 | ind = stc.GetLineIndentation(linenum) # columns |
|---|
| 395 | pos = stc.GetLineIndentPosition(linenum) # absolute character position |
|---|
| 396 | |
|---|
| 397 | # folding says this should be the current indention |
|---|
| 398 | fold = stc.GetFoldLevel(linenum)&wx.stc.STC_FOLDLEVELNUMBERMASK - wx.stc.STC_FOLDLEVELBASE |
|---|
| 399 | self.dprint("ind = %s (char num=%d), fold = %s" % (ind, pos, fold)) |
|---|
| 400 | return fold * stc.GetIndent() |
|---|
| 401 | |
|---|
| 402 | |
|---|
| 403 | class CStyleAutoindent(FoldingAutoindent): |
|---|
| 404 | """Use the STC Folding to reindent a line in a C-like mode. |
|---|
| 405 | |
|---|
| 406 | This autoindenter uses the built-in Scintilla folding to determine the |
|---|
| 407 | correct indent level for C-like modes (C, C++, Java, Javascript, etc.) |
|---|
| 408 | plus a bunch of heuristics to handle things that Scintilla doesn't. |
|---|
| 409 | """ |
|---|
| 410 | debuglevel = 0 |
|---|
| 411 | |
|---|
| 412 | def __init__(self, reIndentAfter=None, reIndent=None, reUnindent=None): |
|---|
| 413 | """Create a regex autoindenter. |
|---|
| 414 | |
|---|
| 415 | Creates an instance of the regex autoindenter. Since this code is |
|---|
| 416 | based on the varindent module from KDE's Kate editor, it uses the same |
|---|
| 417 | regular expressions as Kate to indent code. |
|---|
| 418 | |
|---|
| 419 | @param reIndentAfter: a regular expression used on the nearest line |
|---|
| 420 | above the current line that has content. If it matches, it will add |
|---|
| 421 | indentation to the current line |
|---|
| 422 | |
|---|
| 423 | @param reIndent: regular expression used on the current line. If it |
|---|
| 424 | matches, indentation will be added to the current line. |
|---|
| 425 | |
|---|
| 426 | @param reUnindent: regular expression used on the current line. If it |
|---|
| 427 | matches, indentation is removed from the current line. |
|---|
| 428 | |
|---|
| 429 | @param braces: a list of braces used on the nearest line with content |
|---|
| 430 | above the current. When an opening brace appears without its matching |
|---|
| 431 | closing brace, and indentation level is added to the current line. |
|---|
| 432 | """ |
|---|
| 433 | if reIndentAfter: |
|---|
| 434 | self.reIndentAfter = re.compile(reIndentAfter) |
|---|
| 435 | else: |
|---|
| 436 | self.reIndentAfter = re.compile(r'^(?!.*;\s*//).*[^\s;{}]\s*$') |
|---|
| 437 | if reIndent: |
|---|
| 438 | self.reIndent = re.compile(reIndent) |
|---|
| 439 | else: |
|---|
| 440 | self.reIndent = None |
|---|
| 441 | if reUnindent: |
|---|
| 442 | self.reUnindent = re.compile(reUnindent) |
|---|
| 443 | else: |
|---|
| 444 | self.reUnindent = None |
|---|
| 445 | self.reStatement = re.compile(r'^([^\s]+)\s*(\(.+\)|.+)?$') |
|---|
| 446 | self.reCase = re.compile(r'^\s*(case|default).*:$') |
|---|
| 447 | self.reClassAttrScope = re.compile(r'^\s*(public|private).*:$') |
|---|
| 448 | self.reBreak = re.compile(r'^\s*break\s*;\s*$') |
|---|
| 449 | self.reLabel = re.compile(r'^\s*[a-zA-Z_][a-zA-Z0-9_]*:((?!:)|$)') |
|---|
| 450 | |
|---|
| 451 | def getNonCodeStyles(self, stc): |
|---|
| 452 | """Return a list of styling integers that indicate characters that are |
|---|
| 453 | outside the normal code flow. |
|---|
| 454 | |
|---|
| 455 | Character styles that are outside code flow are strings, comments, |
|---|
| 456 | preprocessor statements, and stuff that should be ignored when |
|---|
| 457 | indenting. |
|---|
| 458 | """ |
|---|
| 459 | styles = [] |
|---|
| 460 | styles.extend(stc.getStringStyles()) |
|---|
| 461 | styles.extend(stc.getCommentStyles()) |
|---|
| 462 | # add preprocessor styles for C-like modes -- everything that uses the |
|---|
| 463 | # C syntax highlighter should also use the STC_C_PREPROCESSOR token |
|---|
| 464 | styles.append(wx.stc.STC_C_PREPROCESSOR) |
|---|
| 465 | #self.dprint(styles) |
|---|
| 466 | return styles |
|---|
| 467 | |
|---|
| 468 | def getBraceOpener(self, stc, linenum): |
|---|
| 469 | """Find the statement related to the brace's opening. |
|---|
| 470 | |
|---|
| 471 | If a block of code is related to a reserved keyword, like |
|---|
| 472 | |
|---|
| 473 | switch (blah) { |
|---|
| 474 | case 0: |
|---|
| 475 | stuff; |
|---|
| 476 | } |
|---|
| 477 | |
|---|
| 478 | return the keyword so it can be used to further indent the contents. |
|---|
| 479 | |
|---|
| 480 | @return: the keyword of interest |
|---|
| 481 | """ |
|---|
| 482 | fold = self.getFold(stc, linenum) |
|---|
| 483 | self.dprint("linenum=%d fold=%d" % (linenum, fold)) |
|---|
| 484 | ln = linenum |
|---|
| 485 | statement = '' |
|---|
| 486 | first = True |
|---|
| 487 | parens = False |
|---|
| 488 | while ln >= 0: |
|---|
| 489 | f = self.getFold(stc, ln) |
|---|
| 490 | if f != fold: |
|---|
| 491 | break |
|---|
| 492 | line = self.getCodeChars(stc, ln).strip() |
|---|
| 493 | self.dprint(line) |
|---|
| 494 | ln -= 1 |
|---|
| 495 | if not line: |
|---|
| 496 | continue |
|---|
| 497 | statement = line + statement |
|---|
| 498 | if first: |
|---|
| 499 | if statement.endswith('{'): |
|---|
| 500 | statement = statement[:-1].strip() |
|---|
| 501 | |
|---|
| 502 | if statement.endswith(';'): |
|---|
| 503 | # A complete statement before an opening brace means it's |
|---|
| 504 | # an anonymous block, so the statement before doesn't |
|---|
| 505 | # relate to the block itself. |
|---|
| 506 | break |
|---|
| 507 | if ')' in statement: |
|---|
| 508 | parens = True |
|---|
| 509 | else: |
|---|
| 510 | break |
|---|
| 511 | first = False |
|---|
| 512 | |
|---|
| 513 | # Parens must match to form a complete statement |
|---|
| 514 | if parens: |
|---|
| 515 | if self.getBraceMatch(statement) == 0: |
|---|
| 516 | break |
|---|
| 517 | if statement: |
|---|
| 518 | match = self.reStatement.match(statement) |
|---|
| 519 | if match: |
|---|
| 520 | statement = match.group(1) |
|---|
| 521 | return statement |
|---|
| 522 | |
|---|
| 523 | def isInsideStatement(self, stc, pos): |
|---|
| 524 | """Find out if the position is inside a statement |
|---|
| 525 | |
|---|
| 526 | Being inside a statement means that the position is after an opening |
|---|
| 527 | paren and the matching close paran either doesn't exist yet or is |
|---|
| 528 | after the position. |
|---|
| 529 | |
|---|
| 530 | @return: True if pos is after an unbalanced amount of opening parens |
|---|
| 531 | """ |
|---|
| 532 | linenum = stc.LineFromPosition(pos) |
|---|
| 533 | |
|---|
| 534 | start = self.getFoldSectionStart(stc, linenum) |
|---|
| 535 | self.dprint("fold start=%d, linenum=%d" % (start, linenum)) |
|---|
| 536 | text = self.getCodeChars(stc, start, pos) |
|---|
| 537 | parens = self.getBraceMatch(text) |
|---|
| 538 | self.dprint("text=%s, parens=%d" % (text, parens)) |
|---|
| 539 | return parens != 0 |
|---|
| 540 | |
|---|
| 541 | def findIndent(self, stc, linenum=None): |
|---|
| 542 | """Reindent the specified line to the correct level. |
|---|
| 543 | |
|---|
| 544 | Given a line, use Scintilla's built-in folding to determine |
|---|
| 545 | the indention level of the current line. |
|---|
| 546 | """ |
|---|
| 547 | if linenum is None: |
|---|
| 548 | linenum = stc.GetCurrentLine() |
|---|
| 549 | linestart = stc.PositionFromLine(linenum) |
|---|
| 550 | |
|---|
| 551 | # actual indention of current line |
|---|
| 552 | col = stc.GetLineIndentation(linenum) # columns |
|---|
| 553 | pos = stc.GetLineIndentPosition(linenum) # absolute character position |
|---|
| 554 | |
|---|
| 555 | # folding says this should be the current indention |
|---|
| 556 | fold = (stc.GetFoldLevel(linenum)&wx.stc.STC_FOLDLEVELNUMBERMASK) - wx.stc.STC_FOLDLEVELBASE |
|---|
| 557 | c = stc.GetCharAt(pos) |
|---|
| 558 | s = stc.GetStyleAt(pos) |
|---|
| 559 | indent = stc.GetIndent() |
|---|
| 560 | partial = 0 |
|---|
| 561 | self.dprint("col=%d (pos=%d), fold=%d char=%s" % (col, pos, fold, repr(chr(c)))) |
|---|
| 562 | if c == ord('}'): |
|---|
| 563 | # Scintilla doesn't automatically dedent the closing brace, so we |
|---|
| 564 | # force that here. |
|---|
| 565 | fold -= 1 |
|---|
| 566 | elif c == ord('{'): |
|---|
| 567 | # Opening brace on a line by itself always stays at the fold level |
|---|
| 568 | pass |
|---|
| 569 | elif c == ord('#') and s == 9: |
|---|
| 570 | # Force preprocessor directives to start at column zero |
|---|
| 571 | fold = 0 |
|---|
| 572 | else: |
|---|
| 573 | start = self.getFoldSectionStart(stc, linenum) |
|---|
| 574 | opener = self.getBraceOpener(stc, start-1) |
|---|
| 575 | self.dprint(opener) |
|---|
| 576 | |
|---|
| 577 | # First, try to match on the current line to see if we know enough |
|---|
| 578 | # about it to figure its indent level |
|---|
| 579 | matched = False |
|---|
| 580 | line = self.getCodeChars(stc, linenum) |
|---|
| 581 | if opener == "switch": |
|---|
| 582 | # case statements are partially dedented relative to the |
|---|
| 583 | # scintilla level |
|---|
| 584 | if self.reCase.match(line): |
|---|
| 585 | matched = True |
|---|
| 586 | partial = - (indent / 2) |
|---|
| 587 | elif opener == "class": |
|---|
| 588 | # public/private/protected statements are partially dedented |
|---|
| 589 | # relative to the scintilla level |
|---|
| 590 | if self.reClassAttrScope.match(line): |
|---|
| 591 | matched = True |
|---|
| 592 | partial = - (indent / 2) |
|---|
| 593 | |
|---|
| 594 | # labels are matched after case statements to prevent the |
|---|
| 595 | # 'default:' label from getting confused with a regular label |
|---|
| 596 | if not matched and self.reLabel.match(line): |
|---|
| 597 | fold = 0 |
|---|
| 598 | matched = True |
|---|
| 599 | |
|---|
| 600 | # If we can't determine the indent level when only looking at |
|---|
| 601 | # the current line, start backing up to find the first non blank |
|---|
| 602 | # statement above the line. We then look to see if we should |
|---|
| 603 | # indent relative to that statement (e.g. if the statement is a |
|---|
| 604 | # continuation) or relative to the fold level supplied by scintilla |
|---|
| 605 | if not matched: |
|---|
| 606 | for ln in xrange(linenum - 1, start - 1, -1): |
|---|
| 607 | line = self.getCodeChars(stc, ln) |
|---|
| 608 | self.dprint(line) |
|---|
| 609 | if not line.strip() or self.reLabel.match(line): |
|---|
| 610 | continue |
|---|
| 611 | if opener == "switch": |
|---|
| 612 | if self.reCase.match(line): |
|---|
| 613 | # a case statement will be interpreted as a continuation |
|---|
| 614 | break |
|---|
| 615 | if self.reIndentAfter.match(line): |
|---|
| 616 | self.dprint("continuation") |
|---|
| 617 | fold += 1 |
|---|
| 618 | else: |
|---|
| 619 | self.dprint("terminated statement") |
|---|
| 620 | break |
|---|
| 621 | |
|---|
| 622 | return (fold * indent) + partial |
|---|
| 623 | |
|---|
| 624 | def electricChar(self, stc, uchar): |
|---|
| 625 | """Reindent the line and insert a newline when special chars are typed. |
|---|
| 626 | |
|---|
| 627 | Like emacs, a semicolon or curly brace causes the line to be reindented |
|---|
| 628 | and the next line to be indented to the correct column. |
|---|
| 629 | |
|---|
| 630 | @param stc: stc instance |
|---|
| 631 | |
|---|
| 632 | @param uchar: unicode character that was just typed by the user (note |
|---|
| 633 | that it hasn't been inserted into the document yet.) |
|---|
| 634 | |
|---|
| 635 | @return: True if this method handled the character and the text |
|---|
| 636 | was modified; False if the calling event handler should handle the |
|---|
| 637 | character. |
|---|
| 638 | """ |
|---|
| 639 | implicit_return = True |
|---|
| 640 | |
|---|
| 641 | if uchar == u';' or uchar == u':' or uchar == '{' or uchar == '}': |
|---|
| 642 | pos = stc.GetCurrentPos() |
|---|
| 643 | s = stc.GetStyleAt(pos) |
|---|
| 644 | if not stc.isStyleComment(s) and not stc.isStyleString(s): |
|---|
| 645 | if uchar == u':': |
|---|
| 646 | # FIXME: currently only process the : if the current |
|---|
| 647 | # line is a case statement. Emacs also indents labels |
|---|
| 648 | # and namespace operators with a :: by checking if the |
|---|
| 649 | # last character on the previous line is a : and if so |
|---|
| 650 | # collapses the current line with the previous line and |
|---|
| 651 | # reindents the new line |
|---|
| 652 | linenum = stc.GetCurrentLine() |
|---|
| 653 | line = self.getCodeChars(stc, linenum, pos) + ":" |
|---|
| 654 | if not self.reCase.match(line) and not self.reClassAttrScope.match(line): |
|---|
| 655 | c, prev = self.getLastNonWhitespaceChar(stc, pos) |
|---|
| 656 | if c == u':': |
|---|
| 657 | # Found previous ':', so make it a double colon |
|---|
| 658 | stc.SetSelection(prev + 1, pos) |
|---|
| 659 | #dprint("selection: %d - %d" % (prev + 1, pos)) |
|---|
| 660 | implicit_return = False |
|---|
| 661 | elif uchar == u';': |
|---|
| 662 | # Don't process the semicolon if we're in the middle of an |
|---|
| 663 | # open statement |
|---|
| 664 | if self.isInsideStatement(stc, pos): |
|---|
| 665 | return False |
|---|
| 666 | stc.BeginUndoAction() |
|---|
| 667 | start, end = stc.GetSelection() |
|---|
| 668 | if start == end: |
|---|
| 669 | stc.AddText(uchar) |
|---|
| 670 | else: |
|---|
| 671 | stc.ReplaceSelection(uchar) |
|---|
| 672 | |
|---|
| 673 | # Always reindent the line, but only process a return if needed |
|---|
| 674 | self.processTab(stc) |
|---|
| 675 | if implicit_return: |
|---|
| 676 | self.processReturn(stc) |
|---|
| 677 | |
|---|
| 678 | stc.EndUndoAction() |
|---|
| 679 | return True |
|---|
| 680 | return False |
|---|
| 681 | |
|---|
| 682 | def electricDelete(self, stc): |
|---|
| 683 | """Delete all whitespace after the cursor unless in a string or comment |
|---|
| 684 | """ |
|---|
| 685 | start, end = stc.GetSelection() |
|---|
| 686 | if start != end: |
|---|
| 687 | stc.ReplaceSelection("") |
|---|
| 688 | return |
|---|
| 689 | pos = stc.GetCurrentPos() |
|---|
| 690 | s = stc.GetStyleAt(pos) |
|---|
| 691 | if stc.isStyleComment(s) or stc.isStyleString(s): |
|---|
| 692 | stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR) |
|---|
| 693 | else: |
|---|
| 694 | self.dprint("deleting from pos %d" % pos) |
|---|
| 695 | end = pos |
|---|
| 696 | while end < stc.GetLength(): |
|---|
| 697 | c = stc.GetCharAt(end) |
|---|
| 698 | if c == ord(' ') or c == ord('\t') or c == 10 or c == 13: |
|---|
| 699 | end += 1 |
|---|
| 700 | else: |
|---|
| 701 | break |
|---|
| 702 | if end > pos: |
|---|
| 703 | stc.SetTargetStart(pos) |
|---|
| 704 | stc.SetTargetEnd(end) |
|---|
| 705 | stc.ReplaceTarget('') |
|---|
| 706 | else: |
|---|
| 707 | stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR) |
|---|
| 708 | |
|---|
| 709 | def electricBackspace(self, stc): |
|---|
| 710 | """Delete all whitespace before the cursor unless in a string or comment |
|---|
| 711 | """ |
|---|
| 712 | start, end = stc.GetSelection() |
|---|
| 713 | if start != end: |
|---|
| 714 | stc.ReplaceSelection("") |
|---|
| 715 | return |
|---|
| 716 | pos = stc.GetCurrentPos() |
|---|
| 717 | if pos <= 0: |
|---|
| 718 | return |
|---|
| 719 | s = stc.GetStyleAt(pos - 1) |
|---|
| 720 | if stc.isStyleComment(s) or stc.isStyleString(s): |
|---|
| 721 | stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK) |
|---|
| 722 | else: |
|---|
| 723 | self.dprint("backspace from pos %d" % pos) |
|---|
| 724 | start = pos |
|---|
| 725 | while start > 0: |
|---|
| 726 | c = stc.GetCharAt(start - 1) |
|---|
| 727 | if c == ord(' ') or c == ord('\t') or c == 10 or c == 13: |
|---|
| 728 | start -= 1 |
|---|
| 729 | else: |
|---|
| 730 | break |
|---|
| 731 | if start < pos: |
|---|
| 732 | stc.SetTargetStart(start) |
|---|
| 733 | stc.SetTargetEnd(pos) |
|---|
| 734 | stc.ReplaceTarget('') |
|---|
| 735 | else: |
|---|
| 736 | stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK) |
|---|
| 737 | |
|---|
| 738 | |
|---|
| 739 | |
|---|
| 740 | class RegexAutoindent(BasicAutoindent): |
|---|
| 741 | """Regex based autoindenter |
|---|
| 742 | |
|---|
| 743 | This is a flexible autointent module that uses regular expressions to |
|---|
| 744 | determine the proper indentation level of a line of code based on the line |
|---|
| 745 | of code above it. |
|---|
| 746 | |
|---|
| 747 | The original module was written by Anders Lund for |
|---|
| 748 | the kate editor of the KDE project. He has a U{blog |
|---|
| 749 | entry<http://www.alweb.dk/blog/anders/autoindentation>} describing his |
|---|
| 750 | thinking that led up to the creation of the code. |
|---|
| 751 | |
|---|
| 752 | The source code for the autoindenting part of Kate is actually in the |
|---|
| 753 | L{kpart<http://websvn.kde.org/branches/KDE/3.5/kdelibs/kate/part/kateautoindent.cpp?revision=620408&view=markup>} |
|---|
| 754 | section of KDE, not with the main Kate application. |
|---|
| 755 | """ |
|---|
| 756 | |
|---|
| 757 | def __init__(self, reIndentAfter, reIndent, reUnindent, braces): |
|---|
| 758 | """Create a regex autoindenter. |
|---|
| 759 | |
|---|
| 760 | Creates an instance of the regex autoindenter. Since this code is |
|---|
| 761 | based on the varindent module from KDE's Kate editor, it uses the same |
|---|
| 762 | regular expressions as Kate to indent code. |
|---|
| 763 | |
|---|
| 764 | @param reIndentAfter: a regular expression used on the nearest line |
|---|
| 765 | above the current line that has content. If it matches, it will add |
|---|
| 766 | indentation to the current line |
|---|
| 767 | |
|---|
| 768 | @param reIndent: regular expression used on the current line. If it |
|---|
| 769 | matches, indentation will be added to the current line. |
|---|
| 770 | |
|---|
| 771 | @param reUnindent: regular expression used on the current line. If it |
|---|
| 772 | matches, indentation is removed from the current line. |
|---|
| 773 | |
|---|
| 774 | @param braces: a list of braces used on the nearest line with content |
|---|
| 775 | above the current. When an opening brace appears without its matching |
|---|
| 776 | closing brace, and indentation level is added to the current line. |
|---|
| 777 | """ |
|---|
| 778 | if reIndentAfter: |
|---|
| 779 | self.reIndentAfter = re.compile(reIndentAfter) |
|---|
| 780 | else: |
|---|
| 781 | self.reIndentAfter = None |
|---|
| 782 | if reIndent: |
|---|
| 783 | self.reIndent = re.compile(reIndent) |
|---|
| 784 | else: |
|---|
| 785 | self.reIndent = None |
|---|
| 786 | if reUnindent: |
|---|
| 787 | self.reUnindent = re.compile(reUnindent) |
|---|
| 788 | else: |
|---|
| 789 | self.reUnindent = None |
|---|
| 790 | |
|---|
| 791 | # list of brace types to handle. Should be the opening brace, e.g. |
|---|
| 792 | # '(', '[', or '{' |
|---|
| 793 | if braces: |
|---|
| 794 | self.couples = braces.replace(')', '(').replace(']', '[').replace('}', '{') |
|---|
| 795 | else: |
|---|
| 796 | self.couples = '' |
|---|
| 797 | |
|---|
| 798 | def findIndent(self, stc, linenum): |
|---|
| 799 | """Determine the correct indentation for the line. |
|---|
| 800 | |
|---|
| 801 | This routine uses regular expressions to determine the indentation |
|---|
| 802 | level of the line. |
|---|
| 803 | |
|---|
| 804 | @param linenum: current line number |
|---|
| 805 | |
|---|
| 806 | @param return: the number of columns to indent, or None to leave as-is |
|---|
| 807 | """ |
|---|
| 808 | if linenum < 1: |
|---|
| 809 | return None |
|---|
| 810 | |
|---|
| 811 | #// find the first line with content that is not starting with comment text, |
|---|
| 812 | #// and take the position from that |
|---|
| 813 | ln = linenum |
|---|
| 814 | pos = 0 |
|---|
| 815 | above = '' |
|---|
| 816 | while ln > 0: |
|---|
| 817 | ln -= 1 |
|---|
| 818 | fc = stc.GetLineIndentPosition(ln) |
|---|
| 819 | lc = stc.GetLineEndPosition(ln) |
|---|
| 820 | self.dprint("ln=%d fc=%d lc=%d line=-->%s<--" % (ln, fc, lc, stc.GetLine(ln))) |
|---|
| 821 | # skip blank lines |
|---|
| 822 | if fc < lc: |
|---|
| 823 | s = stc.GetStyleAt(fc) |
|---|
| 824 | if stc.isStyleComment(s): |
|---|
| 825 | continue |
|---|
| 826 | pos = stc.GetLineIndentation(ln) |
|---|
| 827 | above = stc.GetTextRange(fc, lc) |
|---|
| 828 | break |
|---|
| 829 | |
|---|
| 830 | # // try 'couples' for an opening on the above line first. since we only adjust by 1 unit, |
|---|
| 831 | # // we only need 1 match. |
|---|
| 832 | adjustment = 0 |
|---|
| 833 | if '(' in self.couples and self.coupleBalance(stc, ln, '(', ')') > 0: |
|---|
| 834 | adjustment += 1 |
|---|
| 835 | elif '[' in self.couples and self.coupleBalance(stc, ln, '[', ']') > 0: |
|---|
| 836 | adjustment += 1 |
|---|
| 837 | elif '{' in self.couples and self.coupleBalance(stc, ln, '{', '}') > 0: |
|---|
| 838 | adjustment += 1 |
|---|
| 839 | |
|---|
| 840 | # // Try 'couples' for a closing on this line first. since we only adjust by 1 unit, |
|---|
| 841 | # // we only need 1 match. For unindenting, we look for a closing character |
|---|
| 842 | # // *at the beginning of the line* |
|---|
| 843 | # // NOTE Assume that a closing brace with the configured attribute on the start |
|---|
| 844 | # // of the line is closing. |
|---|
| 845 | # // When acting on processChar, the character isn't highlighted. So I could |
|---|
| 846 | # // either not check, assuming that the first char *is* meant to close, or do a |
|---|
| 847 | # // match test if the attrib is 0. How ever, doing that is |
|---|
| 848 | # // a potentially huge job, if the match is several hundred lines away. |
|---|
| 849 | # // Currently, the check is done. |
|---|
| 850 | # { |
|---|
| 851 | # KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() ); |
|---|
| 852 | # int i = tl->firstChar(); |
|---|
| 853 | # if ( i > -1 ) |
|---|
| 854 | # { |
|---|
| 855 | # QChar ch = tl->getChar( i ); |
|---|
| 856 | # uchar at = tl->attribute( i ); |
|---|
| 857 | # kdDebug(13030)<<"attrib is "<<at<<endl; |
|---|
| 858 | # if ( d->couples & Parens && ch == ')' |
|---|
| 859 | # && ( at == d->coupleAttrib |
|---|
| 860 | # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) |
|---|
| 861 | # ) |
|---|
| 862 | # ) |
|---|
| 863 | # adjustment--; |
|---|
| 864 | # else if ( d->couples & Braces && ch == '}' |
|---|
| 865 | # && ( at == d->coupleAttrib |
|---|
| 866 | # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) |
|---|
| 867 | # ) |
|---|
| 868 | # ) |
|---|
| 869 | # adjustment--; |
|---|
| 870 | # else if ( d->couples & Brackets && ch == ']' |
|---|
| 871 | # && ( at == d->coupleAttrib |
|---|
| 872 | # || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) |
|---|
| 873 | # ) |
|---|
| 874 | # ) |
|---|
| 875 | # adjustment--; |
|---|
| 876 | # } |
|---|
| 877 | # } |
|---|
| 878 | |
|---|
| 879 | # Haven't figured out what that was for, so ignoring for now. |
|---|
| 880 | |
|---|
| 881 | |
|---|
| 882 | # // check if we should indent, unless the line starts with comment text, |
|---|
| 883 | # // or the match is in comment text |
|---|
| 884 | # Here we look at the line above the current line |
|---|
| 885 | if self.reIndentAfter: |
|---|
| 886 | match = self.reIndentAfter.search(above) |
|---|
| 887 | self.dprint(above) |
|---|
| 888 | if match: |
|---|
| 889 | self.dprint("reIndentAfter: found %s at %d" % (match.group(0), match.start(0))) |
|---|
| 890 | adjustment += 1 |
|---|
| 891 | |
|---|
| 892 | # // else, check if this line should indent unless ... |
|---|
| 893 | # ktl = doc->plainKateTextLine( line.line() ); |
|---|
| 894 | # if ( ! d->reIndent.isEmpty() |
|---|
| 895 | # && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1 |
|---|
| 896 | # && ! ISCOMMENT ) |
|---|
| 897 | # adjustment++; |
|---|
| 898 | fc = stc.GetLineIndentPosition(linenum) |
|---|
| 899 | lc = stc.GetLineEndPosition(linenum) |
|---|
| 900 | s = stc.GetStyleAt(fc) |
|---|
| 901 | if self.reIndent and lc > fc and not stc.isStyleComment(s): |
|---|
| 902 | text = stc.GetTextRange(fc, lc) |
|---|
| 903 | match = self.reIndent.search(text) |
|---|
| 904 | self.dprint(text) |
|---|
| 905 | if match: |
|---|
| 906 | self.dprint("reIndent: found %s at %d" % (match.group(0), match.start(0))) |
|---|
| 907 | adjustment += 1 |
|---|
| 908 | |
|---|
| 909 | # // else, check if the current line indicates if we should remove indentation unless ... |
|---|
| 910 | if self.reUnindent and lc > fc and not stc.isStyleComment(s): |
|---|
| 911 | text = stc.GetTextRange(fc, lc) |
|---|
| 912 | match = self.reUnindent.search(text) |
|---|
| 913 | if match: |
|---|
| 914 | self.dprint("reUnndent: found %s at %d" % (match.group(0), match.start(0))) |
|---|
| 915 | adjustment -= 1 |
|---|
| 916 | |
|---|
| 917 | # Find the actual number of spaces to indent |
|---|
| 918 | if adjustment > 0: |
|---|
| 919 | pos += stc.GetIndent() |
|---|
| 920 | elif adjustment < 0: |
|---|
| 921 | pos -= stc.GetIndent() |
|---|
| 922 | if pos < 0: |
|---|
| 923 | pos = 0 |
|---|
| 924 | |
|---|
| 925 | return pos |
|---|
| 926 | |
|---|
| 927 | def coupleBalance(self, stc, ln, open, close): |
|---|
| 928 | """Search the line to see if there are unmatched braces |
|---|
| 929 | |
|---|
| 930 | Search the line for unmatched braces given the open and close matching |
|---|
| 931 | pair. This takes into account the style of the document to make sure |
|---|
| 932 | that the brace isn't in a comment or string. |
|---|
| 933 | |
|---|
| 934 | @param stc: the StyledTextCtrl instance |
|---|
| 935 | @param ln: line number |
|---|
| 936 | @param open: the opening brace character, e.g. "(" |
|---|
| 937 | @param close: the complimentary closing brace character, e.g. ")" |
|---|
| 938 | |
|---|
| 939 | @return: brace mismatch count: 0 for matching braces, positive for a |
|---|
| 940 | surplus of opening braces, and negative for a surplus of closing braces |
|---|
| 941 | """ |
|---|
| 942 | if ln < 0: |
|---|
| 943 | return 0 |
|---|
| 944 | r = 0 |
|---|
| 945 | fc = stc.GetLineIndentPosition(ln) |
|---|
| 946 | lc = stc.GetLineEndPosition(ln) |
|---|
| 947 | line = stc.GetStyledText(fc, lc) |
|---|
| 948 | self.dprint(repr(line)) |
|---|
| 949 | i = len(line) |
|---|
| 950 | while i > 0: |
|---|
| 951 | i -= 1 |
|---|
| 952 | s = line[i] |
|---|
| 953 | i -= 1 |
|---|
| 954 | c = line[i] |
|---|
| 955 | if c == open and not (stc.isStyleComment(s) and stc.isStyleString(s)): |
|---|
| 956 | self.dprint("found %s at column %d" % (open, i/2)) |
|---|
| 957 | r += 1 |
|---|
| 958 | elif c == close and not (stc.isStyleComment(s) and stc.isStyleString(s)): |
|---|
| 959 | self.dprint("found %s at column %d" % (close, i/2)) |
|---|
| 960 | r -= 1 |
|---|
| 961 | return r |
|---|
| 962 | |
|---|
| 963 | # |
|---|
| 964 | #bool KateVarIndent::hasRelevantOpening( const KateDocCursor &end ) const |
|---|
| 965 | #{ |
|---|
| 966 | # KateDocCursor cur = end; |
|---|
| 967 | # int count = 1; |
|---|
| 968 | # |
|---|
| 969 | # QChar close = cur.currentChar(); |
|---|
| 970 | # QChar opener; |
|---|
| 971 | # if ( close == '}' ) opener = '{'; |
|---|
| 972 | # else if ( close = ')' ) opener = '('; |
|---|
| 973 | # else if (close = ']' ) opener = '['; |
|---|
| 974 | # else return false; |
|---|
| 975 | # |
|---|
| 976 | # //Move backwards 1 by 1 and find the opening partner |
|---|
| 977 | # while (cur.moveBackward(1)) |
|---|
| 978 | # { |
|---|
| 979 | # if (cur.currentAttrib() == d->coupleAttrib) |
|---|
| 980 | # { |
|---|
| 981 | # QChar ch = cur.currentChar(); |
|---|
| 982 | # if (ch == opener) |
|---|
| 983 | # count--; |
|---|
| 984 | # else if (ch == close) |
|---|
| 985 | # count++; |
|---|
| 986 | # |
|---|
| 987 | # if (count == 0) |
|---|
| 988 | # return true; |
|---|
| 989 | # } |
|---|
| 990 | # } |
|---|
| 991 | # |
|---|
| 992 | # return false; |
|---|
| 993 | #} |
|---|
| 994 | # |
|---|
| 995 | # |
|---|
| 996 | #//END KateVarIndent |
|---|
| 997 | |
|---|
| 998 | |
|---|
| 999 | class NullAutoindent(debugmixin): |
|---|
| 1000 | """No-op unindenter that doesn't change the indent level at all. |
|---|
| 1001 | """ |
|---|
| 1002 | def findIndent(self, stc, linenum): |
|---|
| 1003 | """No-op that returns the current indent level.""" |
|---|
| 1004 | return stc.GetLineIndentation(linenum) |
|---|
| 1005 | |
|---|
| 1006 | def reindentLine(self, stc, linenum=None, dedent_only=False): |
|---|
| 1007 | """No-op that doesn't change the current indent level.""" |
|---|
| 1008 | return stc.GetCurrentPos() |
|---|
| 1009 | |
|---|
| 1010 | def processReturn(self, stc): |
|---|
| 1011 | """Add a newline only.""" |
|---|
| 1012 | linesep = stc.getLinesep() |
|---|
| 1013 | stc.AddText(linesep) |
|---|
| 1014 | |
|---|
| 1015 | def processTab(self, stc): |
|---|
| 1016 | """Don't reindent but insert the equivalent of a tab character""" |
|---|
| 1017 | stc.AddText(stc.GetIndentString(stc.GetIndent())) |
|---|
| 1018 | |
|---|
| 1019 | def electricChar(self, stc, uchar): |
|---|
| 1020 | """No electric chars in Null autoindenter.""" |
|---|
| 1021 | return False |
|---|
| 1022 | |
|---|
| 1023 | def electricDelete(self, stc): |
|---|
| 1024 | """Defaults to standard delete processing in Null autoindenter.""" |
|---|
| 1025 | stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR) |
|---|
| 1026 | |
|---|
| 1027 | def electricBackspace(self, stc): |
|---|
| 1028 | """Defaults to standard backspace processing in Null autoindenter.""" |
|---|
| 1029 | stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK) |
|---|