home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / !!!python!!! / BeOpen-Python-2.0.exe / FORMATPARAGRAPH.PY < prev    next >
Encoding:
Python Source  |  2000-09-28  |  5.8 KB  |  156 lines

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