Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 2883
3c99a1db1506
parent 2882
9b97bc92fdda
child 2890
86b03a0c94bc
child 2911
ce77f0b1ee67
equal deleted inserted replaced
2882:9b97bc92fdda 2883:3c99a1db1506
57 self.__project = project 57 self.__project = project
58 self.__filename = filename 58 self.__filename = filename
59 self.__origName = "" 59 self.__origName = ""
60 self.__source = sourceLines[:] # save a copy 60 self.__source = sourceLines[:] # save a copy
61 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()] 61 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
62 self.__noFixCodes = [c.strip() for c in noFixCodes.split(",") if c.strip()] 62 self.__noFixCodes = [
63 c.strip() for c in noFixCodes.split(",") if c.strip()]
63 self.__maxLineLength = maxLineLength 64 self.__maxLineLength = maxLineLength
64 self.fixed = 0 65 self.fixed = 0
65 66
66 self.__reindenter = None 67 self.__reindenter = None
67 self.__eol = "" 68 self.__eol = ""
248 self.__eol = Utilities.linesep() 249 self.__eol = Utilities.linesep()
249 return self.__eol 250 return self.__eol
250 251
251 def __findLogical(self): 252 def __findLogical(self):
252 """ 253 """
253 Private method to extract the index of all the starts and ends of lines. 254 Private method to extract the index of all the starts and ends of
255 lines.
254 256
255 @return tuple containing two lists of integer with start and end tuples 257 @return tuple containing two lists of integer with start and end tuples
256 of lines 258 of lines
257 """ 259 """
258 logical_start = [] 260 logical_start = []
340 def __multilineStringLines(self): 342 def __multilineStringLines(self):
341 """ 343 """
342 Private method to determine the line numbers that are within multi line 344 Private method to determine the line numbers that are within multi line
343 strings and these which are part of a documentation string. 345 strings and these which are part of a documentation string.
344 346
345 @return tuple of a set of line numbers belonging to a multi line string 347 @return tuple of a set of line numbers belonging to a multi line
346 and a set of line numbers belonging to a multi line documentation 348 string and a set of line numbers belonging to a multi line
347 string (tuple of two set of integer) 349 documentation string (tuple of two set of integer)
348 """ 350 """
349 if self.__multiLineNumbers is None: 351 if self.__multiLineNumbers is None:
350 source = "".join(self.__source) 352 source = "".join(self.__source)
351 sio = io.StringIO(source) 353 sio = io.StringIO(source)
352 self.__multiLineNumbers = set() 354 self.__multiLineNumbers = set()
358 startRow = t[2][0] 360 startRow = t[2][0]
359 endRow = t[3][0] 361 endRow = t[3][0]
360 362
361 if (tokenType == tokenize.STRING and startRow != endRow): 363 if (tokenType == tokenize.STRING and startRow != endRow):
362 if previousTokenType != tokenize.INDENT: 364 if previousTokenType != tokenize.INDENT:
363 self.__multiLineNumbers |= set(range(startRow, 1 + endRow)) 365 self.__multiLineNumbers |= set(
366 range(startRow, 1 + endRow))
364 else: 367 else:
365 self.__docLineNumbers |= set(range(startRow, 1 + endRow)) 368 self.__docLineNumbers |= set(
369 range(startRow, 1 + endRow))
366 370
367 previousTokenType = tokenType 371 previousTokenType = tokenType
368 except (SyntaxError, tokenize.TokenError): 372 except (SyntaxError, tokenize.TokenError):
369 pass 373 pass
370 374
446 if fixedLine is not None: 450 if fixedLine is not None:
447 self.__source[line - 1] = fixedLine 451 self.__source[line - 1] = fixedLine
448 if code in ["E101", "W191"]: 452 if code in ["E101", "W191"]:
449 msg = self.trUtf8("Tab converted to 4 spaces.") 453 msg = self.trUtf8("Tab converted to 4 spaces.")
450 else: 454 else:
451 msg = self.trUtf8("Indentation adjusted to be a multiple of four.") 455 msg = self.trUtf8(
456 "Indentation adjusted to be a multiple of four.")
452 return (True, msg) 457 return (True, msg)
453 else: 458 else:
454 return (False, self.trUtf8("Fix for {0} failed.").format(code)) 459 return (False, self.trUtf8("Fix for {0} failed.").format(code))
455 460
456 def __fixE121(self, code, line, pos, apply=False): 461 def __fixE121(self, code, line, pos, apply=False):
479 msg = self.trUtf8("Indentation of closing bracket corrected.") 484 msg = self.trUtf8("Indentation of closing bracket corrected.")
480 return (True, msg) 485 return (True, msg)
481 486
482 def __fixE122(self, code, line, pos, apply=False): 487 def __fixE122(self, code, line, pos, apply=False):
483 """ 488 """
484 Private method to fix a missing indentation of continuation lines (E122). 489 Private method to fix a missing indentation of continuation lines
490 (E122).
485 491
486 @param code code of the issue (string) 492 @param code code of the issue (string)
487 @param line line number of the issue (integer) 493 @param line line number of the issue (integer)
488 @param pos position inside line (integer) 494 @param pos position inside line (integer)
489 @keyparam apply flag indicating, that the fix should be applied 495 @keyparam apply flag indicating, that the fix should be applied
503 indentation = self.__getIndent(text) 509 indentation = self.__getIndent(text)
504 self.__source[line] = indentation + \ 510 self.__source[line] = indentation + \
505 self.__indentWord + text.lstrip() 511 self.__indentWord + text.lstrip()
506 else: 512 else:
507 self.__stackLogical.append((code, line, pos)) 513 self.__stackLogical.append((code, line, pos))
508 return (True, self.trUtf8("Missing indentation of continuation line corrected.")) 514 return (
515 True,
516 self.trUtf8("Missing indentation of continuation line corrected."))
509 517
510 def __fixE123(self, code, line, pos, apply=False): 518 def __fixE123(self, code, line, pos, apply=False):
511 """ 519 """
512 Private method to fix the indentation of a closing bracket lines (E123). 520 Private method to fix the indentation of a closing bracket lines
521 (E123).
513 522
514 @param code code of the issue (string) 523 @param code code of the issue (string)
515 @param line line number of the issue (integer) 524 @param line line number of the issue (integer)
516 @param pos position inside line (integer) 525 @param pos position inside line (integer)
517 @keyparam apply flag indicating, that the fix should be applied 526 @keyparam apply flag indicating, that the fix should be applied
532 self.__fixReindent(line, pos, logical) 541 self.__fixReindent(line, pos, logical)
533 else: 542 else:
534 self.__source[row] = newText 543 self.__source[row] = newText
535 else: 544 else:
536 self.__stackLogical.append((code, line, pos)) 545 self.__stackLogical.append((code, line, pos))
537 return (True, self.trUtf8("Closing bracket aligned to opening bracket.")) 546 return (
547 True, self.trUtf8("Closing bracket aligned to opening bracket."))
538 548
539 def __fixE125(self, code, line, pos, apply=False): 549 def __fixE125(self, code, line, pos, apply=False):
540 """ 550 """
541 Private method to fix the indentation of continuation lines not 551 Private method to fix the indentation of continuation lines not
542 distinguishable from next logical line (E125). 552 distinguishable from next logical line (E125).
590 self.__fixReindent(line, pos, logical) 600 self.__fixReindent(line, pos, logical)
591 else: 601 else:
592 self.__source[row] = newText 602 self.__source[row] = newText
593 else: 603 else:
594 self.__stackLogical.append((code, line, pos)) 604 self.__stackLogical.append((code, line, pos))
595 return (True, self.trUtf8("Indentation level of hanging indentation changed.")) 605 return (
606 True,
607 self.trUtf8("Indentation level of hanging indentation changed."))
596 608
597 def __fixE127(self, code, line, pos, apply=False): 609 def __fixE127(self, code, line, pos, apply=False):
598 """ 610 """
599 Private method to fix over/under indented lines (E127, E128). 611 Private method to fix over/under indented lines (E127, E128).
600 612
653 """ 665 """
654 line = line - 1 666 line = line - 1
655 text = self.__source[line] 667 text = self.__source[line]
656 668
657 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): 669 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
658 return (False, self.trUtf8("Extraneous whitespace cannot be removed.")) 670 return (
671 False, self.trUtf8("Extraneous whitespace cannot be removed."))
659 672
660 newText = self.__fixWhitespace(text, pos, '') 673 newText = self.__fixWhitespace(text, pos, '')
661 if newText == text: 674 if newText == text:
662 return (False, "") 675 return (False, "")
663 676
678 """ 691 """
679 line = line - 1 692 line = line - 1
680 text = self.__source[line] 693 text = self.__source[line]
681 694
682 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'): 695 if '"""' in text or "'''" in text or text.rstrip().endswith('\\'):
683 return (False, self.trUtf8("Extraneous whitespace cannot be removed.")) 696 return (
697 False, self.trUtf8("Extraneous whitespace cannot be removed."))
684 698
685 newText = self.__fixWhitespace(text, pos, ' ') 699 newText = self.__fixWhitespace(text, pos, ' ')
686 if newText == text: 700 if newText == text:
687 return (False, "") 701 return (False, "")
688 702
954 @param line line number of the issue (integer) 968 @param line line number of the issue (integer)
955 @param pos position inside line (integer) 969 @param pos position inside line (integer)
956 @return flag indicating an applied fix (boolean) and a message for 970 @return flag indicating an applied fix (boolean) and a message for
957 the fix (string) 971 the fix (string)
958 """ 972 """
959 self.__source[line - 1] = self.__source[line - 1].rstrip("\n\r \t\\") + \ 973 self.__source[line - 1] = \
960 self.__getEol() 974 self.__source[line - 1].rstrip("\n\r \t\\") + self.__getEol()
961 return (True, self.trUtf8("Redundant backslash in brackets removed.")) 975 return (True, self.trUtf8("Redundant backslash in brackets removed."))
962 976
963 def __fixE701(self, code, line, pos, apply=False): 977 def __fixE701(self, code, line, pos, apply=False):
964 """ 978 """
965 Private method to fix colon-separated compund statements (E701). 979 Private method to fix colon-separated compund statements (E701).
1111 return (True, self.trUtf8("'<>' replaced by '!='.")) 1125 return (True, self.trUtf8("'<>' replaced by '!='."))
1112 1126
1113 1127
1114 class Pep8Reindenter(object): 1128 class Pep8Reindenter(object):
1115 """ 1129 """
1116 Class to reindent badly-indented code to uniformly use four-space indentation. 1130 Class to reindent badly-indented code to uniformly use four-space
1131 indentation.
1117 1132
1118 Released to the public domain, by Tim Peters, 03 October 2000. 1133 Released to the public domain, by Tim Peters, 03 October 2000.
1119 """ 1134 """
1120 def __init__(self, sourceLines): 1135 def __init__(self, sourceLines):
1121 """ 1136 """
1190 # in which case we should shift it like its base 1205 # in which case we should shift it like its base
1191 # line got shifted. 1206 # line got shifted.
1192 for j in range(i - 1, -1, -1): 1207 for j in range(i - 1, -1, -1):
1193 jline, jlevel = stats[j] 1208 jline, jlevel = stats[j]
1194 if jlevel >= 0: 1209 if jlevel >= 0:
1195 want = have + self.__getlspace(after[jline - 1]) - \ 1210 want = \
1196 self.__getlspace(lines[jline]) 1211 have + \
1212 self.__getlspace(after[jline - 1]) - \
1213 self.__getlspace(lines[jline])
1197 break 1214 break
1198 if want < 0: 1215 if want < 0:
1199 # Still no luck -- leave it alone. 1216 # Still no luck -- leave it alone.
1200 want = have 1217 want = have
1201 else: 1218 else:
1368 def pep8Expected(self): 1385 def pep8Expected(self):
1369 """ 1386 """
1370 Public method to replicate logic in pep8.py, to know what level to 1387 Public method to replicate logic in pep8.py, to know what level to
1371 indent things to. 1388 indent things to.
1372 1389
1373 @return list of lists, where each list represents valid indent levels for 1390 @return list of lists, where each list represents valid indent levels
1374 the line in question, relative from the initial indent. However, the 1391 for the line in question, relative from the initial indent. However,
1375 first entry is the indent level which was expected. 1392 the first entry is the indent level which was expected.
1376 """ 1393 """
1377 # What follows is an adjusted version of 1394 # What follows is an adjusted version of
1378 # pep8.py:continuation_line_indentation. All of the comments have been 1395 # pep8.py:continuation_line_indentation. All of the comments have been
1379 # stripped and the 'yield' statements replaced with 'pass'. 1396 # stripped and the 'yield' statements replaced with 'pass'.
1380 if not self.tokens: 1397 if not self.tokens:
1533 1550
1534 class Pep8LineShortener(object): 1551 class Pep8LineShortener(object):
1535 """ 1552 """
1536 Class used to shorten lines to a given maximum of characters. 1553 Class used to shorten lines to a given maximum of characters.
1537 """ 1554 """
1538 ShortenOperatorGroups = frozenset([
1539 frozenset([',']),
1540 frozenset(['%']),
1541 frozenset([',', '(', '[', '{']),
1542 frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']),
1543 ])
1544
1545 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n", 1555 def __init__(self, curLine, prevLine, nextLine, maxLength=79, eol="\n",
1546 indentWord=" ", isDocString=False): 1556 indentWord=" ", isDocString=False):
1547 """ 1557 """
1548 Constructor 1558 Constructor
1549 1559
1566 1576
1567 def shorten(self): 1577 def shorten(self):
1568 """ 1578 """
1569 Public method to shorten the line wrapped by the class instance. 1579 Public method to shorten the line wrapped by the class instance.
1570 1580
1571 @return tuple of a flag indicating successful shortening, the shortened line 1581 @return tuple of a flag indicating successful shortening, the
1572 and the changed next line (boolean, string, string) 1582 shortened line and the changed next line (boolean, string, string)
1573 """ 1583 """
1574 # 1. check for comment 1584 # 1. check for comment
1575 if self.__text.lstrip().startswith('#'): 1585 if self.__text.lstrip().startswith('#'):
1576 lastComment = True 1586 lastComment = True
1577 if self.__nextText.lstrip().startswith('#'): 1587 if self.__nextText.lstrip().startswith('#'):
1581 newText = self.__shortenComment(lastComment) 1591 newText = self.__shortenComment(lastComment)
1582 if newText == self.__text: 1592 if newText == self.__text:
1583 return False, "", "" 1593 return False, "", ""
1584 else: 1594 else:
1585 return True, newText, "" 1595 return True, newText, ""
1596 elif '#' in self.__text:
1597 pos = self.__text.rfind("#")
1598 newText = self.__text[:pos].rstrip() + self.__eol + \
1599 self.__getIndent(self.__text) + self.__text[pos:]
1600 if newText == self.__text:
1601 return False, "", ""
1602 else:
1603 return True, newText, ""
1586 1604
1587 # Do multi line doc strings 1605 # Do multi line doc strings
1588 if self.__isDocString: 1606 if self.__isDocString:
1589 source = self.__text.rstrip() 1607 source = self.__text.rstrip()
1590 while len(source) > self.__maxLength: 1608 blank = source.rfind(" ")
1591 source, right = source.rsplit(None, 1) 1609 while blank > self.__maxLength and blank != -1:
1592 self.__nextText = self.__getIndent(self.__nextText) + \ 1610 blank = source.rfind(" ", 0, blank)
1593 right + " " + self.__nextText.lstrip() 1611 if blank == -1:
1594 return True, source + self.__eol, self.__nextText 1612 # Cannot break
1613 return False, "", ""
1614 else:
1615 first = self.__text[:blank]
1616 second = self.__text[blank:].lstrip()
1617 if self.__nextText.strip():
1618 if self.__nextText.lstrip().startswith("@"):
1619 # eric doc comment
1620 # create a new line and indent it
1621 newText = first + self.__eol + \
1622 self.__getIndent(first) + self.__indentWord + \
1623 second
1624 newNext = ""
1625 else:
1626 newText = first + self.__eol
1627 newNext = self.__getIndent(self.__nextText) + \
1628 second.rstrip() + " " + self.__nextText.lstrip()
1629 else:
1630 # empty line, add a new line
1631 newText = first + self.__eol + self.__getIndent(first) + \
1632 second
1633 newNext = ""
1634 return True, newText, newNext
1595 1635
1596 indent = self.__getIndent(self.__text) 1636 indent = self.__getIndent(self.__text)
1597 source = self.__text[len(indent):] 1637 source = self.__text[len(indent):]
1598 assert source.lstrip() == source 1638 assert source.lstrip() == source
1599 sio = io.StringIO(source) 1639 sio = io.StringIO(source)
1600 1640
1601 # Check for multi line string. 1641 # Check for multi line string.
1602 try: 1642 try:
1603 tokens = list(tokenize.generate_tokens(sio.readline)) 1643 tokens = list(tokenize.generate_tokens(sio.readline))
1604 except (SyntaxError, tokenize.TokenError): 1644 except (SyntaxError, tokenize.TokenError):
1605 multilineCandidate = self.__breakMultiline() 1645 if source.rstrip().endswith("\\"):
1606 if multilineCandidate: 1646 # just join the continuation line and let the next run
1607 return True, multilineCandidate, "" 1647 # handle it once it tokenizes ok
1648 newText = indent + source.rstrip()[:-1].rstrip() + " " + \
1649 self.__nextText.lstrip()
1650 if indent:
1651 newNext = indent
1652 else:
1653 newNext = " "
1654 return True, newText, newNext
1608 else: 1655 else:
1609 return False, "", "" 1656 multilineCandidate = self.__breakMultiline()
1657 if multilineCandidate:
1658 return True, multilineCandidate[0], multilineCandidate[1]
1659 else:
1660 return False, "", ""
1610 1661
1611 # Handle statements by putting the right hand side on a line by itself. 1662 # Handle statements by putting the right hand side on a line by itself.
1612 # This should let the next pass shorten it. 1663 # This should let the next pass shorten it.
1613 if source.startswith('return '): 1664 if source.startswith('return '):
1614 newText = ( 1665 newText = (
1617 self.__eol + 1668 self.__eol +
1618 indent + self.__indentWord + re.sub('^return ', '', source) + 1669 indent + self.__indentWord + re.sub('^return ', '', source) +
1619 indent + ')' + self.__eol 1670 indent + ')' + self.__eol
1620 ) 1671 )
1621 return True, newText, "" 1672 return True, newText, ""
1622 1673
1623 candidates = self.__shortenLine(tokens, source, indent) 1674 candidates = self.__shortenLine(tokens, source, indent)
1624 candidates = list(sorted(
1625 set(candidates).union([self.__text]),
1626 key=lambda x: self.__lineShorteningRank(x)))
1627 if candidates: 1675 if candidates:
1676 candidates = list(sorted(
1677 set(candidates).union([self.__text]),
1678 key=lambda x: self.__lineShorteningRank(x)))
1628 return True, candidates[0], "" 1679 return True, candidates[0], ""
1680
1681 source = self.__text
1682 rs = source.rstrip()
1683 if rs.endswith(("'", '"')) and " " in source:
1684 if rs.endswith(('"""', "'''")):
1685 quote = rs[-3:]
1686 else:
1687 quote = rs[-1]
1688 blank = source.rfind(" ")
1689 maxLen = self.__maxLength - 2 - len(quote)
1690 while blank > maxLen and blank != -1:
1691 blank = source.rfind(" ", 0, blank)
1692 if blank != -1:
1693 if source[blank + 1:].startswith(quote):
1694 first = source[:maxLen]
1695 second = source[maxLen:]
1696 else:
1697 first = source[:blank]
1698 second = source[blank + 1:]
1699 return (True,
1700 first + quote + " \\" + self.__eol +
1701 indent + self.__indentWord + quote + second,
1702 "")
1703 else:
1704 # Cannot break
1705 return False, "", ""
1629 1706
1630 return False, "", "" 1707 return False, "", ""
1631 1708
1632 def __shortenComment(self, isLast): 1709 def __shortenComment(self, isLast):
1633 """ 1710 """
1646 indentation = self.__getIndent(newText) + '# ' 1723 indentation = self.__getIndent(newText) + '# '
1647 maxLength = min(self.__maxLength, 1724 maxLength = min(self.__maxLength,
1648 len(indentation) + 72) 1725 len(indentation) + 72)
1649 1726
1650 MIN_CHARACTER_REPEAT = 5 1727 MIN_CHARACTER_REPEAT = 5
1651 if (len(newText) - len(newText.rstrip(newText[-1])) >= MIN_CHARACTER_REPEAT and 1728 if (len(newText) - len(newText.rstrip(newText[-1])) >= \
1729 MIN_CHARACTER_REPEAT and
1652 not newText[-1].isalnum()): 1730 not newText[-1].isalnum()):
1653 # Trim comments that end with things like --------- 1731 # Trim comments that end with things like ---------
1654 return newText[:maxLength] + self.__eol 1732 return newText[:maxLength] + self.__eol
1655 elif isLast and re.match(r"\s*#+\s*\w+", newText): 1733 elif isLast and re.match(r"\s*#+\s*\w+", newText):
1656 import textwrap 1734 import textwrap
1666 1744
1667 def __breakMultiline(self): 1745 def __breakMultiline(self):
1668 """ 1746 """
1669 Private method to break multi line strings. 1747 Private method to break multi line strings.
1670 1748
1671 @return broken multi line string or None, if a break is not possible 1749 @return tuple of the shortened line and the changed next line
1672 (string or None) 1750 (string, string)
1673 """ 1751 """
1674 indentation = self.__getIndent(self.__text) 1752 indentation = self.__getIndent(self.__text)
1675 1753
1676 # Handle special case only. 1754 # Handle special case.
1677 for symbol in '([{': 1755 for symbol in '([{':
1678 # Only valid if symbol is not on a line by itself. 1756 # Only valid if symbol is not on a line by itself.
1679 if ( 1757 if (
1680 symbol in self.__text and 1758 symbol in self.__text and
1681 self.__text.strip() != symbol and 1759 self.__text.strip() != symbol and
1684 index = 1 + self.__text.find(symbol) 1762 index = 1 + self.__text.find(symbol)
1685 1763
1686 if index <= len(self.__indentWord) + len(indentation): 1764 if index <= len(self.__indentWord) + len(indentation):
1687 continue 1765 continue
1688 1766
1689 if self.__isProbablyInsideStringOrComment(self.__text, index - 1): 1767 if self.__isProbablyInsideStringOrComment(
1768 self.__text, index - 1):
1690 continue 1769 continue
1691 1770
1692 return (self.__text[:index].rstrip() + self.__eol + 1771 return (self.__text[:index].rstrip() + self.__eol +
1693 indentation + self.__indentWord + 1772 indentation + self.__indentWord +
1694 self.__text[index:].lstrip()) 1773 self.__text[index:].lstrip(), "")
1695 1774
1696 # TODO: implement the method wrapping the line (see doc strings) 1775 newText = self.__text
1697 return None 1776 newNext = self.__nextText
1777 blank = newText.rfind(" ")
1778 while blank > self.__maxLength and blank != -1:
1779 blank = newText.rfind(" ", 0, blank)
1780 if blank != -1:
1781 first = self.__text[:blank]
1782 second = self.__text[blank:].strip()
1783 if newNext.strip():
1784 newText = first + self.__eol
1785 newNext = self.__getIndent(newNext) + \
1786 second + " " + newNext.lstrip()
1787 else:
1788 # empty line, add a new line
1789 newText = first + self.__eol
1790 newNext = self.__getIndent(newNext) + \
1791 second + self.__eol + newNext.lstrip()
1792 return newText, newNext
1698 1793
1699 def __isProbablyInsideStringOrComment(self, line, index): 1794 def __isProbablyInsideStringOrComment(self, line, index):
1700 """ 1795 """
1701 Private method to check, if the given string might be inside a string 1796 Private method to check, if the given string might be inside a string
1702 or comment. 1797 or comment.
1721 1816
1722 def __shortenLine(self, tokens, source, indent): 1817 def __shortenLine(self, tokens, source, indent):
1723 """ 1818 """
1724 Private method to shorten a line of code at an operator. 1819 Private method to shorten a line of code at an operator.
1725 1820
1726 @param tokens tokens of the line as generated by tokenize (list of token) 1821 @param tokens tokens of the line as generated by tokenize
1822 (list of token)
1727 @param source code string to work at (string) 1823 @param source code string to work at (string)
1728 @param indent indentation string of the code line (string) 1824 @param indent indentation string of the code line (string)
1729 @return list of candidates (list of string) 1825 @return list of candidates (list of string)
1730 """ 1826 """
1731 candidates = [] 1827 candidates = []
1779 1875
1780 # Only fix if syntax is okay. 1876 # Only fix if syntax is okay.
1781 if self.__checkSyntax(self.__normalizeMultiline(newText)): 1877 if self.__checkSyntax(self.__normalizeMultiline(newText)):
1782 candidates.append(indent + newText) 1878 candidates.append(indent + newText)
1783 1879
1784 for keyTokenStrings in self.ShortenOperatorGroups:
1785 shortened = self.__shortenLineAtTokens(
1786 tokens, source, indent, keyTokenStrings)
1787
1788 if shortened is not None and shortened != source:
1789 candidates.append(shortened)
1790
1791 return candidates 1880 return candidates
1792 1881
1793 def __normalizeMultiline(self, text): 1882 def __normalizeMultiline(self, text):
1794 """ 1883 """
1795 Private method to remove multiline-related code that will cause syntax error. 1884 Private method to remove multiline-related code that will cause syntax
1885 error.
1796 1886
1797 @param line code line to work on (string) 1887 @param line code line to work on (string)
1798 @return normalized code line (string) 1888 @return normalized code line (string)
1799 """ 1889 """
1800 for quote in '\'"': 1890 for quote in '\'"':
1810 if ':' not in splitText and 'def' not in splitText: 1900 if ':' not in splitText and 'def' not in splitText:
1811 return text[len('def'):].strip().rstrip(':') 1901 return text[len('def'):].strip().rstrip(':')
1812 1902
1813 return text 1903 return text
1814 1904
1815 def __shortenLineAtTokens(self, tokens, source, indent, keyTokenStrings):
1816 """
1817 Private method to break lines at key tokens.
1818
1819 @param tokens tokens of the line as generated by tokenize (list of token)
1820 @param source code string to work at (string)
1821 @param indent indentation string of the code line (string)
1822 @param keyTokenStrings key tokens to break at
1823 @return broken code line (string)
1824 """
1825 offsets = []
1826 firstParen = True
1827 for tkn in tokens:
1828 tokenType = tkn[0]
1829 tokenString = tkn[1]
1830 nextOffset = tkn[2][1] + 1
1831
1832 assert tokenType != tokenize.INDENT
1833
1834 if tokenString in keyTokenStrings or (firstParen and
1835 tokenString == '('):
1836 # Don't split right before newline.
1837 if nextOffset < len(source) - 1:
1838 offsets.append(nextOffset)
1839
1840 if tokenString == '(':
1841 firstParen = False
1842
1843 currentIndent = None
1844 newText = None
1845 for text in self.__splitAtOffsets(source, offsets):
1846 if newText:
1847 newText += self.__eol + currentIndent + text
1848
1849 for symbol in '([{':
1850 if text.endswith(symbol):
1851 currentIndent += self.__indentWord
1852 else:
1853 # First line.
1854 newText = text
1855 assert not currentIndent
1856 currentIndent = self.__indentWord
1857
1858 assert newText is not None
1859
1860 if self.__checkSyntax(self.__normalizeMultiline(newText)):
1861 return indent + newText
1862 else:
1863 return None
1864
1865 def __splitAtOffsets(self, line, offsets):
1866 """
1867 Private method to split the line at the given offsets.
1868
1869 @param line line to split (string)
1870 @param offsets offsets to split at (list of integer)
1871 @return split line (list of string)
1872 """
1873 result = []
1874
1875 previousOffset = 0
1876 currentOffset = 0
1877 for currentOffset in sorted(offsets):
1878 if currentOffset < len(line) and previousOffset != currentOffset:
1879 result.append(line[previousOffset:currentOffset])
1880 previousOffset = currentOffset
1881
1882 result.append(line[currentOffset:])
1883
1884 return result
1885
1886 def __lineShorteningRank(self, candidate): 1905 def __lineShorteningRank(self, candidate):
1887 """ 1906 """
1888 Private method to rank a candidate. 1907 Private method to rank a candidate.
1889 1908
1890 @param candidate candidate line to rank (string) 1909 @param candidate candidate line to rank (string)
1923 rank += 100 1942 rank += 100
1924 1943
1925 for ending in '([{': 1944 for ending in '([{':
1926 # Avoid lonely opening. They result in longer lines. 1945 # Avoid lonely opening. They result in longer lines.
1927 if (currentLine.endswith(ending) and 1946 if (currentLine.endswith(ending) and
1928 len(currentLine.strip()) <= len(self.__indentWord)): 1947 len(currentLine.strip()) <= \
1948 len(self.__indentWord)):
1929 rank += 100 1949 rank += 100
1930 1950
1931 if currentLine.endswith('%'): 1951 if currentLine.endswith('%'):
1932 rank -= 20 1952 rank -= 20
1933 1953
1936 rank -= 50 1956 rank -= 50
1937 1957
1938 rank += 10 * self.__countUnbalancedBrackets(currentLine) 1958 rank += 10 * self.__countUnbalancedBrackets(currentLine)
1939 else: 1959 else:
1940 rank = 100000 1960 rank = 100000
1941 1961
1942 return max(0, rank) 1962 return max(0, rank)
1943 1963
1944 def __countUnbalancedBrackets(self, line): 1964 def __countUnbalancedBrackets(self, line):
1945 """ 1965 """
1946 Private method to determine the number of unmatched open/close brackets. 1966 Private method to determine the number of unmatched open/close
1967 brackets.
1947 1968
1948 @param line line to work at (string) 1969 @param line line to work at (string)
1949 @return number of unmatched open/close brackets (integer) 1970 @return number of unmatched open/close brackets (integer)
1950 """ 1971 """
1951 count = 0 1972 count = 0

eric ide

mercurial