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