Checking in IDLE 0.2.

Much has changed -- too much, in fact, to write down.
The big news is that there's a standard way to write IDLE extensions;
see extend.txt.  Some sample extensions have been provided, and
some existing code has been converted to extensions.  Probably the
biggest new user feature is a new search dialog with more options,
search and replace, and even search in files (grep).

This is exactly as downloaded from my laptop after returning
from the holidays -- it hasn't even been tested on Unix yet.
This commit is contained in:
Guido van Rossum 1999-01-02 21:28:54 +00:00
parent f07c328c07
commit 504b0bf066
38 changed files with 2204 additions and 899 deletions

View File

@ -1,17 +1,30 @@
import string
import re
###$ event <<expand-word>>
###$ win <Alt-slash>
###$ unix <Alt-slash>
class AutoExpand:
keydefs = {
'<<expand-word>>': ['<Alt-slash>'],
}
menudefs = [
('edit', [
('E_xpand word', '<<expand-word>>'),
]),
]
wordchars = string.letters + string.digits + "_"
def __init__(self, text):
self.text = text
self.text.wordlist = None
def __init__(self, editwin):
self.text = editwin.text
self.text.wordlist = None # XXX what is this?
self.state = None
self.text.bind("<<expand-word>>", self.autoexpand)
def autoexpand(self, event):
def expand_word_event(self, event):
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
if not self.state:
@ -36,7 +49,7 @@ class AutoExpand:
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:
@ -66,7 +79,7 @@ class AutoExpand:
dict[w] = w
words.append(word)
return words
def getprevword(self):
line = self.text.get("insert linestart", "insert")
i = len(line)

View File

@ -1,16 +1,81 @@
import string
from Tkinter import TclError
###$ event <<newline-and-indent>>
###$ win <Key-Return>
###$ win <KP_Enter>
###$ unix <Key-Return>
###$ unix <KP_Enter>
###$ event <<indent-region>>
###$ win <Control-bracketright>
###$ unix <Alt-bracketright>
###$ unix <Control-bracketright>
###$ event <<dedent-region>>
###$ win <Control-bracketleft>
###$ unix <Alt-bracketleft>
###$ unix <Control-bracketleft>
###$ event <<comment-region>>
###$ win <Alt-Key-3>
###$ unix <Alt-Key-3>
###$ event <<uncomment-region>>
###$ win <Alt-Key-4>
###$ unix <Alt-Key-4>
###$ event <<tabify-region>>
###$ win <Alt-Key-5>
###$ unix <Alt-Key-5>
###$ event <<untabify-region>>
###$ win <Alt-Key-6>
###$ unix <Alt-Key-6>
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)
menudefs = [
('edit', [
None,
('_Indent region', '<<indent-region>>'),
('_Dedent region', '<<dedent-region>>'),
('Comment _out region', '<<comment-region>>'),
('U_ncomment region', '<<uncomment-region>>'),
('Tabify region', '<<tabify-region>>'),
('Untabify region', '<<untabify-region>>'),
]),
]
windows_keydefs = {
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
'<<indent-region>>': ['<Control-bracketright>'],
'<<dedent-region>>': ['<Control-bracketleft>'],
'<<comment-region>>': ['<Alt-Key-3>'],
'<<uncomment-region>>': ['<Alt-Key-4>'],
'<<tabify-region>>': ['<Alt-Key-5>'],
'<<untabify-region>>': ['<Alt-Key-6>'],
}
unix_keydefs = {
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
'<<indent-region>>': ['<Alt-bracketright>',
'<Meta-bracketright>',
'<Control-bracketright>'],
'<<dedent-region>>': ['<Alt-bracketleft>',
'<Meta-bracketleft>',
'<Control-bracketleft>'],
'<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
'<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
'<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
'<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
}
prefertabs = 0
spaceindent = 4*" "
def __init__(self, editwin):
self.text = editwin.text
def config(self, **options):
for key, value in options.items():
@ -21,8 +86,16 @@ class AutoIndent:
else:
raise KeyError, "bad option name: %s" % `key`
def autoindent(self, event):
def newline_and_indent_event(self, event):
text = self.text
try:
first = text.index("sel.first")
last = text.index("sel.last")
except TclError:
first = last = None
if first and last:
text.delete(first, last)
text.mark_set("insert", first)
line = text.get("insert linestart", "insert")
i, n = 0, len(line)
while i < n and line[i] in " \t":
@ -43,8 +116,10 @@ class AutoIndent:
text.see("insert")
return "break"
def indentregion(self, event):
head, tail, chars, lines = self.getregion()
auto_indent = newline_and_indent_event
def indent_region_event(self, event):
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if line:
@ -53,11 +128,11 @@ class AutoIndent:
i = i+1
line = line[:i] + " " + line[i:]
lines[pos] = line
self.setregion(head, tail, chars, lines)
self.set_region(head, tail, chars, lines)
return "break"
def dedentregion(self, event):
head, tail, chars, lines = self.getregion()
def dedent_region_event(self, event):
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if line:
@ -75,20 +150,20 @@ class AutoIndent:
indent = indent[:-4]
line = indent + line
lines[pos] = line
self.setregion(head, tail, chars, lines)
self.set_region(head, tail, chars, lines)
return "break"
def commentregion(self, event):
head, tail, chars, lines = self.getregion()
def comment_region_event(self, event):
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if not line:
continue
lines[pos] = '##' + line
self.setregion(head, tail, chars, lines)
self.set_region(head, tail, chars, lines)
def uncommentregion(self, event):
head, tail, chars, lines = self.getregion()
def uncomment_region_event(self, event):
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if not line:
@ -98,9 +173,19 @@ class AutoIndent:
elif line[:1] == '#':
line = line[1:]
lines[pos] = line
self.setregion(head, tail, chars, lines)
self.set_region(head, tail, chars, lines)
def getregion(self):
def tabify_region_event(self, event):
head, tail, chars, lines = self.get_region()
lines = map(tabify, lines)
self.set_region(head, tail, chars, lines)
def untabify_region_event(self, event):
head, tail, chars, lines = self.get_region()
lines = map(string.expandtabs, lines)
self.set_region(head, tail, chars, lines)
def get_region(self):
text = self.text
head = text.index("sel.first linestart")
tail = text.index("sel.last -1c lineend +1c")
@ -111,7 +196,7 @@ class AutoIndent:
lines = string.split(chars, "\n")
return head, tail, chars, lines
def setregion(self, head, tail, chars, lines):
def set_region(self, head, tail, chars, lines):
text = self.text
newchars = string.join(lines, "\n")
if newchars == chars:
@ -122,3 +207,12 @@ class AutoIndent:
text.delete(head, tail)
text.insert(head, newchars)
text.tag_add("sel", head, "insert")
def tabify(line, tabsize=8):
spaces = tabsize * ' '
for i in range(0, len(line), tabsize):
if line[i:i+tabsize] != spaces:
break
else:
i = len(line)
return '\t' * (i/tabsize) + line[i:]

View File

@ -8,6 +8,7 @@
import sys
import string
import re
from keydefs import *
menudefs = [
# underscore prefixes character to underscore
@ -15,7 +16,8 @@ menudefs = [
('_New window', '<<open-new-window>>'),
('_Open...', '<<open-window-from-file>>'),
('Open _module...', '<<open-module>>'),
('Class _browser...', '<<open-class-browser>>'),
('Class _browser', '<<open-class-browser>>'),
('Python shell', '<<open-python-shell>>'),
None,
('_Save', '<<save-window>>'),
('Save _As...', '<<save-window-as-file>>'),
@ -31,19 +33,15 @@ menudefs = [
('Cu_t', '<<Cut>>'),
('_Copy', '<<Copy>>'),
('_Paste', '<<Paste>>'),
None,
('_Find...', '<<find>>'),
('Find _next', '<<find-next>>'),
('Find _same', '<<find-same>>'),
('_Go to line', '<<goto-line>>'),
None,
('_Dedent region', '<<dedent-region>>'),
('_Indent region', '<<indent-region>>'),
('Comment _out region', '<<comment-region>>'),
('U_ncomment region', '<<uncomment-region>>'),
('Select _All', '<<select-all>>'),
]),
('script', [
('Run module', '<<run-module>>'),
('Run script', '<<run-script>>'),
('New shell', '<<new-shell>>'),
]),
('debug', [
('_Go to line from traceback', '<<goto-traceback-line>>'),
('_Go to file/line', '<<goto-file-line>>'),
('_Open stack viewer', '<<open-stack-viewer>>'),
('_Debugger toggle', '<<toggle-debugger>>'),
]),
@ -54,81 +52,6 @@ menudefs = [
]),
]
windows_keydefs = {
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
'<<close-all-windows>>': ['<Control-q>'],
'<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'],
'<<dedent-region>>': ['<Control-bracketleft>'],
'<<dump-undo-state>>': ['<Control-backslash>'],
'<<end-of-file>>': ['<Control-d>'],
'<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'],
'<<find-next>>': ['<F3>', '<Control-g>'],
'<<find-same>>': ['<Control-F3>'],
'<<find>>': ['<Control-f>'],
'<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
'<<history-next>>': ['<Meta-n>', '<Alt-n>'],
'<<history-previous>>': ['<Meta-p>', '<Alt-p>'],
'<<indent-region>>': ['<Control-bracketright>'],
'<<interrupt-execution>>': ['<Control-c>'],
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
'<<open-new-window>>': ['<Control-n>'],
'<<open-window-from-file>>': ['<Control-o>'],
'<<plain-newline-and-indent>>': ['<Control-j>'],
'<<redo>>': ['<Control-y>'],
'<<save-copy-of-window-as-file>>': ['<Meta-w>'],
'<<save-window-as-file>>': ['<Control-w>'],
'<<save-window>>': ['<Control-s>'],
'<<toggle-auto-coloring>>': ['<Control-slash>'],
'<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'],
'<<undo>>': ['<Control-z>'],
}
emacs_keydefs = {
'<<Copy>>': ['<Alt-w>'],
'<<Cut>>': ['<Control-w>'],
'<<Paste>>': ['<Control-y>'],
'<<about-idle>>': [],
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
'<<center-insert>>': ['<Control-l>'],
'<<close-all-windows>>': ['<Control-x><Control-c>'],
'<<close-window>>': ['<Control-x><Control-0>'],
'<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'],
'<<dedent-region>>': ['<Meta-bracketleft>',
'<Alt-bracketleft>',
'<Control-bracketleft>'],
'<<do-nothing>>': ['<Control-x>'],
'<<dump-undo-state>>': ['<Control-backslash>'],
'<<end-of-file>>': ['<Control-d>'],
'<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'],
'<<find-next>>': ['<Control-u><Control-s>'],
'<<find-same>>': ['<Control-s>'],
'<<find>>': ['<Control-u><Control-u><Control-s>'],
'<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
'<<goto-traceback-line>>': [],
'<<help>>': [],
'<<history-next>>': ['<Meta-n>', '<Alt-n>'],
'<<history-previous>>': ['<Meta-p>', '<Alt-p>'],
'<<indent-region>>': ['<Meta-bracketright>',
'<Alt-bracketright>',
'<Control-bracketright>'],
'<<interrupt-execution>>': ['<Control-c>'],
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
'<<open-class-browser>>': ['<Control-x><Control-b>'],
'<<open-module>>': ['<Control-x><Control-m>'],
'<<open-new-window>>': ['<Control-x><Control-n>'],
'<<open-stack-viewer>>': [],
'<<open-window-from-file>>': ['<Control-x><Control-f>'],
'<<plain-newline-and-indent>>': ['<Control-j>'],
'<<redo>>': ['<Alt-z>', '<Meta-z>'],
'<<save-copy-of-window-as-file>>': ['<Control-x><w>'],
'<<save-window-as-file>>': ['<Control-x><Control-w>'],
'<<save-window>>': ['<Control-x><Control-s>'],
'<<toggle-auto-coloring>>': ['<Control-slash>'],
'<<toggle-debugger>>': [],
'<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'],
'<<undo>>': ['<Control-z>'],
}
def prepstr(s):
# Helper to extract the underscore from a string,
# e.g. prepstr("Co_py") returns (2, "Copy").
@ -140,18 +63,14 @@ def prepstr(s):
keynames = {
'bracketleft': '[',
'bracketright': ']',
'slash': '/',
}
def getaccelerator(keydefs, event):
def get_accelerator(keydefs, event):
keylist = keydefs.get(event)
if not keylist:
return ""
s = keylist[0]
if s[:6] == "<Meta-":
# Prefer Alt over Meta -- they should be the same thing anyway
alts = "<Alt-" + s[6:]
if alts in keylist:
s = alts
s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
s = re.sub("Key-", "", s)
@ -165,7 +84,7 @@ def getaccelerator(keydefs, event):
if sys.platform == 'win32':
default_keydefs = windows_keydefs
else:
default_keydefs = emacs_keydefs
default_keydefs = unix_keydefs
def apply_bindings(text, keydefs=default_keydefs):
text.keydefs = keydefs
@ -173,14 +92,10 @@ def apply_bindings(text, keydefs=default_keydefs):
if keylist:
apply(text.event_add, (event,) + tuple(keylist))
def fill_menus(text, menudict, defs=menudefs):
def fill_menus(text, menudict, defs=menudefs, keydefs=default_keydefs):
# Fill the menus for the given text widget. The menudict argument is
# a dictionary containing the menus, keyed by their lowercased name.
# Menus that are absent or None are ignored.
if hasattr(text, "keydefs"):
keydefs = text.keydefs
else:
keydefs = default_keydefs
for mname, itemlist in defs:
menu = menudict.get(mname)
if not menu:
@ -191,7 +106,7 @@ def fill_menus(text, menudict, defs=menudefs):
else:
label, event = item
underline, label = prepstr(label)
accelerator = getaccelerator(keydefs, event)
accelerator = get_accelerator(keydefs, event)
def command(text=text, event=event):
text.event_generate(event)
menu.add_command(label=label, underline=underline,

View File

@ -1,7 +1,7 @@
"""Primitive class browser.
XXX TO DO:
- generalize the scrolling listbox with some behavior into a base class
- add popup menu with more options (e.g. doc strings, base classes, imports)
- show function argument list (have to do pattern matching on source)
@ -14,12 +14,13 @@ import string
import pyclbr
from Tkinter import *
import tkMessageBox
from WindowList import ListedToplevel
from ScrolledList import ScrolledList
class ClassBrowser:
def __init__(self, flist, name, path=[]):
root = flist.root
try:
@ -34,9 +35,10 @@ class ClassBrowser:
self.flist = flist
self.dict = dict
self.root = root
self.top = top = Toplevel(root)
self.top = top = ListedToplevel(root)
self.top.protocol("WM_DELETE_WINDOW", self.close)
top.wm_title("Class browser")
top.wm_title("Class Browser - " + name)
top.wm_iconname("ClBrowser")
self.leftframe = leftframe = Frame(top)
self.leftframe.pack(side="left", fill="both", expand=1)
# Create help label
@ -48,12 +50,12 @@ class ClassBrowser:
self.leftframe, self.flist, self)
# Load the classes
self.load_classes(dict, name)
def close(self):
self.classviewer = None
self.methodviewer = None
self.top.destroy()
def load_classes(self, dict, module):
self.classviewer.load_classes(dict, module)
if self.botframe:
@ -64,7 +66,7 @@ class ClassBrowser:
botframe = None
methodhelplabel = None
methodviewer = None
def show_methods(self, cl):
if not self.botframe:
self.botframe = Frame(self.top)
@ -78,12 +80,12 @@ class ClassBrowser:
class ClassViewer(ScrolledList):
def __init__(self, master, flist, browser):
ScrolledList.__init__(self, master)
self.flist = flist
self.browser = browser
def load_classes(self, dict, module):
self.clear()
self.dict = dict
@ -103,7 +105,7 @@ class ClassViewer(ScrolledList):
super.append(name)
s = s + "(%s)" % string.join(super, ", ")
self.append(s)
def getname(self, index):
name = self.listbox.get(index)
i = string.find(name, '(')
@ -113,13 +115,13 @@ class ClassViewer(ScrolledList):
def getclass(self, index):
return self.dict[self.getname(index)]
def on_select(self, index):
self.show_methods(index)
def on_double(self, index):
self.show_source(index)
def show_methods(self, index):
cl = self.getclass(index)
self.browser.show_methods(cl)
@ -132,13 +134,13 @@ class ClassViewer(ScrolledList):
class MethodViewer(ScrolledList):
def __init__(self, master, flist):
ScrolledList.__init__(self, master)
self.flist = flist
classinfo = None
def load_methods(self, cl):
self.classinfo = cl
self.clear()
@ -151,10 +153,10 @@ class MethodViewer(ScrolledList):
def click_event(self, event):
pass
def on_double(self, index):
self.show_source(self.get(index))
def show_source(self, name):
if os.path.isfile(self.classinfo.file):
edit = self.flist.open(self.classinfo.file)

View File

@ -5,6 +5,10 @@ import keyword
from Tkinter import *
from Delegator import Delegator
#$ event <<toggle-auto-coloring>>
#$ win <Control-slash>
#$ unix <Control-slash>
__debug__ = 0
@ -29,10 +33,10 @@ class ColorDelegator(Delegator):
def __init__(self):
Delegator.__init__(self)
self.prog = prog
self.idprog = idprog
self.idprog = idprog
def setdelegate(self, delegate):
if self.delegate is not None:
if self.delegate is not None:
self.unbind("<<toggle-auto-coloring>>")
Delegator.setdelegate(self, delegate)
if delegate is not None:
@ -54,8 +58,11 @@ class ColorDelegator(Delegator):
"SYNC": {}, #{"background": "#ffff00"},
"TODO": {}, #{"background": "#cccccc"},
"BREAK": {"background": "#FF7777"},
# The following is used by ReplaceDialog:
"hit": {"foreground": "#FFFFFF", "background": "#000000"},
}
def insert(self, index, chars, tags=None):
@ -79,9 +86,9 @@ class ColorDelegator(Delegator):
return
if self.colorizing:
self.stop_colorizing = 1
if __debug__: print "stop colorizing"
if __debug__: print "stop colorizing"
if self.allow_colorizing:
if __debug__: print "schedule colorizing"
if __debug__: print "schedule colorizing"
self.after_id = self.after(1, self.recolorize)
def close(self):
@ -99,29 +106,29 @@ class ColorDelegator(Delegator):
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"
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"
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.stop_colorizing = 0
self.colorizing = 1
if __debug__: print "colorizing..."
t0 = time.clock()
@ -131,63 +138,63 @@ class ColorDelegator(Delegator):
finally:
self.colorizing = 0
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
if __debug__: print "reschedule colorizing"
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"
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
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():

View File

@ -2,28 +2,29 @@ import os
import bdb
import traceback
from Tkinter import *
from WindowList import ListedToplevel
import StackViewer
class Debugger(bdb.Bdb):
interacting = 0
vstack = vsource = vlocals = vglobals = None
def __init__(self, pyshell):
bdb.Bdb.__init__(self)
self.pyshell = pyshell
self.make_gui()
def close(self):
if self.interacting:
self.top.bell()
return
self.pyshell.close_debugger()
self.top.destroy()
def run(self, *args):
try:
self.interacting = 1
@ -41,12 +42,14 @@ class Debugger(bdb.Bdb):
def user_exception(self, frame, info):
self.interaction(frame, info)
def make_gui(self):
pyshell = self.pyshell
self.flist = pyshell.flist
self.root = root = pyshell.root
self.top = top = Toplevel(root)
self.top = top =ListedToplevel(root)
self.top.wm_title("Debug Control")
self.top.wm_iconname("Debug")
top.wm_protocol("WM_DELETE_WINDOW", self.close)
#
self.bframe = bframe = Frame(top)
@ -113,9 +116,9 @@ class Debugger(bdb.Bdb):
self.show_locals()
if self.vglobals.get():
self.show_globals()
frame = None
def interaction(self, frame, info=None):
self.frame = frame
code = frame.f_code
@ -167,7 +170,7 @@ class Debugger(bdb.Bdb):
self.status.configure(text="")
self.error.configure(text="", background=self.errorbg)
self.frame = None
def sync_source_line(self):
frame = self.frame
if not frame:
@ -179,19 +182,19 @@ class Debugger(bdb.Bdb):
edit = self.flist.open(file)
if edit:
edit.gotoline(lineno)
def cont(self):
self.set_continue()
self.root.quit()
def step(self):
self.set_step()
self.root.quit()
def next(self):
self.set_next(self.frame)
self.root.quit()
def ret(self):
self.set_return(self.frame)
self.root.quit()
@ -211,7 +214,7 @@ class Debugger(bdb.Bdb):
self.stackviewer = None
sv.close()
self.fstack['height'] = 1
def show_source(self):
if self.vsource.get():
self.sync_source_line()
@ -277,16 +280,16 @@ class Debugger(bdb.Bdb):
text.bell()
return
text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
# A literal copy of Bdb.set_break() without the print statement at the end
def set_break(self, filename, lineno, temporary=0, cond = None):
import linecache # Import as late as possible
line = linecache.getline(filename, lineno)
if not line:
return 'That line does not exist!'
if not self.breaks.has_key(filename):
self.breaks[filename] = []
list = self.breaks[filename]
if not lineno in list:
list.append(lineno)
bp = bdb.Breakpoint(filename, lineno, temporary, cond)
import linecache # Import as late as possible
line = linecache.getline(filename, lineno)
if not line:
return 'That line does not exist!'
if not self.breaks.has_key(filename):
self.breaks[filename] = []
list = self.breaks[filename]
if not lineno in list:
list.append(lineno)
bp = bdb.Breakpoint(filename, lineno, temporary, cond)

View File

@ -1,3 +1,4 @@
class Delegator:
# The cache is only used to be able to change delegates!

View File

@ -5,15 +5,70 @@ import imp
from Tkinter import *
import tkSimpleDialog
import tkMessageBox
import idlever
# File menu
#$ event <<open-module>>
#$ win <Alt-m>
#$ unix <Control-x><Control-m>
#$ event <<open-class-browser>>
#$ win <Alt-c>
#$ unix <Control-x><Control-b>
#$ event <<close-window>>
#$ unix <Control-x><Control-0>
#$ unix <Control-x><Key-0>
#$ win <Alt-F4>
# Edit menu
#$ event <<Copy>>
#$ win <Control-c>
#$ unix <Alt-w>
#$ event <<Cut>>
#$ win <Control-x>
#$ unix <Control-w>
#$ event <<Paste>>
#$ win <Control-v>
#$ unix <Control-y>
#$ event <<select-all>>
#$ win <Alt-a>
#$ unix <Alt-a>
# Help menu
#$ event <<help>>
#$ win <F1>
#$ unix <F1>
#$ event <<about-idle>>
# Events without menu entries
#$ event <<remove-selection>>
#$ win <Escape>
#$ event <<center-insert>>
#$ win <Control-l>
#$ unix <Control-l>
#$ event <<do-nothing>>
#$ unix <Control-x>
about_title = "About IDLE"
about_text = """\
IDLE 0.1
IDLE %s
A not totally unintegrated development environment for Python
An Integrated DeveLopment Environment for Python
by Guido van Rossum
"""
""" % idlever.IDLE_VERSION
class EditorWindow:
@ -21,44 +76,52 @@ class EditorWindow:
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
about_title = about_title
about_text = about_text
from Tkinter import Toplevel
def __init__(self, root, filename=None):
about_title = about_title
about_text = about_text
def __init__(self, flist=None, filename=None, key=None, root=None):
self.flist = flist
root = root or flist.root
self.root = root
self.menubar = Menu(root)
self.top = top = Toplevel(root, menu=self.menubar)
self.top = top = self.Toplevel(root, menu=self.menubar)
self.vbar = vbar = Scrollbar(top, name='vbar')
self.text = text = Text(top, name='text')
self.text = text = Text(top, name='text', padx=5,
background="white", wrap="none")
self.createmenubar()
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)
self.text.bind("<<help>>", self.help_dialog)
self.text.bind("<<about-idle>>", self.about_dialog)
self.text.bind("<<open-module>>", self.open_module)
self.text.bind("<<do-nothing>>", lambda event: "break")
text.bind("<<center-insert>>", self.center_insert_event)
text.bind("<<help>>", self.help_dialog)
text.bind("<<about-idle>>", self.about_dialog)
text.bind("<<open-module>>", self.open_module)
text.bind("<<do-nothing>>", lambda event: "break")
text.bind("<<select-all>>", self.select_all)
text.bind("<<remove-selection>>", self.remove_selection)
text.bind("<3>", self.right_menu_event)
if flist:
flist.inversedict[self] = key
if key:
flist.dict[key] = self
text.bind("<<open-new-window>>", self.flist.new_callback)
text.bind("<<close-all-windows>>", self.flist.close_all_callback)
text.bind("<<open-class-browser>>", self.open_class_browser)
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)
@ -67,8 +130,7 @@ class EditorWindow:
##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)
self.io = io = self.IOBinding(self)
undo.set_saved_change_hook(self.saved_change_hook)
io.set_filename_change_hook(self.filename_change_hook)
@ -81,9 +143,29 @@ class EditorWindow:
self.saved_change_hook()
self.load_extensions()
menu = self.menudict.get('windows')
if menu:
menu.configure(tearoff=0)
end = menu.index("end")
if end is None:
end = -1
if end >= 0:
menu.add_separator()
end = end + 1
self.wmenu_end = end
menu.configure(postcommand=self.postwindowsmenu)
def wakeup(self):
self.top.tkraise()
self.top.wm_deiconify()
self.text.focus_set()
menu_specs = [
("file", "_File"),
("edit", "_Edit"),
("windows", "_Windows"),
("help", "_Help"),
]
@ -96,15 +178,78 @@ class EditorWindow:
mbar.add_cascade(label=label, menu=menu, underline=underline)
self.Bindings.fill_menus(self.text, mdict)
def postwindowsmenu(self):
# Only called when Windows menu exists
menu = self.menudict['windows']
end = menu.index("end")
if end is None:
end = -1
if end > self.wmenu_end:
menu.delete(self.wmenu_end+1, end)
import WindowList
WindowList.add_windows_to_menu(menu)
rmenu = None
def right_menu_event(self, event):
self.text.tag_remove("sel", "1.0", "end")
self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
if not self.rmenu:
self.make_rmenu()
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")
rmenu_specs = [
# ("Label", "<<virtual-event>>"), ...
("Close", "<<close-window>>"), # Example
]
def make_rmenu(self):
rmenu = Menu(self.text, tearoff=0)
for label, eventname in self.rmenu_specs:
def command(text=self.text, eventname=eventname):
text.event_generate(eventname)
rmenu.add_command(label=label, command=command)
self.rmenu = rmenu
def about_dialog(self, event=None):
tkMessageBox.showinfo(self.about_title, self.about_text,
master=self.text)
helpfile = "help.txt"
def help_dialog(self, event=None):
from HelpWindow import HelpWindow
HelpWindow(root=self.root)
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
if self.flist:
self.flist.open(helpfile)
else:
self.io.loadfile(helpfile)
def select_all(self, event=None):
self.text.tag_add("sel", "1.0", "end-1c")
self.text.mark_set("insert", "1.0")
self.text.see("insert")
return "break"
def remove_selection(self, event=None):
self.text.tag_remove("sel", "1.0", "end")
self.text.see("insert")
def open_module(self, event=None):
# XXX Shouldn't this be in IOBinding or in FileList?
try:
name = self.text.get("sel.first", "sel.last")
except TclError:
@ -120,6 +265,8 @@ class EditorWindow:
name = string.strip(name)
if not name:
return
# XXX Ought to support package syntax
# XXX Ought to insert current file's directory in front of path
try:
(f, file, (suffix, mode, type)) = imp.find_module(name)
except ImportError, msg:
@ -131,7 +278,26 @@ class EditorWindow:
return
if f:
f.close()
self.flist.open(file, self)
if self.flist:
self.flist.open(file)
else:
self.io.loadfile(file)
def open_class_browser(self, event=None):
filename = self.io.filename
if not filename:
tkMessageBox.showerror(
"No filename",
"This buffer has no associated filename",
master=self.text)
return None
head, tail = os.path.split(filename)
base, ext = os.path.splitext(tail)
import pyclbr
if pyclbr._modules.has_key(base):
del pyclbr._modules[base]
import ClassBrowser
ClassBrowser.ClassBrowser(self.flist, base, [head])
def gotoline(self, lineno):
if lineno is not None and lineno > 0:
@ -143,7 +309,8 @@ class EditorWindow:
def ispythonsource(self, filename):
if not filename:
return 1
if os.path.normcase(filename[-3:]) == ".py":
base, ext = os.path.splitext(os.path.basename(filename))
if os.path.normcase(ext) in (".py", ".pyw"):
return 1
try:
f = open(filename)
@ -153,12 +320,16 @@ class EditorWindow:
return 0
return line[:2] == '#!' and string.find(line, 'python') >= 0
close_hook = None
def close_hook(self):
if self.flist:
self.flist.close_edit(self)
def set_close_hook(self, close_hook):
self.close_hook = close_hook
def filename_change_hook(self):
if self.flist:
self.flist.filename_changed_edit(self)
self.saved_change_hook()
if self.ispythonsource(self.io.filename):
self.addcolorizer()
@ -184,13 +355,40 @@ class EditorWindow:
self.per.insertfilter(self.undo)
def saved_change_hook(self):
if self.io.filename:
title = self.io.filename
short = self.short_title()
long = self.long_title()
if short and long:
title = short + " - " + long
elif short:
title = short
elif long:
title = long
else:
title = "(Untitled)"
if not self.undo.get_saved():
title = title + " *"
title = "Untitled"
icon = short or long or title
if not self.get_saved():
title = "*%s*" % title
icon = "*%s" % icon
self.top.wm_title(title)
self.top.wm_iconname(icon)
def get_saved(self):
return self.undo.get_saved()
def set_saved(self, flag):
self.undo.set_saved(flag)
def reset_undo(self):
self.undo.reset_undo()
def short_title(self):
filename = self.io.filename
if filename:
filename = os.path.basename(filename)
return filename
def long_title(self):
return self.io.filename or ""
def center_insert_event(self, event):
self.center()
@ -207,10 +405,14 @@ class EditorWindow:
def close_event(self, event):
self.close()
def maybesave(self):
if self.io:
return self.io.maybesave()
def close(self):
self.top.wm_deiconify()
self.top.tkraise()
reply = self.io.maybesave()
reply = self.maybesave()
if reply != "cancel":
if self.color and self.color.colorizing:
self.color.close()
@ -223,8 +425,59 @@ class EditorWindow:
self.top.destroy()
return reply
def load_extensions(self):
self.extensions = {}
self.load_standard_extensions()
def load_standard_extensions(self):
for name in self.get_standard_extension_names():
try:
self.load_extension(name)
except:
print "Failed to load extension", `name`
import traceback
traceback.print_exc()
def get_standard_extension_names(self):
import extend
return extend.standard
def load_extension(self, name):
mod = __import__(name)
cls = getattr(mod, name)
ins = cls(self)
self.extensions[name] = ins
kdnames = ["keydefs"]
if sys.platform == 'win32':
kdnames.append("windows_keydefs")
elif sys.platform == 'mac':
kdnames.append("mac_keydefs")
else:
kdnames.append("unix_keydefs")
keydefs = {}
for kdname in kdnames:
if hasattr(ins, kdname):
keydefs.update(getattr(ins, kdname))
if keydefs:
self.Bindings.apply_bindings(self.text, keydefs)
for vevent in keydefs.keys():
methodname = string.replace(vevent, "-", "_")
while methodname[:1] == '<':
methodname = methodname[1:]
while methodname[-1:] == '>':
methodname = methodname[:-1]
methodname = methodname + "_event"
if hasattr(ins, methodname):
self.text.bind(vevent, getattr(ins, methodname))
if hasattr(ins, "menudefs"):
self.Bindings.fill_menus(self.text, self. menudict,
ins.menudefs, keydefs)
return ins
def fixwordbreaks(root):
# Make sure that Tk's double-click and next/previous word
# operations use our definition of a word (i.e. an identifier)
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_]')
@ -239,7 +492,7 @@ def test():
filename = sys.argv[1]
else:
filename = None
edit = EditorWindow(root, filename)
edit = EditorWindow(root=root, filename=filename)
edit.set_close_hook(root.quit)
root.mainloop()
root.destroy()

View File

@ -2,125 +2,59 @@ import os
from Tkinter import *
import tkMessageBox
from EditorWindow import EditorWindow, fixwordbreaks
from IOBinding import IOBinding
import WindowList
#$ event <<open-new-window>>
#$ win <Control-n>
#$ unix <Control-x><Control-n>
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
# Override menu bar specs
menu_specs = EditorWindow.menu_specs[:]
menu_specs.insert(len(menu_specs)-1, ("windows", "_Windows"))
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.text.bind("<<open-new-window>>", self.flist.new_callback)
self.text.bind("<<close-all-windows>>", self.flist.close_all_callback)
self.text.bind("<<open-class-browser>>", self.open_class_browser)
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)
def wakeup(self):
self.top.tkraise()
self.top.wm_deiconify()
self.text.focus_set()
def createmenubar(self):
EditorWindow.createmenubar(self)
self.menudict['windows'].configure(postcommand=self.postwindowsmenu)
def postwindowsmenu(self):
wmenu = self.menudict['windows']
wmenu.delete(0, 'end')
self.fixedwindowsmenu(wmenu)
files = self.flist.dict.keys()
files.sort()
for file in files:
def openit(self=self, file=file):
self.flist.open(file)
wmenu.add_command(label=file, command=openit)
def open_class_browser(self, event=None):
filename = self.io.filename
if not filename:
tkMessageBox.showerror(
"No filename",
"This buffer has no associated filename",
master=self.text)
return None
head, tail = os.path.split(filename)
base, ext = os.path.splitext(tail)
import pyclbr
if pyclbr._modules.has_key(base):
del pyclbr._modules[base]
import ClassBrowser
ClassBrowser.ClassBrowser(self.flist, base, [head])
# (This is labeled as 'Exit'in the File menu)
#$ event <<close-all-windows>>
#$ win <Control-q>
#$ unix <Control-x><Control-c>
class FileList:
EditorWindow = MultiEditorWindow
from EditorWindow import EditorWindow
EditorWindow.Toplevel = WindowList.ListedToplevel # XXX Patch it!
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:
def goodname(self, 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.wakeup()
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.wakeup()
return edit
else:
key = None
edit = self.EditorWindow(self, filename, key)
return edit
filename = edit.io.filename or filename
return filename
def open(self, filename):
assert 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.wakeup()
return edit
if not os.path.exists(filename):
tkMessageBox.showinfo(
"New File",
"Opening non-existent file %s" % `filename`,
master=self.root)
return self.EditorWindow(self, filename, key)
def new(self):
return self.EditorWindow(self)
def new_callback(self, event):
self.new()
@ -189,6 +123,7 @@ class FileList:
def test():
from EditorWindow import fixwordbreaks
import sys
root = Tk()
fixwordbreaks(root)

View File

@ -10,7 +10,7 @@ class FrameViewer:
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:
@ -22,7 +22,7 @@ class FrameViewer:
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()

134
Tools/idle/GrepDialog.py Normal file
View File

@ -0,0 +1,134 @@
import string
import os
import re
import fnmatch
from Tkinter import *
import tkMessageBox
import SearchEngine
from SearchDialogBase import SearchDialogBase
def grep(text, io=None, flist=None):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_grepdialog"):
engine._grepdialog = GrepDialog(root, engine, flist)
dialog = engine._grepdialog
dialog.open(io)
class GrepDialog(SearchDialogBase):
title = "Find in Files Dialog"
icon = "Grep"
needwrapbutton = 0
def __init__(self, root, engine, flist):
SearchDialogBase.__init__(self, root, engine)
self.flist = flist
self.globvar = StringVar(root)
self.recvar = BooleanVar(root)
def open(self, io=None):
SearchDialogBase.open(self, None)
if io:
path = io.filename or ""
else:
path = ""
dir, base = os.path.split(path)
head, tail = os.path.splitext(base)
if not tail:
tail = ".py"
self.globvar.set(os.path.join(dir, "*" + tail))
def create_entries(self):
SearchDialogBase.create_entries(self)
self.globent = self.make_entry("In files:", self.globvar)
def create_other_buttons(self):
f = self.make_frame()
btn = Checkbutton(f, anchor="w",
variable=self.recvar,
text="Recurse down subdirectories")
btn.pack(side="top", fill="both")
btn.select()
def create_command_buttons(self):
SearchDialogBase.create_command_buttons(self)
self.make_button("Search Files", self.default_command, 1)
def default_command(self, event=None):
prog = self.engine.getprog()
if not prog:
return
path = self.globvar.get()
if not path:
self.top.bell()
return
from OutputWindow import OutputWindow
save = sys.stdout
try:
sys.stdout = OutputWindow(self.flist)
self.grep_it(prog, path)
finally:
sys.stdout = save
def grep_it(self, prog, path):
dir, base = os.path.split(path)
list = self.findfiles(dir, base, self.recvar.get())
list.sort()
self.close()
pat = self.engine.getpat()
print "Searching %s in %s ..." % (pat, path)
hits = 0
for fn in list:
try:
f = open(fn)
except IOError, msg:
print msg
continue
lineno = 0
while 1:
block = f.readlines(100000)
if not block:
break
for line in block:
lineno = lineno + 1
if line[-1:] == '\n':
line = line[:-1]
if prog.search(line):
sys.stdout.write("%s: %s: %s\n" % (fn, lineno, line))
hits = hits + 1
if hits:
if hits == 1:
s = ""
else:
s = "s"
print "Found", hits, "hit%s." % s
print "(Hint: right-click to open locations.)"
else:
print "No hits."
def findfiles(self, dir, base, rec):
try:
names = os.listdir(dir or os.curdir)
except os.error, msg:
print msg
return []
list = []
subdirs = []
for name in names:
fn = os.path.join(dir, name)
if os.path.isdir(fn):
subdirs.append(fn)
else:
if fnmatch.fnmatch(name, base):
list.append(fn)
if rec:
for subdir in subdirs:
list.extend(self.findfiles(subdir, base, rec))
return list
def close(self, event=None):
if self.top:
self.top.grab_release()
self.top.withdraw()

View File

@ -1,65 +0,0 @@
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()

View File

@ -1,7 +1,7 @@
import string
class History:
def __init__(self, text):
self.text = text
self.history = []

View File

@ -2,20 +2,42 @@ import os
import tkFileDialog
import tkMessageBox
#$ event <<open-window-from-file>>
#$ win <Control-o>
#$ unix <Control-x><Control-f>
#$ event <<save-window>>
#$ win <Control-s>
#$ unix <Control-x><Control-s>
#$ event <<save-window-as-file>>
#$ win <Alt-s>
#$ unix <Control-x><Control-w>
#$ event <<save-copy-of-window-as-file>>
#$ win <Alt-Shift-s>
#$ unix <Control-x><w>
class IOBinding:
# Calls to non-standard text methods:
# reset_undo()
# set_saved(1)
def __init__(self, text):
self.text = text
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.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)
def get_saved(self):
return self.editwin.get_saved()
def set_saved(self, flag):
self.editwin.set_saved(flag)
def reset_undo(self):
self.editwin.reset_undo()
filename_change_hook = None
def set_filename_change_hook(self, hook):
@ -25,18 +47,29 @@ class IOBinding:
def set_filename(self, filename):
self.filename = filename
self.text.set_saved(1)
self.set_saved(1)
if self.filename_change_hook:
self.filename_change_hook()
def open(self, event):
if not self.text.get_saved():
if self.editwin.flist:
filename = self.askopenfile()
if filename:
self.editwin.flist.open(filename)
else:
self.text.focus_set()
return "break"
# Code for use outside IDLE:
if self.get_saved():
reply = self.maybesave()
if reply == "cancel":
self.text.focus_set()
return "break"
filename = self.askopenfile()
if filename:
self.loadfile(filename)
else:
self.text.focus_set()
return "break"
def loadfile(self, filename):
@ -50,14 +83,14 @@ class IOBinding:
self.text.delete("1.0", "end")
self.set_filename(None)
self.text.insert("1.0", chars)
self.text.reset_undo()
self.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():
if self.get_saved():
return "yes"
message = "Do you want to save %s before closing?" % (
self.filename or "this untitled document")
@ -70,8 +103,9 @@ class IOBinding:
reply = m.show()
if reply == "yes":
self.save(None)
if not self.text.get_saved():
if not self.get_saved():
reply = "cancel"
self.text.focus_set()
return reply
def save(self, event):
@ -79,7 +113,8 @@ class IOBinding:
self.save_as(event)
else:
if self.writefile(self.filename):
self.text.set_saved(1)
self.set_saved(1)
self.text.focus_set()
return "break"
def save_as(self, event):
@ -87,22 +122,23 @@ class IOBinding:
if filename:
if self.writefile(filename):
self.set_filename(filename)
self.text.set_saved(1)
self.set_saved(1)
self.text.focus_set()
return "break"
def save_a_copy(self, event):
filename = self.asksavefile()
if filename:
self.writefile(filename)
self.text.focus_set()
return "break"
def writefile(self, filename):
self.fixlastline()
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
@ -111,11 +147,16 @@ class IOBinding:
master=self.text)
return 0
def fixlastline(self):
c = self.text.get("end-2c")
if c != '\n':
self.text.insert("end-1c", "\n")
opendialog = None
savedialog = None
filetypes = [
("Python files", "*.py", "TEXT"),
("Python and text files", "*.py *.pyw *.txt", "TEXT"),
("All text files", "*", "TEXT"),
("All files", "*"),
]
@ -129,10 +170,13 @@ class IOBinding:
def defaultfilename(self, mode="open"):
if self.filename:
dir, base = os.path.split(self.filename)
return os.path.split(self.filename)
else:
dir = base = ""
return dir, base
try:
pwd = os.getcwd()
except os.error:
pwd = ""
return pwd, ""
def asksavefile(self):
dir, base = self.defaultfilename("save")
@ -145,13 +189,30 @@ class IOBinding:
def test():
from Tkinter import *
root = Tk()
class MyText(Text):
def reset_undo(self): pass
class MyEditWin:
def __init__(self, text):
self.text = text
self.flist = None
self.text.bind("<Control-o>", self.open)
self.text.bind("<Control-s>", self.save)
self.text.bind("<Alt-s>", self.save_as)
self.text.bind("<Alt-z>", self.save_a_copy)
def get_saved(self): return 0
def set_saved(self, flag): pass
text = MyText(root)
def reset_undo(self): pass
def open(self, event):
self.text.event_generate("<<open-window-from-file>>")
def save(self, event):
self.text.event_generate("<<save-window>>")
def save_as(self, event):
self.text.event_generate("<<save-window-as-file>>")
def save_a_copy(self, event):
self.text.event_generate("<<save-copy-of-window-as-file>>")
text = Text(root)
text.pack()
text.focus_set()
io = IOBinding(text)
editwin = MyEditWin(text)
io = IOBinding(editwin)
root.mainloop()
if __name__ == "__main__":

View File

@ -1,7 +1,7 @@
import string
class History:
def __init__(self, text):
self.text = text
self.history = []

View File

@ -0,0 +1,90 @@
from Tkinter import *
from EditorWindow import EditorWindow
import re
import tkMessageBox
class OutputWindow(EditorWindow):
"""An editor window that can serve as an output file.
Also the future base class for the Python shell window.
This class has no input facilities.
"""
def __init__(self, *args):
apply(EditorWindow.__init__, (self,) + args)
self.text.bind("<<goto-file-line>>", self.goto_file_line)
# Customize EditorWindow
def ispythonsource(self, filename):
# No colorization needed
return 0
def short_title(self):
return "Output"
def maybesave(self):
# Override base class method -- don't ask any questions
if self.get_saved():
return "yes"
else:
return "no"
# Act as output file
def write(self, s, tags=(), mark="insert"):
self.text.insert(mark, str(s), tags)
self.text.see(mark)
self.text.update()
def writelines(self, l):
map(self.write, l)
# Our own right-button menu
rmenu_specs = [
("Go to file/line", "<<goto-file-line>>"),
]
file_line_pats = [
r'file "([^"]*)", line (\d+)',
r'([^\s]+)\((\d+)\)',
r'([^\s]+):\s*(\d+):',
]
file_line_progs = None
def goto_file_line(self, event=None):
if self.file_line_progs is None:
l = []
for pat in self.file_line_pats:
l.append(re.compile(pat, re.IGNORECASE))
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:
tkMessageBox.showerror("No special line",
"The line you point at doesn't look like "
"a file name followed by a line number.",
master=self.text)
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)

View File

@ -1,86 +0,0 @@
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 StackBrowser
sv = StackBrowser(self.text._root(), self.flist)
def help(self):
from HelpWindow import HelpWindow
HelpWindow(root=self.flist.root)

View File

@ -12,9 +12,10 @@ from code import InteractiveInterpreter
from Tkinter import *
import tkMessageBox
from EditorWindow import fixwordbreaks
from FileList import FileList, MultiEditorWindow, MultiIOBinding
from EditorWindow import EditorWindow, fixwordbreaks
from FileList import FileList
from ColorDelegator import ColorDelegator
from OutputWindow import OutputWindow
# We need to patch linecache.checkcache, because we don't want it
# to throw away our <pyshell#...> entries.
@ -31,36 +32,54 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache):
linecache.checkcache = linecache_checkcache
class PyShellEditorWindow(MultiEditorWindow):
def __init__(self, *args):
apply(MultiEditorWindow.__init__, (self,) + args)
self.text.bind("<3>", self.right_menu_event)
def fixedwindowsmenu(self, wmenu):
wmenu.add_command(label="Python Shell", command=self.flist.open_shell)
wmenu.add_separator()
menu = None
def right_menu_event(self, event):
self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
if not self.menu:
self.make_menu()
menu = self.menu
iswin = sys.platform[:3] == 'win'
if iswin:
self.text.config(cursor="arrow")
menu.tk_popup(event.x_root, event.y_root)
if iswin:
self.text.config(cursor="ibeam")
# Note: <<newline-and-indent>> event is defined in AutoIndent.py
def make_menu(self):
self.menu = menu = Menu(self.text, tearoff=0)
menu.add_command(label="Set breakpoint here",
command=self.set_breakpoint_here)
def set_breakpoint_here(self):
#$ event <<plain-newline-and-indent>>
#$ win <Control-j>
#$ unix <Control-j>
#$ event <<beginning-of-line>>
#$ win <Control-a>
#$ win <Home>
#$ unix <Control-a>
#$ unix <Home>
#$ event <<history-next>>
#$ win <Alt-n>
#$ unix <Alt-n>
#$ event <<history-previous>>
#$ win <Alt-p>
#$ unix <Alt-p>
#$ event <<interrupt-execution>>
#$ win <Control-c>
#$ unix <Control-c>
#$ event <<end-of-file>>
#$ win <Control-d>
#$ unix <Control-d>
#$ event <<open-stack-viewer>>
#$ event <<toggle-debugger>>
class PyShellEditorWindow(EditorWindow):
# Regular text edit window when a shell is present
# XXX ought to merge with regular editor window
def __init__(self, *args):
apply(EditorWindow.__init__, (self,) + args)
self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
self.text.bind("<<open-python-shell>>", self.flist.open_shell)
rmenu_specs = [
("Set breakpoint here", "<<set-breakpoint-here>>"),
]
def set_breakpoint_here(self, event=None):
if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
self.text.bell()
return
@ -68,12 +87,14 @@ class PyShellEditorWindow(MultiEditorWindow):
class PyShellFileList(FileList):
# File list when a shell is present
EditorWindow = PyShellEditorWindow
pyshell = None
def open_shell(self):
def open_shell(self, event=None):
if self.pyshell:
self.pyshell.wakeup()
else:
@ -82,43 +103,29 @@ class PyShellFileList(FileList):
return self.pyshell
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):
# Colorizer for the shell window itself
def recolorize_main(self):
self.tag_remove("TODO", "1.0", "iomark")
self.tag_add("SYNC", "1.0", "iomark")
ColorDelegator.recolorize_main(self)
tagdefs = ColorDelegator.tagdefs.copy()
tagdefs.update({
##"stdin": {"background": "yellow"},
"stdout": {"foreground": "blue"},
"stderr": {"foreground": "#007700"},
"console": {"foreground": "#770000"},
"ERROR": {"background": "#FF7777"},
None: {"foreground": "purple"}, # default
})
class ModifiedInterpreter(InteractiveInterpreter):
def __init__(self, tkconsole):
self.tkconsole = tkconsole
InteractiveInterpreter.__init__(self)
@ -176,7 +183,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.tkconsole.resetoutput()
self.checklinecache()
InteractiveInterpreter.showtraceback(self)
def checklinecache(self):
c = linecache.cache
for key in c.keys():
@ -184,10 +191,10 @@ class ModifiedInterpreter(InteractiveInterpreter):
del c[key]
debugger = None
def setdebugger(self, debugger):
self.debugger = debugger
def getdebugger(self):
return self.debugger
@ -214,25 +221,23 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.showtraceback()
finally:
self.tkconsole.endexecuting()
def write(self, s):
# Override base class write
self.tkconsole.console.write(s)
class PyShell(PyShellEditorWindow):
class PyShell(OutputWindow):
# Override classes
ColorDelegator = ModifiedColorDelegator
IOBinding = ModifiedIOBinding
# Override menu bar specs
menu_specs = PyShellEditorWindow.menu_specs[:]
menu_specs.insert(len(menu_specs)-1, ("debug", "Debug"))
menu_specs.insert(len(menu_specs)-2, ("debug", "_Debug"))
# New classes
from History import History
from PopupMenu import PopupMenu
def __init__(self, flist=None):
self.interp = ModifiedInterpreter(self)
@ -242,23 +247,24 @@ class PyShell(PyShellEditorWindow):
root.withdraw()
flist = PyShellFileList(root)
PyShellEditorWindow.__init__(self, flist, None, None)
self.config_colors()
OutputWindow.__init__(self, flist, None, None)
import __builtin__
__builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
self.auto = self.extensions["AutoIndent"] # Required extension
self.auto.config(prefertabs=1)
text = self.text
text.configure(wrap="char")
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)
text.bind("<<goto-traceback-line>>", self.goto_traceback_line)
text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
text.bind("<<toggle-debugger>>", self.toggle_debugger)
text.bind("<<open-python-shell>>", self.flist.open_shell)
sys.stdout = PseudoFile(self, "stdout")
sys.stderr = PseudoFile(self, "stderr")
@ -266,31 +272,12 @@ class PyShell(PyShellEditorWindow):
self.console = PseudoFile(self, "console")
self.history = self.History(self.text)
self.popup = self.PopupMenu(self.text, self.flist)
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)
self.text.tag_raise("sel")
reading = 0
executing = 0
canceled = 0
endoffile = 0
def toggle_debugger(self, event=None):
if self.executing:
tkMessageBox.showerror("Don't debug now",
@ -362,14 +349,8 @@ class PyShell(PyShellEditorWindow):
# 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 short_title(self):
return "Python Shell"
def begin(self):
self.resetoutput()
@ -382,7 +363,7 @@ class PyShell(PyShellEditorWindow):
self.showprompt()
import Tkinter
Tkinter._default_root = None
def interact(self):
self.begin()
self.top.mainloop()
@ -457,7 +438,7 @@ class PyShell(PyShellEditorWindow):
self.text.insert("insert", "\n")
self.text.see("insert")
else:
self.auto.autoindent(event)
self.auto.auto_indent(event)
return "break"
def enter_callback(self, event):
@ -468,7 +449,7 @@ class PyShell(PyShellEditorWindow):
try:
sel = self.text.get("sel.first", "sel.last")
if sel:
if self.text.compare("self.last", "<=", "iomark"):
if self.text.compare("sel.last", "<=", "iomark"):
self.recall(sel)
return "break"
except:
@ -492,7 +473,7 @@ class PyShell(PyShellEditorWindow):
# If we're in the current input before its last line,
# insert a newline right at the insert point
if self.text.compare("insert", "<", "end-1c linestart"):
self.auto.autoindent(event)
self.auto.auto_indent(event)
return "break"
# We're in the last line; append a newline and submit it
self.text.mark_set("insert", "end-1c")
@ -500,7 +481,7 @@ class PyShell(PyShellEditorWindow):
self.text.insert("insert", "\n")
self.text.see("insert")
else:
self.auto.autoindent(event)
self.auto.auto_indent(event)
self.text.tag_add("stdin", "iomark", "end-1c")
self.text.update_idletasks()
if self.reading:
@ -545,49 +526,7 @@ class PyShell(PyShellEditorWindow):
self.canceled = 0
raise KeyboardInterrupt
return self._cancel_check
file_line_pats = [
r'File "([^"]*)", line (\d+)',
r'([^\s]+)\((\d+)\)',
r'([^\s]+):\s*(\d+):',
]
file_line_progs = None
def goto_traceback_line(self, event=None):
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:
tkMessageBox.showerror("No traceback line",
"The line you point at doesn't look "
"like an error message.",
master=self.text)
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, event=None):
try:
sys.last_traceback
@ -618,26 +557,22 @@ class PyShell(PyShellEditorWindow):
self.text.mark_set("iomark", "end-1c")
sys.stdout.softspace = 0
def write(self, s):
# Overrides base class write
self.console.write(s)
def write(self, s, tags=()):
self.text.mark_gravity("iomark", "right")
OutputWindow.write(self, s, tags, "iomark")
self.text.mark_gravity("iomark", "left")
if self.canceled:
self.canceled = 0
raise KeyboardInterrupt
class PseudoFile:
def __init__(self, interp, tags):
self.interp = interp
self.text = interp.text
def __init__(self, shell, tags):
self.shell = shell
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
self.shell.write(s, self.tags)
def writelines(self, l):
map(self.write, l)

View File

@ -1,23 +1,22 @@
IDLE 0.1 - 10/16/98
IDLE 0.2 - 01/01/99
-------------------
This is a *very* early preliminary release of IDLE, my own attempt at
a Tkinter-based IDE for Python. It currently has the following
features:
a Tkinter-based IDE for Python. It has the following features:
- multi-window text editor with multiple undo and Python colorizing
- Python shell (a.k.a. interactive interpreter) window subclass
- debugger
- 100% pure Python
- works on Windows and Unix (should work on Mac too)
- works on Windows and Unix (probably works on Mac too)
The main program is in the file "idle"; on Windows you can use
idle.pyw to avoid popping up a DOS console. Any arguments passed are
interpreted as files that will be opened for editing.
IDLE requires Python 1.5.2, so it is currently only usable for PSA
members who have the latest 1.5.2 alpha release (a public beta release
is due shortly).
IDLE requires Python 1.5.2, so it is currently only usable with the
Python 1.5.2 beta distribution (luckily, IDLE is bundled with Python
1.5.2).
Please send feedback to the Python newsgroup, comp.lang.python.
@ -27,46 +26,71 @@ Please send feedback to the Python newsgroup, comp.lang.python.
TO DO:
- "GO" command
- "Modularize" command
- command expansion from keywords, module contents, other buffers, etc.
- "Recent documents" menu item
- use platform specific default bindings
- title and Windows menu should have base filename first
- restructure state sensitive code to avoid testing flags all the time
- integrated debugger
- object browser instead of current stack viewer
- save some user state (e.g. window and cursor positions, bindings)
- 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:
- parentheses matching
- reindent, reformat text etc.
- M-[, M-] to move by paragraphs
- smart stuff with whitespace around Return
- filter region?
- grep?
- incremental search?
- ^K should cut to buffer
- command to fill text paragraphs
- restructure state sensitive code to avoid testing flags all the time
- finish debugger
- object browser instead of current stack viewer
- persistent user state (e.g. window and cursor positions, bindings)
- make backups when saving
- check file mtimes at various points
- interface with RCS/CVS/Perforce ???
- status bar?
- better help?
- don't open second class browser on same module
Details:
- when there's a selection, left/right arrow should go to either
end of the selection
- ^O should honor autoindent
- ^O (on Unix -- open-line) should honor autoindent
- after paste, show end of pasted text
- on Windows, should turn short filename to long filename (not only in argv!)
(shouldn't this be done -- or undone -- by ntpath.normpath?)
Structural problems:
- too much knowledge in FileList about EditorWindow (for example)
- Several occurrences of scrollable listbox with title and certain
behavior; should create base class to generalize this
- class browser could become an outline?
======================================================================
Comparison to PTUI
------------------
+ PTUI has a status line
+ PTUI's help is better (HTML!)
+ PTUI can attach a shell to any module
+ PTUI's auto indent is better
(understands that "if a: # blah, blah" opens a block)
+ IDLE requires 4x backspace to dedent a line
+ PTUI has more bells and whistles:
open multiple
append
modularize
examine
go
? PTUI's fontify is faster but synchronous (and still too slow);
does a lousy job if editing affects lines below
- PTUI's shell is worse:
no coloring;
no editing of multi-line commands;
@ -76,34 +100,18 @@ Comparison to PTUI
no redo;
one char at a time
- PTUI's framework is better:
status line
(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
I don't like the multiple buffers in one window model;
I don't like the big buttons at the top of the widow
- PTUI's help is better (HTML!)
- PTUI lacks an integrated debugger
- PTUI's search/replace is better (more features)
- PTUI lacks a class browser
- 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
- PTUI lacks many of IDLE's features:
- expand word
- regular expression search
- search files (grep)
======================================================================

168
Tools/idle/ReplaceDialog.py Normal file
View File

@ -0,0 +1,168 @@
import string
import os
import re
import fnmatch
from Tkinter import *
import tkMessageBox
import SearchEngine
from SearchDialogBase import SearchDialogBase
def replace(text):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_replacedialog"):
engine._replacedialog = ReplaceDialog(root, engine)
dialog = engine._replacedialog
dialog.open(text)
class ReplaceDialog(SearchDialogBase):
title = "Replace Dialog"
icon = "Replace"
def __init__(self, root, engine):
SearchDialogBase.__init__(self, root, engine)
self.replvar = StringVar(root)
def open(self, text):
SearchDialogBase.open(self, text)
try:
first = text.index("sel.first")
except TclError:
first = None
try:
last = text.index("sel.last")
except TclError:
last = None
first = first or text.index("insert")
last = last or first
self.show_hit(first, last)
self.ok = 1
def create_entries(self):
SearchDialogBase.create_entries(self)
self.replent = self.make_entry("Replace with:", self.replvar)
def create_command_buttons(self):
SearchDialogBase.create_command_buttons(self)
self.make_button("Find", self.find_it)
self.make_button("Replace", self.replace_it)
self.make_button("Replace+Find", self.default_command, 1)
self.make_button("Replace All", self.replace_all)
def find_it(self, event=None):
self.do_find(0)
def replace_it(self, event=None):
if self.do_find(self.ok):
self.do_replace()
def default_command(self, event=None):
if self.do_find(self.ok):
self.do_replace()
self.do_find(0)
def replace_all(self, event=None):
prog = self.engine.getprog()
if not prog:
return
repl = self.replvar.get()
text = self.text
res = self.engine.search_text(text, prog)
if not res:
text.bell()
return
text.tag_remove("sel", "1.0", "end")
text.tag_remove("hit", "1.0", "end")
line = res[0]
col = res[1].start()
if self.engine.iswrap():
line = 1
col = 0
ok = 1
first = last = None
# XXX ought to replace circular instead of top-to-bottom when wrapping
while 1:
res = self.engine.search_forward(text, prog, line, col, 0, ok)
if not res:
break
line, m = res
chars = text.get("%d.0" % line, "%d.0" % (line+1))
orig = m.group()
new = re.pcre_expand(m, repl)
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
if new == orig:
text.mark_set("insert", last)
else:
text.mark_set("insert", first)
if first != last:
text.delete(first, last)
if new:
text.insert(first, new)
col = i + len(new)
ok = 0
if first and last:
self.show_hit(first, last)
self.close()
def do_find(self, ok=0):
if not self.engine.getprog():
return 0
text = self.text
res = self.engine.search_text(text, None, ok)
if not res:
text.bell()
return 0
line, m = res
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
self.show_hit(first, last)
self.ok = 1
return 1
def do_replace(self):
prog = self.engine.getprog()
if not prog:
return 0
text = self.text
try:
first = pos = text.index("sel.first")
last = text.index("sel.last")
except TclError:
pos = None
if not pos:
first = last = pos = text.index("insert")
line, col = SearchEngine.get_line_col(pos)
chars = text.get("%d.0" % line, "%d.0" % (line+1))
m = prog.match(chars, col)
if not prog:
return 0
new = re.pcre_expand(m, self.replvar.get())
text.mark_set("insert", first)
if m.group():
text.delete(first, last)
if new:
text.insert(first, new)
self.show_hit(first, text.index("insert"))
self.ok = 0
return 1
def show_hit(self, first, last):
text = self.text
text.mark_set("insert", first)
text.tag_remove("sel", "1.0", "end")
text.tag_add("sel", first, last)
text.tag_remove("hit", "1.0", "end")
if first == last:
text.tag_add("hit", first)
else:
text.tag_add("hit", first, last)
text.see("insert")
text.update_idletasks()
def close(self, event=None):
SearchDialogBase.close(self, event)
self.text.tag_remove("hit", "1.0", "end")

View File

@ -0,0 +1,38 @@
import tkMessageBox
import os
import imp
import sys
class ScriptBinding:
def __init__(self, editwin):
self.editwin = editwin
text = editwin.text
text.bind("<<run-module>>", self.run_module)
text.bind("<<run-script>>", self.run_script)
text.bind("<<new-shell>>", self.new_shell)
def run_module(self, event=None):
filename = self.editwin.io.filename
if not filename:
tkMessageBox.showerror("No file name",
"This window has no file name",
master=self.editwin.text)
return
modname, ext = os.path.splitext(os.path.basename(filename))
try:
mod = sys.modules[modname]
except KeyError:
mod = imp.new_module(modname)
sys.modules[modname] = mod
source = self.editwin.text.get("1.0", "end")
exec source in mod.__dict__
def run_script(self, event=None):
pass
def new_shell(self, event=None):
import PyShell
# XXX Not enough: each shell takes over stdin/stdout/stderr...
pyshell = PyShell.PyShell(self.editwin.flist)
pyshell.begin()

View File

@ -1,7 +1,7 @@
from Tkinter import *
class ScrolledList:
def __init__(self, master, **options):
# Create top frame, with scrollbar and listbox
self.master = master
@ -18,22 +18,22 @@ class ScrolledList:
listbox["yscrollcommand"] = vbar.set
# Bind events to the list box
listbox.bind("<ButtonRelease-1>", self.click_event)
listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
listbox.bind("<ButtonPress-3>", self.popup_event)
listbox.bind("<Key-Up>", self.up_event)
listbox.bind("<Key-Down>", self.down_event)
# Set the focus
listbox.focus_set()
def close(self):
self.frame.destroy()
def clear(self):
self.listbox.delete(0, "end")
def append(self, item):
self.listbox.insert("end", str(item))
def get(self, index):
return self.listbox.get(index)
@ -49,9 +49,9 @@ class ScrolledList:
self.select(index)
self.on_double(index)
return "break"
menu = None
def popup_event(self, event):
if not self.menu:
self.make_menu()
@ -65,7 +65,7 @@ class ScrolledList:
menu = Menu(self.listbox, tearoff=0)
self.menu = menu
self.fill_menu()
def up_event(self, event):
index = self.listbox.index("active")
if self.listbox.selection_includes(index):
@ -78,7 +78,7 @@ class ScrolledList:
self.select(index)
self.on_select(index)
return "break"
def down_event(self, event):
index = self.listbox.index("active")
if self.listbox.selection_includes(index):
@ -91,22 +91,22 @@ class ScrolledList:
self.select(index)
self.on_select(index)
return "break"
def select(self, index):
self.listbox.focus_set()
self.listbox.activate(index)
self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index)
self.listbox.see(index)
# Methods to override for specific actions
def fill_menu(self):
pass
def on_select(self, index):
pass
def on_double(self, index):
pass

View File

@ -1,89 +1,96 @@
import string
import re
import tkSimpleDialog
import tkMessageBox
###$ event <<find>>
###$ win <Control-f>
###$ unix <Control-u><Control-u><Control-s>
###$ event <<find-again>>
###$ win <Control-g>
###$ win <F3>
###$ unix <Control-u><Control-s>
###$ event <<find-selection>>
###$ win <Control-F3>
###$ unix <Control-s>
###$ event <<find-in-files>>
###$ win <Alt-F3>
###$ event <<replace>>
###$ win <Control-h>
###$ event <<goto-line>>
###$ win <Alt-g>
###$ unix <Alt-g>
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"
line, col = map(int,
string.split(self.text.index("insert"), "."))
chars = self.text.get("%d.0" % line, "%d.0" % (line+1))
while chars:
m = self.prog.search(chars, col)
if m:
i, j = m.span()
self.text.mark_set("insert",
"%d.%d" % (line, j))
self.text.tag_remove("sel", "1.0", "end")
self.text.tag_add("sel",
"%d.%d" % (line, i),
"%d.%d" % (line, j))
self.text.see("insert")
break
line = line + 1
col = 0
chars = self.text.get("%d.0" % line, "%d.0" % (line+1))
else:
# Not found
self.text.bell()
return "break"
def goto_line_event(self, event):
lineno = tkSimpleDialog.askinteger("Goto",
"Go to line number:",
parent=self.text)
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")
windows_keydefs = {
'<<find-again>>': ['<Control-g>', '<F3>'],
'<<find-in-files>>': ['<Alt-F3>'],
'<<find-selection>>': ['<Control-F3>'],
'<<find>>': ['<Control-f>'],
'<<replace>>': ['<Control-h>'],
'<<goto-line>>': ['<Alt-g>'],
}
unix_keydefs = {
'<<find-again>>': ['<Control-u><Control-s>'],
'<<find-selection>>': ['<Control-s>'],
'<<find>>': ['<Control-u><Control-u><Control-s>'],
'<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
}
menudefs = [
('edit', [
None,
('_Find...', '<<find>>'),
('Find a_gain', '<<find-again>>'),
('Find _selection', '<<find-selection>>'),
('Find in Files...', '<<find-in-files>>'),
('R_eplace...', '<<replace>>'),
('Go to _line', '<<goto-line>>'),
]),
]
def __init__(self, editwin):
self.editwin = editwin
def find_event(self, event):
import SearchDialog
SearchDialog.find(self.editwin.text)
return "break"
def find_again_event(self, event):
import SearchDialog
SearchDialog.find_again(self.editwin.text)
return "break"
def find_selection_event(self, event):
import SearchDialog
SearchDialog.find_selection(self.editwin.text)
return "break"
def find_in_files_event(self, event):
import GrepDialog
GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist)
return "break"
def replace_event(self, event):
import ReplaceDialog
ReplaceDialog.replace(self.editwin.text)
return "break"
def goto_line_event(self, event):
print event
text = self.editwin.text
lineno = tkSimpleDialog.askinteger("Goto",
"Go to line number:",
parent=text)
if lineno is None:
return "break"
if lineno <= 0:
text.bell()
return "break"
text.mark_set("insert", "%d.0" % lineno)
text.see("insert")

View File

@ -0,0 +1,59 @@
from Tkinter import *
import SearchEngine
from SearchDialogBase import SearchDialogBase
def _setup(text):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_searchdialog"):
engine._searchdialog = SearchDialog(root, engine)
return engine._searchdialog
def find(text):
return _setup(text).open(text)
def find_again(text):
return _setup(text).find_again(text)
def find_selection(text):
return _setup(text).find_selection(text)
class SearchDialog(SearchDialogBase):
def create_widgets(self):
f = SearchDialogBase.create_widgets(self)
self.make_button("Find", self.default_command, 1)
def default_command(self, event=None):
if not self.engine.getprog():
return
if self.find_again(self.text):
self.close()
def find_again(self, text):
if not self.engine.getpat():
self.open(text)
return 0
if not self.engine.getprog():
return 0
res = self.engine.search_text(text)
if res:
line, m = res
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
text.tag_remove("sel", "1.0", "end")
text.tag_add("sel", first, last)
text.mark_set("insert", self.engine.isback() and first or last)
text.see("insert")
return 1
else:
text.bell()
return 0
def find_selection(self, text):
pat = text.get("sel.first", "sel.last")
if pat:
self.engine.setcookedpat(pat)
return self.find_again(text)

View File

@ -0,0 +1,129 @@
import string
from Tkinter import *
class SearchDialogBase:
title = "Search Dialog"
icon = "Search"
needwrapbutton = 1
def __init__(self, root, engine):
self.root = root
self.engine = engine
self.top = None
def open(self, text):
self.text = text
if not self.top:
self.create_widgets()
else:
self.top.deiconify()
self.top.tkraise()
self.ent.focus_set()
self.ent.selection_range(0, "end")
self.ent.icursor(0)
self.top.grab_set()
def close(self, event=None):
if self.top:
self.top.grab_release()
self.top.withdraw()
def create_widgets(self):
top = Toplevel(self.root)
top.bind("<Return>", self.default_command)
top.bind("<Escape>", self.close)
top.protocol("WM_DELETE_WINDOW", self.close)
top.wm_title(self.title)
top.wm_iconname(self.icon)
self.top = top
self.row = 0
self.top.grid_columnconfigure(0, weight=0)
self.top.grid_columnconfigure(1, weight=100)
self.create_entries()
self.create_option_buttons()
self.create_other_buttons()
return self.create_command_buttons()
def make_entry(self, label, var):
l = Label(self.top, text=label)
l.grid(row=self.row, col=0, sticky="w")
e = Entry(self.top, textvariable=var, exportselection=0)
e.grid(row=self.row, col=1, sticky="we")
self.row = self.row + 1
return e
def make_frame(self):
f = Frame(self.top)
f.grid(row=self.row, col=0, columnspan=2, sticky="we")
self.row = self.row + 1
return f
def make_button(self, label, command, isdef=0, side="left"):
b = Button(self.buttonframe,
text=label, command=command,
default=isdef and "active" or "normal")
b.pack(side=side)
return b
def create_entries(self):
self.ent = self.make_entry("Find:", self.engine.patvar)
def create_option_buttons(self):
f = self.make_frame()
btn = Checkbutton(f, anchor="w",
variable=self.engine.revar,
text="Regular expression")
btn.pack(side="left", fill="both")
if self.engine.isre():
btn.select()
btn = Checkbutton(f, anchor="w",
variable=self.engine.casevar,
text="Match case")
btn.pack(side="left", fill="both")
if self.engine.iscase():
btn.select()
btn = Checkbutton(f, anchor="w",
variable=self.engine.wordvar,
text="Whole word")
btn.pack(side="left", fill="both")
if self.engine.isword():
btn.select()
if self.needwrapbutton:
btn = Checkbutton(f, anchor="w",
variable=self.engine.wrapvar,
text="Wrap around")
btn.pack(side="left", fill="both")
if self.engine.iswrap():
btn.select()
def create_other_buttons(self):
f = self.make_frame()
lbl = Label(f, text="Direction: ")
lbl.pack(side="left")
btn = Radiobutton(f, anchor="w",
variable=self.engine.backvar, value=1,
text="Up")
btn.pack(side="left", fill="both")
if self.engine.isback():
btn.select()
btn = Radiobutton(f, anchor="w",
variable=self.engine.backvar, value=0,
text="Down")
btn.pack(side="left", fill="both")
if not self.engine.isback():
btn.select()
def create_command_buttons(self):
f = self.buttonframe = self.make_frame()
b = self.make_button("close", self.close, side="right")
b.lower()

214
Tools/idle/SearchEngine.py Normal file
View File

@ -0,0 +1,214 @@
import string
import re
from Tkinter import *
import tkMessageBox
def get(root):
if not hasattr(root, "_searchengine"):
root._searchengine = SearchEngine(root)
# XXX This will never garbage-collect -- who cares
return root._searchengine
class SearchEngine:
def __init__(self, root):
self.root = root
# State shared by search, replace, and grep;
# the search dialogs bind these to UI elements.
self.patvar = StringVar(root) # search pattern
self.revar = BooleanVar(root) # regular expression?
self.casevar = BooleanVar(root) # match case?
self.wordvar = BooleanVar(root) # match whole word?
self.wrapvar = BooleanVar(root) # wrap around buffer?
self.wrapvar.set(1) # (on by default)
self.backvar = BooleanVar(root) # search backwards?
# Access methods
def getpat(self):
return self.patvar.get()
def setpat(self, pat):
self.patvar.set(pat)
def isre(self):
return self.revar.get()
def iscase(self):
return self.casevar.get()
def isword(self):
return self.wordvar.get()
def iswrap(self):
return self.wrapvar.get()
def isback(self):
return self.backvar.get()
# Higher level access methods
def getcookedpat(self):
pat = self.getpat()
if not self.isre():
pat = re.escape(pat)
if self.isword():
pat = r"\b%s\b" % pat
return pat
def getprog(self):
pat = self.getpat()
if not pat:
self.report_error(pat, "Empty regular expression")
return None
pat = self.getcookedpat()
flags = 0
if not self.iscase():
flags = flags | re.IGNORECASE
try:
prog = re.compile(pat, flags)
except re.error, what:
try:
msg, col = what
except:
msg = str(what)
col = -1
self.report_error(pat, msg, col)
return None
return prog
def report_error(self, pat, msg, col=-1):
# Derived class could overrid this with something fancier
msg = "Error: " + str(msg)
if pat:
msg = msg + "\np\Pattern: " + str(pat)
if col >= 0:
msg = msg + "\nOffset: " + str(col)
tkMessageBox.showerror("Regular expression error",
msg, master=self.root)
def setcookedpat(self, pat):
if self.isre():
pat = re.escape(pat)
self.setpat(pat)
def search_text(self, text, prog=None, ok=0):
"""Search a text widget for the pattern.
If prog is given, it should be the precompiled pattern.
Return a tuple (lineno, matchobj); None if not found.
This obeys the wrap and direction (back) settings.
The search starts at the selection (if there is one) or
at the insert mark (otherwise). If the search is forward,
it starts at the right of the selection; for a backward
search, it starts at the left end. An empty match exactly
at either end of the selection (or at the insert mark if
there is no selection) is ignored unless the ok flag is true
-- this is done to guarantee progress.
If the search is allowed to wrap around, it will return the
original selection if (and only if) it is the only match.
XXX When wrapping around and failing to find anything, the
portion of the text after the selection is searched twice :-(
"""
if not prog:
prog = self.getprog()
if not prog:
return None # Compilation failed -- stop
wrap = self.wrapvar.get()
first, last = get_selection(text)
if self.isback():
if ok:
start = last
else:
start = first
line, col = get_line_col(start)
res = self.search_backward(text, prog, line, col, wrap, ok)
else:
if ok:
start = first
else:
start = last
line, col = get_line_col(start)
res = self.search_forward(text, prog, line, col, wrap, ok)
return res
def search_forward(self, text, prog, line, col, wrap, ok=0):
chars = text.get("%d.0" % line, "%d.0" % (line+1))
while chars:
m = prog.search(chars[:-1], col)
if m:
if ok or m.end() > col:
return line, m
line = line + 1
col = 0
ok = 1
chars = text.get("%d.0" % line, "%d.0" % (line+1))
if not chars and wrap:
wrap = 0
line = 1
chars = text.get("1.0", "2.0")
return None
def search_backward(self, text, prog, line, col, wrap, ok=0):
chars = text.get("%d.0" % line, "%d.0" % (line+1))
while 1:
m = search_reverse(prog, chars[:-1], col)
if m:
i, j = m.span()
if ok or m.start() < col:
return line, m
line = line - 1
ok = 1
if line <= 0:
if not wrap:
break
wrap = 0
pos = text.index("end-1c")
line, col = map(int, string.split(pos, "."))
chars = text.get("%d.0" % line, "%d.0" % (line+1))
col = len(chars) - 1
return None
# Helper to search backwards in a string.
# (Optimized for the case where the pattern isn't found.)
def search_reverse(prog, chars, col):
m = prog.search(chars)
if not m:
return None
found = None
i, j = m.span()
while i < col and j <= col:
found = m
if i == j:
j = j+1
m = prog.search(chars, j)
if not m:
break
i, j = m.span()
return found
# Helper to get selection end points, defaulting to insert mark.
# Return a tuple of indices ("line.col" strings).
def get_selection(text):
try:
first = text.index("sel.first")
last = text.index("sel.last")
except TclError:
first = last = None
if not first:
first = text.index("insert")
if not last:
last = first
return first, last
# Helper to parse a text index into a (line, col) tuple.
def get_line_col(index):
line, col = map(int, string.split(index, ".")) # Fails on invalid index
return line, col

View File

@ -4,16 +4,18 @@ import os
from Tkinter import *
import linecache
from repr import Repr
from WindowList import ListedToplevel
from ScrolledList import ScrolledList
class StackBrowser:
def __init__(self, root, flist, stack=None):
self.top = top = Toplevel(root)
self.top = top = ListedToplevel(root)
top.protocol("WM_DELETE_WINDOW", self.close)
top.wm_title("Stack viewer")
top.wm_iconname("Stack")
# Create help label
self.helplabel = Label(top,
text="Click once to view variables; twice for source",
@ -24,7 +26,7 @@ class StackBrowser:
if stack is None:
stack = get_stack()
self.sv.load_stack(stack)
def close(self):
self.top.destroy()
@ -44,7 +46,7 @@ class StackBrowser:
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__"):
@ -66,7 +68,7 @@ class StackBrowser:
title,
self.globalsdict)
self.globalsframe.pack(fill="both", side="bottom")
def show_locals(self, frame):
self.localsdict = None
if self.localsviewer:
@ -92,7 +94,7 @@ class StackBrowser:
class StackViewer(ScrolledList):
def __init__(self, master, flist, browser):
ScrolledList.__init__(self, master)
self.flist = flist
@ -149,7 +151,7 @@ class StackViewer(ScrolledList):
def show_stack_frame(self):
index = self.listbox.index("active")
self.browser.show_frame(self.stack[index])
def show_source(self, index):
frame, lineno = self.stack[index]
code = frame.f_code
@ -169,7 +171,7 @@ def get_stack(t=None, f=None):
while f is not None:
stack.append((f, f.f_lineno))
if f is self.botframe:
break
break
f = f.f_back
stack.reverse()
while t is not None:
@ -191,7 +193,7 @@ def getexception(type=None, value=None):
class NamespaceViewer:
def __init__(self, master, title, dict=None):
width = 0
height = 40
@ -217,9 +219,9 @@ class NamespaceViewer:
self.subframe = subframe = Frame(canvas)
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
self.load_dict(dict)
dict = -1
def load_dict(self, dict, force=0):
if dict is self.dict and not force:
return

View File

@ -3,6 +3,18 @@ import string
from Tkinter import *
from Delegator import Delegator
#$ event <<redo>>
#$ win <Control-y>
#$ unix <Alt-z>
#$ event <<undo>>
#$ win <Control-z>
#$ unix <Control-z>
#$ event <<dump-undo-state>>
#$ win <Control-backslash>
#$ unix <Control-backslash>
class UndoDelegator(Delegator):
@ -11,7 +23,7 @@ class UndoDelegator(Delegator):
def __init__(self):
Delegator.__init__(self)
self.reset_undo()
def setdelegate(self, delegate):
if self.delegate is not None:
self.unbind("<<undo>>")

53
Tools/idle/WindowList.py Normal file
View File

@ -0,0 +1,53 @@
from Tkinter import *
class WindowList:
def __init__(self):
self.dict = {}
def add(self, window):
self.dict[str(window)] = window
def delete(self, window):
try:
del self.dict[str(window)]
except KeyError:
# Sometimes, destroy() is called twice
pass
def add_windows_to_menu(self, menu):
list = []
for key in self.dict.keys():
window = self.dict[key]
title = window.get_title()
list.append((title, window))
list.sort()
for title, window in list:
if title == "Python Shell":
# Hack -- until we have a better way to this
continue
menu.add_command(label=title, command=window.wakeup)
registry = WindowList()
def add_windows_to_menu(menu):
registry.add_windows_to_menu(menu)
class ListedToplevel(Toplevel):
def __init__(self, master, **kw):
Toplevel.__init__(self, master, kw)
registry.add(self)
def destroy(self):
registry.delete(self)
Toplevel.destroy(self)
def get_title(self):
# Subclass can override
return self.wm_title()
def wakeup(self):
self.tkraise()
self.wm_deiconify()
self.focus_set()

35
Tools/idle/ZoomHeight.py Normal file
View File

@ -0,0 +1,35 @@
# Sample extension: zoom a window to maximum height
import re
class ZoomHeight:
menudefs = [
('windows', [
('_Zoom Height', '<<zoom-height>>'),
])
]
windows_keydefs = {
'<<zoom-height>>': ['<Alt-F2>'],
}
unix_keydefs = {
'<<zoom-height>>': ['<Control-z><Control-z>'],
}
def __init__(self, editwin):
self.editwin = editwin
def zoom_height_event(self, event):
top = self.editwin.top
geom = top.wm_geometry()
m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
if not m:
top.bell()
return
width, height, x, y = map(int, m.groups())
height = top.winfo_screenheight() - 72
newgeom = "%dx%d+%d+%d" % (width, height, x, 0)
if geom == newgeom:
newgeom = ""
top.wm_geometry(newgeom)

93
Tools/idle/eventparse.py Normal file
View File

@ -0,0 +1,93 @@
#! /usr/bin/env python
"""Parse event definitions out of comments in source files."""
import re
import sys
import os
import string
import getopt
import glob
import fileinput
import pprint
def main():
hits = []
sublist = []
args = sys.argv[1:]
if not args:
args = filter(lambda s: 'A' <= s[0] <= 'Z', glob.glob("*.py"))
if not args:
print "No arguments, no [A-Z]*.py files."
return 1
for line in fileinput.input(args):
if line[:2] == '#$':
if not sublist:
sublist.append('file %s' % fileinput.filename())
sublist.append('line %d' % fileinput.lineno())
sublist.append(string.strip(line[2:-1]))
else:
if sublist:
hits.append(sublist)
sublist = []
if sublist:
hits.append(sublist)
sublist = []
dd = {}
for sublist in hits:
d = {}
for line in sublist:
words = string.split(line, None, 1)
if len(words) != 2:
continue
tag = words[0]
l = d.get(tag, [])
l.append(words[1])
d[tag] = l
if d.has_key('event'):
keys = d['event']
if len(keys) != 1:
print "Multiple event keys in", d
print 'File "%s", line %d' % (d['file'], d['line'])
key = keys[0]
if dd.has_key(key):
print "Duplicate event in", d
print 'File "%s", line %d' % (d['file'], d['line'])
return
dd[key] = d
else:
print "No event key in", d
print 'File "%s", line %d' % (d['file'], d['line'])
winevents = getevents(dd, "win")
unixevents = getevents(dd, "unix")
save = sys.stdout
f = open("keydefs.py", "w")
try:
sys.stdout = f
print "windows_keydefs = \\"
pprint.pprint(winevents)
print
print "unix_keydefs = \\"
pprint.pprint(unixevents)
finally:
sys.stdout = save
f.close()
def getevents(dd, key):
res = {}
events = dd.keys()
events.sort()
for e in events:
d = dd[e]
if d.has_key(key) or d.has_key("all"):
list = []
for x in d.get(key, []) + d.get("all", []):
list.append(x)
if key == "unix" and x[:5] == "<Alt-":
x = "<Meta-" + x[5:]
list.append(x)
res[e] = list
return res
if __name__ == '__main__':
sys.exit(main())

9
Tools/idle/extend.py Normal file
View File

@ -0,0 +1,9 @@
# IDLE extensions to be loaded by default (see extend.txt).
# Edit this file to configure your set of IDLE extensions.
standard = [
"SearchBinding",
"AutoIndent",
"AutoExpand",
"ZoomHeight",
]

105
Tools/idle/extend.txt Normal file
View File

@ -0,0 +1,105 @@
Writing an IDLE extension
An IDLE extension can define new key bindings and menu entries for
IDLE edit windows. There is a simple mechanism to load extensions
when IDLE starts up and to attach them to each edit window.
(It is also possible to make other changes to IDLE, but this must
be done by editing the IDLE source code.)
The list of extensions loaded at startup time is configured by editing
the file extend.py; see below for details.
An IDLE extension is defined by a class. Methods of the class define
actions that are invoked by those bindings or menu entries.
Class (or instance) variables define the bindings and menu additions;
these are automatically applied by IDLE when the extension is linked
to an edit window.
An IDLE extension class is instantiated with a single argument,
`editwin', an EditorWindow instance.
The extension cannot assume much about this argument, but it
is guarateed to have the following instance variables:
text a Text instance (a widget)
io an IOBinding instance (more about this later)
flist the FileList instance (shared by all edit windows)
(There are a few more, but they are rarely useful.)
The extension class must not bind key events. Rather, it must define
one or more virtual events, e.g. <<zoom-height>>, and corresponding
methods, e.g. zoom_height(), and have one or more class (or instance)
variables that define mappings between virtual events and key sequences,
e.g. <Alt-F2>. When the extension is loaded, these key sequences will
be bound to the corresponding virtual events, and the virtual events
will be bound to the corresponding methods. (This indirection is done
so that the key bindings can easily be changed, and so that other sources
of virtual events can exist, such as menu entries.)
The following class or instance variables are used to define key
bindings for virtual events:
keydefs for all platforms
mac_keydefs for Macintosh
windows_keydefs for Windows
unix_keydefs for Unix (and other platforms)
Each of these variables, if it exists, must be a dictionary whose
keys are virtual events, and whose values are lists of key sequences.
An extension can define menu entries in a similar fashion. This is done
with a class or instance variable named menudefs; it should be a list of
pair, where each pair is a menu name (lowercase) and a list of menu entries.
Each menu entry is either None (to insert a separator entry) or a pair of
strings (menu_label, virtual_event). Here, menu_label is the label of the
menu entry, and virtual_event is the virtual event to be generated when the
entry is selected. An underscore in the menu label is removed; the
character following the underscore is displayed underlined, to indicate the
shortcut character (for Windows).
At the moment, extensions cannot define whole new menus; they must define
entries in existing menus. Some menus are not present on some windows;
such entry definitions are then ignored, but the key bindings are still
applied. (This should probably be refined in the future.)
Here is a complete example example:
class ZoomHeight:
menudefs = [
('edit', [
None, # Separator
('_Zoom Height', '<<zoom-height>>'),
])
]
windows_keydefs = {
'<<zoom-height>>': ['<Alt-F2>'],
}
unix_keydefs = {
'<<zoom-height>>': ['<Control-z><Control-z>'],
}
def __init__(self, editwin):
self.editwin = editwin
def zoom_height(self, event):
"...Do what you want here..."
The final piece of the puzzle is the file "extend.py", which contains a
simple table used to configure the loading of extensions. This file currently
contains a single list variable named "standard", which is a list of extension
names that are to be loaded. (In the future, other configuration variables
may be added to this module.)
Extensions can define key bindings and menu entries that reference events they
don't implement (including standard events); however this is not recommended
(and may be forbidden in the future).
Extensions are not required to define menu entries for all events
they implement.
Note: in order to change key bindings, you must currently edit the file
keydefs. It contains two dictionaries named and formatted like the
keydefs dictionaries described above, one for the Unix bindings and one for
the Windows bindings. In the future, a better mechanism will be provided.

View File

@ -1,3 +1,5 @@
[See end for tips.]
File menu:
New window -- create a new editing window
@ -75,9 +77,19 @@ Python syntax colors: the coloring is applied in a background thread
Comments red
Definitions blue
Console colors:
Shell colors:
Console output red
Console output dark red
stdout blue
stderr dark green
stdin purple
stdin black
Tips:
To change the font on Windows, open EditorWindow.py and change
text['font'] = ("verdana", 8)
to, e.g.,
text['font'] = ("courier new", 10)
To change the Python syntax colors, edit the tagdefs table
in ColorDelegator.py; to change the shell colors, edit the
tagdefs table in PyShell.py.

3
Tools/idle/idle.bat Normal file
View File

@ -0,0 +1,3 @@
rem idle.bat
"C:\Program Files\Python\python.exe" "idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9

View File

@ -1,3 +1,9 @@
#! /usr/bin/env python
import PyShell
PyShell.main()
try:
import PyShell
PyShell.main()
except SystemExit:
raise
except:
import traceback
traceback.print_exc()
raw_input("Hit return to exit...")

1
Tools/idle/idlever.py Normal file
View File

@ -0,0 +1 @@
IDLE_VERSION = "0.2"

59
Tools/idle/keydefs.py Normal file
View File

@ -0,0 +1,59 @@
windows_keydefs = \
{'<<Copy>>': ['<Control-c>'],
'<<Cut>>': ['<Control-x>'],
'<<Paste>>': ['<Control-v>'],
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
'<<center-insert>>': ['<Control-l>'],
'<<close-all-windows>>': ['<Control-q>'],
'<<close-window>>': ['<Alt-F4>'],
'<<dump-undo-state>>': ['<Control-backslash>'],
'<<end-of-file>>': ['<Control-d>'],
'<<expand-word>>': ['<Alt-slash>'],
'<<help>>': ['<F1>'],
'<<history-next>>': ['<Alt-n>'],
'<<history-previous>>': ['<Alt-p>'],
'<<interrupt-execution>>': ['<Control-c>'],
'<<open-class-browser>>': ['<Alt-c>'],
'<<open-module>>': ['<Alt-m>'],
'<<open-new-window>>': ['<Control-n>'],
'<<open-window-from-file>>': ['<Control-o>'],
'<<plain-newline-and-indent>>': ['<Control-j>'],
'<<redo>>': ['<Control-y>'],
'<<remove-selection>>': ['<Escape>'],
'<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
'<<save-window-as-file>>': ['<Alt-s>'],
'<<save-window>>': ['<Control-s>'],
'<<select-all>>': ['<Alt-a>'],
'<<toggle-auto-coloring>>': ['<Control-slash>'],
'<<undo>>': ['<Control-z>'],
}
unix_keydefs = \
{'<<Copy>>': ['<Alt-w>', '<Meta-w>'],
'<<Cut>>': ['<Control-w>'],
'<<Paste>>': ['<Control-y>'],
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
'<<center-insert>>': ['<Control-l>'],
'<<close-all-windows>>': ['<Control-x><Control-c>'],
'<<close-window>>': ['<Control-x><Control-0>', '<Control-x><Key-0>'],
'<<do-nothing>>': ['<Control-x>'],
'<<dump-undo-state>>': ['<Control-backslash>'],
'<<end-of-file>>': ['<Control-d>'],
'<<expand-word>>': ['<Alt-slash>', '<Meta-slash>'],
'<<help>>': ['<F1>'],
'<<history-next>>': ['<Alt-n>', '<Meta-n>'],
'<<history-previous>>': ['<Alt-p>', '<Meta-p>'],
'<<interrupt-execution>>': ['<Control-c>'],
'<<open-class-browser>>': ['<Control-x><Control-b>'],
'<<open-module>>': ['<Control-x><Control-m>'],
'<<open-new-window>>': ['<Control-x><Control-n>'],
'<<open-window-from-file>>': ['<Control-x><Control-f>'],
'<<plain-newline-and-indent>>': ['<Control-j>'],
'<<redo>>': ['<Alt-z>', '<Meta-z>'],
'<<save-copy-of-window-as-file>>': ['<Control-x><w>'],
'<<save-window-as-file>>': ['<Control-x><Control-w>'],
'<<save-window>>': ['<Control-x><Control-s>'],
'<<select-all>>': ['<Alt-a>', '<Meta-a>'],
'<<toggle-auto-coloring>>': ['<Control-slash>'],
'<<undo>>': ['<Control-z>'],
}