jpayne@68
|
1 """Format all or a selected region (line slice) of text.
|
jpayne@68
|
2
|
jpayne@68
|
3 Region formatting options: paragraph, comment block, indent, deindent,
|
jpayne@68
|
4 comment, uncomment, tabify, and untabify.
|
jpayne@68
|
5
|
jpayne@68
|
6 File renamed from paragraph.py with functions added from editor.py.
|
jpayne@68
|
7 """
|
jpayne@68
|
8 import re
|
jpayne@68
|
9 from tkinter.messagebox import askyesno
|
jpayne@68
|
10 from tkinter.simpledialog import askinteger
|
jpayne@68
|
11 from idlelib.config import idleConf
|
jpayne@68
|
12
|
jpayne@68
|
13
|
jpayne@68
|
14 class FormatParagraph:
|
jpayne@68
|
15 """Format a paragraph, comment block, or selection to a max width.
|
jpayne@68
|
16
|
jpayne@68
|
17 Does basic, standard text formatting, and also understands Python
|
jpayne@68
|
18 comment blocks. Thus, for editing Python source code, this
|
jpayne@68
|
19 extension is really only suitable for reformatting these comment
|
jpayne@68
|
20 blocks or triple-quoted strings.
|
jpayne@68
|
21
|
jpayne@68
|
22 Known problems with comment reformatting:
|
jpayne@68
|
23 * If there is a selection marked, and the first line of the
|
jpayne@68
|
24 selection is not complete, the block will probably not be detected
|
jpayne@68
|
25 as comments, and will have the normal "text formatting" rules
|
jpayne@68
|
26 applied.
|
jpayne@68
|
27 * If a comment block has leading whitespace that mixes tabs and
|
jpayne@68
|
28 spaces, they will not be considered part of the same block.
|
jpayne@68
|
29 * Fancy comments, like this bulleted list, aren't handled :-)
|
jpayne@68
|
30 """
|
jpayne@68
|
31 def __init__(self, editwin):
|
jpayne@68
|
32 self.editwin = editwin
|
jpayne@68
|
33
|
jpayne@68
|
34 @classmethod
|
jpayne@68
|
35 def reload(cls):
|
jpayne@68
|
36 cls.max_width = idleConf.GetOption('extensions', 'FormatParagraph',
|
jpayne@68
|
37 'max-width', type='int', default=72)
|
jpayne@68
|
38
|
jpayne@68
|
39 def close(self):
|
jpayne@68
|
40 self.editwin = None
|
jpayne@68
|
41
|
jpayne@68
|
42 def format_paragraph_event(self, event, limit=None):
|
jpayne@68
|
43 """Formats paragraph to a max width specified in idleConf.
|
jpayne@68
|
44
|
jpayne@68
|
45 If text is selected, format_paragraph_event will start breaking lines
|
jpayne@68
|
46 at the max width, starting from the beginning selection.
|
jpayne@68
|
47
|
jpayne@68
|
48 If no text is selected, format_paragraph_event uses the current
|
jpayne@68
|
49 cursor location to determine the paragraph (lines of text surrounded
|
jpayne@68
|
50 by blank lines) and formats it.
|
jpayne@68
|
51
|
jpayne@68
|
52 The length limit parameter is for testing with a known value.
|
jpayne@68
|
53 """
|
jpayne@68
|
54 limit = self.max_width if limit is None else limit
|
jpayne@68
|
55 text = self.editwin.text
|
jpayne@68
|
56 first, last = self.editwin.get_selection_indices()
|
jpayne@68
|
57 if first and last:
|
jpayne@68
|
58 data = text.get(first, last)
|
jpayne@68
|
59 comment_header = get_comment_header(data)
|
jpayne@68
|
60 else:
|
jpayne@68
|
61 first, last, comment_header, data = \
|
jpayne@68
|
62 find_paragraph(text, text.index("insert"))
|
jpayne@68
|
63 if comment_header:
|
jpayne@68
|
64 newdata = reformat_comment(data, limit, comment_header)
|
jpayne@68
|
65 else:
|
jpayne@68
|
66 newdata = reformat_paragraph(data, limit)
|
jpayne@68
|
67 text.tag_remove("sel", "1.0", "end")
|
jpayne@68
|
68
|
jpayne@68
|
69 if newdata != data:
|
jpayne@68
|
70 text.mark_set("insert", first)
|
jpayne@68
|
71 text.undo_block_start()
|
jpayne@68
|
72 text.delete(first, last)
|
jpayne@68
|
73 text.insert(first, newdata)
|
jpayne@68
|
74 text.undo_block_stop()
|
jpayne@68
|
75 else:
|
jpayne@68
|
76 text.mark_set("insert", last)
|
jpayne@68
|
77 text.see("insert")
|
jpayne@68
|
78 return "break"
|
jpayne@68
|
79
|
jpayne@68
|
80
|
jpayne@68
|
81 FormatParagraph.reload()
|
jpayne@68
|
82
|
jpayne@68
|
83 def find_paragraph(text, mark):
|
jpayne@68
|
84 """Returns the start/stop indices enclosing the paragraph that mark is in.
|
jpayne@68
|
85
|
jpayne@68
|
86 Also returns the comment format string, if any, and paragraph of text
|
jpayne@68
|
87 between the start/stop indices.
|
jpayne@68
|
88 """
|
jpayne@68
|
89 lineno, col = map(int, mark.split("."))
|
jpayne@68
|
90 line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
jpayne@68
|
91
|
jpayne@68
|
92 # Look for start of next paragraph if the index passed in is a blank line
|
jpayne@68
|
93 while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
|
jpayne@68
|
94 lineno = lineno + 1
|
jpayne@68
|
95 line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
jpayne@68
|
96 first_lineno = lineno
|
jpayne@68
|
97 comment_header = get_comment_header(line)
|
jpayne@68
|
98 comment_header_len = len(comment_header)
|
jpayne@68
|
99
|
jpayne@68
|
100 # Once start line found, search for end of paragraph (a blank line)
|
jpayne@68
|
101 while get_comment_header(line)==comment_header and \
|
jpayne@68
|
102 not is_all_white(line[comment_header_len:]):
|
jpayne@68
|
103 lineno = lineno + 1
|
jpayne@68
|
104 line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
jpayne@68
|
105 last = "%d.0" % lineno
|
jpayne@68
|
106
|
jpayne@68
|
107 # Search back to beginning of paragraph (first blank line before)
|
jpayne@68
|
108 lineno = first_lineno - 1
|
jpayne@68
|
109 line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
jpayne@68
|
110 while lineno > 0 and \
|
jpayne@68
|
111 get_comment_header(line)==comment_header and \
|
jpayne@68
|
112 not is_all_white(line[comment_header_len:]):
|
jpayne@68
|
113 lineno = lineno - 1
|
jpayne@68
|
114 line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
jpayne@68
|
115 first = "%d.0" % (lineno+1)
|
jpayne@68
|
116
|
jpayne@68
|
117 return first, last, comment_header, text.get(first, last)
|
jpayne@68
|
118
|
jpayne@68
|
119 # This should perhaps be replaced with textwrap.wrap
|
jpayne@68
|
120 def reformat_paragraph(data, limit):
|
jpayne@68
|
121 """Return data reformatted to specified width (limit)."""
|
jpayne@68
|
122 lines = data.split("\n")
|
jpayne@68
|
123 i = 0
|
jpayne@68
|
124 n = len(lines)
|
jpayne@68
|
125 while i < n and is_all_white(lines[i]):
|
jpayne@68
|
126 i = i+1
|
jpayne@68
|
127 if i >= n:
|
jpayne@68
|
128 return data
|
jpayne@68
|
129 indent1 = get_indent(lines[i])
|
jpayne@68
|
130 if i+1 < n and not is_all_white(lines[i+1]):
|
jpayne@68
|
131 indent2 = get_indent(lines[i+1])
|
jpayne@68
|
132 else:
|
jpayne@68
|
133 indent2 = indent1
|
jpayne@68
|
134 new = lines[:i]
|
jpayne@68
|
135 partial = indent1
|
jpayne@68
|
136 while i < n and not is_all_white(lines[i]):
|
jpayne@68
|
137 # XXX Should take double space after period (etc.) into account
|
jpayne@68
|
138 words = re.split(r"(\s+)", lines[i])
|
jpayne@68
|
139 for j in range(0, len(words), 2):
|
jpayne@68
|
140 word = words[j]
|
jpayne@68
|
141 if not word:
|
jpayne@68
|
142 continue # Can happen when line ends in whitespace
|
jpayne@68
|
143 if len((partial + word).expandtabs()) > limit and \
|
jpayne@68
|
144 partial != indent1:
|
jpayne@68
|
145 new.append(partial.rstrip())
|
jpayne@68
|
146 partial = indent2
|
jpayne@68
|
147 partial = partial + word + " "
|
jpayne@68
|
148 if j+1 < len(words) and words[j+1] != " ":
|
jpayne@68
|
149 partial = partial + " "
|
jpayne@68
|
150 i = i+1
|
jpayne@68
|
151 new.append(partial.rstrip())
|
jpayne@68
|
152 # XXX Should reformat remaining paragraphs as well
|
jpayne@68
|
153 new.extend(lines[i:])
|
jpayne@68
|
154 return "\n".join(new)
|
jpayne@68
|
155
|
jpayne@68
|
156 def reformat_comment(data, limit, comment_header):
|
jpayne@68
|
157 """Return data reformatted to specified width with comment header."""
|
jpayne@68
|
158
|
jpayne@68
|
159 # Remove header from the comment lines
|
jpayne@68
|
160 lc = len(comment_header)
|
jpayne@68
|
161 data = "\n".join(line[lc:] for line in data.split("\n"))
|
jpayne@68
|
162 # Reformat to maxformatwidth chars or a 20 char width,
|
jpayne@68
|
163 # whichever is greater.
|
jpayne@68
|
164 format_width = max(limit - len(comment_header), 20)
|
jpayne@68
|
165 newdata = reformat_paragraph(data, format_width)
|
jpayne@68
|
166 # re-split and re-insert the comment header.
|
jpayne@68
|
167 newdata = newdata.split("\n")
|
jpayne@68
|
168 # If the block ends in a \n, we don't want the comment prefix
|
jpayne@68
|
169 # inserted after it. (Im not sure it makes sense to reformat a
|
jpayne@68
|
170 # comment block that is not made of complete lines, but whatever!)
|
jpayne@68
|
171 # Can't think of a clean solution, so we hack away
|
jpayne@68
|
172 block_suffix = ""
|
jpayne@68
|
173 if not newdata[-1]:
|
jpayne@68
|
174 block_suffix = "\n"
|
jpayne@68
|
175 newdata = newdata[:-1]
|
jpayne@68
|
176 return '\n'.join(comment_header+line for line in newdata) + block_suffix
|
jpayne@68
|
177
|
jpayne@68
|
178 def is_all_white(line):
|
jpayne@68
|
179 """Return True if line is empty or all whitespace."""
|
jpayne@68
|
180
|
jpayne@68
|
181 return re.match(r"^\s*$", line) is not None
|
jpayne@68
|
182
|
jpayne@68
|
183 def get_indent(line):
|
jpayne@68
|
184 """Return the initial space or tab indent of line."""
|
jpayne@68
|
185 return re.match(r"^([ \t]*)", line).group()
|
jpayne@68
|
186
|
jpayne@68
|
187 def get_comment_header(line):
|
jpayne@68
|
188 """Return string with leading whitespace and '#' from line or ''.
|
jpayne@68
|
189
|
jpayne@68
|
190 A null return indicates that the line is not a comment line. A non-
|
jpayne@68
|
191 null return, such as ' #', will be used to find the other lines of
|
jpayne@68
|
192 a comment block with the same indent.
|
jpayne@68
|
193 """
|
jpayne@68
|
194 m = re.match(r"^([ \t]*#*)", line)
|
jpayne@68
|
195 if m is None: return ""
|
jpayne@68
|
196 return m.group(1)
|
jpayne@68
|
197
|
jpayne@68
|
198
|
jpayne@68
|
199 # Copied from editor.py; importing it would cause an import cycle.
|
jpayne@68
|
200 _line_indent_re = re.compile(r'[ \t]*')
|
jpayne@68
|
201
|
jpayne@68
|
202 def get_line_indent(line, tabwidth):
|
jpayne@68
|
203 """Return a line's indentation as (# chars, effective # of spaces).
|
jpayne@68
|
204
|
jpayne@68
|
205 The effective # of spaces is the length after properly "expanding"
|
jpayne@68
|
206 the tabs into spaces, as done by str.expandtabs(tabwidth).
|
jpayne@68
|
207 """
|
jpayne@68
|
208 m = _line_indent_re.match(line)
|
jpayne@68
|
209 return m.end(), len(m.group().expandtabs(tabwidth))
|
jpayne@68
|
210
|
jpayne@68
|
211
|
jpayne@68
|
212 class FormatRegion:
|
jpayne@68
|
213 "Format selected text (region)."
|
jpayne@68
|
214
|
jpayne@68
|
215 def __init__(self, editwin):
|
jpayne@68
|
216 self.editwin = editwin
|
jpayne@68
|
217
|
jpayne@68
|
218 def get_region(self):
|
jpayne@68
|
219 """Return line information about the selected text region.
|
jpayne@68
|
220
|
jpayne@68
|
221 If text is selected, the first and last indices will be
|
jpayne@68
|
222 for the selection. If there is no text selected, the
|
jpayne@68
|
223 indices will be the current cursor location.
|
jpayne@68
|
224
|
jpayne@68
|
225 Return a tuple containing (first index, last index,
|
jpayne@68
|
226 string representation of text, list of text lines).
|
jpayne@68
|
227 """
|
jpayne@68
|
228 text = self.editwin.text
|
jpayne@68
|
229 first, last = self.editwin.get_selection_indices()
|
jpayne@68
|
230 if first and last:
|
jpayne@68
|
231 head = text.index(first + " linestart")
|
jpayne@68
|
232 tail = text.index(last + "-1c lineend +1c")
|
jpayne@68
|
233 else:
|
jpayne@68
|
234 head = text.index("insert linestart")
|
jpayne@68
|
235 tail = text.index("insert lineend +1c")
|
jpayne@68
|
236 chars = text.get(head, tail)
|
jpayne@68
|
237 lines = chars.split("\n")
|
jpayne@68
|
238 return head, tail, chars, lines
|
jpayne@68
|
239
|
jpayne@68
|
240 def set_region(self, head, tail, chars, lines):
|
jpayne@68
|
241 """Replace the text between the given indices.
|
jpayne@68
|
242
|
jpayne@68
|
243 Args:
|
jpayne@68
|
244 head: Starting index of text to replace.
|
jpayne@68
|
245 tail: Ending index of text to replace.
|
jpayne@68
|
246 chars: Expected to be string of current text
|
jpayne@68
|
247 between head and tail.
|
jpayne@68
|
248 lines: List of new lines to insert between head
|
jpayne@68
|
249 and tail.
|
jpayne@68
|
250 """
|
jpayne@68
|
251 text = self.editwin.text
|
jpayne@68
|
252 newchars = "\n".join(lines)
|
jpayne@68
|
253 if newchars == chars:
|
jpayne@68
|
254 text.bell()
|
jpayne@68
|
255 return
|
jpayne@68
|
256 text.tag_remove("sel", "1.0", "end")
|
jpayne@68
|
257 text.mark_set("insert", head)
|
jpayne@68
|
258 text.undo_block_start()
|
jpayne@68
|
259 text.delete(head, tail)
|
jpayne@68
|
260 text.insert(head, newchars)
|
jpayne@68
|
261 text.undo_block_stop()
|
jpayne@68
|
262 text.tag_add("sel", head, "insert")
|
jpayne@68
|
263
|
jpayne@68
|
264 def indent_region_event(self, event=None):
|
jpayne@68
|
265 "Indent region by indentwidth spaces."
|
jpayne@68
|
266 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
267 for pos in range(len(lines)):
|
jpayne@68
|
268 line = lines[pos]
|
jpayne@68
|
269 if line:
|
jpayne@68
|
270 raw, effective = get_line_indent(line, self.editwin.tabwidth)
|
jpayne@68
|
271 effective = effective + self.editwin.indentwidth
|
jpayne@68
|
272 lines[pos] = self.editwin._make_blanks(effective) + line[raw:]
|
jpayne@68
|
273 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
274 return "break"
|
jpayne@68
|
275
|
jpayne@68
|
276 def dedent_region_event(self, event=None):
|
jpayne@68
|
277 "Dedent region by indentwidth spaces."
|
jpayne@68
|
278 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
279 for pos in range(len(lines)):
|
jpayne@68
|
280 line = lines[pos]
|
jpayne@68
|
281 if line:
|
jpayne@68
|
282 raw, effective = get_line_indent(line, self.editwin.tabwidth)
|
jpayne@68
|
283 effective = max(effective - self.editwin.indentwidth, 0)
|
jpayne@68
|
284 lines[pos] = self.editwin._make_blanks(effective) + line[raw:]
|
jpayne@68
|
285 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
286 return "break"
|
jpayne@68
|
287
|
jpayne@68
|
288 def comment_region_event(self, event=None):
|
jpayne@68
|
289 """Comment out each line in region.
|
jpayne@68
|
290
|
jpayne@68
|
291 ## is appended to the beginning of each line to comment it out.
|
jpayne@68
|
292 """
|
jpayne@68
|
293 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
294 for pos in range(len(lines) - 1):
|
jpayne@68
|
295 line = lines[pos]
|
jpayne@68
|
296 lines[pos] = '##' + line
|
jpayne@68
|
297 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
298 return "break"
|
jpayne@68
|
299
|
jpayne@68
|
300 def uncomment_region_event(self, event=None):
|
jpayne@68
|
301 """Uncomment each line in region.
|
jpayne@68
|
302
|
jpayne@68
|
303 Remove ## or # in the first positions of a line. If the comment
|
jpayne@68
|
304 is not in the beginning position, this command will have no effect.
|
jpayne@68
|
305 """
|
jpayne@68
|
306 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
307 for pos in range(len(lines)):
|
jpayne@68
|
308 line = lines[pos]
|
jpayne@68
|
309 if not line:
|
jpayne@68
|
310 continue
|
jpayne@68
|
311 if line[:2] == '##':
|
jpayne@68
|
312 line = line[2:]
|
jpayne@68
|
313 elif line[:1] == '#':
|
jpayne@68
|
314 line = line[1:]
|
jpayne@68
|
315 lines[pos] = line
|
jpayne@68
|
316 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
317 return "break"
|
jpayne@68
|
318
|
jpayne@68
|
319 def tabify_region_event(self, event=None):
|
jpayne@68
|
320 "Convert leading spaces to tabs for each line in selected region."
|
jpayne@68
|
321 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
322 tabwidth = self._asktabwidth()
|
jpayne@68
|
323 if tabwidth is None:
|
jpayne@68
|
324 return
|
jpayne@68
|
325 for pos in range(len(lines)):
|
jpayne@68
|
326 line = lines[pos]
|
jpayne@68
|
327 if line:
|
jpayne@68
|
328 raw, effective = get_line_indent(line, tabwidth)
|
jpayne@68
|
329 ntabs, nspaces = divmod(effective, tabwidth)
|
jpayne@68
|
330 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
jpayne@68
|
331 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
332 return "break"
|
jpayne@68
|
333
|
jpayne@68
|
334 def untabify_region_event(self, event=None):
|
jpayne@68
|
335 "Expand tabs to spaces for each line in region."
|
jpayne@68
|
336 head, tail, chars, lines = self.get_region()
|
jpayne@68
|
337 tabwidth = self._asktabwidth()
|
jpayne@68
|
338 if tabwidth is None:
|
jpayne@68
|
339 return
|
jpayne@68
|
340 for pos in range(len(lines)):
|
jpayne@68
|
341 lines[pos] = lines[pos].expandtabs(tabwidth)
|
jpayne@68
|
342 self.set_region(head, tail, chars, lines)
|
jpayne@68
|
343 return "break"
|
jpayne@68
|
344
|
jpayne@68
|
345 def _asktabwidth(self):
|
jpayne@68
|
346 "Return value for tab width."
|
jpayne@68
|
347 return askinteger(
|
jpayne@68
|
348 "Tab width",
|
jpayne@68
|
349 "Columns per tab? (2-16)",
|
jpayne@68
|
350 parent=self.editwin.text,
|
jpayne@68
|
351 initialvalue=self.editwin.indentwidth,
|
jpayne@68
|
352 minvalue=2,
|
jpayne@68
|
353 maxvalue=16)
|
jpayne@68
|
354
|
jpayne@68
|
355
|
jpayne@68
|
356 class Indents:
|
jpayne@68
|
357 "Change future indents."
|
jpayne@68
|
358
|
jpayne@68
|
359 def __init__(self, editwin):
|
jpayne@68
|
360 self.editwin = editwin
|
jpayne@68
|
361
|
jpayne@68
|
362 def toggle_tabs_event(self, event):
|
jpayne@68
|
363 editwin = self.editwin
|
jpayne@68
|
364 usetabs = editwin.usetabs
|
jpayne@68
|
365 if askyesno(
|
jpayne@68
|
366 "Toggle tabs",
|
jpayne@68
|
367 "Turn tabs " + ("on", "off")[usetabs] +
|
jpayne@68
|
368 "?\nIndent width " +
|
jpayne@68
|
369 ("will be", "remains at")[usetabs] + " 8." +
|
jpayne@68
|
370 "\n Note: a tab is always 8 columns",
|
jpayne@68
|
371 parent=editwin.text):
|
jpayne@68
|
372 editwin.usetabs = not usetabs
|
jpayne@68
|
373 # Try to prevent inconsistent indentation.
|
jpayne@68
|
374 # User must change indent width manually after using tabs.
|
jpayne@68
|
375 editwin.indentwidth = 8
|
jpayne@68
|
376 return "break"
|
jpayne@68
|
377
|
jpayne@68
|
378 def change_indentwidth_event(self, event):
|
jpayne@68
|
379 editwin = self.editwin
|
jpayne@68
|
380 new = askinteger(
|
jpayne@68
|
381 "Indent width",
|
jpayne@68
|
382 "New indent width (2-16)\n(Always use 8 when using tabs)",
|
jpayne@68
|
383 parent=editwin.text,
|
jpayne@68
|
384 initialvalue=editwin.indentwidth,
|
jpayne@68
|
385 minvalue=2,
|
jpayne@68
|
386 maxvalue=16)
|
jpayne@68
|
387 if new and new != editwin.indentwidth and not editwin.usetabs:
|
jpayne@68
|
388 editwin.indentwidth = new
|
jpayne@68
|
389 return "break"
|
jpayne@68
|
390
|
jpayne@68
|
391
|
jpayne@68
|
392 class Rstrip: # 'Strip Trailing Whitespace" on "Format" menu.
|
jpayne@68
|
393 def __init__(self, editwin):
|
jpayne@68
|
394 self.editwin = editwin
|
jpayne@68
|
395
|
jpayne@68
|
396 def do_rstrip(self, event=None):
|
jpayne@68
|
397 text = self.editwin.text
|
jpayne@68
|
398 undo = self.editwin.undo
|
jpayne@68
|
399 undo.undo_block_start()
|
jpayne@68
|
400
|
jpayne@68
|
401 end_line = int(float(text.index('end')))
|
jpayne@68
|
402 for cur in range(1, end_line):
|
jpayne@68
|
403 txt = text.get('%i.0' % cur, '%i.end' % cur)
|
jpayne@68
|
404 raw = len(txt)
|
jpayne@68
|
405 cut = len(txt.rstrip())
|
jpayne@68
|
406 # Since text.delete() marks file as changed, even if not,
|
jpayne@68
|
407 # only call it when needed to actually delete something.
|
jpayne@68
|
408 if cut < raw:
|
jpayne@68
|
409 text.delete('%i.%i' % (cur, cut), '%i.end' % cur)
|
jpayne@68
|
410
|
jpayne@68
|
411 if (text.get('end-2c') == '\n' # File ends with at least 1 newline;
|
jpayne@68
|
412 and not hasattr(self.editwin, 'interp')): # & is not Shell.
|
jpayne@68
|
413 # Delete extra user endlines.
|
jpayne@68
|
414 while (text.index('end-1c') > '1.0' # Stop if file empty.
|
jpayne@68
|
415 and text.get('end-3c') == '\n'):
|
jpayne@68
|
416 text.delete('end-3c')
|
jpayne@68
|
417 # Because tk indexes are slice indexes and never raise,
|
jpayne@68
|
418 # a file with only newlines will be emptied.
|
jpayne@68
|
419 # patchcheck.py does the same.
|
jpayne@68
|
420
|
jpayne@68
|
421 undo.undo_block_stop()
|
jpayne@68
|
422
|
jpayne@68
|
423
|
jpayne@68
|
424 if __name__ == "__main__":
|
jpayne@68
|
425 from unittest import main
|
jpayne@68
|
426 main('idlelib.idle_test.test_format', verbosity=2, exit=False)
|