summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1998-10-10 18:48:31 (GMT)
committerGuido van Rossum <guido@python.org>1998-10-10 18:48:31 (GMT)
commit3b4ca0ddad8d1e224f71e89f4c7fbc8de5c6edc4 (patch)
tree1a66ed7c7eec87f31d61a2a083096e5cad89a39c
parentdc1adabcb86ee0813c9bae2d5cc59be5cad1ff31 (diff)
downloadcpython-3b4ca0ddad8d1e224f71e89f4c7fbc8de5c6edc4.zip
cpython-3b4ca0ddad8d1e224f71e89f4c7fbc8de5c6edc4.tar.gz
cpython-3b4ca0ddad8d1e224f71e89f4c7fbc8de5c6edc4.tar.bz2
Initial checking of Tk-based Python IDE.
Features: text editor with syntax coloring and undo; subclassed into interactive Python shell which adds history.
-rw-r--r--Tools/idle/AutoExpand.py75
-rw-r--r--Tools/idle/AutoIndent.py124
-rw-r--r--Tools/idle/Bindings.py92
-rw-r--r--Tools/idle/ColorDelegator.py203
-rw-r--r--Tools/idle/Delegator.py33
-rw-r--r--Tools/idle/EditorWindow.py175
-rw-r--r--Tools/idle/FileList.py169
-rw-r--r--Tools/idle/FrameViewer.py38
-rw-r--r--Tools/idle/HelpWindow.py65
-rw-r--r--Tools/idle/History.py73
-rw-r--r--Tools/idle/IOBinding.py158
-rw-r--r--Tools/idle/IdleHistory.py73
-rw-r--r--Tools/idle/Outline.py46
-rw-r--r--Tools/idle/Percolator.py77
-rw-r--r--Tools/idle/PopupMenu.py86
-rw-r--r--Tools/idle/PyShell.py475
-rw-r--r--Tools/idle/README79
-rw-r--r--Tools/idle/SearchBinding.py84
-rw-r--r--Tools/idle/StackViewer.py288
-rw-r--r--Tools/idle/UndoDelegator.py269
-rw-r--r--Tools/idle/WidgetRedirector.py84
-rw-r--r--Tools/idle/help.txt60
-rw-r--r--Tools/idle/idle.pyw3
23 files changed, 2829 insertions, 0 deletions
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("<<expand-word>>", 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("<<newline-and-indent>>", self.autoindent)
+ text.bind("<<indent-region>>", self.indentregion)
+ text.bind("<<dedent-region>>", self.dedentregion)
+ text.bind("<<comment-region>>", self.commentregion)
+ text.bind("<<uncomment-region>>", 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 = [
+ ("<<beginning-of-line>>", "<Control-a>", "<Home>"),
+
+ ("<<expand-word>>", "<Meta-slash>", "<Alt-slash>"),
+
+ ("<<newline-and-indent>>", "<Key-Return>", "<KP_Enter>"),
+ ("<<plain-newline-and-indent>>", "<Control-j>"),
+
+ ("<<interrupt-execution>>", "<Control-c>"),
+ ("<<end-of-file>>", "<Control-d>"),
+
+ ("<<dedent-region>>", "<Control-bracketleft>"),
+ ("<<indent-region>>", "<Control-bracketright>"),
+
+ ("<<comment-region>>", "<Meta-Key-3>", "<Alt-Key-3>"),
+ ("<<uncomment-region>>", "<Meta-Key-4>", "<Alt-Key-4>"),
+
+ ("<<history-previous>>", "<Meta-p>", "<Alt-p>"),
+ ("<<history-next>>", "<Meta-n>", "<Alt-n>"),
+
+ ("<<toggle-auto-coloring>>", "<Control-slash>"),
+
+ ("<<close-all-windows>>", "<Control-q>"),
+ ("<<open-new-window>>", "<Control-n>"),
+ ("<<open-window-from-file>>", "<Control-o>"),
+ ("<<save-window>>", "<Control-s>"),
+ ("<<save-window-as-file>>", "<Control-w>"),
+ ("<<save-copy-of-window-as-file>>", "<Meta-w>"),
+
+ ("<<find>>", "<Control-f>"),
+ ("<<find-next>>", "<F3>"),
+ ("<<find-same>>", "<Control-F3>"),
+ ("<<goto-line>>", "<Alt-g>", "<Meta-g>"),
+
+ ("<<undo>>", "<Control-z>"),
+ ("<<redo>>", "<Control-y>"),
+ ("<<dump-undo-state>>", "<Control-backslash>"),
+]
+
+emacs_bindings = [
+ ("<<beginning-of-line>>", "<Control-a>", "<Home>"),
+ ("<<center-insert>>", "<Control-l>"),
+
+ ("<<expand-word>>", "<Meta-slash>", "<Alt-slash>"),
+
+ ("<<newline-and-indent>>", "<Key-Return>", "<KP_Enter>"),
+ ("<<plain-newline-and-indent>>", "<Control-j>"),
+
+ ("<<interrupt-execution>>", "<Control-c>"),
+ ("<<end-of-file>>", "<Control-d>"),
+
+ ("<<dedent-region>>",
+ "<Meta-bracketleft>", "<Alt-bracketleft>", "<Control-bracketleft>"),
+ ("<<indent-region>>",
+ "<Meta-bracketright>", "<Alt-bracketright>", "<Control-bracketright>"),
+
+ ("<<comment-region>>", "<Meta-Key-3>", "<Alt-Key-3>"),
+ ("<<uncomment-region>>", "<Meta-Key-4>", "<Alt-Key-4>"),
+
+ ("<<history-previous>>", "<Meta-p>", "<Alt-p>"),
+ ("<<history-next>>", "<Meta-n>", "<Alt-n>"),
+
+ ("<<toggle-auto-coloring>>", "<Control-slash>"),
+
+ ("<<close-all-windows>>", "<Control-x><Control-c>"),
+ ("<<close-window>>", "<Control-x><Control-0>"),
+ ("<<open-new-window>>", "<Control-x><Control-n>"),
+ ("<<open-window-from-file>>", "<Control-x><Control-f>"),
+ ("<<save-window>>", "<Control-x><Control-s>"),
+ ("<<save-window-as-file>>", "<Control-x><Control-w>"),
+ ("<<save-copy-of-window-as-file>>", "<Control-x><w>"),
+
+ ("<<find>>", "<Control-u><Control-u><Control-s>"),
+ ("<<find-next>>", "<Control-u><Control-s>"),
+ ("<<find-same>>", "<Control-s>"),
+ ("<<goto-line>>", "<Alt-g>", "<Meta-g>"),
+
+ ("<<undo>>", "<Control-z>"),
+ ("<<redo>>", "<Alt-z>", "<Meta-z>"),
+ ("<<dump-undo-state>>", "<Control-backslash>"),
+]
+
+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("<<toggle-auto-coloring>>")
+ Delegator.setdelegate(self, delegate)
+ if delegate is not None:
+ self.config_colors()
+ self.bind("<<toggle-auto-coloring>>", 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("<<close-window>>", self.close_event)
+ self.text.bind("<<center-insert>>", 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("<<open-new-window>>", self.flist.new_callback)
+ self.text.bind("<<close-all-windows>>", 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("<<history-previous>>", self.history_prev)
+ text.bind("<<history-next>>", 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("<<open-window-from-file>>", self.open)
+ self.text.bind("<<save-window>>", self.save)
+ self.text.bind("<<save-window-as-file>>", self.save_as)
+ self.text.bind("<<save-copy-of-window-as-file>>", 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("<<history-previous>>", self.history_prev)
+ text.bind("<<history-next>>", 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 = "<console#%d>" % 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("<<newline-and-indent>>", self.enter_callback)
+ text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
+ text.bind("<<interrupt-execution>>", self.cancel_callback)
+ text.bind("<<beginning-of-line>>", self.home_callback)
+ text.bind("<<end-of-file>>", 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 # <Modifier-Home>; 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("<<find>>", self.find_event)
+ self.text.bind("<<find-next>>", self.find_next_event)
+ self.text.bind("<<find-same>>", self.find_same_event)
+ self.text.bind("<<goto-line>>", 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("<ButtonRelease-1>", self.click_event)
+ self.listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
+ self.listbox.bind("<ButtonPress-3>", self.popup_event)
+ self.listbox.bind("<Key-Up>", self.up_event)
+ self.listbox.bind("<Key-Down>", 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("<<undo>>")
+ self.unbind("<<redo>>")
+ self.unbind("<<dump-undo-state>>")
+ Delegator.setdelegate(self, delegate)
+ if delegate is not None:
+ self.bind("<<undo>>", self.undo_event)
+ self.bind("<<redo>>", self.redo_event)
+ self.bind("<<dump-undo-state>>", 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()