Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
IDLEenvironment.py599 linesDownload Raw Back to scintilla
1# Code that allows Pythonwin to pretend it is IDLE2# (at least as far as most IDLE extensions are concerned)3 4import string5import sys6 7import win32api8import win32con9import win32ui10from pywin import default_scintilla_encoding11from pywin.mfc.dialog import GetSimpleInput12 13wordchars = string.ascii_uppercase + string.ascii_lowercase + string.digits14 15 16class TextError(Exception):  # When a TclError would normally be raised.17    pass18 19 20class EmptyRange(Exception):  # Internally raised.21    pass22 23 24def GetIDLEModule(module):25    try:26        # First get it from Pythonwin it is exists.27        modname = "pywin.idle." + module28        __import__(modname)29    except ImportError as details:30        msg = (31            "The IDLE extension '%s' can not be located.\r\n\r\n"32            "Please correct the installation and restart the"33            " application.\r\n\r\n%s" % (module, details)34        )35        win32ui.MessageBox(msg)36        return None37    mod = sys.modules[modname]38    mod.TclError = TextError  # A hack that can go soon!39    return mod40 41 42# A class that is injected into the IDLE auto-indent extension.43# It allows for decent performance when opening a new file,44# as auto-indent uses the tokenizer module to determine indents.45# The default AutoIndent readline method works OK, but it goes through46# this layer of Tk index indirection for every single line.  For large files47# without indents (and even small files with indents :-) it was pretty slow!48def fast_readline(self):49    if self.finished:50        val = ""51    else:52        if "_scint_lines" not in self.__dict__:53            # XXX - note - assumes this is only called once the file is loaded!54            self._scint_lines = self.text.edit.GetTextRange().split("\n")55        sl = self._scint_lines56        i = self.i = self.i + 157        if i >= len(sl):58            val = ""59        else:60            val = sl[i] + "\n"61    return val.encode(default_scintilla_encoding)62 63 64try:65    GetIDLEModule("AutoIndent").IndentSearcher.readline = fast_readline66except AttributeError:  # GetIDLEModule may return None67    pass68 69 70# A class that attempts to emulate an IDLE editor window.71# Construct with a Pythonwin view.72class IDLEEditorWindow:73    def __init__(self, edit):74        self.edit = edit75        self.text = TkText(edit)76        self.extensions = {}77        self.extension_menus = {}78 79    def close(self):80        self.edit = self.text = None81        self.extension_menus = None82        try:83            for ext in self.extensions.values():84                closer = getattr(ext, "close", None)85                if closer is not None:86                    closer()87        finally:88            self.extensions = {}89 90    def IDLEExtension(self, extension):91        ext = self.extensions.get(extension)92        if ext is not None:93            return ext94        mod = GetIDLEModule(extension)95        if mod is None:96            return None97        klass = getattr(mod, extension)98        ext = self.extensions[extension] = klass(self)99        # Find and bind all the events defined in the extension.100        events = [item for item in dir(klass) if item[-6:] == "_event"]101        for event in events:102            name = "<<%s>>" % (event[:-6].replace("_", "-"),)103            self.edit.bindings.bind(name, getattr(ext, event))104        return ext105 106    def GetMenuItems(self, menu_name):107        # Get all menu items for the menu name (eg, "edit")108        bindings = self.edit.bindings109        ret = []110        for ext in self.extensions.values():111            menudefs = getattr(ext, "menudefs", [])112            for name, items in menudefs:113                if name == menu_name:114                    for text, event in [item for item in items if item is not None]:115                        text = text.replace("&", "&&")116                        text = text.replace("_", "&")117                        ret.append((text, event))118        return ret119 120    ######################################################################121    # The IDLE "Virtual UI" methods that are exposed to the IDLE extensions.122    #123    def askinteger(124        self, caption, prompt, parent=None, initialvalue=0, minvalue=None, maxvalue=None125    ):126        while 1:127            rc = GetSimpleInput(prompt, str(initialvalue), caption)128            if rc is None:129                return 0  # Correct "cancel" semantics?130            err = None131            try:132                rc = int(rc)133            except ValueError:134                err = "Please enter an integer"135            if not err and minvalue is not None and rc < minvalue:136                err = "Please enter an integer greater then or equal to %s" % (137                    minvalue,138                )139            if not err and maxvalue is not None and rc > maxvalue:140                err = "Please enter an integer less then or equal to %s" % (maxvalue,)141            if err:142                win32ui.MessageBox(err, caption, win32con.MB_OK)143                continue144            return rc145 146    def askyesno(self, caption, prompt, parent=None):147        return win32ui.MessageBox(prompt, caption, win32con.MB_YESNO) == win32con.IDYES148 149    ######################################################################150    # The IDLE "Virtual Text Widget" methods that are exposed to the IDLE extensions.151    #152 153    # Is character at text_index in a Python string?  Return 0 for154    # "guaranteed no", true for anything else.155    def is_char_in_string(self, text_index):156        # A helper for the code analyser - we need internal knowledge of157        # the colorizer to get this information158        # This assumes the colorizer has got to this point!159        text_index = self.text._getoffset(text_index)160        c = self.text.edit._GetColorizer()161        if c and c.GetStringStyle(text_index) is None:162            return 0163        return 1164 165    # If a selection is defined in the text widget, return166    # (start, end) as Tkinter text indices, otherwise return167    # (None, None)168    def get_selection_indices(self):169        try:170            first = self.text.index("sel.first")171            last = self.text.index("sel.last")172            return first, last173        except TextError:174            return None, None175 176    def set_tabwidth(self, width):177        self.edit.SCISetTabWidth(width)178 179    def get_tabwidth(self):180        return self.edit.GetTabWidth()181 182 183# A class providing the generic "Call Tips" interface184class CallTips:185    def __init__(self, edit):186        self.edit = edit187 188    def showtip(self, tip_text):189        self.edit.SCICallTipShow(tip_text)190 191    def hidetip(self):192        self.edit.SCICallTipCancel()193 194 195########################################196#197# Helpers for the TkText emulation.198def TkOffsetToIndex(offset, edit):199    lineoff = 0200    # May be 1 > actual end if we pretended there was a trailing '\n'201    offset = min(offset, edit.GetTextLength())202    line = edit.LineFromChar(offset)203    lineIndex = edit.LineIndex(line)204    return "%d.%d" % (line + 1, offset - lineIndex)205 206 207def _NextTok(str, pos):208    # Returns (token, endPos)209    end = len(str)210    if pos >= end:211        return None, 0212    while pos < end and str[pos] in string.whitespace:213        pos = pos + 1214    # Special case for +-215    if str[pos] in "+-":216        return str[pos], pos + 1217    # Digits also a special case.218    endPos = pos219    while endPos < end and str[endPos] in string.digits + ".":220        endPos = endPos + 1221    if pos != endPos:222        return str[pos:endPos], endPos223    endPos = pos224    while endPos < end and str[endPos] not in string.whitespace + string.digits + "+-":225        endPos = endPos + 1226    if pos != endPos:227        return str[pos:endPos], endPos228    return None, 0229 230 231def TkIndexToOffset(bm, edit, marks):232    base, nextTokPos = _NextTok(bm, 0)233    if base is None:234        raise ValueError("Empty bookmark ID!")235    if base.find(".") > 0:236        try:237            line, col = base.split(".", 2)238            if col == "first" or col == "last":239                # Tag name240                if line != "sel":241                    raise ValueError("Tags arent here!")242                sel = edit.GetSel()243                if sel[0] == sel[1]:244                    raise EmptyRange245                if col == "first":246                    pos = sel[0]247                else:248                    pos = sel[1]249            else:250                # Lines are 1 based for tkinter251                line = int(line) - 1252                if line > edit.GetLineCount():253                    pos = edit.GetTextLength() + 1254                else:255                    pos = edit.LineIndex(line)256                    if pos == -1:257                        pos = edit.GetTextLength()258                    pos = pos + int(col)259        except (ValueError, IndexError):260            raise ValueError("Unexpected literal in '%s'" % base)261    elif base == "insert":262        pos = edit.GetSel()[0]263    elif base == "end":264        pos = edit.GetTextLength()265        # Pretend there is a trailing '\n' if necessary266        if pos and edit.SCIGetCharAt(pos - 1) != "\n":267            pos = pos + 1268    else:269        try:270            pos = marks[base]271        except KeyError:272            raise ValueError("Unsupported base offset or undefined mark '%s'" % base)273 274    while 1:275        word, nextTokPos = _NextTok(bm, nextTokPos)276        if word is None:277            break278        if word in ("+", "-"):279            num, nextTokPos = _NextTok(bm, nextTokPos)280            if num is None:281                raise ValueError("+/- operator needs 2 args")282            what, nextTokPos = _NextTok(bm, nextTokPos)283            if what is None:284                raise ValueError("+/- operator needs 2 args")285            if what[0] != "c":286                raise ValueError("+/- only supports chars")287            if word == "+":288                pos = pos + int(num)289            else:290                pos = pos - int(num)291        elif word == "wordstart":292            while pos > 0 and edit.SCIGetCharAt(pos - 1) in wordchars:293                pos = pos - 1294        elif word == "wordend":295            end = edit.GetTextLength()296            while pos < end and edit.SCIGetCharAt(pos) in wordchars:297                pos = pos + 1298        elif word == "linestart":299            while pos > 0 and edit.SCIGetCharAt(pos - 1) not in "\n\r":300                pos = pos - 1301        elif word == "lineend":302            end = edit.GetTextLength()303            while pos < end and edit.SCIGetCharAt(pos) not in "\n\r":304                pos = pos + 1305        else:306            raise ValueError("Unsupported relative offset '%s'" % word)307    return max(pos, 0)  # Tkinter is tollerant of -ve indexes - we aren't308 309 310# A class that resembles an IDLE (ie, a Tk) text widget.311# Construct with an edit object (eg, an editor view)312class TkText:313    def __init__(self, edit):314        self.calltips = None315        self.edit = edit316        self.marks = {}317 318    ##	def __getattr__(self, attr):319    ##		if attr=="tk": return self # So text.tk.call works.320    ##		if attr=="master": return None # ditto!321    ##		raise AttributeError, attr322    ##	def __getitem__(self, item):323    ##		if item=="tabs":324    ##			size = self.edit.GetTabWidth()325    ##			if size==8: return "" # Tk default326    ##			return size # correct semantics?327    ##		elif item=="font": # Used for measurements we dont need to do!328    ##			return "Dont know the font"329    ##		raise IndexError, "Invalid index '%s'" % item330    def make_calltip_window(self):331        if self.calltips is None:332            self.calltips = CallTips(self.edit)333        return self.calltips334 335    def _getoffset(self, index):336        return TkIndexToOffset(index, self.edit, self.marks)337 338    def _getindex(self, off):339        return TkOffsetToIndex(off, self.edit)340 341    def _fix_indexes(self, start, end):342        # first some magic to handle skipping over utf8 extended chars.343        while start > 0 and ord(self.edit.SCIGetCharAt(start)) & 0xC0 == 0x80:344            start -= 1345        while (346            end < self.edit.GetTextLength()347            and ord(self.edit.SCIGetCharAt(end)) & 0xC0 == 0x80348        ):349            end += 1350        # now handling fixing \r\n->\n disparities...351        if (352            start > 0353            and self.edit.SCIGetCharAt(start) == "\n"354            and self.edit.SCIGetCharAt(start - 1) == "\r"355        ):356            start = start - 1357        if (358            end < self.edit.GetTextLength()359            and self.edit.SCIGetCharAt(end - 1) == "\r"360            and self.edit.SCIGetCharAt(end) == "\n"361        ):362            end = end + 1363        return start, end364 365    ##	def get_tab_width(self):366    ##		return self.edit.GetTabWidth()367    ##	def call(self, *rest):368    ##		# Crap to support Tk measurement hacks for tab widths369    ##		if rest[0] != "font" or rest[1] != "measure":370    ##			raise ValueError, "Unsupport call type"371    ##		return len(rest[5])372    ##	def configure(self, **kw):373    ##		for name, val in kw.items():374    ##			if name=="tabs":375    ##				self.edit.SCISetTabWidth(int(val))376    ##			else:377    ##				raise ValueError, "Unsupported configuration item %s" % kw378    def bind(self, binding, handler):379        self.edit.bindings.bind(binding, handler)380 381    def get(self, start, end=None):382        try:383            start = self._getoffset(start)384            if end is None:385                end = start + 1386            else:387                end = self._getoffset(end)388        except EmptyRange:389            return ""390        # Simple semantic checks to conform to the Tk text interface391        if end <= start:392            return ""393        max = self.edit.GetTextLength()394        checkEnd = 0395        if end > max:396            end = max397            checkEnd = 1398        start, end = self._fix_indexes(start, end)399        ret = self.edit.GetTextRange(start, end)400        # pretend a trailing '\n' exists if necessary.401        if checkEnd and (not ret or ret[-1] != "\n"):402            ret = ret + "\n"403        return ret.replace("\r", "")404 405    def index(self, spec):406        try:407            return self._getindex(self._getoffset(spec))408        except EmptyRange:409            return ""410 411    def insert(self, pos, text):412        try:413            pos = self._getoffset(pos)414        except EmptyRange:415            raise TextError("Empty range")416        self.edit.SetSel((pos, pos))417        # IDLE only deals with "\n" - we will be nicer418 419        bits = text.split("\n")420        self.edit.SCIAddText(bits[0])421        for bit in bits[1:]:422            self.edit.SCINewline()423            self.edit.SCIAddText(bit)424 425    def delete(self, start, end=None):426        try:427            start = self._getoffset(start)428            if end is not None:429                end = self._getoffset(end)430        except EmptyRange:431            raise TextError("Empty range")432        # If end is specified and == start, then we must delete nothing.433        if start == end:434            return435        # If end is not specified, delete one char436        if end is None:437            end = start + 1438        else:439            # Tk says not to delete in this case, but our control would.440            if end < start:441                return442        if start == self.edit.GetTextLength():443            return  # Nothing to delete.444        old = self.edit.GetSel()[0]  # Lose a selection445        # Hack for partial '\r\n' and UTF-8 char removal446        start, end = self._fix_indexes(start, end)447        self.edit.SetSel((start, end))448        self.edit.Clear()449        if old >= start and old < end:450            old = start451        elif old >= end:452            old = old - (end - start)453        self.edit.SetSel(old)454 455    def bell(self):456        win32api.MessageBeep()457 458    def see(self, pos):459        # Most commands we use in Scintilla actually force the selection460        # to be seen, making this unnecessary.461        pass462 463    def mark_set(self, name, pos):464        try:465            pos = self._getoffset(pos)466        except EmptyRange:467            raise TextError("Empty range '%s'" % pos)468        if name == "insert":469            self.edit.SetSel(pos)470        else:471            self.marks[name] = pos472 473    def tag_add(self, name, start, end):474        if name != "sel":475            raise ValueError("Only sel tag is supported")476        try:477            start = self._getoffset(start)478            end = self._getoffset(end)479        except EmptyRange:480            raise TextError("Empty range")481        self.edit.SetSel(start, end)482 483    def tag_remove(self, name, start, end):484        if name != "sel" or start != "1.0" or end != "end":485            raise ValueError("Cant remove this tag")486        # Turn the sel into a cursor487        self.edit.SetSel(self.edit.GetSel()[0])488 489    def compare(self, i1, op, i2):490        try:491            i1 = self._getoffset(i1)492        except EmptyRange:493            i1 = ""494        try:495            i2 = self._getoffset(i2)496        except EmptyRange:497            i2 = ""498        return eval("%d%s%d" % (i1, op, i2))499 500    def undo_block_start(self):501        self.edit.SCIBeginUndoAction()502 503    def undo_block_stop(self):504        self.edit.SCIEndUndoAction()505 506 507######################################################################508#509# Test related code.510#511######################################################################512def TestCheck(index, edit, expected=None):513    rc = TkIndexToOffset(index, edit, {})514    if rc != expected:515        print("ERROR: Index", index, ", expected", expected, "but got", rc)516 517 518def TestGet(fr, to, t, expected):519    got = t.get(fr, to)520    if got != expected:521        print(522            "ERROR: get(%s, %s) expected %s, but got %s"523            % (repr(fr), repr(to), repr(expected), repr(got))524        )525 526 527def test():528    import pywin.framework.editor529 530    d = pywin.framework.editor.editorTemplate.OpenDocumentFile(None)531    e = d.GetFirstView()532    t = TkText(e)533    e.SCIAddText("hi there how\nare you today\r\nI hope you are well")534    e.SetSel((4, 4))535 536    skip = """537	TestCheck("insert", e, 4)538	TestCheck("insert wordstart", e, 3)539	TestCheck("insert wordend", e, 8)540	TestCheck("insert linestart", e, 0)541	TestCheck("insert lineend", e, 12)542	TestCheck("insert + 4 chars", e, 8)543	TestCheck("insert +4c", e, 8)544	TestCheck("insert - 2 chars", e, 2)545	TestCheck("insert -2c", e, 2)546	TestCheck("insert-2c", e, 2)547	TestCheck("insert-2 c", e, 2)548	TestCheck("insert- 2c", e, 2)549	TestCheck("1.1", e, 1)550	TestCheck("1.0", e, 0)551	TestCheck("2.0", e, 13)552	try:553		TestCheck("sel.first", e, 0)554		print "*** sel.first worked with an empty selection"555	except TextError:556		pass557	e.SetSel((4,5))558	TestCheck("sel.first- 2c", e, 2)559	TestCheck("sel.last- 2c", e, 3)560	"""561    # Check EOL semantics562    e.SetSel((4, 4))563    TestGet("insert lineend", "insert lineend +1c", t, "\n")564    e.SetSel((20, 20))565    TestGet("insert lineend", "insert lineend +1c", t, "\n")566    e.SetSel((35, 35))567    TestGet("insert lineend", "insert lineend +1c", t, "\n")568 569 570class IDLEWrapper:571    def __init__(self, control):572        self.text = control573 574 575def IDLETest(extension):576    import os577    import sys578 579    modname = "pywin.idle." + extension580    __import__(modname)581    mod = sys.modules[modname]582    mod.TclError = TextError583    klass = getattr(mod, extension)584 585    # Create a new Scintilla Window.586    import pywin.framework.editor587 588    d = pywin.framework.editor.editorTemplate.OpenDocumentFile(None)589    v = d.GetFirstView()590    fname = os.path.splitext(__file__)[0] + ".py"591    v.SCIAddText(open(fname).read())592    d.SetModifiedFlag(0)593    r = klass(IDLEWrapper(TkText(v)))594    return r595 596 597if __name__ == "__main__":598    test()599 
codekingpro/portable-devtools · Team Ai