Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
AutoIndent.py548 linesDownload Raw Back to idle
1import sys2import tokenize3 4from pywin import default_scintilla_encoding5 6from . import PyParse7 8if sys.version_info < (3,):9    # in py2k, tokenize() takes a 'token eater' callback, while10    # generate_tokens is a generator that works with str objects.11    token_generator = tokenize.generate_tokens12else:13    # in py3k tokenize() is the generator working with 'byte' objects, and14    # token_generator is the 'undocumented b/w compat' function that15    # theoretically works with str objects - but actually seems to fail)16    token_generator = tokenize.tokenize17 18 19class AutoIndent:20    menudefs = [21        (22            "edit",23            [24                None,25                ("_Indent region", "<<indent-region>>"),26                ("_Dedent region", "<<dedent-region>>"),27                ("Comment _out region", "<<comment-region>>"),28                ("U_ncomment region", "<<uncomment-region>>"),29                ("Tabify region", "<<tabify-region>>"),30                ("Untabify region", "<<untabify-region>>"),31                ("Toggle tabs", "<<toggle-tabs>>"),32                ("New indent width", "<<change-indentwidth>>"),33            ],34        ),35    ]36 37    keydefs = {38        "<<smart-backspace>>": ["<Key-BackSpace>"],39        "<<newline-and-indent>>": ["<Key-Return>", "<KP_Enter>"],40        "<<smart-indent>>": ["<Key-Tab>"],41    }42 43    windows_keydefs = {44        "<<indent-region>>": ["<Control-bracketright>"],45        "<<dedent-region>>": ["<Control-bracketleft>"],46        "<<comment-region>>": ["<Alt-Key-3>"],47        "<<uncomment-region>>": ["<Alt-Key-4>"],48        "<<tabify-region>>": ["<Alt-Key-5>"],49        "<<untabify-region>>": ["<Alt-Key-6>"],50        "<<toggle-tabs>>": ["<Alt-Key-t>"],51        "<<change-indentwidth>>": ["<Alt-Key-u>"],52    }53 54    unix_keydefs = {55        "<<indent-region>>": [56            "<Alt-bracketright>",57            "<Meta-bracketright>",58            "<Control-bracketright>",59        ],60        "<<dedent-region>>": [61            "<Alt-bracketleft>",62            "<Meta-bracketleft>",63            "<Control-bracketleft>",64        ],65        "<<comment-region>>": ["<Alt-Key-3>", "<Meta-Key-3>"],66        "<<uncomment-region>>": ["<Alt-Key-4>", "<Meta-Key-4>"],67        "<<tabify-region>>": ["<Alt-Key-5>", "<Meta-Key-5>"],68        "<<untabify-region>>": ["<Alt-Key-6>", "<Meta-Key-6>"],69        "<<toggle-tabs>>": ["<Alt-Key-t>"],70        "<<change-indentwidth>>": ["<Alt-Key-u>"],71    }72 73    # usetabs true  -> literal tab characters are used by indent and74    #                  dedent cmds, possibly mixed with spaces if75    #                  indentwidth is not a multiple of tabwidth76    #         false -> tab characters are converted to spaces by indent77    #                  and dedent cmds, and ditto TAB keystrokes78    # indentwidth is the number of characters per logical indent level.79    # tabwidth is the display width of a literal tab character.80    # CAUTION:  telling Tk to use anything other than its default81    # tab setting causes it to use an entirely different tabbing algorithm,82    # treating tab stops as fixed distances from the left margin.83    # Nobody expects this, so for now tabwidth should never be changed.84    usetabs = 185    indentwidth = 486    tabwidth = 8  # for IDLE use, must remain 8 until Tk is fixed87 88    # If context_use_ps1 is true, parsing searches back for a ps1 line;89    # else searches for a popular (if, def, ...) Python stmt.90    context_use_ps1 = 091 92    # When searching backwards for a reliable place to begin parsing,93    # first start num_context_lines[0] lines back, then94    # num_context_lines[1] lines back if that didn't work, and so on.95    # The last value should be huge (larger than the # of lines in a96    # conceivable file).97    # Making the initial values larger slows things down more often.98    num_context_lines = 50, 500, 500000099 100    def __init__(self, editwin):101        self.editwin = editwin102        self.text = editwin.text103 104    def config(self, **options):105        for key, value in options.items():106            if key == "usetabs":107                self.usetabs = value108            elif key == "indentwidth":109                self.indentwidth = value110            elif key == "tabwidth":111                self.tabwidth = value112            elif key == "context_use_ps1":113                self.context_use_ps1 = value114            else:115                raise KeyError("bad option name: %s" % repr(key))116 117    # If ispythonsource and guess are true, guess a good value for118    # indentwidth based on file content (if possible), and if119    # indentwidth != tabwidth set usetabs false.120    # In any case, adjust the Text widget's view of what a tab121    # character means.122 123    def set_indentation_params(self, ispythonsource, guess=1):124        if guess and ispythonsource:125            i = self.guess_indent()126            if 2 <= i <= 8:127                self.indentwidth = i128            if self.indentwidth != self.tabwidth:129                self.usetabs = 0130 131        self.editwin.set_tabwidth(self.tabwidth)132 133    def smart_backspace_event(self, event):134        text = self.text135        first, last = self.editwin.get_selection_indices()136        if first and last:137            text.delete(first, last)138            text.mark_set("insert", first)139            return "break"140        # Delete whitespace left, until hitting a real char or closest141        # preceding virtual tab stop.142        chars = text.get("insert linestart", "insert")143        if chars == "":144            if text.compare("insert", ">", "1.0"):145                # easy: delete preceding newline146                text.delete("insert-1c")147            else:148                text.bell()  # at start of buffer149            return "break"150        if chars[-1] not in " \t":151            # easy: delete preceding real char152            text.delete("insert-1c")153            return "break"154        # Ick.  It may require *inserting* spaces if we back up over a155        # tab character!  This is written to be clear, not fast.156        have = len(chars.expandtabs(self.tabwidth))157        assert have > 0158        want = int((have - 1) / self.indentwidth) * self.indentwidth159        ncharsdeleted = 0160        while 1:161            chars = chars[:-1]162            ncharsdeleted = ncharsdeleted + 1163            have = len(chars.expandtabs(self.tabwidth))164            if have <= want or chars[-1] not in " \t":165                break166        text.undo_block_start()167        text.delete("insert-%dc" % ncharsdeleted, "insert")168        if have < want:169            text.insert("insert", " " * (want - have))170        text.undo_block_stop()171        return "break"172 173    def smart_indent_event(self, event):174        # if intraline selection:175        #     delete it176        # elif multiline selection:177        #     do indent-region & return178        # indent one level179        text = self.text180        first, last = self.editwin.get_selection_indices()181        text.undo_block_start()182        try:183            if first and last:184                if index2line(first) != index2line(last):185                    return self.indent_region_event(event)186                text.delete(first, last)187                text.mark_set("insert", first)188            prefix = text.get("insert linestart", "insert")189            raw, effective = classifyws(prefix, self.tabwidth)190            if raw == len(prefix):191                # only whitespace to the left192                self.reindent_to(effective + self.indentwidth)193            else:194                if self.usetabs:195                    pad = "\t"196                else:197                    effective = len(prefix.expandtabs(self.tabwidth))198                    n = self.indentwidth199                    pad = " " * (n - effective % n)200                text.insert("insert", pad)201            text.see("insert")202            return "break"203        finally:204            text.undo_block_stop()205 206    def newline_and_indent_event(self, event):207        text = self.text208        first, last = self.editwin.get_selection_indices()209        text.undo_block_start()210        try:211            if first and last:212                text.delete(first, last)213                text.mark_set("insert", first)214            line = text.get("insert linestart", "insert")215            i, n = 0, len(line)216            while i < n and line[i] in " \t":217                i = i + 1218            if i == n:219                # the cursor is in or at leading indentation; just inject220                # an empty line at the start and strip space from current line221                text.delete("insert - %d chars" % i, "insert")222                text.insert("insert linestart", "\n")223                return "break"224            indent = line[:i]225            # strip whitespace before insert point226            i = 0227            while line and line[-1] in " \t":228                line = line[:-1]229                i = i + 1230            if i:231                text.delete("insert - %d chars" % i, "insert")232            # strip whitespace after insert point233            while text.get("insert") in " \t":234                text.delete("insert")235            # start new line236            text.insert("insert", "\n")237 238            # adjust indentation for continuations and block239            # open/close first need to find the last stmt240            lno = index2line(text.index("insert"))241            y = PyParse.Parser(self.indentwidth, self.tabwidth)242            for context in self.num_context_lines:243                startat = max(lno - context, 1)244                startatindex = repr(startat) + ".0"245                rawtext = text.get(startatindex, "insert")246                y.set_str(rawtext)247                bod = y.find_good_parse_start(248                    self.context_use_ps1, self._build_char_in_string_func(startatindex)249                )250                if bod is not None or startat == 1:251                    break252            y.set_lo(bod or 0)253            c = y.get_continuation_type()254            if c != PyParse.C_NONE:255                # The current stmt hasn't ended yet.256                if c == PyParse.C_STRING:257                    # inside a string; just mimic the current indent258                    text.insert("insert", indent)259                elif c == PyParse.C_BRACKET:260                    # line up with the first (if any) element of the261                    # last open bracket structure; else indent one262                    # level beyond the indent of the line with the263                    # last open bracket264                    self.reindent_to(y.compute_bracket_indent())265                elif c == PyParse.C_BACKSLASH:266                    # if more than one line in this stmt already, just267                    # mimic the current indent; else if initial line268                    # has a start on an assignment stmt, indent to269                    # beyond leftmost =; else to beyond first chunk of270                    # non-whitespace on initial line271                    if y.get_num_lines_in_stmt() > 1:272                        text.insert("insert", indent)273                    else:274                        self.reindent_to(y.compute_backslash_indent())275                else:276                    assert 0, "bogus continuation type " + repr(c)277                return "break"278 279            # This line starts a brand new stmt; indent relative to280            # indentation of initial line of closest preceding281            # interesting stmt.282            indent = y.get_base_indent_string()283            text.insert("insert", indent)284            if y.is_block_opener():285                self.smart_indent_event(event)286            elif indent and y.is_block_closer():287                self.smart_backspace_event(event)288            return "break"289        finally:290            text.see("insert")291            text.undo_block_stop()292 293    auto_indent = newline_and_indent_event294 295    # Our editwin provides a is_char_in_string function that works296    # with a Tk text index, but PyParse only knows about offsets into297    # a string. This builds a function for PyParse that accepts an298    # offset.299 300    def _build_char_in_string_func(self, startindex):301        def inner(offset, _startindex=startindex, _icis=self.editwin.is_char_in_string):302            return _icis(_startindex + "+%dc" % offset)303 304        return inner305 306    def indent_region_event(self, event):307        head, tail, chars, lines = self.get_region()308        for pos in range(len(lines)):309            line = lines[pos]310            if line:311                raw, effective = classifyws(line, self.tabwidth)312                effective = effective + self.indentwidth313                lines[pos] = self._make_blanks(effective) + line[raw:]314        self.set_region(head, tail, chars, lines)315        return "break"316 317    def dedent_region_event(self, event):318        head, tail, chars, lines = self.get_region()319        for pos in range(len(lines)):320            line = lines[pos]321            if line:322                raw, effective = classifyws(line, self.tabwidth)323                effective = max(effective - self.indentwidth, 0)324                lines[pos] = self._make_blanks(effective) + line[raw:]325        self.set_region(head, tail, chars, lines)326        return "break"327 328    def comment_region_event(self, event):329        head, tail, chars, lines = self.get_region()330        for pos in range(len(lines) - 1):331            line = lines[pos]332            lines[pos] = "##" + line333        self.set_region(head, tail, chars, lines)334 335    def uncomment_region_event(self, event):336        head, tail, chars, lines = self.get_region()337        for pos in range(len(lines)):338            line = lines[pos]339            if not line:340                continue341            if line[:2] == "##":342                line = line[2:]343            elif line[:1] == "#":344                line = line[1:]345            lines[pos] = line346        self.set_region(head, tail, chars, lines)347 348    def tabify_region_event(self, event):349        head, tail, chars, lines = self.get_region()350        tabwidth = self._asktabwidth()351        for pos in range(len(lines)):352            line = lines[pos]353            if line:354                raw, effective = classifyws(line, tabwidth)355                ntabs, nspaces = divmod(effective, tabwidth)356                lines[pos] = "\t" * ntabs + " " * nspaces + line[raw:]357        self.set_region(head, tail, chars, lines)358 359    def untabify_region_event(self, event):360        head, tail, chars, lines = self.get_region()361        tabwidth = self._asktabwidth()362        for pos in range(len(lines)):363            lines[pos] = lines[pos].expandtabs(tabwidth)364        self.set_region(head, tail, chars, lines)365 366    def toggle_tabs_event(self, event):367        if self.editwin.askyesno(368            "Toggle tabs",369            "Turn tabs " + ("on", "off")[self.usetabs] + "?",370            parent=self.text,371        ):372            self.usetabs = not self.usetabs373        return "break"374 375    # XXX this isn't bound to anything -- see class tabwidth comments376    def change_tabwidth_event(self, event):377        new = self._asktabwidth()378        if new != self.tabwidth:379            self.tabwidth = new380            self.set_indentation_params(0, guess=0)381        return "break"382 383    def change_indentwidth_event(self, event):384        new = self.editwin.askinteger(385            "Indent width",386            "New indent width (1-16)",387            parent=self.text,388            initialvalue=self.indentwidth,389            minvalue=1,390            maxvalue=16,391        )392        if new and new != self.indentwidth:393            self.indentwidth = new394        return "break"395 396    def get_region(self):397        text = self.text398        first, last = self.editwin.get_selection_indices()399        if first and last:400            head = text.index(first + " linestart")401            tail = text.index(last + "-1c lineend +1c")402        else:403            head = text.index("insert linestart")404            tail = text.index("insert lineend +1c")405        chars = text.get(head, tail)406        lines = chars.split("\n")407        return head, tail, chars, lines408 409    def set_region(self, head, tail, chars, lines):410        text = self.text411        newchars = "\n".join(lines)412        if newchars == chars:413            text.bell()414            return415        text.tag_remove("sel", "1.0", "end")416        text.mark_set("insert", head)417        text.undo_block_start()418        text.delete(head, tail)419        text.insert(head, newchars)420        text.undo_block_stop()421        text.tag_add("sel", head, "insert")422 423    # Make string that displays as n leading blanks.424 425    def _make_blanks(self, n):426        if self.usetabs:427            ntabs, nspaces = divmod(n, self.tabwidth)428            return "\t" * ntabs + " " * nspaces429        else:430            return " " * n431 432    # Delete from beginning of line to insert point, then reinsert433    # column logical (meaning use tabs if appropriate) spaces.434 435    def reindent_to(self, column):436        text = self.text437        text.undo_block_start()438        if text.compare("insert linestart", "!=", "insert"):439            text.delete("insert linestart", "insert")440        if column:441            text.insert("insert", self._make_blanks(column))442        text.undo_block_stop()443 444    def _asktabwidth(self):445        return (446            self.editwin.askinteger(447                "Tab width",448                "Spaces per tab?",449                parent=self.text,450                initialvalue=self.tabwidth,451                minvalue=1,452                maxvalue=16,453            )454            or self.tabwidth455        )456 457    # Guess indentwidth from text content.458    # Return guessed indentwidth.  This should not be believed unless459    # it's in a reasonable range (e.g., it will be 0 if no indented460    # blocks are found).461 462    def guess_indent(self):463        opener, indented = IndentSearcher(self.text, self.tabwidth).run()464        if opener and indented:465            raw, indentsmall = classifyws(opener, self.tabwidth)466            raw, indentlarge = classifyws(indented, self.tabwidth)467        else:468            indentsmall = indentlarge = 0469        return indentlarge - indentsmall470 471 472# "line.col" -> line, as an int473def index2line(index):474    return int(float(index))475 476 477# Look at the leading whitespace in s.478# Return pair (# of leading ws characters,479#              effective # of leading blanks after expanding480#              tabs to width tabwidth)481 482 483def classifyws(s, tabwidth):484    raw = effective = 0485    for ch in s:486        if ch == " ":487            raw = raw + 1488            effective = effective + 1489        elif ch == "\t":490            raw = raw + 1491            effective = (effective // tabwidth + 1) * tabwidth492        else:493            break494    return raw, effective495 496 497class IndentSearcher:498    # .run() chews over the Text widget, looking for a block opener499    # and the stmt following it.  Returns a pair,500    #     (line containing block opener, line containing stmt)501    # Either or both may be None.502 503    def __init__(self, text, tabwidth):504        self.text = text505        self.tabwidth = tabwidth506        self.i = self.finished = 0507        self.blkopenline = self.indentedline = None508 509    def readline(self):510        if self.finished:511            val = ""512        else:513            i = self.i = self.i + 1514            mark = repr(i) + ".0"515            if self.text.compare(mark, ">=", "end"):516                val = ""517            else:518                val = self.text.get(mark, mark + " lineend+1c")519        # hrm - not sure this is correct in py3k - the source code may have520        # an encoding declared, but the data will *always* be in521        # default_scintilla_encoding - so if anyone looks at the encoding decl522        # in the source they will be wrong.  I think.  Maybe.  Or something...523        return val.encode(default_scintilla_encoding)524 525    def run(self):526        OPENERS = ("class", "def", "for", "if", "try", "while")527        INDENT = tokenize.INDENT528        NAME = tokenize.NAME529 530        save_tabsize = tokenize.tabsize531        tokenize.tabsize = self.tabwidth532        try:533            try:534                for typ, token, start, end, line in token_generator(self.readline):535                    if typ == NAME and token in OPENERS:536                        self.blkopenline = line537                    elif typ == INDENT and self.blkopenline:538                        self.indentedline = line539                        break540 541            except (tokenize.TokenError, IndentationError):542                # since we cut off the tokenizer early, we can trigger543                # spurious errors544                pass545        finally:546            tokenize.tabsize = save_tabsize547        return self.blkopenline, self.indentedline548 
codekingpro/portable-devtools · Team Ai