Initial checking of Tk-based Python IDE.

Features: text editor with syntax coloring and undo;
subclassed into interactive Python shell which adds history.
This commit is contained in:
Guido van Rossum 1998-10-10 18:48:31 +00:00
parent dc1adabcb8
commit 3b4ca0ddad
23 changed files with 2829 additions and 0 deletions

75
Tools/idle/AutoExpand.py Normal file
View file

@ -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:]

124
Tools/idle/AutoIndent.py Normal file
View file

@ -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")

92
Tools/idle/Bindings.py Normal file
View file

@ -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)

View file

@ -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()

33
Tools/idle/Delegator.py Normal file
View file

@ -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

175
Tools/idle/EditorWindow.py Normal file
View file

@ -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()

169
Tools/idle/FileList.py Normal file
View file

@ -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()

38
Tools/idle/FrameViewer.py Normal file
View file

@ -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

65
Tools/idle/HelpWindow.py Normal file
View file

@ -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()

73
Tools/idle/History.py Normal file
View file

@ -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")

158
Tools/idle/IOBinding.py Normal file
View file

@ -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()

73
Tools/idle/IdleHistory.py Normal file
View file

@ -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")

46
Tools/idle/Outline.py Normal file
View file

@ -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()

77
Tools/idle/Percolator.py Normal file
View file

@ -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()

86
Tools/idle/PopupMenu.py Normal file
View file

@ -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()

475
Tools/idle/PyShell.py Normal file
View file

@ -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()

79
Tools/idle/README Normal file
View file

@ -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

View file

@ -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")

288
Tools/idle/StackViewer.py Normal file
View file

@ -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

269
Tools/idle/UndoDelegator.py Normal file
View file

@ -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()

View file

@ -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()

60
Tools/idle/help.txt Normal file
View file

@ -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

3
Tools/idle/idle.pyw Normal file
View file

@ -0,0 +1,3 @@
#! /usr/bin/env python
import PyShell
PyShell.main()