Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 1 | # Extension to format a paragraph |
| 2 | |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 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 | |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 17 | import re |
| 18 | |
| 19 | class FormatParagraph: |
| 20 | |
| 21 | menudefs = [ |
| 22 | ('edit', [ |
| 23 | ('Format Paragraph', '<<format-paragraph>>'), |
| 24 | ]) |
| 25 | ] |
| 26 | |
| 27 | keydefs = { |
| 28 | '<<format-paragraph>>': ['<Alt-q>'], |
| 29 | } |
Tim Peters | 70c4378 | 2001-01-17 08:48:39 +0000 | [diff] [blame] | 30 | |
Guido van Rossum | e911c3e | 1999-01-04 16:34:41 +0000 | [diff] [blame] | 31 | unix_keydefs = { |
| 32 | '<<format-paragraph>>': ['<Meta-q>'], |
Tim Peters | 70c4378 | 2001-01-17 08:48:39 +0000 | [diff] [blame] | 33 | } |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 34 | |
| 35 | def __init__(self, editwin): |
| 36 | self.editwin = editwin |
| 37 | |
Guido van Rossum | e689f00 | 1999-06-25 16:02:22 +0000 | [diff] [blame] | 38 | def close(self): |
| 39 | self.editwin = None |
| 40 | |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 41 | def format_paragraph_event(self, event): |
| 42 | text = self.editwin.text |
Guido van Rossum | 1320560 | 1999-06-11 15:03:00 +0000 | [diff] [blame] | 43 | first, last = self.editwin.get_selection_indices() |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 44 | if first and last: |
| 45 | data = text.get(first, last) |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 46 | comment_header = '' |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 47 | else: |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 48 | first, last, comment_header, data = \ |
| 49 | find_paragraph(text, text.index("insert")) |
| 50 | if comment_header: |
| 51 | # Reformat the comment lines - convert to text sans header. |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 52 | lines = data.split("\n") |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 53 | lines = map(lambda st, l=len(comment_header): st[l:], lines) |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 54 | data = "\n".join(lines) |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 55 | # Reformat to 70 chars or a 20 char width, whichever is greater. |
| 56 | format_width = max(70-len(comment_header), 20) |
| 57 | newdata = reformat_paragraph(data, format_width) |
| 58 | # re-split and re-insert the comment header. |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 59 | newdata = newdata.split("\n") |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 60 | # If the block ends in a \n, we dont want the comment |
| 61 | # prefix inserted after it. (Im not sure it makes sense to |
| 62 | # reformat a comment block that isnt made of complete |
| 63 | # lines, but whatever!) Can't think of a clean soltution, |
| 64 | # so we hack away |
| 65 | block_suffix = "" |
| 66 | if not newdata[-1]: |
| 67 | block_suffix = "\n" |
| 68 | newdata = newdata[:-1] |
| 69 | builder = lambda item, prefix=comment_header: prefix+item |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 70 | newdata = '\n'.join(map(builder, newdata)) + block_suffix |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 71 | else: |
| 72 | # Just a normal text format |
| 73 | newdata = reformat_paragraph(data) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 74 | text.tag_remove("sel", "1.0", "end") |
| 75 | if newdata != data: |
| 76 | text.mark_set("insert", first) |
Guido van Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 77 | text.undo_block_start() |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 78 | text.delete(first, last) |
| 79 | text.insert(first, newdata) |
Guido van Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 80 | text.undo_block_stop() |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 81 | else: |
| 82 | text.mark_set("insert", last) |
| 83 | text.see("insert") |
| 84 | |
| 85 | def find_paragraph(text, mark): |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 86 | lineno, col = map(int, mark.split(".")) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 87 | line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno) |
Guido van Rossum | 1e899cd | 1999-01-04 21:19:09 +0000 | [diff] [blame] | 88 | while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line): |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 89 | lineno = lineno + 1 |
| 90 | line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno) |
| 91 | first_lineno = lineno |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 92 | comment_header = get_comment_header(line) |
| 93 | comment_header_len = len(comment_header) |
| 94 | while get_comment_header(line)==comment_header and \ |
| 95 | not is_all_white(line[comment_header_len:]): |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 96 | lineno = lineno + 1 |
| 97 | line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno) |
| 98 | last = "%d.0" % lineno |
| 99 | # Search back to beginning of paragraph |
| 100 | lineno = first_lineno - 1 |
| 101 | line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno) |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 102 | while lineno > 0 and \ |
| 103 | get_comment_header(line)==comment_header and \ |
| 104 | not is_all_white(line[comment_header_len:]): |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 105 | lineno = lineno - 1 |
| 106 | line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno) |
| 107 | first = "%d.0" % (lineno+1) |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 108 | return first, last, comment_header, text.get(first, last) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 109 | |
Guido van Rossum | 629082e | 1999-01-07 00:12:15 +0000 | [diff] [blame] | 110 | def reformat_paragraph(data, limit=70): |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 111 | lines = data.split("\n") |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 112 | i = 0 |
| 113 | n = len(lines) |
| 114 | while i < n and is_all_white(lines[i]): |
| 115 | i = i+1 |
| 116 | if i >= n: |
| 117 | return data |
| 118 | indent1 = get_indent(lines[i]) |
| 119 | if i+1 < n and not is_all_white(lines[i+1]): |
| 120 | indent2 = get_indent(lines[i+1]) |
| 121 | else: |
| 122 | indent2 = indent1 |
| 123 | new = lines[:i] |
| 124 | partial = indent1 |
| 125 | while i < n and not is_all_white(lines[i]): |
| 126 | # XXX Should take double space after period (etc.) into account |
| 127 | words = re.split("(\s+)", lines[i]) |
| 128 | for j in range(0, len(words), 2): |
| 129 | word = words[j] |
| 130 | if not word: |
| 131 | continue # Can happen when line ends in whitespace |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 132 | if len((partial + word).expandtabs()) > limit and \ |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 133 | partial != indent1: |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 134 | new.append(partial.rstrip()) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 135 | partial = indent2 |
| 136 | partial = partial + word + " " |
| 137 | if j+1 < len(words) and words[j+1] != " ": |
| 138 | partial = partial + " " |
| 139 | i = i+1 |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 140 | new.append(partial.rstrip()) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 141 | # XXX Should reformat remaining paragraphs as well |
| 142 | new.extend(lines[i:]) |
Walter Dörwald | aaab30e | 2002-09-11 20:36:02 +0000 | [diff] [blame] | 143 | return "\n".join(new) |
Guido van Rossum | 94e82ce | 1999-01-04 13:04:54 +0000 | [diff] [blame] | 144 | |
| 145 | def is_all_white(line): |
| 146 | return re.match(r"^\s*$", line) is not None |
| 147 | |
| 148 | def get_indent(line): |
| 149 | return re.match(r"^(\s*)", line).group() |
Guido van Rossum | 3dd3689 | 1999-06-10 17:48:02 +0000 | [diff] [blame] | 150 | |
| 151 | def get_comment_header(line): |
| 152 | m = re.match(r"^(\s*#*)", line) |
| 153 | if m is None: return "" |
| 154 | return m.group(1) |