summaryrefslogtreecommitdiffstats
path: root/Tools/idle
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1999-01-02 21:28:54 (GMT)
committerGuido van Rossum <guido@python.org>1999-01-02 21:28:54 (GMT)
commit504b0bf066e4fddb21646331e89c2f6836c5c638 (patch)
treef5454648430eb4818810305325561aabb02cf035 /Tools/idle
parentf07c328c072e62ada8671ec30392572add22d904 (diff)
downloadcpython-504b0bf066e4fddb21646331e89c2f6836c5c638.zip
cpython-504b0bf066e4fddb21646331e89c2f6836c5c638.tar.gz
cpython-504b0bf066e4fddb21646331e89c2f6836c5c638.tar.bz2
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.
Diffstat (limited to 'Tools/idle')
-rw-r--r--Tools/idle/AutoExpand.py31
-rw-r--r--Tools/idle/AutoIndent.py142
-rw-r--r--Tools/idle/Bindings.py115
-rw-r--r--Tools/idle/ClassBrowser.py38
-rw-r--r--Tools/idle/ColorDelegator.py155
-rw-r--r--Tools/idle/Debugger.py55
-rw-r--r--Tools/idle/Delegator.py1
-rw-r--r--Tools/idle/EditorWindow.py321
-rw-r--r--Tools/idle/FileList.py143
-rw-r--r--Tools/idle/FrameViewer.py4
-rw-r--r--Tools/idle/GrepDialog.py134
-rw-r--r--Tools/idle/HelpWindow.py65
-rw-r--r--Tools/idle/History.py2
-rw-r--r--Tools/idle/IOBinding.py107
-rw-r--r--Tools/idle/IdleHistory.py2
-rw-r--r--Tools/idle/OutputWindow.py90
-rw-r--r--Tools/idle/PopupMenu.py86
-rw-r--r--Tools/idle/PyShell.py267
-rw-r--r--Tools/idle/README.txt (renamed from Tools/idle/README)100
-rw-r--r--Tools/idle/ReplaceDialog.py168
-rw-r--r--Tools/idle/ScriptBinding.py38
-rw-r--r--Tools/idle/ScrolledList.py30
-rw-r--r--Tools/idle/SearchBinding.py179
-rw-r--r--Tools/idle/SearchDialog.py59
-rw-r--r--Tools/idle/SearchDialogBase.py129
-rw-r--r--Tools/idle/SearchEngine.py214
-rw-r--r--Tools/idle/StackViewer.py24
-rw-r--r--Tools/idle/UndoDelegator.py14
-rw-r--r--Tools/idle/WindowList.py53
-rw-r--r--Tools/idle/ZoomHeight.py35
-rw-r--r--Tools/idle/eventparse.py93
-rw-r--r--Tools/idle/extend.py9
-rw-r--r--Tools/idle/extend.txt105
-rw-r--r--Tools/idle/help.txt18
-rw-r--r--Tools/idle/idle.bat3
-rw-r--r--Tools/idle/idle.pyw12
-rw-r--r--Tools/idle/idlever.py1
-rw-r--r--Tools/idle/keydefs.py59
38 files changed, 2203 insertions, 898 deletions
diff --git a/Tools/idle/AutoExpand.py b/Tools/idle/AutoExpand.py
index 0d80ce8..1ebd7d5 100644
--- a/Tools/idle/AutoExpand.py
+++ b/Tools/idle/AutoExpand.py
@@ -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)
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py
index d800589..329f492 100644
--- a/Tools/idle/AutoIndent.py
+++ b/Tools/idle/AutoIndent.py
@@ -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:]
diff --git a/Tools/idle/Bindings.py b/Tools/idle/Bindings.py
index 5a13d22..dbca89a 100644
--- a/Tools/idle/Bindings.py
+++ b/Tools/idle/Bindings.py
@@ -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,
diff --git a/Tools/idle/ClassBrowser.py b/Tools/idle/ClassBrowser.py
index 21ff22e..1224964 100644
--- a/Tools/idle/ClassBrowser.py
+++ b/Tools/idle/ClassBrowser.py
@@ -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)
diff --git a/Tools/idle/ColorDelegator.py b/Tools/idle/ColorDelegator.py
index 5bf921d..357358f 100644
--- a/Tools/idle/ColorDelegator.py
+++ b/Tools/idle/ColorDelegator.py
@@ -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"
-
- 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
+ next = "1.0"
+ was_ok = is_ok = 0
+ while 1:
+ item = self.tag_nextrange("TODO", next)
+ if not item:
+ break
+ head, tail = item
+ self.tag_remove("SYNC", head, tail)
+ item = self.tag_prevrange("SYNC", head)
+ if item:
+ head = item[1]
+ else:
+ head = "1.0"
+
+ chars = ""
+ mark = head
+ is_ok = was_ok = 0
+ while not (was_ok and is_ok):
+ next = self.index(mark + " lineend +1c")
+ was_ok = "SYNC" in self.tag_names(next + "-1c")
+ line = self.get(mark, next)
+ ##print head, "get", mark, next, "->", `line`
+ if not line:
+ return
+ for tag in self.tagdefs.keys():
+ self.tag_remove(tag, mark, next)
+ chars = chars + line
+ m = self.prog.search(chars)
+ while m:
+ i, j = m.span()
+ for key, value in m.groupdict().items():
+ if value:
+ a, b = m.span(key)
+ self.tag_add(key,
+ head + "+%dc" % a,
+ head + "+%dc" % b)
+ if value in ("def", "class"):
+ m1 = self.idprog.match(chars, b)
+ if m1:
+ a, b = m1.span(1)
+ self.tag_add("DEFINITION",
+ head + "+%dc" % a,
+ head + "+%dc" % b)
+ m = self.prog.search(chars, j)
+ is_ok = "SYNC" in self.tag_names(next + "-1c")
+ mark = next
+ if is_ok:
+ head = mark
+ chars = ""
+ self.update()
+ if self.stop_colorizing:
+ if __debug__: print "colorizing stopped"
+ return
def main():
diff --git a/Tools/idle/Debugger.py b/Tools/idle/Debugger.py
index 7cb2fdb..24a7376 100644
--- a/Tools/idle/Debugger.py
+++ b/Tools/idle/Debugger.py
@@ -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)
diff --git a/Tools/idle/Delegator.py b/Tools/idle/Delegator.py
index 6125591..3665247 100644
--- a/Tools/idle/Delegator.py
+++ b/Tools/idle/Delegator.py
@@ -1,3 +1,4 @@
+
class Delegator:
# The cache is only used to be able to change delegates!
diff --git a/Tools/idle/EditorWindow.py b/Tools/idle/EditorWindow.py
index 24b62dc..b4c9156 100644
--- a/Tools/idle/EditorWindow.py
+++ b/Tools/idle/EditorWindow.py
@@ -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
-
+ from Tkinter import Toplevel
+
about_title = about_title
- about_text = about_text
+ about_text = about_text
- def __init__(self, root, filename=None):
+ 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()
diff --git a/Tools/idle/FileList.py b/Tools/idle/FileList.py
index d9378e3..393b81c 100644
--- a/Tools/idle/FileList.py
+++ b/Tools/idle/FileList.py
@@ -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):
+# (This is labeled as 'Exit'in the File menu)
+#$ event <<close-all-windows>>
+#$ win <Control-q>
+#$ unix <Control-x><Control-c>
- 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])
+class FileList:
+ from EditorWindow import EditorWindow
+ EditorWindow.Toplevel = WindowList.ListedToplevel # XXX Patch it!
-class FileList:
-
- EditorWindow = MultiEditorWindow
-
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)
diff --git a/Tools/idle/FrameViewer.py b/Tools/idle/FrameViewer.py
index e5a6051..2ce0935 100644
--- a/Tools/idle/FrameViewer.py
+++ b/Tools/idle/FrameViewer.py
@@ -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()
diff --git a/Tools/idle/GrepDialog.py b/Tools/idle/GrepDialog.py
new file mode 100644
index 0000000..df3504b
--- /dev/null
+++ b/Tools/idle/GrepDialog.py
@@ -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()
diff --git a/Tools/idle/HelpWindow.py b/Tools/idle/HelpWindow.py
deleted file mode 100644
index a1b13c3..0000000
--- a/Tools/idle/HelpWindow.py
+++ /dev/null
@@ -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()
diff --git a/Tools/idle/History.py b/Tools/idle/History.py
index 0798098..3094173 100644
--- a/Tools/idle/History.py
+++ b/Tools/idle/History.py
@@ -1,7 +1,7 @@
import string
class History:
-
+
def __init__(self, text):
self.text = text
self.history = []
diff --git a/Tools/idle/IOBinding.py b/Tools/idle/IOBinding.py
index 0d61afc..6a41a37 100644
--- a/Tools/idle/IOBinding.py
+++ b/Tools/idle/IOBinding.py
@@ -2,20 +2,42 @@ import os
import tkFileDialog
import tkMessageBox
+#$ event <<open-window-from-file>>
+#$ win <Control-o>
+#$ unix <Control-x><Control-f>
-class IOBinding:
+#$ 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>
- # Calls to non-standard text methods:
- # reset_undo()
- # set_saved(1)
- def __init__(self, text):
- self.text = text
+class IOBinding:
+
+ 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__":
diff --git a/Tools/idle/IdleHistory.py b/Tools/idle/IdleHistory.py
index 0798098..3094173 100644
--- a/Tools/idle/IdleHistory.py
+++ b/Tools/idle/IdleHistory.py
@@ -1,7 +1,7 @@
import string
class History:
-
+
def __init__(self, text):
self.text = text
self.history = []
diff --git a/Tools/idle/OutputWindow.py b/Tools/idle/OutputWindow.py
new file mode 100644
index 0000000..c13b3e4
--- /dev/null
+++ b/Tools/idle/OutputWindow.py
@@ -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)
diff --git a/Tools/idle/PopupMenu.py b/Tools/idle/PopupMenu.py
deleted file mode 100644
index edda3a3..0000000
--- a/Tools/idle/PopupMenu.py
+++ /dev/null
@@ -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)
diff --git a/Tools/idle/PyShell.py b/Tools/idle/PyShell.py
index 887da1e..6df38c3 100644
--- a/Tools/idle/PyShell.py
+++ b/Tools/idle/PyShell.py
@@ -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):
-
+# Note: <<newline-and-indent>> event is defined in AutoIndent.py
+
+#$ 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(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")
-
- 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):
+ 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
-class ModifiedColorDelegator(ColorDelegator):
-
def recolorize_main(self):
self.tag_remove("TODO", "1.0", "iomark")
self.tag_add("SYNC", "1.0", "iomark")
ColorDelegator.recolorize_main(self)
+ 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)
diff --git a/Tools/idle/README b/Tools/idle/README.txt
index 1a835f6..62b34cd 100644
--- a/Tools/idle/README
+++ b/Tools/idle/README.txt
@@ -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
-
-- PTUI's help is better (HTML!)
+ 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 search/replace is better (more features)
+- PTUI lacks an integrated debugger
-- 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 lacks a class browser
-- 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)
======================================================================
diff --git a/Tools/idle/ReplaceDialog.py b/Tools/idle/ReplaceDialog.py
new file mode 100644
index 0000000..3bff8b5
--- /dev/null
+++ b/Tools/idle/ReplaceDialog.py
@@ -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")
diff --git a/Tools/idle/ScriptBinding.py b/Tools/idle/ScriptBinding.py
new file mode 100644
index 0000000..a112fc5
--- /dev/null
+++ b/Tools/idle/ScriptBinding.py
@@ -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()
diff --git a/Tools/idle/ScrolledList.py b/Tools/idle/ScrolledList.py
index ef2fde4..a5f9a29 100644
--- a/Tools/idle/ScrolledList.py
+++ b/Tools/idle/ScrolledList.py
@@ -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
diff --git a/Tools/idle/SearchBinding.py b/Tools/idle/SearchBinding.py
index ccbdc6c..d73db13 100644
--- a/Tools/idle/SearchBinding.py
+++ b/Tools/idle/SearchBinding.py
@@ -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")
diff --git a/Tools/idle/SearchDialog.py b/Tools/idle/SearchDialog.py
new file mode 100644
index 0000000..501b6d0
--- /dev/null
+++ b/Tools/idle/SearchDialog.py
@@ -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)
diff --git a/Tools/idle/SearchDialogBase.py b/Tools/idle/SearchDialogBase.py
new file mode 100644
index 0000000..faf5269
--- /dev/null
+++ b/Tools/idle/SearchDialogBase.py
@@ -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()
diff --git a/Tools/idle/SearchEngine.py b/Tools/idle/SearchEngine.py
new file mode 100644
index 0000000..d9361d0
--- /dev/null
+++ b/Tools/idle/SearchEngine.py
@@ -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
diff --git a/Tools/idle/StackViewer.py b/Tools/idle/StackViewer.py
index 688c1b4..93923f9 100644
--- a/Tools/idle/StackViewer.py
+++ b/Tools/idle/StackViewer.py
@@ -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
diff --git a/Tools/idle/UndoDelegator.py b/Tools/idle/UndoDelegator.py
index ee49651..39c8e63 100644
--- a/Tools/idle/UndoDelegator.py
+++ b/Tools/idle/UndoDelegator.py
@@ -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>>")
diff --git a/Tools/idle/WindowList.py b/Tools/idle/WindowList.py
new file mode 100644
index 0000000..b9b0bb1
--- /dev/null
+++ b/Tools/idle/WindowList.py
@@ -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()
diff --git a/Tools/idle/ZoomHeight.py b/Tools/idle/ZoomHeight.py
new file mode 100644
index 0000000..eee901c
--- /dev/null
+++ b/Tools/idle/ZoomHeight.py
@@ -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)
diff --git a/Tools/idle/eventparse.py b/Tools/idle/eventparse.py
new file mode 100644
index 0000000..cb2028d
--- /dev/null
+++ b/Tools/idle/eventparse.py
@@ -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())
diff --git a/Tools/idle/extend.py b/Tools/idle/extend.py
new file mode 100644
index 0000000..7d8117d
--- /dev/null
+++ b/Tools/idle/extend.py
@@ -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",
+]
diff --git a/Tools/idle/extend.txt b/Tools/idle/extend.txt
new file mode 100644
index 0000000..83b6428
--- /dev/null
+++ b/Tools/idle/extend.txt
@@ -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.
diff --git a/Tools/idle/help.txt b/Tools/idle/help.txt
index beafc42..5307fa6 100644
--- a/Tools/idle/help.txt
+++ b/Tools/idle/help.txt
@@ -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.
diff --git a/Tools/idle/idle.bat b/Tools/idle/idle.bat
new file mode 100644
index 0000000..a416001
--- /dev/null
+++ b/Tools/idle/idle.bat
@@ -0,0 +1,3 @@
+rem idle.bat
+
+"C:\Program Files\Python\python.exe" "idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/Tools/idle/idle.pyw b/Tools/idle/idle.pyw
index 3c06e05..a1fc021 100644
--- a/Tools/idle/idle.pyw
+++ b/Tools/idle/idle.pyw
@@ -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...")
diff --git a/Tools/idle/idlever.py b/Tools/idle/idlever.py
new file mode 100644
index 0000000..7465590
--- /dev/null
+++ b/Tools/idle/idlever.py
@@ -0,0 +1 @@
+IDLE_VERSION = "0.2"
diff --git a/Tools/idle/keydefs.py b/Tools/idle/keydefs.py
new file mode 100644
index 0000000..1e94904
--- /dev/null
+++ b/Tools/idle/keydefs.py
@@ -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>'],
+}