/Lib/idlelib/FormatParagraph.py

http://unladen-swallow.googlecode.com/ · Python · 149 lines · 111 code · 13 blank · 25 comment · 30 complexity · b677cb84e11e3345cbd3d9b5f0293acc MD5 · raw file

  1. # Extension to format a paragraph
  2. # Does basic, standard text formatting, and also understands Python
  3. # comment blocks. Thus, for editing Python source code, this
  4. # extension is really only suitable for reformatting these comment
  5. # blocks or triple-quoted strings.
  6. # Known problems with comment reformatting:
  7. # * If there is a selection marked, and the first line of the
  8. # selection is not complete, the block will probably not be detected
  9. # as comments, and will have the normal "text formatting" rules
  10. # applied.
  11. # * If a comment block has leading whitespace that mixes tabs and
  12. # spaces, they will not be considered part of the same block.
  13. # * Fancy comments, like this bulleted list, arent handled :-)
  14. import re
  15. from configHandler import idleConf
  16. class FormatParagraph:
  17. menudefs = [
  18. ('format', [ # /s/edit/format dscherer@cmu.edu
  19. ('Format Paragraph', '<<format-paragraph>>'),
  20. ])
  21. ]
  22. def __init__(self, editwin):
  23. self.editwin = editwin
  24. def close(self):
  25. self.editwin = None
  26. def format_paragraph_event(self, event):
  27. maxformatwidth = int(idleConf.GetOption('main','FormatParagraph','paragraph'))
  28. text = self.editwin.text
  29. first, last = self.editwin.get_selection_indices()
  30. if first and last:
  31. data = text.get(first, last)
  32. comment_header = ''
  33. else:
  34. first, last, comment_header, data = \
  35. find_paragraph(text, text.index("insert"))
  36. if comment_header:
  37. # Reformat the comment lines - convert to text sans header.
  38. lines = data.split("\n")
  39. lines = map(lambda st, l=len(comment_header): st[l:], lines)
  40. data = "\n".join(lines)
  41. # Reformat to maxformatwidth chars or a 20 char width, whichever is greater.
  42. format_width = max(maxformatwidth - len(comment_header), 20)
  43. newdata = reformat_paragraph(data, format_width)
  44. # re-split and re-insert the comment header.
  45. newdata = newdata.split("\n")
  46. # If the block ends in a \n, we dont want the comment
  47. # prefix inserted after it. (Im not sure it makes sense to
  48. # reformat a comment block that isnt made of complete
  49. # lines, but whatever!) Can't think of a clean soltution,
  50. # so we hack away
  51. block_suffix = ""
  52. if not newdata[-1]:
  53. block_suffix = "\n"
  54. newdata = newdata[:-1]
  55. builder = lambda item, prefix=comment_header: prefix+item
  56. newdata = '\n'.join(map(builder, newdata)) + block_suffix
  57. else:
  58. # Just a normal text format
  59. newdata = reformat_paragraph(data, maxformatwidth)
  60. text.tag_remove("sel", "1.0", "end")
  61. if newdata != data:
  62. text.mark_set("insert", first)
  63. text.undo_block_start()
  64. text.delete(first, last)
  65. text.insert(first, newdata)
  66. text.undo_block_stop()
  67. else:
  68. text.mark_set("insert", last)
  69. text.see("insert")
  70. return "break"
  71. def find_paragraph(text, mark):
  72. lineno, col = map(int, mark.split("."))
  73. line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
  74. while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
  75. lineno = lineno + 1
  76. line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
  77. first_lineno = lineno
  78. comment_header = get_comment_header(line)
  79. comment_header_len = len(comment_header)
  80. while get_comment_header(line)==comment_header and \
  81. not is_all_white(line[comment_header_len:]):
  82. lineno = lineno + 1
  83. line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
  84. last = "%d.0" % lineno
  85. # Search back to beginning of paragraph
  86. lineno = first_lineno - 1
  87. line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
  88. while lineno > 0 and \
  89. get_comment_header(line)==comment_header and \
  90. not is_all_white(line[comment_header_len:]):
  91. lineno = lineno - 1
  92. line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
  93. first = "%d.0" % (lineno+1)
  94. return first, last, comment_header, text.get(first, last)
  95. def reformat_paragraph(data, limit):
  96. lines = data.split("\n")
  97. i = 0
  98. n = len(lines)
  99. while i < n and is_all_white(lines[i]):
  100. i = i+1
  101. if i >= n:
  102. return data
  103. indent1 = get_indent(lines[i])
  104. if i+1 < n and not is_all_white(lines[i+1]):
  105. indent2 = get_indent(lines[i+1])
  106. else:
  107. indent2 = indent1
  108. new = lines[:i]
  109. partial = indent1
  110. while i < n and not is_all_white(lines[i]):
  111. # XXX Should take double space after period (etc.) into account
  112. words = re.split("(\s+)", lines[i])
  113. for j in range(0, len(words), 2):
  114. word = words[j]
  115. if not word:
  116. continue # Can happen when line ends in whitespace
  117. if len((partial + word).expandtabs()) > limit and \
  118. partial != indent1:
  119. new.append(partial.rstrip())
  120. partial = indent2
  121. partial = partial + word + " "
  122. if j+1 < len(words) and words[j+1] != " ":
  123. partial = partial + " "
  124. i = i+1
  125. new.append(partial.rstrip())
  126. # XXX Should reformat remaining paragraphs as well
  127. new.extend(lines[i:])
  128. return "\n".join(new)
  129. def is_all_white(line):
  130. return re.match(r"^\s*$", line) is not None
  131. def get_indent(line):
  132. return re.match(r"^(\s*)", line).group()
  133. def get_comment_header(line):
  134. m = re.match(r"^(\s*#*)", line)
  135. if m is None: return ""
  136. return m.group(1)