]> granicus.if.org Git - python/commitdiff
Checking in IDLE 0.2.
authorGuido van Rossum <guido@python.org>
Sat, 2 Jan 1999 21:28:54 +0000 (21:28 +0000)
committerGuido van Rossum <guido@python.org>
Sat, 2 Jan 1999 21:28:54 +0000 (21:28 +0000)
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.

38 files changed:
Tools/idle/AutoExpand.py
Tools/idle/AutoIndent.py
Tools/idle/Bindings.py
Tools/idle/ClassBrowser.py
Tools/idle/ColorDelegator.py
Tools/idle/Debugger.py
Tools/idle/Delegator.py
Tools/idle/EditorWindow.py
Tools/idle/FileList.py
Tools/idle/FrameViewer.py
Tools/idle/GrepDialog.py [new file with mode: 0644]
Tools/idle/HelpWindow.py [deleted file]
Tools/idle/History.py
Tools/idle/IOBinding.py
Tools/idle/IdleHistory.py
Tools/idle/OutputWindow.py [new file with mode: 0644]
Tools/idle/PopupMenu.py [deleted file]
Tools/idle/PyShell.py
Tools/idle/README.txt [moved from Tools/idle/README with 61% similarity]
Tools/idle/ReplaceDialog.py [new file with mode: 0644]
Tools/idle/ScriptBinding.py [new file with mode: 0644]
Tools/idle/ScrolledList.py
Tools/idle/SearchBinding.py
Tools/idle/SearchDialog.py [new file with mode: 0644]
Tools/idle/SearchDialogBase.py [new file with mode: 0644]
Tools/idle/SearchEngine.py [new file with mode: 0644]
Tools/idle/StackViewer.py
Tools/idle/UndoDelegator.py
Tools/idle/WindowList.py [new file with mode: 0644]
Tools/idle/ZoomHeight.py [new file with mode: 0644]
Tools/idle/eventparse.py [new file with mode: 0644]
Tools/idle/extend.py [new file with mode: 0644]
Tools/idle/extend.txt [new file with mode: 0644]
Tools/idle/help.txt
Tools/idle/idle.bat [new file with mode: 0644]
Tools/idle/idle.pyw
Tools/idle/idlever.py [new file with mode: 0644]
Tools/idle/keydefs.py [new file with mode: 0644]

index 0d80ce8e17c0f4b011b75bda18d11be0095735b5..1ebd7d53cbd012ad78ba88863034d5542a0dcd13 100644 (file)
@@ -1,17 +1,30 @@
 import string
 import re
 
+###$ event <<expand-word>>
+###$ win <Alt-slash>
+###$ unix <Alt-slash>
+
 class AutoExpand:
-    
+
+    keydefs = {
+        '<<expand-word>>': ['<Alt-slash>'],
+    }
+
+    menudefs = [
+        ('edit', [
+            ('E_xpand word', '<<expand-word>>'),
+         ]),
+    ]
+
     wordchars = string.letters + string.digits + "_"
 
-    def __init__(self, text):
-        self.text = text
-        self.text.wordlist = None
+    def __init__(self, editwin):
+        self.text = editwin.text
+        self.text.wordlist = None # XXX what is this?
         self.state = None
-        self.text.bind("<<expand-word>>", self.autoexpand)
-        
-    def autoexpand(self, event):
+
+    def expand_word_event(self, event):
         curinsert = self.text.index("insert")
         curline = self.text.get("insert linestart", "insert lineend")
         if not self.state:
@@ -36,7 +49,7 @@ class AutoExpand:
         curline = self.text.get("insert linestart", "insert lineend")
         self.state = words, index, curinsert, curline
         return "break"
-        
+
     def getwords(self):
         word = self.getprevword()
         if not word:
@@ -66,7 +79,7 @@ class AutoExpand:
             dict[w] = w
         words.append(word)
         return words
-        
+
     def getprevword(self):
         line = self.text.get("insert linestart", "insert")
         i = len(line)
index d800589bc51fda4adfd1ec3cff4ad9470b953358..329f49215fe52f6f72c931d12f57584f5227a688 100644 (file)
@@ -1,16 +1,81 @@
 import string
+from Tkinter import TclError
+
+###$ event <<newline-and-indent>>
+###$ win <Key-Return>
+###$ win <KP_Enter>
+###$ unix <Key-Return>
+###$ unix <KP_Enter>
+
+###$ event <<indent-region>>
+###$ win <Control-bracketright>
+###$ unix <Alt-bracketright>
+###$ unix <Control-bracketright>
+
+###$ event <<dedent-region>>
+###$ win <Control-bracketleft>
+###$ unix <Alt-bracketleft>
+###$ unix <Control-bracketleft>
+
+###$ event <<comment-region>>
+###$ win <Alt-Key-3>
+###$ unix <Alt-Key-3>
+
+###$ event <<uncomment-region>>
+###$ win <Alt-Key-4>
+###$ unix <Alt-Key-4>
+
+###$ event <<tabify-region>>
+###$ win <Alt-Key-5>
+###$ unix <Alt-Key-5>
+
+###$ event <<untabify-region>>
+###$ win <Alt-Key-6>
+###$ unix <Alt-Key-6>
 
 class AutoIndent:
 
-    def __init__(self, text, prefertabs=0, spaceindent=4*" "):
-        self.text = text
-        self.prefertabs = prefertabs
-        self.spaceindent = spaceindent
-        text.bind("<<newline-and-indent>>", self.autoindent)
-        text.bind("<<indent-region>>", self.indentregion)
-        text.bind("<<dedent-region>>", self.dedentregion)
-        text.bind("<<comment-region>>", self.commentregion)
-        text.bind("<<uncomment-region>>", self.uncommentregion)
+    menudefs = [
+        ('edit', [
+            None,
+            ('_Indent region', '<<indent-region>>'),
+            ('_Dedent region', '<<dedent-region>>'),
+            ('Comment _out region', '<<comment-region>>'),
+            ('U_ncomment region', '<<uncomment-region>>'),
+            ('Tabify region', '<<tabify-region>>'),
+            ('Untabify region', '<<untabify-region>>'),
+        ]),
+    ]
+
+    windows_keydefs = {
+        '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
+        '<<indent-region>>': ['<Control-bracketright>'],
+        '<<dedent-region>>': ['<Control-bracketleft>'],
+        '<<comment-region>>': ['<Alt-Key-3>'],
+        '<<uncomment-region>>': ['<Alt-Key-4>'],
+        '<<tabify-region>>': ['<Alt-Key-5>'],
+        '<<untabify-region>>': ['<Alt-Key-6>'],
+    }
+
+    unix_keydefs = {
+        '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
+        '<<indent-region>>': ['<Alt-bracketright>',
+                              '<Meta-bracketright>',
+                              '<Control-bracketright>'],
+        '<<dedent-region>>': ['<Alt-bracketleft>',
+                              '<Meta-bracketleft>',
+                              '<Control-bracketleft>'],
+        '<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
+        '<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
+        '<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
+        '<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
+    }
+
+    prefertabs = 0
+    spaceindent = 4*" "
+
+    def __init__(self, editwin):
+        self.text = editwin.text
 
     def config(self, **options):
         for key, value in options.items():
@@ -21,8 +86,16 @@ class AutoIndent:
             else:
                 raise KeyError, "bad option name: %s" % `key`
 
-    def autoindent(self, event):
+    def newline_and_indent_event(self, event):
         text = self.text
+        try:
+            first = text.index("sel.first")
+            last = text.index("sel.last")
+        except TclError:
+            first = last = None
+        if first and last:
+            text.delete(first, last)
+            text.mark_set("insert", first)
         line = text.get("insert linestart", "insert")
         i, n = 0, len(line)
         while i < n and line[i] in " \t":
@@ -43,8 +116,10 @@ class AutoIndent:
         text.see("insert")
         return "break"
 
-    def indentregion(self, event):
-        head, tail, chars, lines = self.getregion()
+    auto_indent = newline_and_indent_event
+
+    def indent_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
         for pos in range(len(lines)):
             line = lines[pos]
             if line:
@@ -53,11 +128,11 @@ class AutoIndent:
                     i = i+1
                 line = line[:i] + "    " + line[i:]
                 lines[pos] = line
-        self.setregion(head, tail, chars, lines)
+        self.set_region(head, tail, chars, lines)
         return "break"
 
-    def dedentregion(self, event):
-        head, tail, chars, lines = self.getregion()
+    def dedent_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
         for pos in range(len(lines)):
             line = lines[pos]
             if line:
@@ -75,20 +150,20 @@ class AutoIndent:
                         indent = indent[:-4]
                     line = indent + line
                 lines[pos] = line
-        self.setregion(head, tail, chars, lines)
+        self.set_region(head, tail, chars, lines)
         return "break"
 
-    def commentregion(self, event):
-        head, tail, chars, lines = self.getregion()
+    def comment_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
         for pos in range(len(lines)):
             line = lines[pos]
             if not line:
                 continue
             lines[pos] = '##' + line
-        self.setregion(head, tail, chars, lines)
+        self.set_region(head, tail, chars, lines)
 
-    def uncommentregion(self, event):
-        head, tail, chars, lines = self.getregion()
+    def uncomment_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
         for pos in range(len(lines)):
             line = lines[pos]
             if not line:
@@ -98,9 +173,19 @@ class AutoIndent:
             elif line[:1] == '#':
                 line = line[1:]
             lines[pos] = line
-        self.setregion(head, tail, chars, lines)
+        self.set_region(head, tail, chars, lines)
 
-    def getregion(self):
+    def tabify_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
+        lines = map(tabify, lines)
+        self.set_region(head, tail, chars, lines)
+
+    def untabify_region_event(self, event):
+        head, tail, chars, lines = self.get_region()
+        lines = map(string.expandtabs, lines)
+        self.set_region(head, tail, chars, lines)
+
+    def get_region(self):
         text = self.text
         head = text.index("sel.first linestart")
         tail = text.index("sel.last -1c lineend +1c")
@@ -111,7 +196,7 @@ class AutoIndent:
         lines = string.split(chars, "\n")
         return head, tail, chars, lines
 
-    def setregion(self, head, tail, chars, lines):
+    def set_region(self, head, tail, chars, lines):
         text = self.text
         newchars = string.join(lines, "\n")
         if newchars == chars:
@@ -122,3 +207,12 @@ class AutoIndent:
         text.delete(head, tail)
         text.insert(head, newchars)
         text.tag_add("sel", head, "insert")
+
+def tabify(line, tabsize=8):
+    spaces = tabsize * ' '
+    for i in range(0, len(line), tabsize):
+        if line[i:i+tabsize] != spaces:
+            break
+    else:
+        i = len(line)
+    return '\t' * (i/tabsize) + line[i:]
index 5a13d22e92a755d25e4d0d22db75717338da76a3..dbca89a9788a46e52dc28bc460079117cbded3b6 100644 (file)
@@ -8,6 +8,7 @@
 import sys
 import string
 import re
+from keydefs import *
 
 menudefs = [
  # underscore prefixes character to underscore
@@ -15,7 +16,8 @@ menudefs = [
    ('_New window', '<<open-new-window>>'),
    ('_Open...', '<<open-window-from-file>>'),
    ('Open _module...', '<<open-module>>'),
-   ('Class _browser...', '<<open-class-browser>>'),
+   ('Class _browser', '<<open-class-browser>>'),
+   ('Python shell', '<<open-python-shell>>'),
    None,
    ('_Save', '<<save-window>>'),
    ('Save _As...', '<<save-window-as-file>>'),
@@ -31,19 +33,15 @@ menudefs = [
    ('Cu_t', '<<Cut>>'),
    ('_Copy', '<<Copy>>'),
    ('_Paste', '<<Paste>>'),
-   None,
-   ('_Find...', '<<find>>'),
-   ('Find _next', '<<find-next>>'),
-   ('Find _same', '<<find-same>>'),
-   ('_Go to line', '<<goto-line>>'),
-   None,
-   ('_Dedent region', '<<dedent-region>>'),
-   ('_Indent region', '<<indent-region>>'),
-   ('Comment _out region', '<<comment-region>>'),
-   ('U_ncomment region', '<<uncomment-region>>'),
+   ('Select _All', '<<select-all>>'),
+  ]),
+ ('script', [
+   ('Run module', '<<run-module>>'),
+   ('Run script', '<<run-script>>'),
+   ('New shell', '<<new-shell>>'),
   ]),
  ('debug', [
-   ('_Go to line from traceback', '<<goto-traceback-line>>'),
+   ('_Go to file/line', '<<goto-file-line>>'),
    ('_Open stack viewer', '<<open-stack-viewer>>'),
    ('_Debugger toggle', '<<toggle-debugger>>'),
   ]),
@@ -54,81 +52,6 @@ menudefs = [
   ]),
 ]
 
-windows_keydefs = {
- '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
- '<<close-all-windows>>': ['<Control-q>'],
- '<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'],
- '<<dedent-region>>': ['<Control-bracketleft>'],
- '<<dump-undo-state>>': ['<Control-backslash>'],
- '<<end-of-file>>': ['<Control-d>'],
- '<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'],
- '<<find-next>>': ['<F3>', '<Control-g>'],
- '<<find-same>>': ['<Control-F3>'],
- '<<find>>': ['<Control-f>'],
- '<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
- '<<history-next>>': ['<Meta-n>', '<Alt-n>'],
- '<<history-previous>>': ['<Meta-p>', '<Alt-p>'],
- '<<indent-region>>': ['<Control-bracketright>'],
- '<<interrupt-execution>>': ['<Control-c>'],
- '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
- '<<open-new-window>>': ['<Control-n>'],
- '<<open-window-from-file>>': ['<Control-o>'],
- '<<plain-newline-and-indent>>': ['<Control-j>'],
- '<<redo>>': ['<Control-y>'],
- '<<save-copy-of-window-as-file>>': ['<Meta-w>'],
- '<<save-window-as-file>>': ['<Control-w>'],
- '<<save-window>>': ['<Control-s>'],
- '<<toggle-auto-coloring>>': ['<Control-slash>'],
- '<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'],
- '<<undo>>': ['<Control-z>'],
-}
-
-emacs_keydefs = {
- '<<Copy>>': ['<Alt-w>'],
- '<<Cut>>': ['<Control-w>'],
- '<<Paste>>': ['<Control-y>'],
- '<<about-idle>>': [],
- '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
- '<<center-insert>>': ['<Control-l>'],
- '<<close-all-windows>>': ['<Control-x><Control-c>'],
- '<<close-window>>': ['<Control-x><Control-0>'],
- '<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'],
- '<<dedent-region>>': ['<Meta-bracketleft>',
-                       '<Alt-bracketleft>',
-                       '<Control-bracketleft>'],
- '<<do-nothing>>': ['<Control-x>'],
- '<<dump-undo-state>>': ['<Control-backslash>'],
- '<<end-of-file>>': ['<Control-d>'],
- '<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'],
- '<<find-next>>': ['<Control-u><Control-s>'],
- '<<find-same>>': ['<Control-s>'],
- '<<find>>': ['<Control-u><Control-u><Control-s>'],
- '<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
- '<<goto-traceback-line>>': [],
- '<<help>>': [],
- '<<history-next>>': ['<Meta-n>', '<Alt-n>'],
- '<<history-previous>>': ['<Meta-p>', '<Alt-p>'],
- '<<indent-region>>': ['<Meta-bracketright>',
-                       '<Alt-bracketright>',
-                       '<Control-bracketright>'],
- '<<interrupt-execution>>': ['<Control-c>'],
- '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
- '<<open-class-browser>>': ['<Control-x><Control-b>'],
- '<<open-module>>': ['<Control-x><Control-m>'],
- '<<open-new-window>>': ['<Control-x><Control-n>'],
- '<<open-stack-viewer>>': [],
- '<<open-window-from-file>>': ['<Control-x><Control-f>'],
- '<<plain-newline-and-indent>>': ['<Control-j>'],
- '<<redo>>': ['<Alt-z>', '<Meta-z>'],
- '<<save-copy-of-window-as-file>>': ['<Control-x><w>'],
- '<<save-window-as-file>>': ['<Control-x><Control-w>'],
- '<<save-window>>': ['<Control-x><Control-s>'],
- '<<toggle-auto-coloring>>': ['<Control-slash>'],
- '<<toggle-debugger>>': [],
- '<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'],
- '<<undo>>': ['<Control-z>'],
-}
-
 def prepstr(s):
     # Helper to extract the underscore from a string,
     # e.g. prepstr("Co_py") returns (2, "Copy").
@@ -140,18 +63,14 @@ def prepstr(s):
 keynames = {
  'bracketleft': '[',
  'bracketright': ']',
+ 'slash': '/',
 }
 
-def getaccelerator(keydefs, event):
+def get_accelerator(keydefs, event):
     keylist = keydefs.get(event)
     if not keylist:
         return ""
     s = keylist[0]
-    if s[:6] == "<Meta-":
-        # Prefer Alt over Meta -- they should be the same thing anyway
-        alts = "<Alt-" + s[6:]
-        if alts in keylist:
-            s = alts
     s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
     s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
     s = re.sub("Key-", "", s)
@@ -165,7 +84,7 @@ def getaccelerator(keydefs, event):
 if sys.platform == 'win32':
     default_keydefs = windows_keydefs
 else:
-    default_keydefs = emacs_keydefs
+    default_keydefs = unix_keydefs
 
 def apply_bindings(text, keydefs=default_keydefs):
     text.keydefs = keydefs
@@ -173,14 +92,10 @@ def apply_bindings(text, keydefs=default_keydefs):
         if keylist:
             apply(text.event_add, (event,) + tuple(keylist))
 
-def fill_menus(text, menudict, defs=menudefs):
+def fill_menus(text, menudict, defs=menudefs, keydefs=default_keydefs):
     # Fill the menus for the given text widget.  The menudict argument is
     # a dictionary containing the menus, keyed by their lowercased name.
     # Menus that are absent or None are ignored.
-    if hasattr(text, "keydefs"):
-        keydefs = text.keydefs
-    else:
-        keydefs = default_keydefs
     for mname, itemlist in defs:
         menu = menudict.get(mname)
         if not menu:
@@ -191,7 +106,7 @@ def fill_menus(text, menudict, defs=menudefs):
             else:
                 label, event = item
                 underline, label = prepstr(label)
-                accelerator = getaccelerator(keydefs, event)
+                accelerator = get_accelerator(keydefs, event)
                 def command(text=text, event=event):
                     text.event_generate(event)
                 menu.add_command(label=label, underline=underline,
index 21ff22e77ef05ac636a92df8bf21f733161f0613..1224964c42e66868b12aede367a35d9d9753516a 100644 (file)
@@ -1,7 +1,7 @@
 """Primitive class browser.
 
 XXX TO DO:
-    
+
 - generalize the scrolling listbox with some behavior into a base class
 - add popup menu with more options (e.g. doc strings, base classes, imports)
 - show function argument list (have to do pattern matching on source)
@@ -14,12 +14,13 @@ import string
 import pyclbr
 from Tkinter import *
 import tkMessageBox
+from WindowList import ListedToplevel
 
 from ScrolledList import ScrolledList
 
 
 class ClassBrowser:
-    
+
     def __init__(self, flist, name, path=[]):
         root = flist.root
         try:
@@ -34,9 +35,10 @@ class ClassBrowser:
         self.flist = flist
         self.dict = dict
         self.root = root
-        self.top = top = Toplevel(root)
+        self.top = top = ListedToplevel(root)
         self.top.protocol("WM_DELETE_WINDOW", self.close)
-        top.wm_title("Class browser")
+        top.wm_title("Class Browser - " + name)
+        top.wm_iconname("ClBrowser")
         self.leftframe = leftframe = Frame(top)
         self.leftframe.pack(side="left", fill="both", expand=1)
         # Create help label
@@ -48,12 +50,12 @@ class ClassBrowser:
             self.leftframe, self.flist, self)
         # Load the classes
         self.load_classes(dict, name)
-    
+
     def close(self):
         self.classviewer = None
         self.methodviewer = None
         self.top.destroy()
-        
+
     def load_classes(self, dict, module):
         self.classviewer.load_classes(dict, module)
         if self.botframe:
@@ -64,7 +66,7 @@ class ClassBrowser:
     botframe = None
     methodhelplabel = None
     methodviewer = None
-    
+
     def show_methods(self, cl):
         if not self.botframe:
             self.botframe = Frame(self.top)
@@ -78,12 +80,12 @@ class ClassBrowser:
 
 
 class ClassViewer(ScrolledList):
-    
+
     def __init__(self, master, flist, browser):
         ScrolledList.__init__(self, master)
         self.flist = flist
         self.browser = browser
-        
+
     def load_classes(self, dict, module):
         self.clear()
         self.dict = dict
@@ -103,7 +105,7 @@ class ClassViewer(ScrolledList):
                     super.append(name)
                 s = s + "(%s)" % string.join(super, ", ")
             self.append(s)
-    
+
     def getname(self, index):
         name = self.listbox.get(index)
         i = string.find(name, '(')
@@ -113,13 +115,13 @@ class ClassViewer(ScrolledList):
 
     def getclass(self, index):
         return self.dict[self.getname(index)]
-    
+
     def on_select(self, index):
         self.show_methods(index)
-    
+
     def on_double(self, index):
         self.show_source(index)
-    
+
     def show_methods(self, index):
         cl = self.getclass(index)
         self.browser.show_methods(cl)
@@ -132,13 +134,13 @@ class ClassViewer(ScrolledList):
 
 
 class MethodViewer(ScrolledList):
-    
+
     def __init__(self, master, flist):
         ScrolledList.__init__(self, master)
         self.flist = flist
-        
+
     classinfo = None
-    
+
     def load_methods(self, cl):
         self.classinfo = cl
         self.clear()
@@ -151,10 +153,10 @@ class MethodViewer(ScrolledList):
 
     def click_event(self, event):
         pass
-    
+
     def on_double(self, index):
         self.show_source(self.get(index))
-    
+
     def show_source(self, name):
         if os.path.isfile(self.classinfo.file):
             edit = self.flist.open(self.classinfo.file)
index 5bf921d99b062aa41e398bed5cdbb4dcbd808017..357358fc48c038816a193f1fd8f986e8f7e75d2d 100644 (file)
@@ -5,6 +5,10 @@ import keyword
 from Tkinter import *
 from Delegator import Delegator
 
+#$ event <<toggle-auto-coloring>>
+#$ win <Control-slash>
+#$ unix <Control-slash>
+
 __debug__ = 0
 
 
@@ -29,10 +33,10 @@ class ColorDelegator(Delegator):
     def __init__(self):
         Delegator.__init__(self)
         self.prog = prog
-       self.idprog = idprog
+        self.idprog = idprog
 
     def setdelegate(self, delegate):
-       if self.delegate is not None:
+        if self.delegate is not None:
             self.unbind("<<toggle-auto-coloring>>")
         Delegator.setdelegate(self, delegate)
         if delegate is not None:
@@ -54,8 +58,11 @@ class ColorDelegator(Delegator):
 
         "SYNC":       {}, #{"background": "#ffff00"},
         "TODO":       {}, #{"background": "#cccccc"},
-        
+
         "BREAK":      {"background": "#FF7777"},
+
+        # The following is used by ReplaceDialog:
+        "hit":        {"foreground": "#FFFFFF", "background": "#000000"},
         }
 
     def insert(self, index, chars, tags=None):
@@ -79,9 +86,9 @@ class ColorDelegator(Delegator):
             return
         if self.colorizing:
             self.stop_colorizing = 1
-           if __debug__: print "stop colorizing"
+            if __debug__: print "stop colorizing"
         if self.allow_colorizing:
-           if __debug__: print "schedule colorizing"
+            if __debug__: print "schedule colorizing"
             self.after_id = self.after(1, self.recolorize)
 
     def close(self):
@@ -99,29 +106,29 @@ class ColorDelegator(Delegator):
             self.after_id = None
             if __debug__: print "cancel scheduled recolorizer"
             self.after_cancel(after_id)
-       if self.allow_colorizing and self.colorizing:
-           if __debug__: print "stop colorizing"
-           self.stop_colorizing = 1
-       self.allow_colorizing = not self.allow_colorizing
-       if self.allow_colorizing and not self.colorizing:
-           self.after_id = self.after(1, self.recolorize)
-       if __debug__:
-           print "auto colorizing turned", self.allow_colorizing and "on" or "off"
-       return "break"
+        if self.allow_colorizing and self.colorizing:
+            if __debug__: print "stop colorizing"
+            self.stop_colorizing = 1
+        self.allow_colorizing = not self.allow_colorizing
+        if self.allow_colorizing and not self.colorizing:
+            self.after_id = self.after(1, self.recolorize)
+        if __debug__:
+            print "auto colorizing turned", self.allow_colorizing and "on" or "off"
+        return "break"
 
     def recolorize(self):
         self.after_id = None
         if not self.delegate:
             if __debug__: print "no delegate"
             return
-       if not self.allow_colorizing:
-           if __debug__: print "auto colorizing is off"
-           return
-       if self.colorizing:
-           if __debug__: print "already colorizing"
+        if not self.allow_colorizing:
+            if __debug__: print "auto colorizing is off"
+            return
+        if self.colorizing:
+            if __debug__: print "already colorizing"
             return
         try:
-           self.stop_colorizing = 0
+            self.stop_colorizing = 0
             self.colorizing = 1
             if __debug__: print "colorizing..."
             t0 = time.clock()
@@ -131,63 +138,63 @@ class ColorDelegator(Delegator):
         finally:
             self.colorizing = 0
         if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
-           if __debug__: print "reschedule colorizing"
+            if __debug__: print "reschedule colorizing"
             self.after_id = self.after(1, self.recolorize)
 
     def recolorize_main(self):
-       next = "1.0"
-       was_ok = is_ok = 0
-       while 1:
-           item = self.tag_nextrange("TODO", next)
-           if not item:
-               break
-           head, tail = item
-           self.tag_remove("SYNC", head, tail)
-           item = self.tag_prevrange("SYNC", head)
-           if item:
-               head = item[1]
-           else:
-               head = "1.0"
-
-           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():
index 7cb2fdbdde31ec0b5cb4a922432f2a2c3e1dae3d..24a7376cd4b650a76d99a2a15ce82148378e0ea5 100644 (file)
@@ -2,28 +2,29 @@ import os
 import bdb
 import traceback
 from Tkinter import *
+from WindowList import ListedToplevel
 
 import StackViewer
 
 
 class Debugger(bdb.Bdb):
-    
+
     interacting = 0
-    
+
     vstack = vsource = vlocals = vglobals = None
-    
+
     def __init__(self, pyshell):
         bdb.Bdb.__init__(self)
         self.pyshell = pyshell
         self.make_gui()
-    
+
     def close(self):
         if self.interacting:
             self.top.bell()
             return
         self.pyshell.close_debugger()
         self.top.destroy()
-        
+
     def run(self, *args):
         try:
             self.interacting = 1
@@ -41,12 +42,14 @@ class Debugger(bdb.Bdb):
 
     def user_exception(self, frame, info):
         self.interaction(frame, info)
-    
+
     def make_gui(self):
         pyshell = self.pyshell
         self.flist = pyshell.flist
         self.root = root = pyshell.root
-        self.top = top = Toplevel(root)
+        self.top = top =ListedToplevel(root)
+        self.top.wm_title("Debug Control")
+        self.top.wm_iconname("Debug")
         top.wm_protocol("WM_DELETE_WINDOW", self.close)
         #
         self.bframe = bframe = Frame(top)
@@ -113,9 +116,9 @@ class Debugger(bdb.Bdb):
             self.show_locals()
         if self.vglobals.get():
             self.show_globals()
-    
+
     frame = None
-    
+
     def interaction(self, frame, info=None):
         self.frame = frame
         code = frame.f_code
@@ -167,7 +170,7 @@ class Debugger(bdb.Bdb):
         self.status.configure(text="")
         self.error.configure(text="", background=self.errorbg)
         self.frame = None
-    
+
     def sync_source_line(self):
         frame = self.frame
         if not frame:
@@ -179,19 +182,19 @@ class Debugger(bdb.Bdb):
             edit = self.flist.open(file)
             if edit:
                 edit.gotoline(lineno)
-    
+
     def cont(self):
         self.set_continue()
         self.root.quit()
-        
+
     def step(self):
         self.set_step()
         self.root.quit()
-    
+
     def next(self):
         self.set_next(self.frame)
         self.root.quit()
-    
+
     def ret(self):
         self.set_return(self.frame)
         self.root.quit()
@@ -211,7 +214,7 @@ class Debugger(bdb.Bdb):
                 self.stackviewer = None
                 sv.close()
             self.fstack['height'] = 1
-    
+
     def show_source(self):
         if self.vsource.get():
             self.sync_source_line()
@@ -277,16 +280,16 @@ class Debugger(bdb.Bdb):
             text.bell()
             return
         text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
-    
+
     # A literal copy of Bdb.set_break() without the print statement at the end
     def set_break(self, filename, lineno, temporary=0, cond = None):
-       import linecache # Import as late as possible
-       line = linecache.getline(filename, lineno)
-       if not line:
-               return 'That line does not exist!'
-       if not self.breaks.has_key(filename):
-               self.breaks[filename] = []
-       list = self.breaks[filename]
-       if not lineno in list:
-               list.append(lineno)
-       bp = bdb.Breakpoint(filename, lineno, temporary, cond)
+        import linecache # Import as late as possible
+        line = linecache.getline(filename, lineno)
+        if not line:
+                return 'That line does not exist!'
+        if not self.breaks.has_key(filename):
+                self.breaks[filename] = []
+        list = self.breaks[filename]
+        if not lineno in list:
+                list.append(lineno)
+        bp = bdb.Breakpoint(filename, lineno, temporary, cond)
index 6125591fe0dd1aef5ad67dda101001c8f63b97ae..3665247c97d5c294fcb1fc5a0978868678a4a332 100644 (file)
@@ -1,3 +1,4 @@
+
 class Delegator:
 
     # The cache is only used to be able to change delegates!
index 24b62dcbaabb4334b7e402c4833c9fea2b3b4e2d..b4c9156adbdaf853f7d981f33162c85d2cd758a9 100644 (file)
@@ -5,15 +5,70 @@ import imp
 from Tkinter import *
 import tkSimpleDialog
 import tkMessageBox
+import idlever
+
+# File menu
+
+#$ event <<open-module>>
+#$ win <Alt-m>
+#$ unix <Control-x><Control-m>
+
+#$ event <<open-class-browser>>
+#$ win <Alt-c>
+#$ unix <Control-x><Control-b>
+
+#$ event <<close-window>>
+#$ unix <Control-x><Control-0>
+#$ unix <Control-x><Key-0>
+#$ win <Alt-F4>
+
+# Edit menu
+
+#$ event <<Copy>>
+#$ win <Control-c>
+#$ unix <Alt-w>
+
+#$ event <<Cut>>
+#$ win <Control-x>
+#$ unix <Control-w>
+
+#$ event <<Paste>>
+#$ win <Control-v>
+#$ unix <Control-y>
+
+#$ event <<select-all>>
+#$ win <Alt-a>
+#$ unix <Alt-a>
+
+# Help menu
+
+#$ event <<help>>
+#$ win <F1>
+#$ unix <F1>
+
+#$ event <<about-idle>>
+
+# Events without menu entries
+
+#$ event <<remove-selection>>
+#$ win <Escape>
+
+#$ event <<center-insert>>
+#$ win <Control-l>
+#$ unix <Control-l>
+
+#$ event <<do-nothing>>
+#$ unix <Control-x>
+
 
 about_title = "About IDLE"
 about_text = """\
-IDLE 0.1
+IDLE %s
 
-A not totally unintegrated development environment for Python
+An Integrated DeveLopment Environment for Python
 
 by Guido van Rossum
-"""
+""" % idlever.IDLE_VERSION
 
 class EditorWindow:
 
@@ -21,44 +76,52 @@ class EditorWindow:
     from ColorDelegator import ColorDelegator
     from UndoDelegator import UndoDelegator
     from IOBinding import IOBinding
-    from SearchBinding import SearchBinding
-    from AutoIndent import AutoIndent
-    from AutoExpand import AutoExpand
     import Bindings
-    
+    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(rootfilename)
+    edit = EditorWindow(root=root, filename=filename)
     edit.set_close_hook(root.quit)
     root.mainloop()
     root.destroy()
index d9378e3cd2ff452f6cb8c4977f5b9d70a84eb569..393b81cebe8e05557467e2234be0bf15b9875bc4 100644 (file)
@@ -2,125 +2,59 @@ import os
 from Tkinter import *
 import tkMessageBox
 
-from EditorWindow import EditorWindow, fixwordbreaks
-from IOBinding import IOBinding
+import WindowList
 
+#$ event <<open-new-window>>
+#$ win <Control-n>
+#$ unix <Control-x><Control-n>
 
-class MultiIOBinding(IOBinding):
+# (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)
index e5a60514495f053767f090a7b16f11fa8fc09a34..2ce0935ba384f1d3cf784c853335d33188378eb4 100644 (file)
@@ -10,7 +10,7 @@ class FrameViewer:
         self.repr = Repr()
         self.repr.maxstring = 60
         self.load_variables()
-        
+
     def load_variables(self):
         row = 0
         if self.frame.f_locals is not self.frame.f_globals:
@@ -22,7 +22,7 @@ class FrameViewer:
                   borderwidth=2, relief="raised")
         l.grid(row=row, column=0, columnspan=2, sticky="ew")
         row = self.load_names(self.frame.f_globals, row+1)
-        
+
     def load_names(self, dict, row):
         names = dict.keys()
         names.sort()
diff --git a/Tools/idle/GrepDialog.py b/Tools/idle/GrepDialog.py
new file mode 100644 (file)
index 0000000..df3504b
--- /dev/null
@@ -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 (file)
index a1b13c3..0000000
+++ /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()
index 0798098836a3ca0dd3bcd4a190661f13579dedce..3094173bdfa773fa85a02e9d60bb3f1a50d4d77f 100644 (file)
@@ -1,7 +1,7 @@
 import string
 
 class History:
-    
+
     def __init__(self, text):
         self.text = text
         self.history = []
index 0d61afce7952ae266fa055448b82cd8441e62904..6a41a37500cb5cfa2e2d73c2c1965a62f7a7ab94 100644 (file)
@@ -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__":
index 0798098836a3ca0dd3bcd4a190661f13579dedce..3094173bdfa773fa85a02e9d60bb3f1a50d4d77f 100644 (file)
@@ -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 (file)
index 0000000..c13b3e4
--- /dev/null
@@ -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 (file)
index edda3a3..0000000
+++ /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)
index 887da1e244adec369558eb6b9801c6073e5a64f0..6df38c392d963efa6db8189e4a98b7e3f6f155b4 100644 (file)
@@ -12,9 +12,10 @@ from code import InteractiveInterpreter
 from Tkinter import *
 import tkMessageBox
 
-from EditorWindow import fixwordbreaks
-from FileList import FileList, MultiEditorWindow, MultiIOBinding
+from EditorWindow import EditorWindow, fixwordbreaks
+from FileList import FileList
 from ColorDelegator import ColorDelegator
+from OutputWindow import OutputWindow
 
 # We need to patch linecache.checkcache, because we don't want it
 # to throw away our <pyshell#...> entries.
@@ -31,36 +32,54 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache):
 linecache.checkcache = linecache_checkcache
 
 
-class PyShellEditorWindow(MultiEditorWindow):
-    
+# 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)
similarity index 61%
rename from Tools/idle/README
rename to Tools/idle/README.txt
index 1a835f6db691eed5a71ba54c303843e085eef11d..62b34cdb0f96d222e4f2403984a76f4504221765 100644 (file)
@@ -1,23 +1,22 @@
-IDLE 0.1 - 10/16/98
+IDLE 0.2 - 01/01/99
 -------------------
 
 This is a *very* early preliminary release of IDLE, my own attempt at
-a Tkinter-based IDE for Python.  It currently has the following
-features:
+a Tkinter-based IDE for Python.  It has the following features:
 
 - multi-window text editor with multiple undo and Python colorizing
 - Python shell (a.k.a. interactive interpreter) window subclass
 - debugger
 - 100% pure Python
-- works on Windows and Unix (should work on Mac too)
+- works on Windows and Unix (probably works on Mac too)
 
 The main program is in the file "idle"; on Windows you can use
 idle.pyw to avoid popping up a DOS console.  Any arguments passed are
 interpreted as files that will be opened for editing.
 
-IDLE requires Python 1.5.2, so it is currently only usable for PSA
-members who have the latest 1.5.2 alpha release (a public beta release
-is due shortly).
+IDLE requires Python 1.5.2, so it is currently only usable with the
+Python 1.5.2 beta distribution (luckily, IDLE is bundled with Python
+1.5.2).
 
 Please send feedback to the Python newsgroup, comp.lang.python.
 
@@ -27,46 +26,71 @@ Please send feedback to the Python newsgroup, comp.lang.python.
 
 TO DO:
 
+- "GO" command
+- "Modularize" command
+- command expansion from keywords, module contents, other buffers, etc.
 - "Recent documents" menu item
-- use platform specific default bindings
-- title and Windows menu should have base filename first
-- restructure state sensitive code to avoid testing flags all the time
-- integrated debugger
-- object browser instead of current stack viewer
-- save some user state (e.g. window and cursor positions, bindings)
-- make backups when saving
-- check file mtimes at various points
-- interface with RCS/CVS/Perforce ???
-- more search options: case [in]sensitive, fwd/back, string/regex
-- global query replace
-- incremental search
 - more emacsisms:
+  - parentheses matching
   - reindent, reformat text etc.
   - M-[, M-] to move by paragraphs
   - smart stuff with whitespace around Return
   - filter region?
-  - grep?
+  - incremental search?
+  - ^K should cut to buffer
+  - command to fill text paragraphs
+- restructure state sensitive code to avoid testing flags all the time
+- finish debugger
+- object browser instead of current stack viewer
+- persistent user state (e.g. window and cursor positions, bindings)
+- make backups when saving
+- check file mtimes at various points
+- interface with RCS/CVS/Perforce ???
 - status bar?
 - better help?
+- don't open second class browser on same module
 
 Details:
 
 - when there's a selection, left/right arrow should go to either
   end of the selection
-- ^O should honor autoindent
+- ^O (on Unix -- open-line) should honor autoindent
+- after paste, show end of pasted text
+- on Windows, should turn short filename to long filename (not only in argv!)
+  (shouldn't this be done -- or undone -- by ntpath.normpath?)
 
 Structural problems:
 
 - too much knowledge in FileList about EditorWindow (for example)
 - Several occurrences of scrollable listbox with title and certain
   behavior; should create base class to generalize this
-- class browser could become an outline?
 
 ======================================================================
 
 Comparison to PTUI
 ------------------
 
++ PTUI has a status line
+
++ PTUI's help is better (HTML!)
+
++ PTUI can attach a shell to any module
+
++ PTUI's auto indent is better
+  (understands that "if a: # blah, blah" opens a block)
+
++ IDLE requires 4x backspace to dedent a line
+
++ PTUI has more bells and whistles:
+  open multiple
+  append
+  modularize
+  examine
+  go
+
+? PTUI's fontify is faster but synchronous (and still too slow);
+  does a lousy job if editing affects lines below
+
 - PTUI's shell is worse:
   no coloring;
   no editing of multi-line commands;
@@ -76,34 +100,18 @@ Comparison to PTUI
   no redo;
   one char at a time
 
-- PTUI's framework is better:
-  status line
-  (not sure if I like the toolbar)
-
 - PTUI's GUI is a tad ugly:
-  I don't like the multiple buffers in one window model
-
-- 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 (file)
index 0000000..3bff8b5
--- /dev/null
@@ -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 (file)
index 0000000..a112fc5
--- /dev/null
@@ -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()
index ef2fde40571fad9d97096de84f5cb191f14326e8..a5f9a2914fdce4c5439e0438926e5142bdfde302 100644 (file)
@@ -1,7 +1,7 @@
 from Tkinter import *
 
 class ScrolledList:
-    
+
     def __init__(self, master, **options):
         # Create top frame, with scrollbar and listbox
         self.master = master
@@ -18,22 +18,22 @@ class ScrolledList:
         listbox["yscrollcommand"] = vbar.set
         # Bind events to the list box
         listbox.bind("<ButtonRelease-1>", self.click_event)
-       listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
+        listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
         listbox.bind("<ButtonPress-3>", self.popup_event)
         listbox.bind("<Key-Up>", self.up_event)
         listbox.bind("<Key-Down>", self.down_event)
         # Set the focus
         listbox.focus_set()
-    
+
     def close(self):
         self.frame.destroy()
-    
+
     def clear(self):
         self.listbox.delete(0, "end")
-    
+
     def append(self, item):
         self.listbox.insert("end", str(item))
-    
+
     def get(self, index):
         return self.listbox.get(index)
 
@@ -49,9 +49,9 @@ class ScrolledList:
         self.select(index)
         self.on_double(index)
         return "break"
-    
+
     menu = None
-    
+
     def popup_event(self, event):
         if not self.menu:
             self.make_menu()
@@ -65,7 +65,7 @@ class ScrolledList:
         menu = Menu(self.listbox, tearoff=0)
         self.menu = menu
         self.fill_menu()
-   
+
     def up_event(self, event):
         index = self.listbox.index("active")
         if self.listbox.selection_includes(index):
@@ -78,7 +78,7 @@ class ScrolledList:
             self.select(index)
             self.on_select(index)
         return "break"
-        
+
     def down_event(self, event):
         index = self.listbox.index("active")
         if self.listbox.selection_includes(index):
@@ -91,22 +91,22 @@ class ScrolledList:
             self.select(index)
             self.on_select(index)
         return "break"
-    
+
     def select(self, index):
         self.listbox.focus_set()
         self.listbox.activate(index)
         self.listbox.selection_clear(0, "end")
         self.listbox.selection_set(index)
         self.listbox.see(index)
-    
+
     # Methods to override for specific actions
-    
+
     def fill_menu(self):
         pass
+
     def on_select(self, index):
         pass
-        
+
     def on_double(self, index):
         pass
 
index ccbdc6c39cfb08329d7c0310817e7a6d0d4a5279..d73db13df59423db2d74f58e1351801202cedced 100644 (file)
@@ -1,89 +1,96 @@
-import string
-import re
 import tkSimpleDialog
-import tkMessageBox
+
+###$ event <<find>>
+###$ win <Control-f>
+###$ unix <Control-u><Control-u><Control-s>
+
+###$ event <<find-again>>
+###$ win <Control-g>
+###$ win <F3>
+###$ unix <Control-u><Control-s>
+
+###$ event <<find-selection>>
+###$ win <Control-F3>
+###$ unix <Control-s>
+
+###$ event <<find-in-files>>
+###$ win <Alt-F3>
+
+###$ event <<replace>>
+###$ win <Control-h>
+
+###$ event <<goto-line>>
+###$ win <Alt-g>
+###$ unix <Alt-g>
 
 class SearchBinding:
-       
-       def __init__(self, text):
-               self.text = text
-               self.pat = ""
-               self.prog = None
-               self.text.bind("<<find>>", self.find_event)
-               self.text.bind("<<find-next>>", self.find_next_event)
-               self.text.bind("<<find-same>>", self.find_same_event)
-               self.text.bind("<<goto-line>>", self.goto_line_event)
-       
-       def find_event(self, event):
-               default = self.text.get("self.first", "sel.last") or self.pat
-               new = tkSimpleDialog.askstring("Find",
-                       "Regular Expression:",
-                       initialvalue=default,
-                       parent=self.text)
-               if not new:
-                       return "break"
-               self.pat = new
-               try:
-                       self.prog = re.compile(self.pat)
-               except re.error, msg:
-                       tkMessageBox.showerror("RE error", str(msg),
-                                              master=self.text)
-                       return "break"
-               return self.find_next_event(event)
-       
-       def find_same_event(self, event):
-               pat = self.text.get("sel.first", "sel.last")
-               if not pat:
-                       return self.find_event(event)
-               self.pat = re.escape(pat)
-               self.prog = None
-               try:
-                       self.prog = re.compile(self.pat)
-               except re.error, msg:
-                       tkMessageBox.showerror("RE error", str(message),
-                                              master=self.text)
-                       return "break"
-               self.text.mark_set("insert", "sel.last")
-               return self.find_next_event(event)
-
-       def find_next_event(self, event):
-               if not self.pat:
-                       return self.find_event(event)
-               if not self.prog:
-                       self.text.bell()
-                       ##print "No program"
-                       return "break"
-               line, col = map(int,
-                               string.split(self.text.index("insert"), "."))
-               chars = self.text.get("%d.0" % line, "%d.0" % (line+1))
-               while chars:
-                       m = self.prog.search(chars, col)
-                       if m:
-                               i, j = m.span()
-                               self.text.mark_set("insert",
-                                                  "%d.%d" % (line, j))
-                               self.text.tag_remove("sel", "1.0", "end")
-                               self.text.tag_add("sel",
-                                                 "%d.%d" % (line, i),
-                                                 "%d.%d" % (line, j))
-                               self.text.see("insert")
-                               break
-                       line = line + 1
-                       col = 0
-                       chars = self.text.get("%d.0" % line, "%d.0" % (line+1))
-               else:
-                       # Not found
-                       self.text.bell()
-               return "break"
-       
-       def goto_line_event(self, event):
-               lineno = tkSimpleDialog.askinteger("Goto",
-                                                  "Go to line number:",
-                                                  parent=self.text)
-               if lineno is None:
-                       return "break"
-               if lineno <= 0:
-                       self.text.bell()
-                       return "break"
-               self.text.mark_set("insert", "%d.0" % lineno)
-               self.text.see("insert")
+
+    windows_keydefs = {
+        '<<find-again>>': ['<Control-g>', '<F3>'],
+        '<<find-in-files>>': ['<Alt-F3>'],
+        '<<find-selection>>': ['<Control-F3>'],
+        '<<find>>': ['<Control-f>'],
+        '<<replace>>': ['<Control-h>'],
+        '<<goto-line>>': ['<Alt-g>'],
+    }
+
+    unix_keydefs = {
+        '<<find-again>>': ['<Control-u><Control-s>'],
+        '<<find-selection>>': ['<Control-s>'],
+        '<<find>>': ['<Control-u><Control-u><Control-s>'],
+        '<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
+    }
+
+    menudefs = [
+        ('edit', [
+            None,
+            ('_Find...', '<<find>>'),
+            ('Find a_gain', '<<find-again>>'),
+            ('Find _selection', '<<find-selection>>'),
+            ('Find in Files...', '<<find-in-files>>'),
+            ('R_eplace...', '<<replace>>'),
+            ('Go to _line', '<<goto-line>>'),
+         ]),
+    ]
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+
+    def find_event(self, event):
+        import SearchDialog
+        SearchDialog.find(self.editwin.text)
+        return "break"
+
+    def find_again_event(self, event):
+        import SearchDialog
+        SearchDialog.find_again(self.editwin.text)
+        return "break"
+
+    def find_selection_event(self, event):
+        import SearchDialog
+        SearchDialog.find_selection(self.editwin.text)
+        return "break"
+
+    def find_in_files_event(self, event):
+        import GrepDialog
+        GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist)
+        return "break"
+
+    def replace_event(self, event):
+        import ReplaceDialog
+        ReplaceDialog.replace(self.editwin.text)
+        return "break"
+
+    def goto_line_event(self, event):
+        print event
+        text = self.editwin.text
+        lineno = tkSimpleDialog.askinteger("Goto",
+                                           "Go to line number:",
+                                           parent=text)
+        if lineno is None:
+            return "break"
+        if lineno <= 0:
+            text.bell()
+            return "break"
+        text.mark_set("insert", "%d.0" % lineno)
+        text.see("insert")
diff --git a/Tools/idle/SearchDialog.py b/Tools/idle/SearchDialog.py
new file mode 100644 (file)
index 0000000..501b6d0
--- /dev/null
@@ -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 (file)
index 0000000..faf5269
--- /dev/null
@@ -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 (file)
index 0000000..d9361d0
--- /dev/null
@@ -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
index 688c1b4335f5278e9f882b3fede7a469a8ff8ccf..93923f9a27d94c642495f4fefa283e9e320bc0fb 100644 (file)
@@ -4,16 +4,18 @@ import os
 from Tkinter import *
 import linecache
 from repr import Repr
+from WindowList import ListedToplevel
 
 from ScrolledList import ScrolledList
 
 
 class StackBrowser:
-    
+
     def __init__(self, root, flist, stack=None):
-        self.top = top = Toplevel(root)
+        self.top = top = ListedToplevel(root)
         top.protocol("WM_DELETE_WINDOW", self.close)
         top.wm_title("Stack viewer")
+        top.wm_iconname("Stack")
         # Create help label
         self.helplabel = Label(top,
             text="Click once to view variables; twice for source",
@@ -24,7 +26,7 @@ class StackBrowser:
         if stack is None:
             stack = get_stack()
         self.sv.load_stack(stack)
-    
+
     def close(self):
         self.top.destroy()
 
@@ -44,7 +46,7 @@ class StackBrowser:
             self.show_globals(frame)
         self.show_locals(frame)
         self.curframe = frame
-    
+
     def show_globals(self, frame):
         title = "Global Variables"
         if frame.f_globals.has_key("__name__"):
@@ -66,7 +68,7 @@ class StackBrowser:
             title,
             self.globalsdict)
         self.globalsframe.pack(fill="both", side="bottom")
-    
+
     def show_locals(self, frame):
         self.localsdict = None
         if self.localsviewer:
@@ -92,7 +94,7 @@ class StackBrowser:
 
 
 class StackViewer(ScrolledList):
-    
+
     def __init__(self, master, flist, browser):
         ScrolledList.__init__(self, master)
         self.flist = flist
@@ -149,7 +151,7 @@ class StackViewer(ScrolledList):
     def show_stack_frame(self):
         index = self.listbox.index("active")
         self.browser.show_frame(self.stack[index])
-    
+
     def show_source(self, index):
         frame, lineno = self.stack[index]
         code = frame.f_code
@@ -169,7 +171,7 @@ def get_stack(t=None, f=None):
     while f is not None:
         stack.append((f, f.f_lineno))
         if f is self.botframe:
-           break
+            break
         f = f.f_back
     stack.reverse()
     while t is not None:
@@ -191,7 +193,7 @@ def getexception(type=None, value=None):
 
 
 class NamespaceViewer:
-    
+
     def __init__(self, master, title, dict=None):
         width = 0
         height = 40
@@ -217,9 +219,9 @@ class NamespaceViewer:
         self.subframe = subframe = Frame(canvas)
         self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
         self.load_dict(dict)
-    
+
     dict = -1
-    
+
     def load_dict(self, dict, force=0):
         if dict is self.dict and not force:
             return
index ee49651dc9edcd7ea07e70fc50fec827382dab8a..39c8e63466534428a0d39f106d93805264eaad4a 100644 (file)
@@ -3,6 +3,18 @@ import string
 from Tkinter import *
 from Delegator import Delegator
 
+#$ event <<redo>>
+#$ win <Control-y>
+#$ unix <Alt-z>
+
+#$ event <<undo>>
+#$ win <Control-z>
+#$ unix <Control-z>
+
+#$ event <<dump-undo-state>>
+#$ win <Control-backslash>
+#$ unix <Control-backslash>
+
 
 class UndoDelegator(Delegator):
 
@@ -11,7 +23,7 @@ class UndoDelegator(Delegator):
     def __init__(self):
         Delegator.__init__(self)
         self.reset_undo()
-    
+
     def setdelegate(self, delegate):
         if self.delegate is not None:
             self.unbind("<<undo>>")
diff --git a/Tools/idle/WindowList.py b/Tools/idle/WindowList.py
new file mode 100644 (file)
index 0000000..b9b0bb1
--- /dev/null
@@ -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 (file)
index 0000000..eee901c
--- /dev/null
@@ -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 (file)
index 0000000..cb2028d
--- /dev/null
@@ -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 (file)
index 0000000..7d8117d
--- /dev/null
@@ -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 (file)
index 0000000..83b6428
--- /dev/null
@@ -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.
index beafc42acbf3295c7b1ffc26154b0e25258df71b..5307fa645707369d45469c2e21993d78cad8c46b 100644 (file)
@@ -1,3 +1,5 @@
+[See end for tips.]
+
 File menu:
 
        New window -- create a new editing window
@@ -75,9 +77,19 @@ Python syntax colors: the coloring is applied in a background thread
        Comments        red
        Definitions     blue
 
-Console colors:
+Shell colors:
 
-       Console output  red
+       Console output  dark red
        stdout          blue
        stderr          dark green
-       stdin           purple
+       stdin           black
+
+Tips:
+       To change the font on Windows, open EditorWindow.py and change
+           text['font'] = ("verdana", 8)
+       to, e.g.,
+           text['font'] = ("courier new", 10)
+
+       To change the Python syntax colors, edit the tagdefs table
+       in ColorDelegator.py; to change the shell colors, edit the
+       tagdefs table in PyShell.py.
diff --git a/Tools/idle/idle.bat b/Tools/idle/idle.bat
new file mode 100644 (file)
index 0000000..a416001
--- /dev/null
@@ -0,0 +1,3 @@
+rem idle.bat
+
+"C:\Program Files\Python\python.exe" "idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9
index 3c06e05488dcd83d1beea94c2ee2025ccadf2047..a1fc021aedc1c0aebe553133a2bb6f0804c9d059 100644 (file)
@@ -1,3 +1,9 @@
-#! /usr/bin/env python
-import PyShell
-PyShell.main()
+try:
+    import PyShell
+    PyShell.main()
+except SystemExit:
+    raise
+except:
+    import traceback
+    traceback.print_exc()
+    raw_input("Hit return to exit...")
diff --git a/Tools/idle/idlever.py b/Tools/idle/idlever.py
new file mode 100644 (file)
index 0000000..7465590
--- /dev/null
@@ -0,0 +1 @@
+IDLE_VERSION = "0.2"
diff --git a/Tools/idle/keydefs.py b/Tools/idle/keydefs.py
new file mode 100644 (file)
index 0000000..1e94904
--- /dev/null
@@ -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>'],
+}