17db96d56Sopenharmony_ci"""Format all or a selected region (line slice) of text. 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciRegion formatting options: paragraph, comment block, indent, deindent, 47db96d56Sopenharmony_cicomment, uncomment, tabify, and untabify. 57db96d56Sopenharmony_ci 67db96d56Sopenharmony_ciFile renamed from paragraph.py with functions added from editor.py. 77db96d56Sopenharmony_ci""" 87db96d56Sopenharmony_ciimport re 97db96d56Sopenharmony_cifrom tkinter.messagebox import askyesno 107db96d56Sopenharmony_cifrom tkinter.simpledialog import askinteger 117db96d56Sopenharmony_cifrom idlelib.config import idleConf 127db96d56Sopenharmony_ci 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ciclass FormatParagraph: 157db96d56Sopenharmony_ci """Format a paragraph, comment block, or selection to a max width. 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci Does basic, standard text formatting, and also understands Python 187db96d56Sopenharmony_ci comment blocks. Thus, for editing Python source code, this 197db96d56Sopenharmony_ci extension is really only suitable for reformatting these comment 207db96d56Sopenharmony_ci blocks or triple-quoted strings. 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci Known problems with comment reformatting: 237db96d56Sopenharmony_ci * If there is a selection marked, and the first line of the 247db96d56Sopenharmony_ci selection is not complete, the block will probably not be detected 257db96d56Sopenharmony_ci as comments, and will have the normal "text formatting" rules 267db96d56Sopenharmony_ci applied. 277db96d56Sopenharmony_ci * If a comment block has leading whitespace that mixes tabs and 287db96d56Sopenharmony_ci spaces, they will not be considered part of the same block. 297db96d56Sopenharmony_ci * Fancy comments, like this bulleted list, aren't handled :-) 307db96d56Sopenharmony_ci """ 317db96d56Sopenharmony_ci def __init__(self, editwin): 327db96d56Sopenharmony_ci self.editwin = editwin 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci @classmethod 357db96d56Sopenharmony_ci def reload(cls): 367db96d56Sopenharmony_ci cls.max_width = idleConf.GetOption('extensions', 'FormatParagraph', 377db96d56Sopenharmony_ci 'max-width', type='int', default=72) 387db96d56Sopenharmony_ci 397db96d56Sopenharmony_ci def close(self): 407db96d56Sopenharmony_ci self.editwin = None 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci def format_paragraph_event(self, event, limit=None): 437db96d56Sopenharmony_ci """Formats paragraph to a max width specified in idleConf. 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ci If text is selected, format_paragraph_event will start breaking lines 467db96d56Sopenharmony_ci at the max width, starting from the beginning selection. 477db96d56Sopenharmony_ci 487db96d56Sopenharmony_ci If no text is selected, format_paragraph_event uses the current 497db96d56Sopenharmony_ci cursor location to determine the paragraph (lines of text surrounded 507db96d56Sopenharmony_ci by blank lines) and formats it. 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci The length limit parameter is for testing with a known value. 537db96d56Sopenharmony_ci """ 547db96d56Sopenharmony_ci limit = self.max_width if limit is None else limit 557db96d56Sopenharmony_ci text = self.editwin.text 567db96d56Sopenharmony_ci first, last = self.editwin.get_selection_indices() 577db96d56Sopenharmony_ci if first and last: 587db96d56Sopenharmony_ci data = text.get(first, last) 597db96d56Sopenharmony_ci comment_header = get_comment_header(data) 607db96d56Sopenharmony_ci else: 617db96d56Sopenharmony_ci first, last, comment_header, data = \ 627db96d56Sopenharmony_ci find_paragraph(text, text.index("insert")) 637db96d56Sopenharmony_ci if comment_header: 647db96d56Sopenharmony_ci newdata = reformat_comment(data, limit, comment_header) 657db96d56Sopenharmony_ci else: 667db96d56Sopenharmony_ci newdata = reformat_paragraph(data, limit) 677db96d56Sopenharmony_ci text.tag_remove("sel", "1.0", "end") 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci if newdata != data: 707db96d56Sopenharmony_ci text.mark_set("insert", first) 717db96d56Sopenharmony_ci text.undo_block_start() 727db96d56Sopenharmony_ci text.delete(first, last) 737db96d56Sopenharmony_ci text.insert(first, newdata) 747db96d56Sopenharmony_ci text.undo_block_stop() 757db96d56Sopenharmony_ci else: 767db96d56Sopenharmony_ci text.mark_set("insert", last) 777db96d56Sopenharmony_ci text.see("insert") 787db96d56Sopenharmony_ci return "break" 797db96d56Sopenharmony_ci 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ciFormatParagraph.reload() 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_cidef find_paragraph(text, mark): 847db96d56Sopenharmony_ci """Returns the start/stop indices enclosing the paragraph that mark is in. 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ci Also returns the comment format string, if any, and paragraph of text 877db96d56Sopenharmony_ci between the start/stop indices. 887db96d56Sopenharmony_ci """ 897db96d56Sopenharmony_ci lineno, col = map(int, mark.split(".")) 907db96d56Sopenharmony_ci line = text.get("%d.0" % lineno, "%d.end" % lineno) 917db96d56Sopenharmony_ci 927db96d56Sopenharmony_ci # Look for start of next paragraph if the index passed in is a blank line 937db96d56Sopenharmony_ci while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line): 947db96d56Sopenharmony_ci lineno = lineno + 1 957db96d56Sopenharmony_ci line = text.get("%d.0" % lineno, "%d.end" % lineno) 967db96d56Sopenharmony_ci first_lineno = lineno 977db96d56Sopenharmony_ci comment_header = get_comment_header(line) 987db96d56Sopenharmony_ci comment_header_len = len(comment_header) 997db96d56Sopenharmony_ci 1007db96d56Sopenharmony_ci # Once start line found, search for end of paragraph (a blank line) 1017db96d56Sopenharmony_ci while get_comment_header(line)==comment_header and \ 1027db96d56Sopenharmony_ci not is_all_white(line[comment_header_len:]): 1037db96d56Sopenharmony_ci lineno = lineno + 1 1047db96d56Sopenharmony_ci line = text.get("%d.0" % lineno, "%d.end" % lineno) 1057db96d56Sopenharmony_ci last = "%d.0" % lineno 1067db96d56Sopenharmony_ci 1077db96d56Sopenharmony_ci # Search back to beginning of paragraph (first blank line before) 1087db96d56Sopenharmony_ci lineno = first_lineno - 1 1097db96d56Sopenharmony_ci line = text.get("%d.0" % lineno, "%d.end" % lineno) 1107db96d56Sopenharmony_ci while lineno > 0 and \ 1117db96d56Sopenharmony_ci get_comment_header(line)==comment_header and \ 1127db96d56Sopenharmony_ci not is_all_white(line[comment_header_len:]): 1137db96d56Sopenharmony_ci lineno = lineno - 1 1147db96d56Sopenharmony_ci line = text.get("%d.0" % lineno, "%d.end" % lineno) 1157db96d56Sopenharmony_ci first = "%d.0" % (lineno+1) 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci return first, last, comment_header, text.get(first, last) 1187db96d56Sopenharmony_ci 1197db96d56Sopenharmony_ci# This should perhaps be replaced with textwrap.wrap 1207db96d56Sopenharmony_cidef reformat_paragraph(data, limit): 1217db96d56Sopenharmony_ci """Return data reformatted to specified width (limit).""" 1227db96d56Sopenharmony_ci lines = data.split("\n") 1237db96d56Sopenharmony_ci i = 0 1247db96d56Sopenharmony_ci n = len(lines) 1257db96d56Sopenharmony_ci while i < n and is_all_white(lines[i]): 1267db96d56Sopenharmony_ci i = i+1 1277db96d56Sopenharmony_ci if i >= n: 1287db96d56Sopenharmony_ci return data 1297db96d56Sopenharmony_ci indent1 = get_indent(lines[i]) 1307db96d56Sopenharmony_ci if i+1 < n and not is_all_white(lines[i+1]): 1317db96d56Sopenharmony_ci indent2 = get_indent(lines[i+1]) 1327db96d56Sopenharmony_ci else: 1337db96d56Sopenharmony_ci indent2 = indent1 1347db96d56Sopenharmony_ci new = lines[:i] 1357db96d56Sopenharmony_ci partial = indent1 1367db96d56Sopenharmony_ci while i < n and not is_all_white(lines[i]): 1377db96d56Sopenharmony_ci # XXX Should take double space after period (etc.) into account 1387db96d56Sopenharmony_ci words = re.split(r"(\s+)", lines[i]) 1397db96d56Sopenharmony_ci for j in range(0, len(words), 2): 1407db96d56Sopenharmony_ci word = words[j] 1417db96d56Sopenharmony_ci if not word: 1427db96d56Sopenharmony_ci continue # Can happen when line ends in whitespace 1437db96d56Sopenharmony_ci if len((partial + word).expandtabs()) > limit and \ 1447db96d56Sopenharmony_ci partial != indent1: 1457db96d56Sopenharmony_ci new.append(partial.rstrip()) 1467db96d56Sopenharmony_ci partial = indent2 1477db96d56Sopenharmony_ci partial = partial + word + " " 1487db96d56Sopenharmony_ci if j+1 < len(words) and words[j+1] != " ": 1497db96d56Sopenharmony_ci partial = partial + " " 1507db96d56Sopenharmony_ci i = i+1 1517db96d56Sopenharmony_ci new.append(partial.rstrip()) 1527db96d56Sopenharmony_ci # XXX Should reformat remaining paragraphs as well 1537db96d56Sopenharmony_ci new.extend(lines[i:]) 1547db96d56Sopenharmony_ci return "\n".join(new) 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_cidef reformat_comment(data, limit, comment_header): 1577db96d56Sopenharmony_ci """Return data reformatted to specified width with comment header.""" 1587db96d56Sopenharmony_ci 1597db96d56Sopenharmony_ci # Remove header from the comment lines 1607db96d56Sopenharmony_ci lc = len(comment_header) 1617db96d56Sopenharmony_ci data = "\n".join(line[lc:] for line in data.split("\n")) 1627db96d56Sopenharmony_ci # Reformat to maxformatwidth chars or a 20 char width, 1637db96d56Sopenharmony_ci # whichever is greater. 1647db96d56Sopenharmony_ci format_width = max(limit - len(comment_header), 20) 1657db96d56Sopenharmony_ci newdata = reformat_paragraph(data, format_width) 1667db96d56Sopenharmony_ci # re-split and re-insert the comment header. 1677db96d56Sopenharmony_ci newdata = newdata.split("\n") 1687db96d56Sopenharmony_ci # If the block ends in a \n, we don't want the comment prefix 1697db96d56Sopenharmony_ci # inserted after it. (Im not sure it makes sense to reformat a 1707db96d56Sopenharmony_ci # comment block that is not made of complete lines, but whatever!) 1717db96d56Sopenharmony_ci # Can't think of a clean solution, so we hack away 1727db96d56Sopenharmony_ci block_suffix = "" 1737db96d56Sopenharmony_ci if not newdata[-1]: 1747db96d56Sopenharmony_ci block_suffix = "\n" 1757db96d56Sopenharmony_ci newdata = newdata[:-1] 1767db96d56Sopenharmony_ci return '\n'.join(comment_header+line for line in newdata) + block_suffix 1777db96d56Sopenharmony_ci 1787db96d56Sopenharmony_cidef is_all_white(line): 1797db96d56Sopenharmony_ci """Return True if line is empty or all whitespace.""" 1807db96d56Sopenharmony_ci 1817db96d56Sopenharmony_ci return re.match(r"^\s*$", line) is not None 1827db96d56Sopenharmony_ci 1837db96d56Sopenharmony_cidef get_indent(line): 1847db96d56Sopenharmony_ci """Return the initial space or tab indent of line.""" 1857db96d56Sopenharmony_ci return re.match(r"^([ \t]*)", line).group() 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_cidef get_comment_header(line): 1887db96d56Sopenharmony_ci """Return string with leading whitespace and '#' from line or ''. 1897db96d56Sopenharmony_ci 1907db96d56Sopenharmony_ci A null return indicates that the line is not a comment line. A non- 1917db96d56Sopenharmony_ci null return, such as ' #', will be used to find the other lines of 1927db96d56Sopenharmony_ci a comment block with the same indent. 1937db96d56Sopenharmony_ci """ 1947db96d56Sopenharmony_ci m = re.match(r"^([ \t]*#*)", line) 1957db96d56Sopenharmony_ci if m is None: return "" 1967db96d56Sopenharmony_ci return m.group(1) 1977db96d56Sopenharmony_ci 1987db96d56Sopenharmony_ci 1997db96d56Sopenharmony_ci# Copied from editor.py; importing it would cause an import cycle. 2007db96d56Sopenharmony_ci_line_indent_re = re.compile(r'[ \t]*') 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_cidef get_line_indent(line, tabwidth): 2037db96d56Sopenharmony_ci """Return a line's indentation as (# chars, effective # of spaces). 2047db96d56Sopenharmony_ci 2057db96d56Sopenharmony_ci The effective # of spaces is the length after properly "expanding" 2067db96d56Sopenharmony_ci the tabs into spaces, as done by str.expandtabs(tabwidth). 2077db96d56Sopenharmony_ci """ 2087db96d56Sopenharmony_ci m = _line_indent_re.match(line) 2097db96d56Sopenharmony_ci return m.end(), len(m.group().expandtabs(tabwidth)) 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ci 2127db96d56Sopenharmony_ciclass FormatRegion: 2137db96d56Sopenharmony_ci "Format selected text (region)." 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ci def __init__(self, editwin): 2167db96d56Sopenharmony_ci self.editwin = editwin 2177db96d56Sopenharmony_ci 2187db96d56Sopenharmony_ci def get_region(self): 2197db96d56Sopenharmony_ci """Return line information about the selected text region. 2207db96d56Sopenharmony_ci 2217db96d56Sopenharmony_ci If text is selected, the first and last indices will be 2227db96d56Sopenharmony_ci for the selection. If there is no text selected, the 2237db96d56Sopenharmony_ci indices will be the current cursor location. 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ci Return a tuple containing (first index, last index, 2267db96d56Sopenharmony_ci string representation of text, list of text lines). 2277db96d56Sopenharmony_ci """ 2287db96d56Sopenharmony_ci text = self.editwin.text 2297db96d56Sopenharmony_ci first, last = self.editwin.get_selection_indices() 2307db96d56Sopenharmony_ci if first and last: 2317db96d56Sopenharmony_ci head = text.index(first + " linestart") 2327db96d56Sopenharmony_ci tail = text.index(last + "-1c lineend +1c") 2337db96d56Sopenharmony_ci else: 2347db96d56Sopenharmony_ci head = text.index("insert linestart") 2357db96d56Sopenharmony_ci tail = text.index("insert lineend +1c") 2367db96d56Sopenharmony_ci chars = text.get(head, tail) 2377db96d56Sopenharmony_ci lines = chars.split("\n") 2387db96d56Sopenharmony_ci return head, tail, chars, lines 2397db96d56Sopenharmony_ci 2407db96d56Sopenharmony_ci def set_region(self, head, tail, chars, lines): 2417db96d56Sopenharmony_ci """Replace the text between the given indices. 2427db96d56Sopenharmony_ci 2437db96d56Sopenharmony_ci Args: 2447db96d56Sopenharmony_ci head: Starting index of text to replace. 2457db96d56Sopenharmony_ci tail: Ending index of text to replace. 2467db96d56Sopenharmony_ci chars: Expected to be string of current text 2477db96d56Sopenharmony_ci between head and tail. 2487db96d56Sopenharmony_ci lines: List of new lines to insert between head 2497db96d56Sopenharmony_ci and tail. 2507db96d56Sopenharmony_ci """ 2517db96d56Sopenharmony_ci text = self.editwin.text 2527db96d56Sopenharmony_ci newchars = "\n".join(lines) 2537db96d56Sopenharmony_ci if newchars == chars: 2547db96d56Sopenharmony_ci text.bell() 2557db96d56Sopenharmony_ci return 2567db96d56Sopenharmony_ci text.tag_remove("sel", "1.0", "end") 2577db96d56Sopenharmony_ci text.mark_set("insert", head) 2587db96d56Sopenharmony_ci text.undo_block_start() 2597db96d56Sopenharmony_ci text.delete(head, tail) 2607db96d56Sopenharmony_ci text.insert(head, newchars) 2617db96d56Sopenharmony_ci text.undo_block_stop() 2627db96d56Sopenharmony_ci text.tag_add("sel", head, "insert") 2637db96d56Sopenharmony_ci 2647db96d56Sopenharmony_ci def indent_region_event(self, event=None): 2657db96d56Sopenharmony_ci "Indent region by indentwidth spaces." 2667db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 2677db96d56Sopenharmony_ci for pos in range(len(lines)): 2687db96d56Sopenharmony_ci line = lines[pos] 2697db96d56Sopenharmony_ci if line: 2707db96d56Sopenharmony_ci raw, effective = get_line_indent(line, self.editwin.tabwidth) 2717db96d56Sopenharmony_ci effective = effective + self.editwin.indentwidth 2727db96d56Sopenharmony_ci lines[pos] = self.editwin._make_blanks(effective) + line[raw:] 2737db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 2747db96d56Sopenharmony_ci return "break" 2757db96d56Sopenharmony_ci 2767db96d56Sopenharmony_ci def dedent_region_event(self, event=None): 2777db96d56Sopenharmony_ci "Dedent region by indentwidth spaces." 2787db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 2797db96d56Sopenharmony_ci for pos in range(len(lines)): 2807db96d56Sopenharmony_ci line = lines[pos] 2817db96d56Sopenharmony_ci if line: 2827db96d56Sopenharmony_ci raw, effective = get_line_indent(line, self.editwin.tabwidth) 2837db96d56Sopenharmony_ci effective = max(effective - self.editwin.indentwidth, 0) 2847db96d56Sopenharmony_ci lines[pos] = self.editwin._make_blanks(effective) + line[raw:] 2857db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 2867db96d56Sopenharmony_ci return "break" 2877db96d56Sopenharmony_ci 2887db96d56Sopenharmony_ci def comment_region_event(self, event=None): 2897db96d56Sopenharmony_ci """Comment out each line in region. 2907db96d56Sopenharmony_ci 2917db96d56Sopenharmony_ci ## is appended to the beginning of each line to comment it out. 2927db96d56Sopenharmony_ci """ 2937db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 2947db96d56Sopenharmony_ci for pos in range(len(lines) - 1): 2957db96d56Sopenharmony_ci line = lines[pos] 2967db96d56Sopenharmony_ci lines[pos] = '##' + line 2977db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 2987db96d56Sopenharmony_ci return "break" 2997db96d56Sopenharmony_ci 3007db96d56Sopenharmony_ci def uncomment_region_event(self, event=None): 3017db96d56Sopenharmony_ci """Uncomment each line in region. 3027db96d56Sopenharmony_ci 3037db96d56Sopenharmony_ci Remove ## or # in the first positions of a line. If the comment 3047db96d56Sopenharmony_ci is not in the beginning position, this command will have no effect. 3057db96d56Sopenharmony_ci """ 3067db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 3077db96d56Sopenharmony_ci for pos in range(len(lines)): 3087db96d56Sopenharmony_ci line = lines[pos] 3097db96d56Sopenharmony_ci if not line: 3107db96d56Sopenharmony_ci continue 3117db96d56Sopenharmony_ci if line[:2] == '##': 3127db96d56Sopenharmony_ci line = line[2:] 3137db96d56Sopenharmony_ci elif line[:1] == '#': 3147db96d56Sopenharmony_ci line = line[1:] 3157db96d56Sopenharmony_ci lines[pos] = line 3167db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 3177db96d56Sopenharmony_ci return "break" 3187db96d56Sopenharmony_ci 3197db96d56Sopenharmony_ci def tabify_region_event(self, event=None): 3207db96d56Sopenharmony_ci "Convert leading spaces to tabs for each line in selected region." 3217db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 3227db96d56Sopenharmony_ci tabwidth = self._asktabwidth() 3237db96d56Sopenharmony_ci if tabwidth is None: 3247db96d56Sopenharmony_ci return 3257db96d56Sopenharmony_ci for pos in range(len(lines)): 3267db96d56Sopenharmony_ci line = lines[pos] 3277db96d56Sopenharmony_ci if line: 3287db96d56Sopenharmony_ci raw, effective = get_line_indent(line, tabwidth) 3297db96d56Sopenharmony_ci ntabs, nspaces = divmod(effective, tabwidth) 3307db96d56Sopenharmony_ci lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:] 3317db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 3327db96d56Sopenharmony_ci return "break" 3337db96d56Sopenharmony_ci 3347db96d56Sopenharmony_ci def untabify_region_event(self, event=None): 3357db96d56Sopenharmony_ci "Expand tabs to spaces for each line in region." 3367db96d56Sopenharmony_ci head, tail, chars, lines = self.get_region() 3377db96d56Sopenharmony_ci tabwidth = self._asktabwidth() 3387db96d56Sopenharmony_ci if tabwidth is None: 3397db96d56Sopenharmony_ci return 3407db96d56Sopenharmony_ci for pos in range(len(lines)): 3417db96d56Sopenharmony_ci lines[pos] = lines[pos].expandtabs(tabwidth) 3427db96d56Sopenharmony_ci self.set_region(head, tail, chars, lines) 3437db96d56Sopenharmony_ci return "break" 3447db96d56Sopenharmony_ci 3457db96d56Sopenharmony_ci def _asktabwidth(self): 3467db96d56Sopenharmony_ci "Return value for tab width." 3477db96d56Sopenharmony_ci return askinteger( 3487db96d56Sopenharmony_ci "Tab width", 3497db96d56Sopenharmony_ci "Columns per tab? (2-16)", 3507db96d56Sopenharmony_ci parent=self.editwin.text, 3517db96d56Sopenharmony_ci initialvalue=self.editwin.indentwidth, 3527db96d56Sopenharmony_ci minvalue=2, 3537db96d56Sopenharmony_ci maxvalue=16) 3547db96d56Sopenharmony_ci 3557db96d56Sopenharmony_ci 3567db96d56Sopenharmony_ciclass Indents: 3577db96d56Sopenharmony_ci "Change future indents." 3587db96d56Sopenharmony_ci 3597db96d56Sopenharmony_ci def __init__(self, editwin): 3607db96d56Sopenharmony_ci self.editwin = editwin 3617db96d56Sopenharmony_ci 3627db96d56Sopenharmony_ci def toggle_tabs_event(self, event): 3637db96d56Sopenharmony_ci editwin = self.editwin 3647db96d56Sopenharmony_ci usetabs = editwin.usetabs 3657db96d56Sopenharmony_ci if askyesno( 3667db96d56Sopenharmony_ci "Toggle tabs", 3677db96d56Sopenharmony_ci "Turn tabs " + ("on", "off")[usetabs] + 3687db96d56Sopenharmony_ci "?\nIndent width " + 3697db96d56Sopenharmony_ci ("will be", "remains at")[usetabs] + " 8." + 3707db96d56Sopenharmony_ci "\n Note: a tab is always 8 columns", 3717db96d56Sopenharmony_ci parent=editwin.text): 3727db96d56Sopenharmony_ci editwin.usetabs = not usetabs 3737db96d56Sopenharmony_ci # Try to prevent inconsistent indentation. 3747db96d56Sopenharmony_ci # User must change indent width manually after using tabs. 3757db96d56Sopenharmony_ci editwin.indentwidth = 8 3767db96d56Sopenharmony_ci return "break" 3777db96d56Sopenharmony_ci 3787db96d56Sopenharmony_ci def change_indentwidth_event(self, event): 3797db96d56Sopenharmony_ci editwin = self.editwin 3807db96d56Sopenharmony_ci new = askinteger( 3817db96d56Sopenharmony_ci "Indent width", 3827db96d56Sopenharmony_ci "New indent width (2-16)\n(Always use 8 when using tabs)", 3837db96d56Sopenharmony_ci parent=editwin.text, 3847db96d56Sopenharmony_ci initialvalue=editwin.indentwidth, 3857db96d56Sopenharmony_ci minvalue=2, 3867db96d56Sopenharmony_ci maxvalue=16) 3877db96d56Sopenharmony_ci if new and new != editwin.indentwidth and not editwin.usetabs: 3887db96d56Sopenharmony_ci editwin.indentwidth = new 3897db96d56Sopenharmony_ci return "break" 3907db96d56Sopenharmony_ci 3917db96d56Sopenharmony_ci 3927db96d56Sopenharmony_ciclass Rstrip: # 'Strip Trailing Whitespace" on "Format" menu. 3937db96d56Sopenharmony_ci def __init__(self, editwin): 3947db96d56Sopenharmony_ci self.editwin = editwin 3957db96d56Sopenharmony_ci 3967db96d56Sopenharmony_ci def do_rstrip(self, event=None): 3977db96d56Sopenharmony_ci text = self.editwin.text 3987db96d56Sopenharmony_ci undo = self.editwin.undo 3997db96d56Sopenharmony_ci undo.undo_block_start() 4007db96d56Sopenharmony_ci 4017db96d56Sopenharmony_ci end_line = int(float(text.index('end'))) 4027db96d56Sopenharmony_ci for cur in range(1, end_line): 4037db96d56Sopenharmony_ci txt = text.get('%i.0' % cur, '%i.end' % cur) 4047db96d56Sopenharmony_ci raw = len(txt) 4057db96d56Sopenharmony_ci cut = len(txt.rstrip()) 4067db96d56Sopenharmony_ci # Since text.delete() marks file as changed, even if not, 4077db96d56Sopenharmony_ci # only call it when needed to actually delete something. 4087db96d56Sopenharmony_ci if cut < raw: 4097db96d56Sopenharmony_ci text.delete('%i.%i' % (cur, cut), '%i.end' % cur) 4107db96d56Sopenharmony_ci 4117db96d56Sopenharmony_ci if (text.get('end-2c') == '\n' # File ends with at least 1 newline; 4127db96d56Sopenharmony_ci and not hasattr(self.editwin, 'interp')): # & is not Shell. 4137db96d56Sopenharmony_ci # Delete extra user endlines. 4147db96d56Sopenharmony_ci while (text.index('end-1c') > '1.0' # Stop if file empty. 4157db96d56Sopenharmony_ci and text.get('end-3c') == '\n'): 4167db96d56Sopenharmony_ci text.delete('end-3c') 4177db96d56Sopenharmony_ci # Because tk indexes are slice indexes and never raise, 4187db96d56Sopenharmony_ci # a file with only newlines will be emptied. 4197db96d56Sopenharmony_ci # patchcheck.py does the same. 4207db96d56Sopenharmony_ci 4217db96d56Sopenharmony_ci undo.undo_block_stop() 4227db96d56Sopenharmony_ci 4237db96d56Sopenharmony_ci 4247db96d56Sopenharmony_ciif __name__ == "__main__": 4257db96d56Sopenharmony_ci from unittest import main 4267db96d56Sopenharmony_ci main('idlelib.idle_test.test_format', verbosity=2, exit=False) 427