From 3b4ca0ddad8d1e224f71e89f4c7fbc8de5c6edc4 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 10 Oct 1998 18:48:31 +0000 Subject: Initial checking of Tk-based Python IDE. Features: text editor with syntax coloring and undo; subclassed into interactive Python shell which adds history. --- Tools/idle/AutoExpand.py | 75 +++++++ Tools/idle/AutoIndent.py | 124 +++++++++++ Tools/idle/Bindings.py | 92 ++++++++ Tools/idle/ColorDelegator.py | 203 ++++++++++++++++++ Tools/idle/Delegator.py | 33 +++ Tools/idle/EditorWindow.py | 175 +++++++++++++++ Tools/idle/FileList.py | 169 +++++++++++++++ Tools/idle/FrameViewer.py | 38 ++++ Tools/idle/HelpWindow.py | 65 ++++++ Tools/idle/History.py | 73 +++++++ Tools/idle/IOBinding.py | 158 ++++++++++++++ Tools/idle/IdleHistory.py | 73 +++++++ Tools/idle/Outline.py | 46 ++++ Tools/idle/Percolator.py | 77 +++++++ Tools/idle/PopupMenu.py | 86 ++++++++ Tools/idle/PyShell.py | 475 +++++++++++++++++++++++++++++++++++++++++ Tools/idle/README | 79 +++++++ Tools/idle/SearchBinding.py | 84 ++++++++ Tools/idle/StackViewer.py | 288 +++++++++++++++++++++++++ Tools/idle/UndoDelegator.py | 269 +++++++++++++++++++++++ Tools/idle/WidgetRedirector.py | 84 ++++++++ Tools/idle/help.txt | 60 ++++++ Tools/idle/idle.pyw | 3 + 23 files changed, 2829 insertions(+) create mode 100644 Tools/idle/AutoExpand.py create mode 100644 Tools/idle/AutoIndent.py create mode 100644 Tools/idle/Bindings.py create mode 100644 Tools/idle/ColorDelegator.py create mode 100644 Tools/idle/Delegator.py create mode 100644 Tools/idle/EditorWindow.py create mode 100644 Tools/idle/FileList.py create mode 100644 Tools/idle/FrameViewer.py create mode 100644 Tools/idle/HelpWindow.py create mode 100644 Tools/idle/History.py create mode 100644 Tools/idle/IOBinding.py create mode 100644 Tools/idle/IdleHistory.py create mode 100644 Tools/idle/Outline.py create mode 100644 Tools/idle/Percolator.py create mode 100644 Tools/idle/PopupMenu.py create mode 100644 Tools/idle/PyShell.py create mode 100644 Tools/idle/README create mode 100644 Tools/idle/SearchBinding.py create mode 100644 Tools/idle/StackViewer.py create mode 100644 Tools/idle/UndoDelegator.py create mode 100644 Tools/idle/WidgetRedirector.py create mode 100644 Tools/idle/help.txt create mode 100644 Tools/idle/idle.pyw diff --git a/Tools/idle/AutoExpand.py b/Tools/idle/AutoExpand.py new file mode 100644 index 0000000..0d80ce8 --- /dev/null +++ b/Tools/idle/AutoExpand.py @@ -0,0 +1,75 @@ +import string +import re + +class AutoExpand: + + wordchars = string.letters + string.digits + "_" + + def __init__(self, text): + self.text = text + self.text.wordlist = None + self.state = None + self.text.bind("<>", self.autoexpand) + + def autoexpand(self, event): + curinsert = self.text.index("insert") + curline = self.text.get("insert linestart", "insert lineend") + if not self.state: + words = self.getwords() + index = 0 + else: + words, index, insert, line = self.state + if insert != curinsert or line != curline: + words = self.getwords() + index = 0 + if not words: + self.text.bell() + return "break" + word = self.getprevword() + self.text.delete("insert - %d chars" % len(word), "insert") + newword = words[index] + index = (index + 1) % len(words) + if index == 0: + self.text.bell() # Warn we cycled around + self.text.insert("insert", newword) + curinsert = self.text.index("insert") + curline = self.text.get("insert linestart", "insert lineend") + self.state = words, index, curinsert, curline + return "break" + + def getwords(self): + word = self.getprevword() + if not word: + return [] + before = self.text.get("1.0", "insert wordstart") + wbefore = re.findall(r"\b" + word + r"\w+\b", before) + del before + after = self.text.get("insert wordend", "end") + wafter = re.findall(r"\b" + word + r"\w+\b", after) + del after + if not wbefore and not wafter: + return [] + words = [] + dict = {} + # search backwards through words before + wbefore.reverse() + for w in wbefore: + if dict.get(w): + continue + words.append(w) + dict[w] = w + # search onwards through words after + for w in wafter: + if dict.get(w): + continue + words.append(w) + dict[w] = w + words.append(word) + return words + + def getprevword(self): + line = self.text.get("insert linestart", "insert") + i = len(line) + while i > 0 and line[i-1] in self.wordchars: + i = i-1 + return line[i:] diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py new file mode 100644 index 0000000..d800589 --- /dev/null +++ b/Tools/idle/AutoIndent.py @@ -0,0 +1,124 @@ +import string + +class AutoIndent: + + def __init__(self, text, prefertabs=0, spaceindent=4*" "): + self.text = text + self.prefertabs = prefertabs + self.spaceindent = spaceindent + text.bind("<>", self.autoindent) + text.bind("<>", self.indentregion) + text.bind("<>", self.dedentregion) + text.bind("<>", self.commentregion) + text.bind("<>", self.uncommentregion) + + def config(self, **options): + for key, value in options.items(): + if key == 'prefertabs': + self.prefertabs = value + elif key == 'spaceindent': + self.spaceindent = value + else: + raise KeyError, "bad option name: %s" % `key` + + def autoindent(self, event): + text = self.text + line = text.get("insert linestart", "insert") + i, n = 0, len(line) + while i < n and line[i] in " \t": + i = i+1 + indent = line[:i] + lastchar = text.get("insert -1c") + if lastchar == ":": + if not indent: + if self.prefertabs: + indent = "\t" + else: + indent = self.spaceindent + elif indent[-1] == "\t": + indent = indent + "\t" + else: + indent = indent + self.spaceindent + text.insert("insert", "\n" + indent) + text.see("insert") + return "break" + + def indentregion(self, event): + head, tail, chars, lines = self.getregion() + for pos in range(len(lines)): + line = lines[pos] + if line: + i, n = 0, len(line) + while i < n and line[i] in " \t": + i = i+1 + line = line[:i] + " " + line[i:] + lines[pos] = line + self.setregion(head, tail, chars, lines) + return "break" + + def dedentregion(self, event): + head, tail, chars, lines = self.getregion() + for pos in range(len(lines)): + line = lines[pos] + if line: + i, n = 0, len(line) + while i < n and line[i] in " \t": + i = i+1 + indent, line = line[:i], line[i:] + if indent: + if indent == "\t" or indent[-2:] == "\t\t": + indent = indent[:-1] + " " + elif indent[-4:] == " ": + indent = indent[:-4] + else: + indent = string.expandtabs(indent, 8) + indent = indent[:-4] + line = indent + line + lines[pos] = line + self.setregion(head, tail, chars, lines) + return "break" + + def commentregion(self, event): + head, tail, chars, lines = self.getregion() + for pos in range(len(lines)): + line = lines[pos] + if not line: + continue + lines[pos] = '##' + line + self.setregion(head, tail, chars, lines) + + def uncommentregion(self, event): + head, tail, chars, lines = self.getregion() + for pos in range(len(lines)): + line = lines[pos] + if not line: + continue + if line[:2] == '##': + line = line[2:] + elif line[:1] == '#': + line = line[1:] + lines[pos] = line + self.setregion(head, tail, chars, lines) + + def getregion(self): + text = self.text + head = text.index("sel.first linestart") + tail = text.index("sel.last -1c lineend +1c") + if not (head and tail): + head = text.index("insert linestart") + tail = text.index("insert lineend +1c") + chars = text.get(head, tail) + lines = string.split(chars, "\n") + return head, tail, chars, lines + + def setregion(self, head, tail, chars, lines): + text = self.text + newchars = string.join(lines, "\n") + if newchars == chars: + text.bell() + return + text.tag_remove("sel", "1.0", "end") + text.mark_set("insert", head) + text.delete(head, tail) + text.insert(head, newchars) + text.tag_add("sel", head, "insert") diff --git a/Tools/idle/Bindings.py b/Tools/idle/Bindings.py new file mode 100644 index 0000000..9e2e791 --- /dev/null +++ b/Tools/idle/Bindings.py @@ -0,0 +1,92 @@ +# The first item of each tuple is the virtual event; +# each of the remaining items is an actual key binding for the event. +# (This conveniently forms an argument list for event_add().) + +win_bindings = [ + ("<>", "", ""), + + ("<>", "", ""), + + ("<>", "", ""), + ("<>", ""), + + ("<>", ""), + ("<>", ""), + + ("<>", ""), + ("<>", ""), + + ("<>", "", ""), + ("<>", "", ""), + + ("<>", "", ""), + ("<>", "", ""), + + ("<>", ""), + + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", "", ""), + + ("<>", ""), + ("<>", ""), + ("<>", ""), +] + +emacs_bindings = [ + ("<>", "", ""), + ("<>", ""), + + ("<>", "", ""), + + ("<>", "", ""), + ("<>", ""), + + ("<>", ""), + ("<>", ""), + + ("<>", + "", "", ""), + ("<>", + "", "", ""), + + ("<>", "", ""), + ("<>", "", ""), + + ("<>", "", ""), + ("<>", "", ""), + + ("<>", ""), + + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", ""), + + ("<>", ""), + ("<>", ""), + ("<>", ""), + ("<>", "", ""), + + ("<>", ""), + ("<>", "", ""), + ("<>", ""), +] + +default_bindings = emacs_bindings + +def apply_bindings(text, bindings=default_bindings): + event_add = text.event_add + for args in bindings: + apply(event_add, args) diff --git a/Tools/idle/ColorDelegator.py b/Tools/idle/ColorDelegator.py new file mode 100644 index 0000000..0cbf2fb --- /dev/null +++ b/Tools/idle/ColorDelegator.py @@ -0,0 +1,203 @@ +import time +import string +import re +import keyword +from Tkinter import * +from Delegator import Delegator + +__debug__ = 0 + + +def any(name, list): + return "(?P<%s>" % name + string.join(list, "|") + ")" + +def make_pat(): + kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b" + comment = any("COMMENT", [r"#[^\n]*"]) + sqstring = r"(\b[rR])?'([^'\\\n]|\\.)*'?" + dqstring = r'(\b[rR])?"([^"\\\n]|\\.)*"?' + sq3string = r"(\b[rR])?'''([^'\\]|\\.|'(?!''))*(''')?" + dq3string = r'(\b[rR])?"""([^"\\]|\\.|"(?!""))*(""")?' + string = any("STRING", [sq3string, dq3string, sqstring, dqstring]) + return kw + "|" + comment + "|" + string + "|" + any("SYNC", [r"\n"]) + +prog = re.compile(make_pat(), re.S) +idprog = re.compile(r"\s+(\w+)", re.S) + +class ColorDelegator(Delegator): + + def __init__(self): + Delegator.__init__(self) + self.prog = prog + self.idprog = idprog + + def setdelegate(self, delegate): + if self.delegate is not None: + self.unbind("<>") + Delegator.setdelegate(self, delegate) + if delegate is not None: + self.config_colors() + self.bind("<>", self.toggle_colorize_event) + self.notify_range("1.0", "end") + + def config_colors(self): + for tag, cnf in self.tagdefs.items(): + if cnf: + apply(self.tag_configure, (tag,), cnf) + + tagdefs = { + "COMMENT": {"foreground": "#dd0000"}, + "KEYWORD": {"foreground": "#ff7700"}, + "STRING": {"foreground": "#00aa00"}, + "DEFINITION": {"foreground": "#0000ff"}, + + "SYNC": {}, #{"background": "#ffff00"}, + "TODO": {}, #{"background": "#cccccc"}, + } + + def insert(self, index, chars, tags=None): + index = self.index(index) + self.delegate.insert(index, chars, tags) + self.notify_range(index, index + "+%dc" % len(chars)) + + def delete(self, index1, index2=None): + index1 = self.index(index1) + self.delegate.delete(index1, index2) + self.notify_range(index1) + + after_id = None + allow_colorizing = 1 + colorizing = 0 + + def notify_range(self, index1, index2=None): + self.tag_add("TODO", index1, index2) + if self.after_id: + if __debug__: print "colorizing already scheduled" + return + if self.colorizing: + self.stop_colorizing = 1 + if __debug__: print "stop colorizing" + if self.allow_colorizing: + if __debug__: print "schedule colorizing" + self.after_id = self.after(1, self.recolorize) + + def close(self): + if self.after_id: + after_id = self.after_id + self.after_id = None + if __debug__: print "cancel scheduled recolorizer" + self.after_cancel(after_id) + self.allow_colorizing = 0 + self.stop_colorizing = 1 + + def toggle_colorize_event(self, event): + if self.after_id: + after_id = self.after_id + self.after_id = None + if __debug__: print "cancel scheduled recolorizer" + self.after_cancel(after_id) + if self.allow_colorizing and self.colorizing: + if __debug__: print "stop colorizing" + self.stop_colorizing = 1 + self.allow_colorizing = not self.allow_colorizing + if self.allow_colorizing and not self.colorizing: + self.after_id = self.after(1, self.recolorize) + if __debug__: + print "auto colorizing turned", self.allow_colorizing and "on" or "off" + return "break" + + def recolorize(self): + self.after_id = None + if not self.delegate: + if __debug__: print "no delegate" + return + if not self.allow_colorizing: + if __debug__: print "auto colorizing is off" + return + if self.colorizing: + if __debug__: print "already colorizing" + return + try: + self.stop_colorizing = 0 + self.colorizing = 1 + if __debug__: print "colorizing..." + t0 = time.clock() + self.recolorize_main() + t1 = time.clock() + if __debug__: print "%.3f seconds" % (t1-t0) + finally: + self.colorizing = 0 + if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"): + if __debug__: print "reschedule colorizing" + self.after_id = self.after(1, self.recolorize) + + def recolorize_main(self): + next = "1.0" + was_ok = is_ok = 0 + while 1: + item = self.tag_nextrange("TODO", next) + if not item: + break + head, tail = item + self.tag_remove("SYNC", head, tail) + item = self.tag_prevrange("SYNC", head) + if item: + head = item[1] + else: + head = "1.0" + + chars = "" + mark = head + is_ok = was_ok = 0 + while not (was_ok and is_ok): + next = self.index(mark + " lineend +1c") + was_ok = "SYNC" in self.tag_names(next + "-1c") + line = self.get(mark, next) + ##print head, "get", mark, next, "->", `line` + if not line: + return + for tag in self.tagdefs.keys(): + self.tag_remove(tag, mark, next) + chars = chars + line + m = self.prog.search(chars) + while m: + i, j = m.span() + for key, value in m.groupdict().items(): + if value: + a, b = m.span(key) + self.tag_add(key, + head + "+%dc" % a, + head + "+%dc" % b) + if value in ("def", "class"): + m1 = self.idprog.match(chars, b) + if m1: + a, b = m1.span(1) + self.tag_add("DEFINITION", + head + "+%dc" % a, + head + "+%dc" % b) + m = self.prog.search(chars, j) + is_ok = "SYNC" in self.tag_names(next + "-1c") + mark = next + if is_ok: + head = mark + chars = "" + self.update() + if self.stop_colorizing: + if __debug__: print "colorizing stopped" + return + + +def main(): + from Percolator import Percolator + root = Tk() + root.wm_protocol("WM_DELETE_WINDOW", root.quit) + text = Text(background="white") + text.pack(expand=1, fill="both") + text.focus_set() + p = Percolator(text) + d = ColorDelegator() + p.insertfilter(d) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/Delegator.py b/Tools/idle/Delegator.py new file mode 100644 index 0000000..6125591 --- /dev/null +++ b/Tools/idle/Delegator.py @@ -0,0 +1,33 @@ +class Delegator: + + # The cache is only used to be able to change delegates! + + def __init__(self, delegate=None): + self.delegate = delegate + self.__cache = {} + + def __getattr__(self, name): + attr = getattr(self.delegate, name) # May raise AttributeError + setattr(self, name, attr) + self.__cache[name] = attr + return attr + + def resetcache(self): + for key in self.__cache.keys(): + try: + delattr(self, key) + except AttributeError: + pass + self.__cache.clear() + + def cachereport(self): + keys = self.__cache.keys() + keys.sort() + print keys + + def setdelegate(self, delegate): + self.resetcache() + self.delegate = delegate + + def getdelegate(self): + return self.delegate diff --git a/Tools/idle/EditorWindow.py b/Tools/idle/EditorWindow.py new file mode 100644 index 0000000..2028ed1 --- /dev/null +++ b/Tools/idle/EditorWindow.py @@ -0,0 +1,175 @@ +import sys +import os +import string +from Tkinter import * + + +class EditorWindow: + + from Percolator import Percolator + from ColorDelegator import ColorDelegator + from UndoDelegator import UndoDelegator + from IOBinding import IOBinding + from SearchBinding import SearchBinding + from AutoIndent import AutoIndent + from AutoExpand import AutoExpand + import Bindings + + def __init__(self, root, filename=None): + self.top = top = Toplevel(root) + self.vbar = vbar = Scrollbar(top, name='vbar') + self.text = text = Text(top, name='text') + + self.Bindings.apply_bindings(text) + + self.top.protocol("WM_DELETE_WINDOW", self.close) + self.top.bind("<>", self.close_event) + self.text.bind("<>", self.center_insert_event) + + vbar['command'] = text.yview + vbar.pack(side=RIGHT, fill=Y) + + text['yscrollcommand'] = vbar.set + text['background'] = 'white' + if sys.platform[:3] == 'win': + text['font'] = ("lucida console", 8) + text.pack(side=LEFT, fill=BOTH, expand=1) + text.focus_set() + + self.auto = auto = self.AutoIndent(text) + self.autoex = self.AutoExpand(text) + self.per = per = self.Percolator(text) + if self.ispythonsource(filename): + self.color = color = self.ColorDelegator(); per.insertfilter(color) + ##print "Initial colorizer" + else: + ##print "No initial colorizer" + self.color = None + self.undo = undo = self.UndoDelegator(); per.insertfilter(undo) + self.search = search = self.SearchBinding(undo) + self.io = io = self.IOBinding(undo) + + undo.set_saved_change_hook(self.saved_change_hook) + io.set_filename_change_hook(self.filename_change_hook) + + if filename: + if os.path.exists(filename): + io.loadfile(filename) + else: + io.set_filename(filename) + + self.saved_change_hook() + + def gotoline(self, lineno): + if lineno is not None and lineno > 0: + self.text.mark_set("insert", "%d.0" % lineno) + self.text.tag_remove("sel", "1.0", "end") + self.text.tag_add("sel", "insert", "insert +1l") + self.center() + + def ispythonsource(self, filename): + if not filename: + return 1 + if os.path.normcase(filename[-3:]) == ".py": + return 1 + try: + f = open(filename) + line = f.readline() + f.close() + except IOError: + return 0 + return line[:2] == '#!' and string.find(line, 'python') >= 0 + + close_hook = None + + def set_close_hook(self, close_hook): + self.close_hook = close_hook + + def filename_change_hook(self): + self.saved_change_hook() + if self.ispythonsource(self.io.filename): + self.addcolorizer() + else: + self.rmcolorizer() + + def addcolorizer(self): + if self.color: + return + ##print "Add colorizer" + self.per.removefilter(self.undo) + self.color = self.ColorDelegator() + self.per.insertfilter(self.color) + self.per.insertfilter(self.undo) + + def rmcolorizer(self): + if not self.color: + return + ##print "Remove colorizer" + self.per.removefilter(self.undo) + self.per.removefilter(self.color) + self.color = None + self.per.insertfilter(self.undo) + + def saved_change_hook(self): + if self.io.filename: + title = self.io.filename + else: + title = "(Untitled)" + if not self.undo.get_saved(): + title = title + " *" + self.top.wm_title(title) + + def center_insert_event(self, event): + self.center() + + def center(self, mark="insert"): + insert = float(self.text.index(mark + " linestart")) + end = float(self.text.index("end")) + if insert > end-insert: + self.text.see("1.0") + else: + self.text.see("end") + self.text.see(mark) + + def close_event(self, event): + self.close() + + def close(self): + self.top.wm_deiconify() + self.top.tkraise() + reply = self.io.maybesave() + if reply != "cancel": + if self.color and self.color.colorizing: + self.color.close() + self.top.bell() + return "cancel" + if self.close_hook: + self.close_hook() + if self.color: + self.color.close() # Cancel colorization + self.top.destroy() + return reply + + +def fixwordbreaks(root): + tk = root.tk + tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded + tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]') + tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]') + + +def test(): + root = Tk() + fixwordbreaks(root) + root.withdraw() + if sys.argv[1:]: + filename = sys.argv[1] + else: + filename = None + edit = EditorWindow(root, filename) + edit.set_close_hook(root.quit) + root.mainloop() + root.destroy() + +if __name__ == '__main__': + test() diff --git a/Tools/idle/FileList.py b/Tools/idle/FileList.py new file mode 100644 index 0000000..6ffeb30 --- /dev/null +++ b/Tools/idle/FileList.py @@ -0,0 +1,169 @@ +import os +from Tkinter import * +import tkMessageBox + +from EditorWindow import EditorWindow, fixwordbreaks +from IOBinding import IOBinding + + +class MultiIOBinding(IOBinding): + + def open(self, event): + filename = self.askopenfile() + if filename: + self.flist.open(filename, self.edit) + return "break" + + +class MultiEditorWindow(EditorWindow): + + IOBinding = MultiIOBinding + from PopupMenu import PopupMenu + + def __init__(self, flist, filename, key): + self.flist = flist + flist.inversedict[self] = key + if key: + flist.dict[key] = self + EditorWindow.__init__(self, flist.root, filename) + self.io.flist = flist + self.io.edit = self + self.popup = self.PopupMenu(self.text, self.flist) + self.text.bind("<>", self.flist.new_callback) + self.text.bind("<>", self.flist.close_all_callback) + + def close_hook(self): + self.flist.close_edit(self) + + def filename_change_hook(self): + self.flist.filename_changed_edit(self) + EditorWindow.filename_change_hook(self) + + +class FileList: + + def __init__(self, root): + self.root = root + self.dict = {} + self.inversedict = {} + + def new(self): + return self.open(None) + + def open(self, filename, edit=None): + if filename: + filename = self.canonize(filename) + if os.path.isdir(filename): + tkMessageBox.showerror( + "Is A Directory", + "The path %s is a directory." % `filename`, + master=self.root) + return None + key = os.path.normcase(filename) + if self.dict.has_key(key): + edit = self.dict[key] + edit.top.tkraise() + edit.top.wm_deiconify() + edit.text.focus_set() + return edit + if not os.path.exists(filename): + tkMessageBox.showinfo( + "New File", + "Opening non-existent file %s" % `filename`, + master=self.root) + if edit and not edit.io.filename and edit.undo.get_saved(): + # Reuse existing Untitled window for new file + edit.io.loadfile(filename) + self.dict[key] = edit + self.inversedict[edit] = key + edit.top.tkraise() + edit.top.wm_deiconify() + edit.text.focus_set() + return edit + else: + key = None + edit = MultiEditorWindow(self, filename, key) + return edit + + def new_callback(self, event): + self.new() + return "break" + + def close_all_callback(self, event): + for edit in self.inversedict.keys(): + reply = edit.close() + if reply == "cancel": + break + return "break" + + def close_edit(self, edit): + try: + key = self.inversedict[edit] + except KeyError: + print "Don't know this EditorWindow object. (close)" + return + if key: + del self.dict[key] + del self.inversedict[edit] + if not self.inversedict: + self.root.quit() + + def filename_changed_edit(self, edit): + edit.saved_change_hook() + try: + key = self.inversedict[edit] + except KeyError: + print "Don't know this EditorWindow object. (rename)" + return + filename = edit.io.filename + if not filename: + if key: + del self.dict[key] + self.inversedict[edit] = None + return + filename = self.canonize(filename) + newkey = os.path.normcase(filename) + if newkey == key: + return + if self.dict.has_key(newkey): + conflict = self.dict[newkey] + self.inversedict[conflict] = None + tkMessageBox.showerror( + "Name Conflict", + "You now have multiple edit windows open for %s" % `filename`, + master=self.root) + self.dict[newkey] = edit + self.inversedict[edit] = newkey + if key: + try: + del self.dict[key] + except KeyError: + pass + + def canonize(self, filename): + if not os.path.isabs(filename): + try: + pwd = os.getcwd() + except os.error: + pass + else: + filename = os.path.join(pwd, filename) + return os.path.normpath(filename) + + +def test(): + import sys + root = Tk() + fixwordbreaks(root) + root.withdraw() + flist = FileList(root) + if sys.argv[1:]: + for filename in sys.argv[1:]: + flist.open(filename) + else: + flist.new() + if flist.inversedict: + root.mainloop() + +if __name__ == '__main__': + test() diff --git a/Tools/idle/FrameViewer.py b/Tools/idle/FrameViewer.py new file mode 100644 index 0000000..e5a6051 --- /dev/null +++ b/Tools/idle/FrameViewer.py @@ -0,0 +1,38 @@ +from repr import Repr +from Tkinter import * + +class FrameViewer: + + def __init__(self, root, frame): + self.root = root + self.frame = frame + self.top = Toplevel(self.root) + self.repr = Repr() + self.repr.maxstring = 60 + self.load_variables() + + def load_variables(self): + row = 0 + if self.frame.f_locals is not self.frame.f_globals: + l = Label(self.top, text="Local Variables", + borderwidth=2, relief="raised") + l.grid(row=row, column=0, columnspan=2, sticky="ew") + row = self.load_names(self.frame.f_locals, row+1) + l = Label(self.top, text="Global Variables", + borderwidth=2, relief="raised") + l.grid(row=row, column=0, columnspan=2, sticky="ew") + row = self.load_names(self.frame.f_globals, row+1) + + def load_names(self, dict, row): + names = dict.keys() + names.sort() + for name in names: + value = dict[name] + svalue = self.repr.repr(value) + l = Label(self.top, text=name) + l.grid(row=row, column=0, sticky="w") + l = Entry(self.top, width=60, borderwidth=0) + l.insert(0, svalue) + l.grid(row=row, column=1, sticky="w") + row = row+1 + return row diff --git a/Tools/idle/HelpWindow.py b/Tools/idle/HelpWindow.py new file mode 100644 index 0000000..a1b13c3 --- /dev/null +++ b/Tools/idle/HelpWindow.py @@ -0,0 +1,65 @@ +import os +import sys +from Tkinter import * + + +class HelpWindow: + + helpfile = "help.txt" + helptitle = "Help Window" + + def __init__(self, root=None): + if not root: + import Tkinter + root = Tkinter._default_root + if root: + self.top = top = Toplevel(root) + else: + self.top = top = root = Tk() + + helpfile = self.helpfile + if not os.path.exists(helpfile): + base = os.path.basename(self.helpfile) + for dir in sys.path: + fullname = os.path.join(dir, base) + if os.path.exists(fullname): + helpfile = fullname + break + try: + f = open(helpfile) + data = f.read() + f.close() + except IOError, msg: + data = "Can't open the help file (%s)" % `helpfile` + + top.protocol("WM_DELETE_WINDOW", self.close_command) + top.wm_title(self.helptitle) + + self.close_button = Button(top, text="close", + command=self.close_command) + self.close_button.pack(side="bottom") + + self.vbar = vbar = Scrollbar(top, name="vbar") + self.text = text = Text(top) + + vbar["command"] = text.yview + text["yscrollcommand"] = vbar.set + + vbar.pack(side="right", fill="y") + text.pack(side="left", fill="both", expand=1) + + text.insert("1.0", data) + + text.config(state="disabled") + text.see("1.0") + + def close_command(self): + self.top.destroy() + + +def main(): + h = HelpWindow() + h.top.mainloop() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/History.py b/Tools/idle/History.py new file mode 100644 index 0000000..0798098 --- /dev/null +++ b/Tools/idle/History.py @@ -0,0 +1,73 @@ +import string + +class History: + + def __init__(self, text): + self.text = text + self.history = [] + self.history_prefix = None + self.history_pointer = None + text.bind("<>", self.history_prev) + text.bind("<>", self.history_next) + + def history_next(self, event): + self.history_do(0) + return "break" + + def history_prev(self, event): + self.history_do(1) + return "break" + + def history_do(self, reverse): + nhist = len(self.history) + pointer = self.history_pointer + prefix = self.history_prefix + if pointer is not None and prefix is not None: + if self.text.compare("insert", "!=", "end-1c") or \ + self.text.get("iomark", "end-1c") != self.history[pointer]: + pointer = prefix = None + if pointer is None or prefix is None: + prefix = self.text.get("iomark", "end-1c") + if reverse: + pointer = nhist + else: + pointer = -1 + nprefix = len(prefix) + while 1: + if reverse: + pointer = pointer - 1 + else: + pointer = pointer + 1 + if pointer < 0 or pointer >= nhist: + self.text.bell() + if self.text.get("iomark", "end-1c") != prefix: + self.text.delete("iomark", "end-1c") + self.text.insert("iomark", prefix) + pointer = prefix = None + break + item = self.history[pointer] + if item[:nprefix] == prefix and len(item) > nprefix: + self.text.delete("iomark", "end-1c") + self.text.insert("iomark", item) + break + self.text.mark_set("insert", "end-1c") + self.text.see("insert") + self.text.tag_remove("sel", "1.0", "end") + self.history_pointer = pointer + self.history_prefix = prefix + + def history_store(self, source): + source = string.strip(source) + if len(source) > 2: + self.history.append(source) + self.history_pointer = None + self.history_prefix = None + + def recall(self, s): + s = string.strip(s) + self.text.tag_remove("sel", "1.0", "end") + self.text.delete("iomark", "end-1c") + self.text.mark_set("insert", "end-1c") + self.text.insert("insert", s) + self.text.see("insert") + diff --git a/Tools/idle/IOBinding.py b/Tools/idle/IOBinding.py new file mode 100644 index 0000000..0d61afc --- /dev/null +++ b/Tools/idle/IOBinding.py @@ -0,0 +1,158 @@ +import os +import tkFileDialog +import tkMessageBox + + +class IOBinding: + + # Calls to non-standard text methods: + # reset_undo() + # set_saved(1) + + def __init__(self, text): + self.text = text + self.text.bind("<>", self.open) + self.text.bind("<>", self.save) + self.text.bind("<>", self.save_as) + self.text.bind("<>", self.save_a_copy) + + filename_change_hook = None + + def set_filename_change_hook(self, hook): + self.filename_change_hook = hook + + filename = None + + def set_filename(self, filename): + self.filename = filename + self.text.set_saved(1) + if self.filename_change_hook: + self.filename_change_hook() + + def open(self, event): + if not self.text.get_saved(): + reply = self.maybesave() + if reply == "cancel": + return "break" + filename = self.askopenfile() + if filename: + self.loadfile(filename) + return "break" + + def loadfile(self, filename): + try: + f = open(filename) + chars = f.read() + f.close() + except IOError, msg: + tkMessageBox.showerror("I/O Error", str(msg), master=self.text) + return 0 + self.text.delete("1.0", "end") + self.set_filename(None) + self.text.insert("1.0", chars) + self.text.reset_undo() + self.set_filename(filename) + self.text.mark_set("insert", "1.0") + self.text.see("insert") + return 1 + + def maybesave(self): + if self.text.get_saved(): + return "yes" + message = "Do you want to save %s before closing?" % ( + self.filename or "this untitled document") + m = tkMessageBox.Message( + title="Save On Close", + message=message, + icon=tkMessageBox.QUESTION, + type=tkMessageBox.YESNOCANCEL, + master=self.text) + reply = m.show() + if reply == "yes": + self.save(None) + if not self.text.get_saved(): + reply = "cancel" + return reply + + def save(self, event): + if not self.filename: + self.save_as(event) + else: + if self.writefile(self.filename): + self.text.set_saved(1) + return "break" + + def save_as(self, event): + filename = self.asksavefile() + if filename: + if self.writefile(filename): + self.set_filename(filename) + self.text.set_saved(1) + return "break" + + def save_a_copy(self, event): + filename = self.asksavefile() + if filename: + self.writefile(filename) + return "break" + + def writefile(self, filename): + try: + f = open(filename, "w") + chars = self.text.get("1.0", "end-1c") + f.write(chars) + if chars and chars[-1] != "\n": + f.write("\n") + f.close() + ## print "saved to", `filename` + return 1 + except IOError, msg: + tkMessageBox.showerror("I/O Error", str(msg), + master=self.text) + return 0 + + opendialog = None + savedialog = None + + filetypes = [ + ("Python files", "*.py", "TEXT"), + ("All text files", "*", "TEXT"), + ("All files", "*"), + ] + + def askopenfile(self): + dir, base = self.defaultfilename("open") + if not self.opendialog: + self.opendialog = tkFileDialog.Open(master=self.text, + filetypes=self.filetypes) + return self.opendialog.show(initialdir=dir, initialfile=base) + + def defaultfilename(self, mode="open"): + if self.filename: + dir, base = os.path.split(self.filename) + else: + dir = base = "" + return dir, base + + def asksavefile(self): + dir, base = self.defaultfilename("save") + if not self.savedialog: + self.savedialog = tkFileDialog.SaveAs(master=self.text, + filetypes=self.filetypes) + return self.savedialog.show(initialdir=dir, initialfile=base) + + +def test(): + from Tkinter import * + root = Tk() + class MyText(Text): + def reset_undo(self): pass + def set_saved(self, flag): pass + text = MyText(root) + text.pack() + text.focus_set() + io = IOBinding(text) + root.mainloop() + +if __name__ == "__main__": + test() diff --git a/Tools/idle/IdleHistory.py b/Tools/idle/IdleHistory.py new file mode 100644 index 0000000..0798098 --- /dev/null +++ b/Tools/idle/IdleHistory.py @@ -0,0 +1,73 @@ +import string + +class History: + + def __init__(self, text): + self.text = text + self.history = [] + self.history_prefix = None + self.history_pointer = None + text.bind("<>", self.history_prev) + text.bind("<>", self.history_next) + + def history_next(self, event): + self.history_do(0) + return "break" + + def history_prev(self, event): + self.history_do(1) + return "break" + + def history_do(self, reverse): + nhist = len(self.history) + pointer = self.history_pointer + prefix = self.history_prefix + if pointer is not None and prefix is not None: + if self.text.compare("insert", "!=", "end-1c") or \ + self.text.get("iomark", "end-1c") != self.history[pointer]: + pointer = prefix = None + if pointer is None or prefix is None: + prefix = self.text.get("iomark", "end-1c") + if reverse: + pointer = nhist + else: + pointer = -1 + nprefix = len(prefix) + while 1: + if reverse: + pointer = pointer - 1 + else: + pointer = pointer + 1 + if pointer < 0 or pointer >= nhist: + self.text.bell() + if self.text.get("iomark", "end-1c") != prefix: + self.text.delete("iomark", "end-1c") + self.text.insert("iomark", prefix) + pointer = prefix = None + break + item = self.history[pointer] + if item[:nprefix] == prefix and len(item) > nprefix: + self.text.delete("iomark", "end-1c") + self.text.insert("iomark", item) + break + self.text.mark_set("insert", "end-1c") + self.text.see("insert") + self.text.tag_remove("sel", "1.0", "end") + self.history_pointer = pointer + self.history_prefix = prefix + + def history_store(self, source): + source = string.strip(source) + if len(source) > 2: + self.history.append(source) + self.history_pointer = None + self.history_prefix = None + + def recall(self, s): + s = string.strip(s) + self.text.tag_remove("sel", "1.0", "end") + self.text.delete("iomark", "end-1c") + self.text.mark_set("insert", "end-1c") + self.text.insert("insert", s) + self.text.see("insert") + diff --git a/Tools/idle/Outline.py b/Tools/idle/Outline.py new file mode 100644 index 0000000..194d058 --- /dev/null +++ b/Tools/idle/Outline.py @@ -0,0 +1,46 @@ +from Tkinter import * + +class Outline: + + def __init__(self, root=None): + if not root: + import Tkinter + root = Tkinter._default_root + if not root: + root = top = Tk() + else: + top = Toplevel(root) + top.wm_title("Outline") + self.canvas = canvas = Canvas(top, width=400, height=300, + borderwidth=2, relief="sunken", + background="#FFBBBB") + canvas.pack(expand=1, fill="both") + self.items = [] + + def additem(self, level, open, label): + x = 15*level + 5 + y = 15*len(self.items) + 5 + if open: + id1 = self.canvas.create_polygon(x+3, y+3, x+13, y+3, x+8, y+8, + outline="black", + fill="green") + else: + id1 = self.canvas.create_polygon(x+3, y+4, x+7, y+8, x+3, y+12, + outline="black", + fill="red") + w = Entry(self.canvas, borderwidth=0, background="#FFBBBB", width=0) + w.insert("end", label) + id2 = self.canvas.create_window(x+15, y, anchor="nw", window=w) + self.items.append((level, open, label, id1, w, id2)) + + +def main(): + o = Outline() + o.additem(0, 1, "hello world") + o.additem(1, 0, "sub1") + o.additem(1, 1, "sub2") + o.additem(2, 0, "sub2.a") + o.additem(2, 0, "sub2.b") + o.additem(1, 0, "sub3") + +main() diff --git a/Tools/idle/Percolator.py b/Tools/idle/Percolator.py new file mode 100644 index 0000000..a5f503f --- /dev/null +++ b/Tools/idle/Percolator.py @@ -0,0 +1,77 @@ +from WidgetRedirector import WidgetRedirector +from Delegator import Delegator + +class Percolator: + + def __init__(self, text): + # XXX would be nice to inherit from Delegator + self.text = text + self.redir = WidgetRedirector(text) + self.top = self.bottom = Delegator(text) + self.bottom.insert = self.redir.register("insert", self.insert) + self.bottom.delete = self.redir.register("delete", self.delete) + self.filters = [] + + def insert(self, index, chars, tags=None): + # Could go away if inheriting from Delegator + self.top.insert(index, chars, tags) + + def delete(self, index1, index2=None): + # Could go away if inheriting from Delegator + self.top.delete(index1, index2) + + def insertfilter(self, filter): + # Perhaps rename to pushfilter()? + assert isinstance(filter, Delegator) + assert filter.delegate is None + filter.setdelegate(self.top) + self.top = filter + + def removefilter(self, filter): + # XXX Perhaps should only support popfilter()? + assert isinstance(filter, Delegator) + assert filter.delegate is not None + f = self.top + if f is filter: + self.top = filter.delegate + filter.setdelegate(None) + else: + while f.delegate is not filter: + assert f is not self.bottom + f.resetcache() + f = f.delegate + f.setdelegate(filter.delegate) + filter.setdelegate(None) + + +def main(): + class Tracer(Delegator): + def __init__(self, name): + self.name = name + Delegator.__init__(self, None) + def insert(self, *args): + print self.name, ": insert", args + apply(self.delegate.insert, args) + def delete(self, *args): + print self.name, ": delete", args + apply(self.delegate.delete, args) + from Tkinter import * + root = Tk() + root.wm_protocol("WM_DELETE_WINDOW", root.quit) + text = Text() + text.pack() + text.focus_set() + p = Percolator(text) + t1 = Tracer("t1") + t2 = Tracer("t2") + p.insertfilter(t1) + p.insertfilter(t2) + root.mainloop() + p.removefilter(t2) + root.mainloop() + p.insertfilter(t2) + p.removefilter(t1) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/PopupMenu.py b/Tools/idle/PopupMenu.py new file mode 100644 index 0000000..d08b18d --- /dev/null +++ b/Tools/idle/PopupMenu.py @@ -0,0 +1,86 @@ +import sys +import re + +from Tkinter import * + +class PopupMenu: + + def __init__(self, text, flist): + self.text = text + self.flist = flist + self.text.bind("<3>", self.right_menu_event) + + rmenu = None + + def right_menu_event(self, event): + if not self.rmenu: + self.make_menu() + rmenu = self.rmenu + self.event = event + iswin = sys.platform[:3] == 'win' + if iswin: + self.text.config(cursor="arrow") + rmenu.tk_popup(event.x_root, event.y_root) + if iswin: + self.text.config(cursor="ibeam") + + def make_menu(self): + rmenu = Menu(self.text, tearoff=0) + rmenu.add_command(label="Go to line from traceback", + command=self.goto_traceback_line) + rmenu.add_command(label="Open stack viewer", + command=self.open_stack_viewer) + rmenu.add_command(label="Help", command=self.help) + self.rmenu = rmenu + + file_line_pats = [ + r'File "([^"]*)", line (\d+)', + r'([^\s]+)\((\d+)\)', + r'([^\s]+):\s*(\d+):', + ] + + file_line_progs = None + + def goto_traceback_line(self): + if self.file_line_progs is None: + l = [] + for pat in self.file_line_pats: + l.append(re.compile(pat)) + self.file_line_progs = l + x, y = self.event.x, self.event.y + self.text.mark_set("insert", "@%d,%d" % (x, y)) + line = self.text.get("insert linestart", "insert lineend") + for prog in self.file_line_progs: + m = prog.search(line) + if m: + break + else: + self.text.bell() + return + filename, lineno = m.group(1, 2) + try: + f = open(filename, "r") + f.close() + except IOError, msg: + self.text.bell() + return + edit = self.flist.open(filename) + try: + lineno = int(lineno) + except ValueError, msg: + self.text.bell() + return + edit.gotoline(lineno) + + def open_stack_viewer(self): + try: + sys.last_traceback + except: + print "No stack trace yet" + return + from StackViewer import StackViewer + sv = StackViewer(self.text._root(), self.flist) + + def help(self): + from HelpWindow import HelpWindow + HelpWindow() diff --git a/Tools/idle/PyShell.py b/Tools/idle/PyShell.py new file mode 100644 index 0000000..053f400 --- /dev/null +++ b/Tools/idle/PyShell.py @@ -0,0 +1,475 @@ +#! /usr/bin/env python + +import os +import sys +import string + +import linecache +from code import InteractiveInterpreter + +from Tkinter import * +import tkMessageBox + +from EditorWindow import fixwordbreaks +from FileList import FileList, MultiEditorWindow, MultiIOBinding +from ColorDelegator import ColorDelegator + + +class ModifiedIOBinding(MultiIOBinding): + + def defaultfilename(self, mode="open"): + if self.filename: + return MultiIOBinding.defaultfilename(self, mode) + else: + try: + pwd = os.getcwd() + except os.error: + pwd = "" + return pwd, "" + + def open(self, event): + # Override base class method -- don't allow reusing this window + filename = self.askopenfile() + if filename: + self.flist.open(filename) + return "break" + + def maybesave(self): + # Override base class method -- don't ask any questions + if self.text.get_saved(): + return "yes" + else: + return "no" + + +class ModifiedColorDelegator(ColorDelegator): + + def recolorize_main(self): + self.tag_remove("TODO", "1.0", "iomark") + self.tag_add("SYNC", "1.0", "iomark") + ColorDelegator.recolorize_main(self) + + +class ModifiedInterpreter(InteractiveInterpreter): + + def __init__(self, tkconsole): + self.tkconsole = tkconsole + InteractiveInterpreter.__init__(self) + + gid = 0 + + def runsource(self, source): + # Extend base class to stuff the source in the line cache + filename = "" % self.gid + self.gid = self.gid + 1 + lines = string.split(source, "\n") + linecache.cache[filename] = len(source)+1, 0, lines, filename + self.more = 0 + return InteractiveInterpreter.runsource(self, source, filename) + + def showsyntaxerror(self, filename=None): + # Extend base class to color the offending position + # (instead of printing it and pointing at it with a caret) + text = self.tkconsole.text + stuff = self.unpackerror() + if not stuff: + self.tkconsole.resetoutput() + InteractiveInterpreter.showsyntaxerror(self, filename) + return + msg, lineno, offset, line = stuff + if lineno == 1: + pos = "iomark + %d chars" % (offset-1) + else: + pos = "iomark linestart + %d lines + %d chars" % (lineno-1, + offset-1) + text.tag_add("ERROR", pos) + text.see(pos) + char = text.get(pos) + if char in string.letters + string.digits + "_": + text.tag_add("ERROR", pos + " wordstart", pos) + self.tkconsole.resetoutput() + self.write("SyntaxError: %s\n" % str(msg)) + + def unpackerror(self): + type, value, tb = sys.exc_info() + ok = type == SyntaxError + if ok: + try: + msg, (dummy_filename, lineno, offset, line) = value + except: + ok = 0 + if ok: + return msg, lineno, offset, line + else: + return None + + def showtraceback(self): + # Extend base class method to reset output properly + text = self.tkconsole.text + self.tkconsole.resetoutput() + InteractiveInterpreter.showtraceback(self) + + def runcode(self, code): + # Override base class method + try: + self.tkconsole.beginexecuting() + try: + exec code in self.locals + except SystemExit: + if tkMessageBox.askyesno( + "Exit?", + "Do you want to exit altogether?", + default="yes", + master=self.tkconsole.text): + raise + else: + self.showtraceback() + except: + self.showtraceback() + finally: + self.tkconsole.endexecuting() + + def write(self, s): + # Override base class write + self.tkconsole.console.write(s) + + +class PyShell(MultiEditorWindow): + + # Override classes + ColorDelegator = ModifiedColorDelegator + IOBinding = ModifiedIOBinding + + # New class + from History import History + + def __init__(self, flist=None): + self.interp = ModifiedInterpreter(self) + if flist is None: + root = Tk() + fixwordbreaks(root) + root.withdraw() + flist = FileList(root) + + MultiEditorWindow.__init__(self, flist, None, None) + self.config_colors() + + import __builtin__ + __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D." + + self.auto.config(prefertabs=1) + + text = self.text + text.bind("<>", self.enter_callback) + text.bind("<>", self.linefeed_callback) + text.bind("<>", self.cancel_callback) + text.bind("<>", self.home_callback) + text.bind("<>", self.eof_callback) + + sys.stdout = PseudoFile(self, "stdout") + ##sys.stderr = PseudoFile(self, "stderr") + sys.stdin = self + self.console = PseudoFile(self, "console") + + self.history = self.History(self.text) + + tagdefs = { + ##"stdin": {"background": "yellow"}, + "stdout": {"foreground": "blue"}, + "stderr": {"foreground": "#007700"}, + "console": {"foreground": "red"}, + "ERROR": {"background": "#FF7777"}, + None: {"foreground": "purple"}, # default + } + + def config_colors(self): + for tag, cnf in self.tagdefs.items(): + if cnf: + if not tag: + apply(self.text.configure, (), cnf) + else: + apply(self.text.tag_configure, (tag,), cnf) + + reading = 0 + executing = 0 + canceled = 0 + endoffile = 0 + + def beginexecuting(self): + # Helper for ModifiedInterpreter + self.resetoutput() + self.executing = 1 + self._cancel_check = self.cancel_check + ##sys.settrace(self._cancel_check) + + def endexecuting(self): + # Helper for ModifiedInterpreter + sys.settrace(None) + self.executing = 0 + self.canceled = 0 + + def close(self): + # Extend base class method + if self.executing: + # XXX Need to ask a question here + if not tkMessageBox.askokcancel( + "Cancel?", + "The program is still running; do you want to cancel it?", + default="ok", + master=self.text): + return "cancel" + self.canceled = 1 + if self.reading: + self.top.quit() + return "cancel" + reply = MultiEditorWindow.close(self) + if reply != "cancel": + # Restore std streams + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + sys.stdin = sys.__stdin__ + # Break cycles + self.interp = None + self.console = None + return reply + + def ispythonsource(self, filename): + # Override this so EditorWindow never removes the colorizer + return 1 + + def saved_change_hook(self): + # Override this to get the title right + title = "Python Shell" + if self.io.filename: + title = title + ": " + self.io.filename + if not self.undo.get_saved(): + title = title + " *" + self.top.wm_title(title) + + def interact(self): + self.resetoutput() + self.write("Python %s on %s\n%s\n" % + (sys.version, sys.platform, sys.copyright)) + try: + sys.ps1 + except AttributeError: + sys.ps1 = ">>> " + self.showprompt() + import Tkinter + Tkinter._default_root = None + self.top.mainloop() + + def readline(self): + save = self.reading + try: + self.reading = 1 + self.top.mainloop() + finally: + self.reading = save + line = self.text.get("iomark", "end-1c") + self.resetoutput() + if self.canceled: + self.canceled = 0 + raise KeyboardInterrupt + if self.endoffile: + self.endoffile = 0 + return "" + return line + + def cancel_callback(self, event): + try: + if self.text.compare("sel.first", "!=", "sel.last"): + return # Active selection -- always use default binding + except: + pass + if not (self.executing or self.reading): + self.resetoutput() + self.write("KeyboardInterrupt\n") + self.showprompt() + return "break" + self.endoffile = 0 + self.canceled = 1 + if self.reading: + self.top.quit() + return "break" + + def eof_callback(self, event): + if self.executing and not self.reading: + return # Let the default binding (delete next char) take over + if not (self.text.compare("iomark", "==", "insert") and + self.text.compare("insert", "==", "end-1c")): + return # Let the default binding (delete next char) take over + if not self.executing: +## if not tkMessageBox.askokcancel( +## "Exit?", +## "Are you sure you want to exit?", +## default="ok", master=self.text): +## return "break" + self.resetoutput() + self.close() + else: + self.canceled = 0 + self.endoffile = 1 + self.top.quit() + return "break" + + def home_callback(self, event): + if event.state != 0 and event.keysym == "Home": + return # ; fall back to class binding + if self.text.compare("iomark", "<=", "insert") and \ + self.text.compare("insert linestart", "<=", "iomark"): + self.text.mark_set("insert", "iomark") + self.text.tag_remove("sel", "1.0", "end") + self.text.see("insert") + return "break" + + def linefeed_callback(self, event): + # Insert a linefeed without entering anything (still autoindented) + if self.reading: + self.text.insert("insert", "\n") + self.text.see("insert") + else: + self.auto.autoindent(event) + return "break" + + def enter_callback(self, event): + if self.executing and not self.reading: + return # Let the default binding (insert '\n') take over + # If some text is selected, recall the selection + try: + sel = self.text.get("sel.first", "sel.last") + if sel: + self.recall(sel) + return "break" + except: + pass + # If we're strictly before the line containing iomark, recall + # the current line, less a leading prompt, less leading or + # trailing whitespace + if self.text.compare("insert", "<", "iomark linestart"): + # Check if there's a relevant stdin mark -- if so, use it + prev = self.text.tag_prevrange("stdin", "insert") + if prev and self.text.compare("insert", "<", prev[1]): + self.recall(self.text.get(prev[0], prev[1])) + return "break" + next = self.text.tag_nextrange("stdin", "insert") + if next and self.text.compare("insert lineend", ">=", next[0]): + self.recall(self.text.get(next[0], next[1])) + return "break" + # No stdin mark -- just get the current line + self.recall(self.text.get("insert linestart", "insert lineend")) + return "break" + # If we're anywhere in the current input (including in the + # prompt) but not at the very end, move the cursor to the end. + if self.text.compare("insert", "<", "end-1c"): + self.text.mark_set("insert", "end-1c") + self.text.see("insert") + return "break" + # OK, we're already at the end -- insert a newline and run it. + if self.reading: + self.text.insert("insert", "\n") + self.text.see("insert") + else: + self.auto.autoindent(event) + self.text.tag_add("stdin", "iomark", "end-1c") + self.text.update_idletasks() + if self.reading: + self.top.quit() # Break out of recursive mainloop() in raw_input() + else: + self.runit() + return "break" + + def recall(self, s): + if self.history: + self.history.recall(s) + + def runit(self): + line = self.text.get("iomark", "end-1c") + # Strip off last newline and surrounding whitespace. + # (To allow you to hit return twice to end a statement.) + i = len(line) + while i > 0 and line[i-1] in " \t": + i = i-1 + if i > 0 and line[i-1] == "\n": + i = i-1 + while i > 0 and line[i-1] in " \t": + i = i-1 + line = line[:i] + more = self.interp.runsource(line) + if not more: + self.showprompt() + + def cancel_check(self, frame, what, args, + dooneevent=tkinter.dooneevent, + dontwait=tkinter.DONT_WAIT): + # Hack -- use the debugger hooks to be able to handle events + # and interrupt execution at any time. + # This slows execution down quite a bit, so you may want to + # disable this (by not calling settrace() in runcode() above) + # for full-bore (uninterruptable) speed. + # XXX This should become a user option. + if self.canceled: + return + dooneevent(dontwait) + if self.canceled: + self.canceled = 0 + raise KeyboardInterrupt + return self._cancel_check + + def showprompt(self): + self.resetoutput() + try: + s = str(sys.ps1) + except: + s = "" + self.console.write(s) + self.text.mark_set("insert", "end-1c") + + def resetoutput(self): + source = self.text.get("iomark", "end-1c") + if self.history: + self.history.history_store(source) + if self.text.get("end-2c") != "\n": + self.text.insert("end-1c", "\n") + self.text.mark_set("iomark", "end-1c") + sys.stdout.softspace = 0 + + def write(self, s): + # Overrides base class write + self.console.write(s) + +class PseudoFile: + + def __init__(self, interp, tags): + self.interp = interp + self.text = interp.text + self.tags = tags + + def write(self, s): + self.text.mark_gravity("iomark", "right") + self.text.insert("iomark", str(s), self.tags) + self.text.mark_gravity("iomark", "left") + self.text.see("iomark") + self.text.update() + if self.interp.canceled: + self.interp.canceled = 0 + raise KeyboardInterrupt + + def writelines(self, l): + map(self.write, l) + + +def main(): + global flist, root + root = Tk() + fixwordbreaks(root) + root.withdraw() + flist = FileList(root) + if sys.argv[1:]: + for filename in sys.argv[1:]: + flist.open(filename) + t = PyShell(flist) + t.interact() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/README b/Tools/idle/README new file mode 100644 index 0000000..ed3d7c5 --- /dev/null +++ b/Tools/idle/README @@ -0,0 +1,79 @@ +BUGS: + +- when there's a selection, typing ^X will delete the selection! + (cause: ^X is a binding for cut ;-( ) + +TO DO: + +- restructure state sensitive code to avoid testing flags all the time +- integrated debugger +- object browser +- save some user state (e.g. window and cursor positions, bindings) + +- menu bar +- make backups when saving +- check file mtimes at various points +- interface with RCS/CVS/Perforce ??? +- more search options: case [in]sensitive, fwd/back, string/regex +- global query replace +- incremental search +- more emacsisms: + - reindent, reformat text etc. + - M-[, M-] to move by paragraphs + - smart stuff with whitespace around Return +- status bar? +- better help? + +Details: + +- when there's a selection, left/right arrow should go to either + end of the selection + +Structural problems: + +- too much knowledge in FileList about EditorWindow (for example) + +====================================================================== + +Comparison to PTUI +------------------ + +- PTUI's shell is worse: + no coloring; + no editing of multi-line commands; + ^P seems to permanently remove some text from the buffer + +- PTUI's undo is worse: + no redo; + one char at a time + +- PTUI's framework is better: + status line + menu bar + buffer menu + (not sure if I like the toolbar) + +- PTUI's GUI is a tad ugly: + I don't like the multiple buffers in one window model + +- PTUI's help is better (HTML!) + +- PTUI's search/replace is better (more features) + +- PTUI's auto indent is better + (understands that "if a: # blah, blah" opens a block) + +- PTUI's key bindings are a bit weird (DEL to dedent a line!?!?!?) + +- PTUI's fontify is faster but synchronous (and still too slow); + also doesn't do as good a job if editing affects lines far below + +- PTUI has more bells and whistles: + open multiple + append + zap tabs + fontify (you could argue it's not needed in my code) + comment/uncomment + modularize + examine + go diff --git a/Tools/idle/SearchBinding.py b/Tools/idle/SearchBinding.py new file mode 100644 index 0000000..6abe5e0 --- /dev/null +++ b/Tools/idle/SearchBinding.py @@ -0,0 +1,84 @@ +import re +import tkSimpleDialog +import tkMessageBox + +class SearchBinding: + + def __init__(self, text): + self.text = text + self.pat = "" + self.prog = None + self.text.bind("<>", self.find_event) + self.text.bind("<>", self.find_next_event) + self.text.bind("<>", self.find_same_event) + self.text.bind("<>", self.goto_line_event) + + def find_event(self, event): + default = self.text.get("self.first", "sel.last") or self.pat + new = tkSimpleDialog.askstring("Find", + "Regular Expression:", + initialvalue=default, + parent=self.text) + if not new: + return "break" + self.pat = new + try: + self.prog = re.compile(self.pat) + except re.error, msg: + tkMessageBox.showerror("RE error", str(msg), + master=self.text) + return "break" + return self.find_next_event(event) + + def find_same_event(self, event): + pat = self.text.get("sel.first", "sel.last") + if not pat: + return self.find_event(event) + self.pat = re.escape(pat) + self.prog = None + try: + self.prog = re.compile(self.pat) + except re.error, msg: + tkMessageBox.showerror("RE error", str(message), + master=self.text) + return "break" + self.text.mark_set("insert", "sel.last") + return self.find_next_event(event) + + def find_next_event(self, event): + if not self.pat: + return self.find_event(event) + if not self.prog: + self.text.bell() + ##print "No program" + return "break" + self.text.mark_set("find", "insert") + while 1: + chars = self.text.get("find", "find lineend +1c") + ##print "Searching", `chars` + if not chars: + self.text.bell() + ##print "end of buffer" + break + m = self.prog.search(chars) + if m: + i, j = m.span() + self.text.mark_set("insert", "find +%dc" % j) + self.text.mark_set("find", "find +%dc" % i) + self.text.tag_remove("sel", "1.0", "end") + self.text.tag_add("sel", "find", "insert") + self.text.see("insert") + break + self.text.mark_set("find", "find lineend +1c") + return "break" + + def goto_line_event(self, event): + lineno = tkSimpleDialog.askinteger("Goto", + "Go to line number:") + if lineno is None: + return "break" + if lineno <= 0: + self.text.bell() + return "break" + self.text.mark_set("insert", "%d.0" % lineno) + self.text.see("insert") diff --git a/Tools/idle/StackViewer.py b/Tools/idle/StackViewer.py new file mode 100644 index 0000000..a59668a --- /dev/null +++ b/Tools/idle/StackViewer.py @@ -0,0 +1,288 @@ +import string +import sys +import os +from Tkinter import * +import linecache +from repr import Repr + + +class StackViewer: + + def __init__(self, root=None, flist=None): + self.flist = flist + # Create root and/or toplevel window + if not root: + import Tkinter + root = Tkinter._default_root + if not root: + root = top = Tk() + else: + top = Toplevel(root) + self.root = root + self.top = top + top.wm_title("Stack viewer") + # Create top frame, with scrollbar and listbox + self.topframe = Frame(top) + self.topframe.pack(fill="both", expand=1) + self.vbar = Scrollbar(self.topframe, name="vbar") + self.vbar.pack(side="right", fill="y") + self.listbox = Listbox(self.topframe, exportselection=0, + takefocus=1, width=60) + self.listbox.pack(expand=1, fill="both") + # Tie listbox and scrollbar together + self.vbar["command"] = self.listbox.yview + self.listbox["yscrollcommand"] = self.vbar.set + # Bind events to the list box + self.listbox.bind("", self.click_event) + self.listbox.bind("", self.double_click_event) + self.listbox.bind("", self.popup_event) + self.listbox.bind("", self.up_event) + self.listbox.bind("", self.down_event) + # Load the stack + linecache.checkcache() + stack = getstack() + self.load_stack(stack) + + def load_stack(self, stack): + self.stack = stack + l = self.listbox + l.delete(0, END) + if len(stack) > 10: + l["height"] = 10 + self.topframe.pack(expand=1) + else: + l["height"] = len(stack) + self.topframe.pack(expand=0) + for frame, lineno in stack: + try: + modname = frame.f_globals["__name__"] + except: + modname = "?" + code = frame.f_code + filename = code.co_filename + funcname = code.co_name + sourceline = linecache.getline(filename, lineno) + sourceline = string.strip(sourceline) + if funcname in ("?", "", None): + item = "%s, line %d: %s" % (modname, lineno, sourceline) + else: + item = "%s.%s(), line %d: %s" % (modname, funcname, + lineno, sourceline) + l.insert(END, item) + l.focus_set() + l.selection_clear(0, "end") + l.activate("end") + l.see("end") + + rmenu = None + + def click_event(self, event): + self.listbox.activate("@%d,%d" % (event.x, event.y)) + self.show_stack_frame() + return "break" + + def popup_event(self, event): + if not self.rmenu: + self.make_menu() + rmenu = self.rmenu + self.event = event + self.listbox.activate("@%d,%d" % (event.x, event.y)) + rmenu.tk_popup(event.x_root, event.y_root) + + def make_menu(self): + rmenu = Menu(self.top, tearoff=0) + rmenu.add_command(label="Go to source line", + command=self.goto_source_line) + rmenu.add_command(label="Show stack frame", + command=self.show_stack_frame) + self.rmenu = rmenu + + def goto_source_line(self): + index = self.listbox.index("active") + self.show_source(index) + + def show_stack_frame(self): + index = self.listbox.index("active") + self.show_frame(index) + + def double_click_event(self, event): + index = self.listbox.index("active") + self.show_source(index) + return "break" + + def up_event(self, event): + index = self.listbox.index("active") - 1 + if index < 0: + self.top.bell() + return "break" + self.show_frame(index) + return "break" + + def down_event(self, event): + index = self.listbox.index("active") + 1 + if index >= len(self.stack): + self.top.bell() + return "break" + self.show_frame(index) + return "break" + + def show_source(self, index): + if not 0 <= index < len(self.stack): + self.top.bell() + return + frame, lineno = self.stack[index] + code = frame.f_code + filename = code.co_filename + if not self.flist: + self.top.bell() + return + if not os.path.exists(filename): + self.top.bell() + return + edit = self.flist.open(filename) + edit.gotoline(lineno) + + localsframe = None + localsviewer = None + localsdict = None + globalsframe = None + globalsviewer = None + globalsdict = None + curframe = None + + def show_frame(self, index): + if not 0 <= index < len(self.stack): + self.top.bell() + return + self.listbox.selection_clear(0, "end") + self.listbox.selection_set(index) + self.listbox.activate(index) + self.listbox.see(index) + self.listbox.focus_set() + frame, lineno = self.stack[index] + if frame is self.curframe: + return + self.curframe = None + if frame.f_globals is not self.globalsdict: + self.show_globals(frame) + self.show_locals(frame) + self.curframe = frame + + def show_globals(self, frame): + title = "Global Variables" + if frame.f_globals.has_key("__name__"): + try: + name = str(frame.f_globals["__name__"]) + "" + except: + name = "" + if name: + title = title + " in module " + name + self.globalsdict = None + if self.globalsviewer: + self.globalsviewer.close() + self.globalsviewer = None + if not self.globalsframe: + self.globalsframe = Frame(self.top) + self.globalsdict = frame.f_globals + self.globalsviewer = NamespaceViewer( + self.globalsframe, + title, + self.globalsdict) + self.globalsframe.pack(fill="both", side="bottom") + + def show_locals(self, frame): + self.localsdict = None + if self.localsviewer: + self.localsviewer.close() + self.localsviewer = None + if frame.f_locals is not frame.f_globals: + title = "Local Variables" + code = frame.f_code + funcname = code.co_name + if funcname not in ("?", "", None): + title = title + " in " + funcname + if not self.localsframe: + self.localsframe = Frame(self.top) + self.localsdict = frame.f_locals + self.localsviewer = NamespaceViewer( + self.localsframe, + title, + self.localsdict) + self.localsframe.pack(fill="both", side="top") + else: + if self.localsframe: + self.localsframe.forget() + + +def getstack(t=None, f=None): + if t is None: + t = sys.last_traceback + stack = [] + if t and t.tb_frame is f: + t = t.tb_next + while f is not None: + stack.append((f, f.f_lineno)) + if f is self.botframe: + break + f = f.f_back + stack.reverse() + while t is not None: + stack.append((t.tb_frame, t.tb_lineno)) + t = t.tb_next + return stack + + +class NamespaceViewer: + + def __init__(self, frame, title, dict): + width = 0 + height = 20*len(dict) # XXX 20 == observed height of Entry widget + self.frame = frame + self.title = title + self.dict = dict + self.repr = Repr() + self.repr.maxstring = 60 + self.repr.maxother = 60 + self.label = Label(frame, text=title, borderwidth=2, relief="groove") + self.label.pack(fill="x") + self.vbar = vbar = Scrollbar(frame, name="vbar") + vbar.pack(side="right", fill="y") + self.canvas = canvas = Canvas(frame, + height=min(300, max(40, height)), + scrollregion=(0, 0, width, height)) + canvas.pack(side="left", fill="both", expand=1) + vbar["command"] = canvas.yview + canvas["yscrollcommand"] = vbar.set + self.subframe = subframe = Frame(canvas) + self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw") + names = dict.keys() + names.sort() + row = 0 + for name in names: + value = dict[name] + svalue = self.repr.repr(value) # repr(value) + l = Label(subframe, text=name) + l.grid(row=row, column=0, sticky="nw") +## l = Label(subframe, text=svalue, justify="l", wraplength=300) + l = Entry(subframe, width=0, borderwidth=0) + l.insert(0, svalue) +## l["state"] = "disabled" + l.grid(row=row, column=1, sticky="nw") + row = row+1 + frame.update_idletasks() # Alas! + width = subframe.winfo_reqwidth() + height = subframe.winfo_reqheight() + canvas["scrollregion"] = (0, 0, width, height) + if height > 300: + canvas["height"] = 300 + frame.pack(expand=1) + else: + canvas["height"] = height + frame.pack(expand=0) + + def close(self): + for c in self.subframe, self.label, self.vbar, self.canvas: + try: + c.destroy() + except: + pass diff --git a/Tools/idle/UndoDelegator.py b/Tools/idle/UndoDelegator.py new file mode 100644 index 0000000..ee49651 --- /dev/null +++ b/Tools/idle/UndoDelegator.py @@ -0,0 +1,269 @@ +import sys +import string +from Tkinter import * +from Delegator import Delegator + + +class UndoDelegator(Delegator): + + max_undo = 1000 + + def __init__(self): + Delegator.__init__(self) + self.reset_undo() + + def setdelegate(self, delegate): + if self.delegate is not None: + self.unbind("<>") + self.unbind("<>") + self.unbind("<>") + Delegator.setdelegate(self, delegate) + if delegate is not None: + self.bind("<>", self.undo_event) + self.bind("<>", self.redo_event) + self.bind("<>", self.dump_event) + + def dump_event(self, event): + from pprint import pprint + pprint(self.undolist[:self.pointer]) + print "pointer:", self.pointer, + print "saved:", self.saved, + print "can_merge:", self.can_merge, + print "get_saved():", self.get_saved() + pprint(self.undolist[self.pointer:]) + return "break" + + def reset_undo(self): + self.was_saved = -1 + self.pointer = 0 + self.undolist = [] + self.set_saved(1) + + def set_saved(self, flag): + if flag: + self.saved = self.pointer + else: + self.saved = -1 + self.can_merge = 0 + self.check_saved() + + def get_saved(self): + return self.saved == self.pointer + + saved_change_hook = None + + def set_saved_change_hook(self, hook): + self.saved_change_hook = hook + + was_saved = -1 + + def check_saved(self): + is_saved = self.get_saved() + if is_saved != self.was_saved: + self.was_saved = is_saved + if self.saved_change_hook: + self.saved_change_hook() + + def insert(self, index, chars, tags=None): + self.addcmd(InsertCommand(index, chars, tags)) + + def delete(self, index1, index2=None): + self.addcmd(DeleteCommand(index1, index2)) + + def addcmd(self, cmd): + cmd.do(self.delegate) + if self.can_merge and self.pointer > 0: + lastcmd = self.undolist[self.pointer-1] + if lastcmd.merge(cmd): + return + self.undolist[self.pointer:] = [cmd] + if self.saved > self.pointer: + self.saved = -1 + self.pointer = self.pointer + 1 + if len(self.undolist) > self.max_undo: + ##print "truncating undo list" + del self.undolist[0] + self.pointer = self.pointer - 1 + if self.saved >= 0: + self.saved = self.saved - 1 + self.can_merge = 1 + self.check_saved() + + def undo_event(self, event): + if self.pointer == 0: + self.bell() + return "break" + cmd = self.undolist[self.pointer - 1] + cmd.undo(self.delegate) + self.pointer = self.pointer - 1 + self.can_merge = 0 + self.check_saved() + return "break" + + def redo_event(self, event): + if self.pointer >= len(self.undolist): + self.bell() + return "break" + cmd = self.undolist[self.pointer] + cmd.redo(self.delegate) + self.pointer = self.pointer + 1 + self.can_merge = 0 + self.check_saved() + return "break" + + +class Command: + + # Base class for Undoable commands + + tags = None + + def __init__(self, index1, index2, chars, tags=None): + self.marks_before = {} + self.marks_after = {} + self.index1 = index1 + self.index2 = index2 + self.chars = chars + if tags: + self.tags = tags + + def __repr__(self): + s = self.__class__.__name__ + t = (self.index1, self.index2, self.chars, self.tags) + if self.tags is None: + t = t[:-1] + return s + `t` + + def do(self, text): + pass + + def redo(self, text): + pass + + def undo(self, text): + pass + + def merge(self, cmd): + return 0 + + def save_marks(self, text): + marks = {} + for name in text.mark_names(): + if name != "insert" and name != "current": + marks[name] = text.index(name) + return marks + + def set_marks(self, text, marks): + for name, index in marks.items(): + text.mark_set(name, index) + + +class InsertCommand(Command): + + # Undoable insert command + + def __init__(self, index1, chars, tags=None): + Command.__init__(self, index1, None, chars, tags) + + def do(self, text): + self.marks_before = self.save_marks(text) + self.index1 = text.index(self.index1) + if text.compare(self.index1, ">", "end-1c"): + # Insert before the final newline + self.index1 = text.index("end-1c") + text.insert(self.index1, self.chars, self.tags) + self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars))) + self.marks_after = self.save_marks(text) + ##sys.__stderr__.write("do: %s\n" % self) + + def redo(self, text): + text.mark_set('insert', self.index1) + text.insert(self.index1, self.chars, self.tags) + self.set_marks(text, self.marks_after) + text.see('insert') + ##sys.__stderr__.write("redo: %s\n" % self) + + def undo(self, text): + text.mark_set('insert', self.index1) + text.delete(self.index1, self.index2) + self.set_marks(text, self.marks_before) + text.see('insert') + ##sys.__stderr__.write("undo: %s\n" % self) + + def merge(self, cmd): + if self.__class__ is not cmd.__class__: + return 0 + if self.index2 != cmd.index1: + return 0 + if self.tags != cmd.tags: + return 0 + if len(cmd.chars) != 1: + return 0 + if self.chars and \ + self.classify(self.chars[-1]) != self.classify(cmd.chars): + return 0 + self.index2 = cmd.index2 + self.chars = self.chars + cmd.chars + return 1 + + alphanumeric = string.letters + string.digits + "_" + + def classify(self, c): + if c in self.alphanumeric: + return "alphanumeric" + if c == "\n": + return "newline" + return "punctuation" + + +class DeleteCommand(Command): + + # Undoable delete command + + def __init__(self, index1, index2=None): + Command.__init__(self, index1, index2, None, None) + + def do(self, text): + self.marks_before = self.save_marks(text) + self.index1 = text.index(self.index1) + if self.index2: + self.index2 = text.index(self.index2) + else: + self.index2 = text.index(self.index1 + " +1c") + if text.compare(self.index2, ">", "end-1c"): + # Don't delete the final newline + self.index2 = text.index("end-1c") + self.chars = text.get(self.index1, self.index2) + text.delete(self.index1, self.index2) + self.marks_after = self.save_marks(text) + ##sys.__stderr__.write("do: %s\n" % self) + + def redo(self, text): + text.mark_set('insert', self.index1) + text.delete(self.index1, self.index2) + self.set_marks(text, self.marks_after) + text.see('insert') + ##sys.__stderr__.write("redo: %s\n" % self) + + def undo(self, text): + text.mark_set('insert', self.index1) + text.insert(self.index1, self.chars) + self.set_marks(text, self.marks_before) + text.see('insert') + ##sys.__stderr__.write("undo: %s\n" % self) + + +def main(): + from Percolator import Percolator + root = Tk() + root.wm_protocol("WM_DELETE_WINDOW", root.quit) + text = Text() + text.pack() + text.focus_set() + p = Percolator(text) + d = UndoDelegator() + p.insertfilter(d) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/WidgetRedirector.py b/Tools/idle/WidgetRedirector.py new file mode 100644 index 0000000..45f86f7 --- /dev/null +++ b/Tools/idle/WidgetRedirector.py @@ -0,0 +1,84 @@ +from Tkinter import * + + +class WidgetRedirector: + + """Support for redirecting arbitrary widget subcommands.""" + + def __init__(self, widget): + self.dict = {} + self.widget = widget + self.tk = tk = widget.tk + w = widget._w + self.orig = w + "_orig" + tk.call("rename", w, self.orig) + tk.createcommand(w, self.dispatch) + + def __repr__(self): + return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__, + self.widget._w) + + def __del__(self): + self.close() + + def close(self): + self.dict = {} + widget = self.widget; del self.widget + orig = self.orig; del self.orig + tk = widget.tk + w = widget._w + tk.deletecommand(w) + tk.call("rename", w, orig) + + def register(self, name, function): + if self.dict.has_key(name): + previous = function + else: + previous = OriginalCommand(self, name) + self.dict[name] = function + setattr(self.widget, name, function) + return previous + + def dispatch(self, cmd, *args): + m = self.dict.get(cmd) + try: + if m: + return apply(m, args) + else: + return self.tk.call((self.orig, cmd) + args) + except TclError: + return "" + + +class OriginalCommand: + + def __init__(self, redir, name): + self.redir = redir + self.name = name + self.tk = redir.tk + self.orig = redir.orig + self.tk_call = self.tk.call + self.orig_and_name = (self.orig, self.name) + + def __repr__(self): + return "OriginalCommand(%s, %s)" % (`self.redir`, `self.name`) + + def __call__(self, *args): + return self.tk_call(self.orig_and_name + args) + + +def main(): + root = Tk() + text = Text() + text.pack() + text.focus_set() + redir = WidgetRedirector(text) + global orig_insert + def my_insert(*args): + print "insert", args + apply(orig_insert, args) + orig_insert = redir.register("insert", my_insert) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/Tools/idle/help.txt b/Tools/idle/help.txt new file mode 100644 index 0000000..6dbf2fa --- /dev/null +++ b/Tools/idle/help.txt @@ -0,0 +1,60 @@ +Windows and files: + + ^X ^N creates new empty text editor window + ^X ^C closes all windows + Alt-F4 or ^X ^0 (that's control-x-control-zero) closes current window + ^X ^D opens a file from dialog box + ^X ^S saves to current file + ^X ^W saves to file from dialog box + ^X w save a copy to file from dialog box + +Navigation: + + Arrow keys and Page Up/Down to move around + Home/End go to begin/end of line + Control-Home/End go to begin/end of file + Some Emacs bindings may also work, e.g. ^A/^E + +Searching: all searches are forward from the cursor without +wrap-around, case sensitive, Perl-style regular expression matches + + ^S without a selection opens search dialog box + ^S with a selection searches for selected text + ^U ^S repeats last search + Alt-G opens dialog box to go to a specific line + +Editing: + + Backspace deletes left of cursor, Delete right of cursor + Cut and paste use platform's conventions + ^[ or Alt-[ left-shifts (dedents) the current line or selection + ^] or Alt-] right-shifts (indents) the current line or selection + Alt-/ expands last word you type (like Emacs dabbrev) + +Undo: + + ^Z undoes last change; repeat to undo more + Alt-Z redoes last undone change; repeat to redo more + +Console window: + + ^C interrupts executing command + ^D sends end-of-file; closes console if typed at >>> prompt + + If you get a traceback, right-click on any line listing a + filename and line number and select "Go to line from + traceback" to open that file and go to the indicated line + +Python syntax colors: the coloring is applied in a background thread + + Keywords orange + Strings green + Comments red + Definitions blue + +Console colors: + + Console output red + stdout blue + stderr dark green + stdin purple diff --git a/Tools/idle/idle.pyw b/Tools/idle/idle.pyw new file mode 100644 index 0000000..3c06e05 --- /dev/null +++ b/Tools/idle/idle.pyw @@ -0,0 +1,3 @@ +#! /usr/bin/env python +import PyShell +PyShell.main() -- cgit v0.12