diff options
38 files changed, 2203 insertions, 898 deletions
diff --git a/Tools/idle/AutoExpand.py b/Tools/idle/AutoExpand.py index 0d80ce8..1ebd7d5 100644 --- a/Tools/idle/AutoExpand.py +++ b/Tools/idle/AutoExpand.py @@ -1,17 +1,30 @@ import string import re +###$ event <<expand-word>> +###$ win <Alt-slash> +###$ unix <Alt-slash> + class AutoExpand: - + + keydefs = { + '<<expand-word>>': ['<Alt-slash>'], + } + + menudefs = [ + ('edit', [ + ('E_xpand word', '<<expand-word>>'), + ]), + ] + wordchars = string.letters + string.digits + "_" - def __init__(self, text): - self.text = text - self.text.wordlist = None + def __init__(self, editwin): + self.text = editwin.text + self.text.wordlist = None # XXX what is this? self.state = None - self.text.bind("<<expand-word>>", self.autoexpand) - - def autoexpand(self, event): + + def expand_word_event(self, event): curinsert = self.text.index("insert") curline = self.text.get("insert linestart", "insert lineend") if not self.state: @@ -36,7 +49,7 @@ class AutoExpand: curline = self.text.get("insert linestart", "insert lineend") self.state = words, index, curinsert, curline return "break" - + def getwords(self): word = self.getprevword() if not word: @@ -66,7 +79,7 @@ class AutoExpand: dict[w] = w words.append(word) return words - + def getprevword(self): line = self.text.get("insert linestart", "insert") i = len(line) diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py index d800589..329f492 100644 --- a/Tools/idle/AutoIndent.py +++ b/Tools/idle/AutoIndent.py @@ -1,16 +1,81 @@ import string +from Tkinter import TclError + +###$ event <<newline-and-indent>> +###$ win <Key-Return> +###$ win <KP_Enter> +###$ unix <Key-Return> +###$ unix <KP_Enter> + +###$ event <<indent-region>> +###$ win <Control-bracketright> +###$ unix <Alt-bracketright> +###$ unix <Control-bracketright> + +###$ event <<dedent-region>> +###$ win <Control-bracketleft> +###$ unix <Alt-bracketleft> +###$ unix <Control-bracketleft> + +###$ event <<comment-region>> +###$ win <Alt-Key-3> +###$ unix <Alt-Key-3> + +###$ event <<uncomment-region>> +###$ win <Alt-Key-4> +###$ unix <Alt-Key-4> + +###$ event <<tabify-region>> +###$ win <Alt-Key-5> +###$ unix <Alt-Key-5> + +###$ event <<untabify-region>> +###$ win <Alt-Key-6> +###$ unix <Alt-Key-6> class AutoIndent: - def __init__(self, text, prefertabs=0, spaceindent=4*" "): - self.text = text - self.prefertabs = prefertabs - self.spaceindent = spaceindent - text.bind("<<newline-and-indent>>", self.autoindent) - text.bind("<<indent-region>>", self.indentregion) - text.bind("<<dedent-region>>", self.dedentregion) - text.bind("<<comment-region>>", self.commentregion) - text.bind("<<uncomment-region>>", self.uncommentregion) + menudefs = [ + ('edit', [ + None, + ('_Indent region', '<<indent-region>>'), + ('_Dedent region', '<<dedent-region>>'), + ('Comment _out region', '<<comment-region>>'), + ('U_ncomment region', '<<uncomment-region>>'), + ('Tabify region', '<<tabify-region>>'), + ('Untabify region', '<<untabify-region>>'), + ]), + ] + + windows_keydefs = { + '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], + '<<indent-region>>': ['<Control-bracketright>'], + '<<dedent-region>>': ['<Control-bracketleft>'], + '<<comment-region>>': ['<Alt-Key-3>'], + '<<uncomment-region>>': ['<Alt-Key-4>'], + '<<tabify-region>>': ['<Alt-Key-5>'], + '<<untabify-region>>': ['<Alt-Key-6>'], + } + + unix_keydefs = { + '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], + '<<indent-region>>': ['<Alt-bracketright>', + '<Meta-bracketright>', + '<Control-bracketright>'], + '<<dedent-region>>': ['<Alt-bracketleft>', + '<Meta-bracketleft>', + '<Control-bracketleft>'], + '<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'], + '<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'], + '<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'], + '<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'], + } + + prefertabs = 0 + spaceindent = 4*" " + + def __init__(self, editwin): + self.text = editwin.text def config(self, **options): for key, value in options.items(): @@ -21,8 +86,16 @@ class AutoIndent: else: raise KeyError, "bad option name: %s" % `key` - def autoindent(self, event): + def newline_and_indent_event(self, event): text = self.text + try: + first = text.index("sel.first") + last = text.index("sel.last") + except TclError: + first = last = None + if first and last: + text.delete(first, last) + text.mark_set("insert", first) line = text.get("insert linestart", "insert") i, n = 0, len(line) while i < n and line[i] in " \t": @@ -43,8 +116,10 @@ class AutoIndent: text.see("insert") return "break" - def indentregion(self, event): - head, tail, chars, lines = self.getregion() + auto_indent = newline_and_indent_event + + def indent_region_event(self, event): + head, tail, chars, lines = self.get_region() for pos in range(len(lines)): line = lines[pos] if line: @@ -53,11 +128,11 @@ class AutoIndent: i = i+1 line = line[:i] + " " + line[i:] lines[pos] = line - self.setregion(head, tail, chars, lines) + self.set_region(head, tail, chars, lines) return "break" - def dedentregion(self, event): - head, tail, chars, lines = self.getregion() + def dedent_region_event(self, event): + head, tail, chars, lines = self.get_region() for pos in range(len(lines)): line = lines[pos] if line: @@ -75,20 +150,20 @@ class AutoIndent: indent = indent[:-4] line = indent + line lines[pos] = line - self.setregion(head, tail, chars, lines) + self.set_region(head, tail, chars, lines) return "break" - def commentregion(self, event): - head, tail, chars, lines = self.getregion() + def comment_region_event(self, event): + head, tail, chars, lines = self.get_region() for pos in range(len(lines)): line = lines[pos] if not line: continue lines[pos] = '##' + line - self.setregion(head, tail, chars, lines) + self.set_region(head, tail, chars, lines) - def uncommentregion(self, event): - head, tail, chars, lines = self.getregion() + def uncomment_region_event(self, event): + head, tail, chars, lines = self.get_region() for pos in range(len(lines)): line = lines[pos] if not line: @@ -98,9 +173,19 @@ class AutoIndent: elif line[:1] == '#': line = line[1:] lines[pos] = line - self.setregion(head, tail, chars, lines) + self.set_region(head, tail, chars, lines) - def getregion(self): + def tabify_region_event(self, event): + head, tail, chars, lines = self.get_region() + lines = map(tabify, lines) + self.set_region(head, tail, chars, lines) + + def untabify_region_event(self, event): + head, tail, chars, lines = self.get_region() + lines = map(string.expandtabs, lines) + self.set_region(head, tail, chars, lines) + + def get_region(self): text = self.text head = text.index("sel.first linestart") tail = text.index("sel.last -1c lineend +1c") @@ -111,7 +196,7 @@ class AutoIndent: lines = string.split(chars, "\n") return head, tail, chars, lines - def setregion(self, head, tail, chars, lines): + def set_region(self, head, tail, chars, lines): text = self.text newchars = string.join(lines, "\n") if newchars == chars: @@ -122,3 +207,12 @@ class AutoIndent: text.delete(head, tail) text.insert(head, newchars) text.tag_add("sel", head, "insert") + +def tabify(line, tabsize=8): + spaces = tabsize * ' ' + for i in range(0, len(line), tabsize): + if line[i:i+tabsize] != spaces: + break + else: + i = len(line) + return '\t' * (i/tabsize) + line[i:] diff --git a/Tools/idle/Bindings.py b/Tools/idle/Bindings.py index 5a13d22..dbca89a 100644 --- a/Tools/idle/Bindings.py +++ b/Tools/idle/Bindings.py @@ -8,6 +8,7 @@ import sys import string import re +from keydefs import * menudefs = [ # underscore prefixes character to underscore @@ -15,7 +16,8 @@ menudefs = [ ('_New window', '<<open-new-window>>'), ('_Open...', '<<open-window-from-file>>'), ('Open _module...', '<<open-module>>'), - ('Class _browser...', '<<open-class-browser>>'), + ('Class _browser', '<<open-class-browser>>'), + ('Python shell', '<<open-python-shell>>'), None, ('_Save', '<<save-window>>'), ('Save _As...', '<<save-window-as-file>>'), @@ -31,19 +33,15 @@ menudefs = [ ('Cu_t', '<<Cut>>'), ('_Copy', '<<Copy>>'), ('_Paste', '<<Paste>>'), - None, - ('_Find...', '<<find>>'), - ('Find _next', '<<find-next>>'), - ('Find _same', '<<find-same>>'), - ('_Go to line', '<<goto-line>>'), - None, - ('_Dedent region', '<<dedent-region>>'), - ('_Indent region', '<<indent-region>>'), - ('Comment _out region', '<<comment-region>>'), - ('U_ncomment region', '<<uncomment-region>>'), + ('Select _All', '<<select-all>>'), + ]), + ('script', [ + ('Run module', '<<run-module>>'), + ('Run script', '<<run-script>>'), + ('New shell', '<<new-shell>>'), ]), ('debug', [ - ('_Go to line from traceback', '<<goto-traceback-line>>'), + ('_Go to file/line', '<<goto-file-line>>'), ('_Open stack viewer', '<<open-stack-viewer>>'), ('_Debugger toggle', '<<toggle-debugger>>'), ]), @@ -54,81 +52,6 @@ menudefs = [ ]), ] -windows_keydefs = { - '<<beginning-of-line>>': ['<Control-a>', '<Home>'], - '<<close-all-windows>>': ['<Control-q>'], - '<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'], - '<<dedent-region>>': ['<Control-bracketleft>'], - '<<dump-undo-state>>': ['<Control-backslash>'], - '<<end-of-file>>': ['<Control-d>'], - '<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'], - '<<find-next>>': ['<F3>', '<Control-g>'], - '<<find-same>>': ['<Control-F3>'], - '<<find>>': ['<Control-f>'], - '<<goto-line>>': ['<Alt-g>', '<Meta-g>'], - '<<history-next>>': ['<Meta-n>', '<Alt-n>'], - '<<history-previous>>': ['<Meta-p>', '<Alt-p>'], - '<<indent-region>>': ['<Control-bracketright>'], - '<<interrupt-execution>>': ['<Control-c>'], - '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], - '<<open-new-window>>': ['<Control-n>'], - '<<open-window-from-file>>': ['<Control-o>'], - '<<plain-newline-and-indent>>': ['<Control-j>'], - '<<redo>>': ['<Control-y>'], - '<<save-copy-of-window-as-file>>': ['<Meta-w>'], - '<<save-window-as-file>>': ['<Control-w>'], - '<<save-window>>': ['<Control-s>'], - '<<toggle-auto-coloring>>': ['<Control-slash>'], - '<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'], - '<<undo>>': ['<Control-z>'], -} - -emacs_keydefs = { - '<<Copy>>': ['<Alt-w>'], - '<<Cut>>': ['<Control-w>'], - '<<Paste>>': ['<Control-y>'], - '<<about-idle>>': [], - '<<beginning-of-line>>': ['<Control-a>', '<Home>'], - '<<center-insert>>': ['<Control-l>'], - '<<close-all-windows>>': ['<Control-x><Control-c>'], - '<<close-window>>': ['<Control-x><Control-0>'], - '<<comment-region>>': ['<Meta-Key-3>', '<Alt-Key-3>'], - '<<dedent-region>>': ['<Meta-bracketleft>', - '<Alt-bracketleft>', - '<Control-bracketleft>'], - '<<do-nothing>>': ['<Control-x>'], - '<<dump-undo-state>>': ['<Control-backslash>'], - '<<end-of-file>>': ['<Control-d>'], - '<<expand-word>>': ['<Meta-slash>', '<Alt-slash>'], - '<<find-next>>': ['<Control-u><Control-s>'], - '<<find-same>>': ['<Control-s>'], - '<<find>>': ['<Control-u><Control-u><Control-s>'], - '<<goto-line>>': ['<Alt-g>', '<Meta-g>'], - '<<goto-traceback-line>>': [], - '<<help>>': [], - '<<history-next>>': ['<Meta-n>', '<Alt-n>'], - '<<history-previous>>': ['<Meta-p>', '<Alt-p>'], - '<<indent-region>>': ['<Meta-bracketright>', - '<Alt-bracketright>', - '<Control-bracketright>'], - '<<interrupt-execution>>': ['<Control-c>'], - '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], - '<<open-class-browser>>': ['<Control-x><Control-b>'], - '<<open-module>>': ['<Control-x><Control-m>'], - '<<open-new-window>>': ['<Control-x><Control-n>'], - '<<open-stack-viewer>>': [], - '<<open-window-from-file>>': ['<Control-x><Control-f>'], - '<<plain-newline-and-indent>>': ['<Control-j>'], - '<<redo>>': ['<Alt-z>', '<Meta-z>'], - '<<save-copy-of-window-as-file>>': ['<Control-x><w>'], - '<<save-window-as-file>>': ['<Control-x><Control-w>'], - '<<save-window>>': ['<Control-x><Control-s>'], - '<<toggle-auto-coloring>>': ['<Control-slash>'], - '<<toggle-debugger>>': [], - '<<uncomment-region>>': ['<Meta-Key-4>', '<Alt-Key-4>'], - '<<undo>>': ['<Control-z>'], -} - def prepstr(s): # Helper to extract the underscore from a string, # e.g. prepstr("Co_py") returns (2, "Copy"). @@ -140,18 +63,14 @@ def prepstr(s): keynames = { 'bracketleft': '[', 'bracketright': ']', + 'slash': '/', } -def getaccelerator(keydefs, event): +def get_accelerator(keydefs, event): keylist = keydefs.get(event) if not keylist: return "" s = keylist[0] - if s[:6] == "<Meta-": - # Prefer Alt over Meta -- they should be the same thing anyway - alts = "<Alt-" + s[6:] - if alts in keylist: - s = alts s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s) s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s) s = re.sub("Key-", "", s) @@ -165,7 +84,7 @@ def getaccelerator(keydefs, event): if sys.platform == 'win32': default_keydefs = windows_keydefs else: - default_keydefs = emacs_keydefs + default_keydefs = unix_keydefs def apply_bindings(text, keydefs=default_keydefs): text.keydefs = keydefs @@ -173,14 +92,10 @@ def apply_bindings(text, keydefs=default_keydefs): if keylist: apply(text.event_add, (event,) + tuple(keylist)) -def fill_menus(text, menudict, defs=menudefs): +def fill_menus(text, menudict, defs=menudefs, keydefs=default_keydefs): # Fill the menus for the given text widget. The menudict argument is # a dictionary containing the menus, keyed by their lowercased name. # Menus that are absent or None are ignored. - if hasattr(text, "keydefs"): - keydefs = text.keydefs - else: - keydefs = default_keydefs for mname, itemlist in defs: menu = menudict.get(mname) if not menu: @@ -191,7 +106,7 @@ def fill_menus(text, menudict, defs=menudefs): else: label, event = item underline, label = prepstr(label) - accelerator = getaccelerator(keydefs, event) + accelerator = get_accelerator(keydefs, event) def command(text=text, event=event): text.event_generate(event) menu.add_command(label=label, underline=underline, diff --git a/Tools/idle/ClassBrowser.py b/Tools/idle/ClassBrowser.py index 21ff22e..1224964 100644 --- a/Tools/idle/ClassBrowser.py +++ b/Tools/idle/ClassBrowser.py @@ -1,7 +1,7 @@ """Primitive class browser. XXX TO DO: - + - generalize the scrolling listbox with some behavior into a base class - add popup menu with more options (e.g. doc strings, base classes, imports) - show function argument list (have to do pattern matching on source) @@ -14,12 +14,13 @@ import string import pyclbr from Tkinter import * import tkMessageBox +from WindowList import ListedToplevel from ScrolledList import ScrolledList class ClassBrowser: - + def __init__(self, flist, name, path=[]): root = flist.root try: @@ -34,9 +35,10 @@ class ClassBrowser: self.flist = flist self.dict = dict self.root = root - self.top = top = Toplevel(root) + self.top = top = ListedToplevel(root) self.top.protocol("WM_DELETE_WINDOW", self.close) - top.wm_title("Class browser") + top.wm_title("Class Browser - " + name) + top.wm_iconname("ClBrowser") self.leftframe = leftframe = Frame(top) self.leftframe.pack(side="left", fill="both", expand=1) # Create help label @@ -48,12 +50,12 @@ class ClassBrowser: self.leftframe, self.flist, self) # Load the classes self.load_classes(dict, name) - + def close(self): self.classviewer = None self.methodviewer = None self.top.destroy() - + def load_classes(self, dict, module): self.classviewer.load_classes(dict, module) if self.botframe: @@ -64,7 +66,7 @@ class ClassBrowser: botframe = None methodhelplabel = None methodviewer = None - + def show_methods(self, cl): if not self.botframe: self.botframe = Frame(self.top) @@ -78,12 +80,12 @@ class ClassBrowser: class ClassViewer(ScrolledList): - + def __init__(self, master, flist, browser): ScrolledList.__init__(self, master) self.flist = flist self.browser = browser - + def load_classes(self, dict, module): self.clear() self.dict = dict @@ -103,7 +105,7 @@ class ClassViewer(ScrolledList): super.append(name) s = s + "(%s)" % string.join(super, ", ") self.append(s) - + def getname(self, index): name = self.listbox.get(index) i = string.find(name, '(') @@ -113,13 +115,13 @@ class ClassViewer(ScrolledList): def getclass(self, index): return self.dict[self.getname(index)] - + def on_select(self, index): self.show_methods(index) - + def on_double(self, index): self.show_source(index) - + def show_methods(self, index): cl = self.getclass(index) self.browser.show_methods(cl) @@ -132,13 +134,13 @@ class ClassViewer(ScrolledList): class MethodViewer(ScrolledList): - + def __init__(self, master, flist): ScrolledList.__init__(self, master) self.flist = flist - + classinfo = None - + def load_methods(self, cl): self.classinfo = cl self.clear() @@ -151,10 +153,10 @@ class MethodViewer(ScrolledList): def click_event(self, event): pass - + def on_double(self, index): self.show_source(self.get(index)) - + def show_source(self, name): if os.path.isfile(self.classinfo.file): edit = self.flist.open(self.classinfo.file) diff --git a/Tools/idle/ColorDelegator.py b/Tools/idle/ColorDelegator.py index 5bf921d..357358f 100644 --- a/Tools/idle/ColorDelegator.py +++ b/Tools/idle/ColorDelegator.py @@ -5,6 +5,10 @@ import keyword from Tkinter import * from Delegator import Delegator +#$ event <<toggle-auto-coloring>> +#$ win <Control-slash> +#$ unix <Control-slash> + __debug__ = 0 @@ -29,10 +33,10 @@ class ColorDelegator(Delegator): def __init__(self): Delegator.__init__(self) self.prog = prog - self.idprog = idprog + self.idprog = idprog def setdelegate(self, delegate): - if self.delegate is not None: + if self.delegate is not None: self.unbind("<<toggle-auto-coloring>>") Delegator.setdelegate(self, delegate) if delegate is not None: @@ -54,8 +58,11 @@ class ColorDelegator(Delegator): "SYNC": {}, #{"background": "#ffff00"}, "TODO": {}, #{"background": "#cccccc"}, - + "BREAK": {"background": "#FF7777"}, + + # The following is used by ReplaceDialog: + "hit": {"foreground": "#FFFFFF", "background": "#000000"}, } def insert(self, index, chars, tags=None): @@ -79,9 +86,9 @@ class ColorDelegator(Delegator): return if self.colorizing: self.stop_colorizing = 1 - if __debug__: print "stop colorizing" + if __debug__: print "stop colorizing" if self.allow_colorizing: - if __debug__: print "schedule colorizing" + if __debug__: print "schedule colorizing" self.after_id = self.after(1, self.recolorize) def close(self): @@ -99,29 +106,29 @@ class ColorDelegator(Delegator): self.after_id = None if __debug__: print "cancel scheduled recolorizer" self.after_cancel(after_id) - if self.allow_colorizing and self.colorizing: - if __debug__: print "stop colorizing" - self.stop_colorizing = 1 - self.allow_colorizing = not self.allow_colorizing - if self.allow_colorizing and not self.colorizing: - self.after_id = self.after(1, self.recolorize) - if __debug__: - print "auto colorizing turned", self.allow_colorizing and "on" or "off" - return "break" + if self.allow_colorizing and self.colorizing: + if __debug__: print "stop colorizing" + self.stop_colorizing = 1 + self.allow_colorizing = not self.allow_colorizing + if self.allow_colorizing and not self.colorizing: + self.after_id = self.after(1, self.recolorize) + if __debug__: + print "auto colorizing turned", self.allow_colorizing and "on" or "off" + return "break" def recolorize(self): self.after_id = None if not self.delegate: if __debug__: print "no delegate" return - if not self.allow_colorizing: - if __debug__: print "auto colorizing is off" - return - if self.colorizing: - if __debug__: print "already colorizing" + if not self.allow_colorizing: + if __debug__: print "auto colorizing is off" + return + if self.colorizing: + if __debug__: print "already colorizing" return try: - self.stop_colorizing = 0 + self.stop_colorizing = 0 self.colorizing = 1 if __debug__: print "colorizing..." t0 = time.clock() @@ -131,63 +138,63 @@ class ColorDelegator(Delegator): finally: self.colorizing = 0 if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"): - if __debug__: print "reschedule colorizing" + if __debug__: print "reschedule colorizing" self.after_id = self.after(1, self.recolorize) def recolorize_main(self): - next = "1.0" - was_ok = is_ok = 0 - while 1: - item = self.tag_nextrange("TODO", next) - if not item: - break - head, tail = item - self.tag_remove("SYNC", head, tail) - item = self.tag_prevrange("SYNC", head) - if item: - head = item[1] - else: - head = "1.0" - - chars = "" - mark = head - is_ok = was_ok = 0 - while not (was_ok and is_ok): - next = self.index(mark + " lineend +1c") - was_ok = "SYNC" in self.tag_names(next + "-1c") - line = self.get(mark, next) - ##print head, "get", mark, next, "->", `line` - if not line: - return - for tag in self.tagdefs.keys(): - self.tag_remove(tag, mark, next) - chars = chars + line - m = self.prog.search(chars) - while m: - i, j = m.span() - for key, value in m.groupdict().items(): - if value: - a, b = m.span(key) - self.tag_add(key, - head + "+%dc" % a, - head + "+%dc" % b) - if value in ("def", "class"): - m1 = self.idprog.match(chars, b) - if m1: - a, b = m1.span(1) - self.tag_add("DEFINITION", - head + "+%dc" % a, - head + "+%dc" % b) - m = self.prog.search(chars, j) - is_ok = "SYNC" in self.tag_names(next + "-1c") - mark = next - if is_ok: - head = mark - chars = "" - self.update() - if self.stop_colorizing: - if __debug__: print "colorizing stopped" - return + next = "1.0" + was_ok = is_ok = 0 + while 1: + item = self.tag_nextrange("TODO", next) + if not item: + break + head, tail = item + self.tag_remove("SYNC", head, tail) + item = self.tag_prevrange("SYNC", head) + if item: + head = item[1] + else: + head = "1.0" + + chars = "" + mark = head + is_ok = was_ok = 0 + while not (was_ok and is_ok): + next = self.index(mark + " lineend +1c") + was_ok = "SYNC" in self.tag_names(next + "-1c") + line = self.get(mark, next) + ##print head, "get", mark, next, "->", `line` + if not line: + return + for tag in self.tagdefs.keys(): + self.tag_remove(tag, mark, next) + chars = chars + line + m = self.prog.search(chars) + while m: + i, j = m.span() + for key, value in m.groupdict().items(): + if value: + a, b = m.span(key) + self.tag_add(key, + head + "+%dc" % a, + head + "+%dc" % b) + if value in ("def", "class"): + m1 = self.idprog.match(chars, b) + if m1: + a, b = m1.span(1) + self.tag_add("DEFINITION", + head + "+%dc" % a, + head + "+%dc" % b) + m = self.prog.search(chars, j) + is_ok = "SYNC" in self.tag_names(next + "-1c") + mark = next + if is_ok: + head = mark + chars = "" + self.update() + if self.stop_colorizing: + if __debug__: print "colorizing stopped" + return def main(): diff --git a/Tools/idle/Debugger.py b/Tools/idle/Debugger.py index 7cb2fdb..24a7376 100644 --- a/Tools/idle/Debugger.py +++ b/Tools/idle/Debugger.py @@ -2,28 +2,29 @@ import os import bdb import traceback from Tkinter import * +from WindowList import ListedToplevel import StackViewer class Debugger(bdb.Bdb): - + interacting = 0 - + vstack = vsource = vlocals = vglobals = None - + def __init__(self, pyshell): bdb.Bdb.__init__(self) self.pyshell = pyshell self.make_gui() - + def close(self): if self.interacting: self.top.bell() return self.pyshell.close_debugger() self.top.destroy() - + def run(self, *args): try: self.interacting = 1 @@ -41,12 +42,14 @@ class Debugger(bdb.Bdb): def user_exception(self, frame, info): self.interaction(frame, info) - + def make_gui(self): pyshell = self.pyshell self.flist = pyshell.flist self.root = root = pyshell.root - self.top = top = Toplevel(root) + self.top = top =ListedToplevel(root) + self.top.wm_title("Debug Control") + self.top.wm_iconname("Debug") top.wm_protocol("WM_DELETE_WINDOW", self.close) # self.bframe = bframe = Frame(top) @@ -113,9 +116,9 @@ class Debugger(bdb.Bdb): self.show_locals() if self.vglobals.get(): self.show_globals() - + frame = None - + def interaction(self, frame, info=None): self.frame = frame code = frame.f_code @@ -167,7 +170,7 @@ class Debugger(bdb.Bdb): self.status.configure(text="") self.error.configure(text="", background=self.errorbg) self.frame = None - + def sync_source_line(self): frame = self.frame if not frame: @@ -179,19 +182,19 @@ class Debugger(bdb.Bdb): edit = self.flist.open(file) if edit: edit.gotoline(lineno) - + def cont(self): self.set_continue() self.root.quit() - + def step(self): self.set_step() self.root.quit() - + def next(self): self.set_next(self.frame) self.root.quit() - + def ret(self): self.set_return(self.frame) self.root.quit() @@ -211,7 +214,7 @@ class Debugger(bdb.Bdb): self.stackviewer = None sv.close() self.fstack['height'] = 1 - + def show_source(self): if self.vsource.get(): self.sync_source_line() @@ -277,16 +280,16 @@ class Debugger(bdb.Bdb): text.bell() return text.tag_add("BREAK", "insert linestart", "insert lineend +1char") - + # A literal copy of Bdb.set_break() without the print statement at the end def set_break(self, filename, lineno, temporary=0, cond = None): - import linecache # Import as late as possible - line = linecache.getline(filename, lineno) - if not line: - return 'That line does not exist!' - if not self.breaks.has_key(filename): - self.breaks[filename] = [] - list = self.breaks[filename] - if not lineno in list: - list.append(lineno) - bp = bdb.Breakpoint(filename, lineno, temporary, cond) + import linecache # Import as late as possible + line = linecache.getline(filename, lineno) + if not line: + return 'That line does not exist!' + if not self.breaks.has_key(filename): + self.breaks[filename] = [] + list = self.breaks[filename] + if not lineno in list: + list.append(lineno) + bp = bdb.Breakpoint(filename, lineno, temporary, cond) diff --git a/Tools/idle/Delegator.py b/Tools/idle/Delegator.py index 6125591..3665247 100644 --- a/Tools/idle/Delegator.py +++ b/Tools/idle/Delegator.py @@ -1,3 +1,4 @@ + class Delegator: # The cache is only used to be able to change delegates! diff --git a/Tools/idle/EditorWindow.py b/Tools/idle/EditorWindow.py index 24b62dc..b4c9156 100644 --- a/Tools/idle/EditorWindow.py +++ b/Tools/idle/EditorWindow.py @@ -5,15 +5,70 @@ import imp from Tkinter import * import tkSimpleDialog import tkMessageBox +import idlever + +# File menu + +#$ event <<open-module>> +#$ win <Alt-m> +#$ unix <Control-x><Control-m> + +#$ event <<open-class-browser>> +#$ win <Alt-c> +#$ unix <Control-x><Control-b> + +#$ event <<close-window>> +#$ unix <Control-x><Control-0> +#$ unix <Control-x><Key-0> +#$ win <Alt-F4> + +# Edit menu + +#$ event <<Copy>> +#$ win <Control-c> +#$ unix <Alt-w> + +#$ event <<Cut>> +#$ win <Control-x> +#$ unix <Control-w> + +#$ event <<Paste>> +#$ win <Control-v> +#$ unix <Control-y> + +#$ event <<select-all>> +#$ win <Alt-a> +#$ unix <Alt-a> + +# Help menu + +#$ event <<help>> +#$ win <F1> +#$ unix <F1> + +#$ event <<about-idle>> + +# Events without menu entries + +#$ event <<remove-selection>> +#$ win <Escape> + +#$ event <<center-insert>> +#$ win <Control-l> +#$ unix <Control-l> + +#$ event <<do-nothing>> +#$ unix <Control-x> + about_title = "About IDLE" about_text = """\ -IDLE 0.1 +IDLE %s -A not totally unintegrated development environment for Python +An Integrated DeveLopment Environment for Python by Guido van Rossum -""" +""" % idlever.IDLE_VERSION class EditorWindow: @@ -21,44 +76,52 @@ class EditorWindow: from ColorDelegator import ColorDelegator from UndoDelegator import UndoDelegator from IOBinding import IOBinding - from SearchBinding import SearchBinding - from AutoIndent import AutoIndent - from AutoExpand import AutoExpand import Bindings - + from Tkinter import Toplevel + about_title = about_title - about_text = about_text + about_text = about_text - def __init__(self, root, filename=None): + def __init__(self, flist=None, filename=None, key=None, root=None): + self.flist = flist + root = root or flist.root self.root = root self.menubar = Menu(root) - self.top = top = Toplevel(root, menu=self.menubar) + self.top = top = self.Toplevel(root, menu=self.menubar) self.vbar = vbar = Scrollbar(top, name='vbar') - self.text = text = Text(top, name='text') + self.text = text = Text(top, name='text', padx=5, + background="white", wrap="none") self.createmenubar() self.Bindings.apply_bindings(text) self.top.protocol("WM_DELETE_WINDOW", self.close) self.top.bind("<<close-window>>", self.close_event) - self.text.bind("<<center-insert>>", self.center_insert_event) - self.text.bind("<<help>>", self.help_dialog) - self.text.bind("<<about-idle>>", self.about_dialog) - self.text.bind("<<open-module>>", self.open_module) - self.text.bind("<<do-nothing>>", lambda event: "break") + text.bind("<<center-insert>>", self.center_insert_event) + text.bind("<<help>>", self.help_dialog) + text.bind("<<about-idle>>", self.about_dialog) + text.bind("<<open-module>>", self.open_module) + text.bind("<<do-nothing>>", lambda event: "break") + text.bind("<<select-all>>", self.select_all) + text.bind("<<remove-selection>>", self.remove_selection) + text.bind("<3>", self.right_menu_event) + if flist: + flist.inversedict[self] = key + if key: + flist.dict[key] = self + text.bind("<<open-new-window>>", self.flist.new_callback) + text.bind("<<close-all-windows>>", self.flist.close_all_callback) + text.bind("<<open-class-browser>>", self.open_class_browser) vbar['command'] = text.yview vbar.pack(side=RIGHT, fill=Y) text['yscrollcommand'] = vbar.set - text['background'] = 'white' if sys.platform[:3] == 'win': text['font'] = ("lucida console", 8) text.pack(side=LEFT, fill=BOTH, expand=1) text.focus_set() - self.auto = auto = self.AutoIndent(text) - self.autoex = self.AutoExpand(text) self.per = per = self.Percolator(text) if self.ispythonsource(filename): self.color = color = self.ColorDelegator(); per.insertfilter(color) @@ -67,8 +130,7 @@ class EditorWindow: ##print "No initial colorizer" self.color = None self.undo = undo = self.UndoDelegator(); per.insertfilter(undo) - self.search = search = self.SearchBinding(undo) - self.io = io = self.IOBinding(undo) + self.io = io = self.IOBinding(self) undo.set_saved_change_hook(self.saved_change_hook) io.set_filename_change_hook(self.filename_change_hook) @@ -81,9 +143,29 @@ class EditorWindow: self.saved_change_hook() + self.load_extensions() + + menu = self.menudict.get('windows') + if menu: + menu.configure(tearoff=0) + end = menu.index("end") + if end is None: + end = -1 + if end >= 0: + menu.add_separator() + end = end + 1 + self.wmenu_end = end + menu.configure(postcommand=self.postwindowsmenu) + + def wakeup(self): + self.top.tkraise() + self.top.wm_deiconify() + self.text.focus_set() + menu_specs = [ ("file", "_File"), ("edit", "_Edit"), + ("windows", "_Windows"), ("help", "_Help"), ] @@ -96,15 +178,78 @@ class EditorWindow: mbar.add_cascade(label=label, menu=menu, underline=underline) self.Bindings.fill_menus(self.text, mdict) + def postwindowsmenu(self): + # Only called when Windows menu exists + menu = self.menudict['windows'] + end = menu.index("end") + if end is None: + end = -1 + if end > self.wmenu_end: + menu.delete(self.wmenu_end+1, end) + import WindowList + WindowList.add_windows_to_menu(menu) + + rmenu = None + + def right_menu_event(self, event): + self.text.tag_remove("sel", "1.0", "end") + self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) + if not self.rmenu: + self.make_rmenu() + rmenu = self.rmenu + self.event = event + iswin = sys.platform[:3] == 'win' + if iswin: + self.text.config(cursor="arrow") + rmenu.tk_popup(event.x_root, event.y_root) + if iswin: + self.text.config(cursor="ibeam") + + rmenu_specs = [ + # ("Label", "<<virtual-event>>"), ... + ("Close", "<<close-window>>"), # Example + ] + + def make_rmenu(self): + rmenu = Menu(self.text, tearoff=0) + for label, eventname in self.rmenu_specs: + def command(text=self.text, eventname=eventname): + text.event_generate(eventname) + rmenu.add_command(label=label, command=command) + self.rmenu = rmenu + def about_dialog(self, event=None): tkMessageBox.showinfo(self.about_title, self.about_text, master=self.text) + helpfile = "help.txt" + def help_dialog(self, event=None): - from HelpWindow import HelpWindow - HelpWindow(root=self.root) - + helpfile = self.helpfile + if not os.path.exists(helpfile): + base = os.path.basename(self.helpfile) + for dir in sys.path: + fullname = os.path.join(dir, base) + if os.path.exists(fullname): + helpfile = fullname + break + if self.flist: + self.flist.open(helpfile) + else: + self.io.loadfile(helpfile) + + def select_all(self, event=None): + self.text.tag_add("sel", "1.0", "end-1c") + self.text.mark_set("insert", "1.0") + self.text.see("insert") + return "break" + + def remove_selection(self, event=None): + self.text.tag_remove("sel", "1.0", "end") + self.text.see("insert") + def open_module(self, event=None): + # XXX Shouldn't this be in IOBinding or in FileList? try: name = self.text.get("sel.first", "sel.last") except TclError: @@ -120,6 +265,8 @@ class EditorWindow: name = string.strip(name) if not name: return + # XXX Ought to support package syntax + # XXX Ought to insert current file's directory in front of path try: (f, file, (suffix, mode, type)) = imp.find_module(name) except ImportError, msg: @@ -131,7 +278,26 @@ class EditorWindow: return if f: f.close() - self.flist.open(file, self) + if self.flist: + self.flist.open(file) + else: + self.io.loadfile(file) + + def open_class_browser(self, event=None): + filename = self.io.filename + if not filename: + tkMessageBox.showerror( + "No filename", + "This buffer has no associated filename", + master=self.text) + return None + head, tail = os.path.split(filename) + base, ext = os.path.splitext(tail) + import pyclbr + if pyclbr._modules.has_key(base): + del pyclbr._modules[base] + import ClassBrowser + ClassBrowser.ClassBrowser(self.flist, base, [head]) def gotoline(self, lineno): if lineno is not None and lineno > 0: @@ -143,7 +309,8 @@ class EditorWindow: def ispythonsource(self, filename): if not filename: return 1 - if os.path.normcase(filename[-3:]) == ".py": + base, ext = os.path.splitext(os.path.basename(filename)) + if os.path.normcase(ext) in (".py", ".pyw"): return 1 try: f = open(filename) @@ -153,12 +320,16 @@ class EditorWindow: return 0 return line[:2] == '#!' and string.find(line, 'python') >= 0 - close_hook = None + def close_hook(self): + if self.flist: + self.flist.close_edit(self) def set_close_hook(self, close_hook): self.close_hook = close_hook def filename_change_hook(self): + if self.flist: + self.flist.filename_changed_edit(self) self.saved_change_hook() if self.ispythonsource(self.io.filename): self.addcolorizer() @@ -184,13 +355,40 @@ class EditorWindow: self.per.insertfilter(self.undo) def saved_change_hook(self): - if self.io.filename: - title = self.io.filename + short = self.short_title() + long = self.long_title() + if short and long: + title = short + " - " + long + elif short: + title = short + elif long: + title = long else: - title = "(Untitled)" - if not self.undo.get_saved(): - title = title + " *" + title = "Untitled" + icon = short or long or title + if not self.get_saved(): + title = "*%s*" % title + icon = "*%s" % icon self.top.wm_title(title) + self.top.wm_iconname(icon) + + def get_saved(self): + return self.undo.get_saved() + + def set_saved(self, flag): + self.undo.set_saved(flag) + + def reset_undo(self): + self.undo.reset_undo() + + def short_title(self): + filename = self.io.filename + if filename: + filename = os.path.basename(filename) + return filename + + def long_title(self): + return self.io.filename or "" def center_insert_event(self, event): self.center() @@ -207,10 +405,14 @@ class EditorWindow: def close_event(self, event): self.close() + def maybesave(self): + if self.io: + return self.io.maybesave() + def close(self): self.top.wm_deiconify() self.top.tkraise() - reply = self.io.maybesave() + reply = self.maybesave() if reply != "cancel": if self.color and self.color.colorizing: self.color.close() @@ -223,8 +425,59 @@ class EditorWindow: self.top.destroy() return reply + def load_extensions(self): + self.extensions = {} + self.load_standard_extensions() + + def load_standard_extensions(self): + for name in self.get_standard_extension_names(): + try: + self.load_extension(name) + except: + print "Failed to load extension", `name` + import traceback + traceback.print_exc() + + def get_standard_extension_names(self): + import extend + return extend.standard + + def load_extension(self, name): + mod = __import__(name) + cls = getattr(mod, name) + ins = cls(self) + self.extensions[name] = ins + kdnames = ["keydefs"] + if sys.platform == 'win32': + kdnames.append("windows_keydefs") + elif sys.platform == 'mac': + kdnames.append("mac_keydefs") + else: + kdnames.append("unix_keydefs") + keydefs = {} + for kdname in kdnames: + if hasattr(ins, kdname): + keydefs.update(getattr(ins, kdname)) + if keydefs: + self.Bindings.apply_bindings(self.text, keydefs) + for vevent in keydefs.keys(): + methodname = string.replace(vevent, "-", "_") + while methodname[:1] == '<': + methodname = methodname[1:] + while methodname[-1:] == '>': + methodname = methodname[:-1] + methodname = methodname + "_event" + if hasattr(ins, methodname): + self.text.bind(vevent, getattr(ins, methodname)) + if hasattr(ins, "menudefs"): + self.Bindings.fill_menus(self.text, self. menudict, + ins.menudefs, keydefs) + return ins + def fixwordbreaks(root): + # Make sure that Tk's double-click and next/previous word + # operations use our definition of a word (i.e. an identifier) tk = root.tk tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]') @@ -239,7 +492,7 @@ def test(): filename = sys.argv[1] else: filename = None - edit = EditorWindow(root, filename) + edit = EditorWindow(root=root, filename=filename) edit.set_close_hook(root.quit) root.mainloop() root.destroy() diff --git a/Tools/idle/FileList.py b/Tools/idle/FileList.py index d9378e3..393b81c 100644 --- a/Tools/idle/FileList.py +++ b/Tools/idle/FileList.py @@ -2,125 +2,59 @@ import os from Tkinter import * import tkMessageBox -from EditorWindow import EditorWindow, fixwordbreaks -from IOBinding import IOBinding +import WindowList +#$ event <<open-new-window>> +#$ win <Control-n> +#$ unix <Control-x><Control-n> -class MultiIOBinding(IOBinding): +# (This is labeled as 'Exit'in the File menu) +#$ event <<close-all-windows>> +#$ win <Control-q> +#$ unix <Control-x><Control-c> - def open(self, event): - filename = self.askopenfile() - if filename: - self.flist.open(filename, self.edit) - return "break" - - -class MultiEditorWindow(EditorWindow): - - IOBinding = MultiIOBinding - - # Override menu bar specs - menu_specs = EditorWindow.menu_specs[:] - menu_specs.insert(len(menu_specs)-1, ("windows", "_Windows")) - - def __init__(self, flist, filename, key): - self.flist = flist - flist.inversedict[self] = key - if key: - flist.dict[key] = self - EditorWindow.__init__(self, flist.root, filename) - self.io.flist = flist - self.io.edit = self - self.text.bind("<<open-new-window>>", self.flist.new_callback) - self.text.bind("<<close-all-windows>>", self.flist.close_all_callback) - self.text.bind("<<open-class-browser>>", self.open_class_browser) - - def close_hook(self): - self.flist.close_edit(self) - - def filename_change_hook(self): - self.flist.filename_changed_edit(self) - EditorWindow.filename_change_hook(self) - - def wakeup(self): - self.top.tkraise() - self.top.wm_deiconify() - self.text.focus_set() - - def createmenubar(self): - EditorWindow.createmenubar(self) - self.menudict['windows'].configure(postcommand=self.postwindowsmenu) - - def postwindowsmenu(self): - wmenu = self.menudict['windows'] - wmenu.delete(0, 'end') - self.fixedwindowsmenu(wmenu) - files = self.flist.dict.keys() - files.sort() - for file in files: - def openit(self=self, file=file): - self.flist.open(file) - wmenu.add_command(label=file, command=openit) - - def open_class_browser(self, event=None): - filename = self.io.filename - if not filename: - tkMessageBox.showerror( - "No filename", - "This buffer has no associated filename", - master=self.text) - return None - head, tail = os.path.split(filename) - base, ext = os.path.splitext(tail) - import pyclbr - if pyclbr._modules.has_key(base): - del pyclbr._modules[base] - import ClassBrowser - ClassBrowser.ClassBrowser(self.flist, base, [head]) +class FileList: + from EditorWindow import EditorWindow + EditorWindow.Toplevel = WindowList.ListedToplevel # XXX Patch it! -class FileList: - - EditorWindow = MultiEditorWindow - def __init__(self, root): self.root = root self.dict = {} self.inversedict = {} - def new(self): - return self.open(None) - def open(self, filename, edit=None): - if filename: + def goodname(self, filename): filename = self.canonize(filename) - if os.path.isdir(filename): - tkMessageBox.showerror( - "Is A Directory", - "The path %s is a directory." % `filename`, - master=self.root) - return None key = os.path.normcase(filename) if self.dict.has_key(key): edit = self.dict[key] - edit.wakeup() - return edit - if not os.path.exists(filename): - tkMessageBox.showinfo( - "New File", - "Opening non-existent file %s" % `filename`, - master=self.root) - if edit and not edit.io.filename and edit.undo.get_saved(): - # Reuse existing Untitled window for new file - edit.io.loadfile(filename) - self.dict[key] = edit - self.inversedict[edit] = key - edit.wakeup() - return edit - else: - key = None - edit = self.EditorWindow(self, filename, key) - return edit + filename = edit.io.filename or filename + return filename + + def open(self, filename): + assert filename + filename = self.canonize(filename) + if os.path.isdir(filename): + tkMessageBox.showerror( + "Is A Directory", + "The path %s is a directory." % `filename`, + master=self.root) + return None + key = os.path.normcase(filename) + if self.dict.has_key(key): + edit = self.dict[key] + edit.wakeup() + return edit + if not os.path.exists(filename): + tkMessageBox.showinfo( + "New File", + "Opening non-existent file %s" % `filename`, + master=self.root) + return self.EditorWindow(self, filename, key) + + def new(self): + return self.EditorWindow(self) def new_callback(self, event): self.new() @@ -189,6 +123,7 @@ class FileList: def test(): + from EditorWindow import fixwordbreaks import sys root = Tk() fixwordbreaks(root) diff --git a/Tools/idle/FrameViewer.py b/Tools/idle/FrameViewer.py index e5a6051..2ce0935 100644 --- a/Tools/idle/FrameViewer.py +++ b/Tools/idle/FrameViewer.py @@ -10,7 +10,7 @@ class FrameViewer: self.repr = Repr() self.repr.maxstring = 60 self.load_variables() - + def load_variables(self): row = 0 if self.frame.f_locals is not self.frame.f_globals: @@ -22,7 +22,7 @@ class FrameViewer: borderwidth=2, relief="raised") l.grid(row=row, column=0, columnspan=2, sticky="ew") row = self.load_names(self.frame.f_globals, row+1) - + def load_names(self, dict, row): names = dict.keys() names.sort() diff --git a/Tools/idle/GrepDialog.py b/Tools/idle/GrepDialog.py new file mode 100644 index 0000000..df3504b --- /dev/null +++ b/Tools/idle/GrepDialog.py @@ -0,0 +1,134 @@ +import string +import os +import re +import fnmatch +from Tkinter import * +import tkMessageBox +import SearchEngine +from SearchDialogBase import SearchDialogBase + +def grep(text, io=None, flist=None): + root = text._root() + engine = SearchEngine.get(root) + if not hasattr(engine, "_grepdialog"): + engine._grepdialog = GrepDialog(root, engine, flist) + dialog = engine._grepdialog + dialog.open(io) + +class GrepDialog(SearchDialogBase): + + title = "Find in Files Dialog" + icon = "Grep" + needwrapbutton = 0 + + def __init__(self, root, engine, flist): + SearchDialogBase.__init__(self, root, engine) + self.flist = flist + self.globvar = StringVar(root) + self.recvar = BooleanVar(root) + + def open(self, io=None): + SearchDialogBase.open(self, None) + if io: + path = io.filename or "" + else: + path = "" + dir, base = os.path.split(path) + head, tail = os.path.splitext(base) + if not tail: + tail = ".py" + self.globvar.set(os.path.join(dir, "*" + tail)) + + def create_entries(self): + SearchDialogBase.create_entries(self) + self.globent = self.make_entry("In files:", self.globvar) + + def create_other_buttons(self): + f = self.make_frame() + + btn = Checkbutton(f, anchor="w", + variable=self.recvar, + text="Recurse down subdirectories") + btn.pack(side="top", fill="both") + btn.select() + + def create_command_buttons(self): + SearchDialogBase.create_command_buttons(self) + self.make_button("Search Files", self.default_command, 1) + + def default_command(self, event=None): + prog = self.engine.getprog() + if not prog: + return + path = self.globvar.get() + if not path: + self.top.bell() + return + from OutputWindow import OutputWindow + save = sys.stdout + try: + sys.stdout = OutputWindow(self.flist) + self.grep_it(prog, path) + finally: + sys.stdout = save + + def grep_it(self, prog, path): + dir, base = os.path.split(path) + list = self.findfiles(dir, base, self.recvar.get()) + list.sort() + self.close() + pat = self.engine.getpat() + print "Searching %s in %s ..." % (pat, path) + hits = 0 + for fn in list: + try: + f = open(fn) + except IOError, msg: + print msg + continue + lineno = 0 + while 1: + block = f.readlines(100000) + if not block: + break + for line in block: + lineno = lineno + 1 + if line[-1:] == '\n': + line = line[:-1] + if prog.search(line): + sys.stdout.write("%s: %s: %s\n" % (fn, lineno, line)) + hits = hits + 1 + if hits: + if hits == 1: + s = "" + else: + s = "s" + print "Found", hits, "hit%s." % s + print "(Hint: right-click to open locations.)" + else: + print "No hits." + + def findfiles(self, dir, base, rec): + try: + names = os.listdir(dir or os.curdir) + except os.error, msg: + print msg + return [] + list = [] + subdirs = [] + for name in names: + fn = os.path.join(dir, name) + if os.path.isdir(fn): + subdirs.append(fn) + else: + if fnmatch.fnmatch(name, base): + list.append(fn) + if rec: + for subdir in subdirs: + list.extend(self.findfiles(subdir, base, rec)) + return list + + def close(self, event=None): + if self.top: + self.top.grab_release() + self.top.withdraw() diff --git a/Tools/idle/HelpWindow.py b/Tools/idle/HelpWindow.py deleted file mode 100644 index a1b13c3..0000000 --- a/Tools/idle/HelpWindow.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import sys -from Tkinter import * - - -class HelpWindow: - - helpfile = "help.txt" - helptitle = "Help Window" - - def __init__(self, root=None): - if not root: - import Tkinter - root = Tkinter._default_root - if root: - self.top = top = Toplevel(root) - else: - self.top = top = root = Tk() - - helpfile = self.helpfile - if not os.path.exists(helpfile): - base = os.path.basename(self.helpfile) - for dir in sys.path: - fullname = os.path.join(dir, base) - if os.path.exists(fullname): - helpfile = fullname - break - try: - f = open(helpfile) - data = f.read() - f.close() - except IOError, msg: - data = "Can't open the help file (%s)" % `helpfile` - - top.protocol("WM_DELETE_WINDOW", self.close_command) - top.wm_title(self.helptitle) - - self.close_button = Button(top, text="close", - command=self.close_command) - self.close_button.pack(side="bottom") - - self.vbar = vbar = Scrollbar(top, name="vbar") - self.text = text = Text(top) - - vbar["command"] = text.yview - text["yscrollcommand"] = vbar.set - - vbar.pack(side="right", fill="y") - text.pack(side="left", fill="both", expand=1) - - text.insert("1.0", data) - - text.config(state="disabled") - text.see("1.0") - - def close_command(self): - self.top.destroy() - - -def main(): - h = HelpWindow() - h.top.mainloop() - -if __name__ == "__main__": - main() diff --git a/Tools/idle/History.py b/Tools/idle/History.py index 0798098..3094173 100644 --- a/Tools/idle/History.py +++ b/Tools/idle/History.py @@ -1,7 +1,7 @@ import string class History: - + def __init__(self, text): self.text = text self.history = [] diff --git a/Tools/idle/IOBinding.py b/Tools/idle/IOBinding.py index 0d61afc..6a41a37 100644 --- a/Tools/idle/IOBinding.py +++ b/Tools/idle/IOBinding.py @@ -2,20 +2,42 @@ import os import tkFileDialog import tkMessageBox +#$ event <<open-window-from-file>> +#$ win <Control-o> +#$ unix <Control-x><Control-f> -class IOBinding: +#$ event <<save-window>> +#$ win <Control-s> +#$ unix <Control-x><Control-s> + +#$ event <<save-window-as-file>> +#$ win <Alt-s> +#$ unix <Control-x><Control-w> + +#$ event <<save-copy-of-window-as-file>> +#$ win <Alt-Shift-s> +#$ unix <Control-x><w> - # Calls to non-standard text methods: - # reset_undo() - # set_saved(1) - def __init__(self, text): - self.text = text +class IOBinding: + + def __init__(self, editwin): + self.editwin = editwin + self.text = editwin.text self.text.bind("<<open-window-from-file>>", self.open) self.text.bind("<<save-window>>", self.save) self.text.bind("<<save-window-as-file>>", self.save_as) self.text.bind("<<save-copy-of-window-as-file>>", self.save_a_copy) + def get_saved(self): + return self.editwin.get_saved() + + def set_saved(self, flag): + self.editwin.set_saved(flag) + + def reset_undo(self): + self.editwin.reset_undo() + filename_change_hook = None def set_filename_change_hook(self, hook): @@ -25,18 +47,29 @@ class IOBinding: def set_filename(self, filename): self.filename = filename - self.text.set_saved(1) + self.set_saved(1) if self.filename_change_hook: self.filename_change_hook() def open(self, event): - if not self.text.get_saved(): + if self.editwin.flist: + filename = self.askopenfile() + if filename: + self.editwin.flist.open(filename) + else: + self.text.focus_set() + return "break" + # Code for use outside IDLE: + if self.get_saved(): reply = self.maybesave() if reply == "cancel": + self.text.focus_set() return "break" filename = self.askopenfile() if filename: self.loadfile(filename) + else: + self.text.focus_set() return "break" def loadfile(self, filename): @@ -50,14 +83,14 @@ class IOBinding: self.text.delete("1.0", "end") self.set_filename(None) self.text.insert("1.0", chars) - self.text.reset_undo() + self.reset_undo() self.set_filename(filename) self.text.mark_set("insert", "1.0") self.text.see("insert") return 1 def maybesave(self): - if self.text.get_saved(): + if self.get_saved(): return "yes" message = "Do you want to save %s before closing?" % ( self.filename or "this untitled document") @@ -70,8 +103,9 @@ class IOBinding: reply = m.show() if reply == "yes": self.save(None) - if not self.text.get_saved(): + if not self.get_saved(): reply = "cancel" + self.text.focus_set() return reply def save(self, event): @@ -79,7 +113,8 @@ class IOBinding: self.save_as(event) else: if self.writefile(self.filename): - self.text.set_saved(1) + self.set_saved(1) + self.text.focus_set() return "break" def save_as(self, event): @@ -87,22 +122,23 @@ class IOBinding: if filename: if self.writefile(filename): self.set_filename(filename) - self.text.set_saved(1) + self.set_saved(1) + self.text.focus_set() return "break" def save_a_copy(self, event): filename = self.asksavefile() if filename: self.writefile(filename) + self.text.focus_set() return "break" def writefile(self, filename): + self.fixlastline() try: f = open(filename, "w") chars = self.text.get("1.0", "end-1c") f.write(chars) - if chars and chars[-1] != "\n": - f.write("\n") f.close() ## print "saved to", `filename` return 1 @@ -111,11 +147,16 @@ class IOBinding: master=self.text) return 0 + def fixlastline(self): + c = self.text.get("end-2c") + if c != '\n': + self.text.insert("end-1c", "\n") + opendialog = None savedialog = None filetypes = [ - ("Python files", "*.py", "TEXT"), + ("Python and text files", "*.py *.pyw *.txt", "TEXT"), ("All text files", "*", "TEXT"), ("All files", "*"), ] @@ -129,10 +170,13 @@ class IOBinding: def defaultfilename(self, mode="open"): if self.filename: - dir, base = os.path.split(self.filename) + return os.path.split(self.filename) else: - dir = base = "" - return dir, base + try: + pwd = os.getcwd() + except os.error: + pwd = "" + return pwd, "" def asksavefile(self): dir, base = self.defaultfilename("save") @@ -145,13 +189,30 @@ class IOBinding: def test(): from Tkinter import * root = Tk() - class MyText(Text): - def reset_undo(self): pass + class MyEditWin: + def __init__(self, text): + self.text = text + self.flist = None + self.text.bind("<Control-o>", self.open) + self.text.bind("<Control-s>", self.save) + self.text.bind("<Alt-s>", self.save_as) + self.text.bind("<Alt-z>", self.save_a_copy) + def get_saved(self): return 0 def set_saved(self, flag): pass - text = MyText(root) + def reset_undo(self): pass + def open(self, event): + self.text.event_generate("<<open-window-from-file>>") + def save(self, event): + self.text.event_generate("<<save-window>>") + def save_as(self, event): + self.text.event_generate("<<save-window-as-file>>") + def save_a_copy(self, event): + self.text.event_generate("<<save-copy-of-window-as-file>>") + text = Text(root) text.pack() text.focus_set() - io = IOBinding(text) + editwin = MyEditWin(text) + io = IOBinding(editwin) root.mainloop() if __name__ == "__main__": diff --git a/Tools/idle/IdleHistory.py b/Tools/idle/IdleHistory.py index 0798098..3094173 100644 --- a/Tools/idle/IdleHistory.py +++ b/Tools/idle/IdleHistory.py @@ -1,7 +1,7 @@ import string class History: - + def __init__(self, text): self.text = text self.history = [] diff --git a/Tools/idle/OutputWindow.py b/Tools/idle/OutputWindow.py new file mode 100644 index 0000000..c13b3e4 --- /dev/null +++ b/Tools/idle/OutputWindow.py @@ -0,0 +1,90 @@ +from Tkinter import * +from EditorWindow import EditorWindow +import re +import tkMessageBox + +class OutputWindow(EditorWindow): + + """An editor window that can serve as an output file. + + Also the future base class for the Python shell window. + This class has no input facilities. + """ + + def __init__(self, *args): + apply(EditorWindow.__init__, (self,) + args) + self.text.bind("<<goto-file-line>>", self.goto_file_line) + + # Customize EditorWindow + + def ispythonsource(self, filename): + # No colorization needed + return 0 + + def short_title(self): + return "Output" + + def maybesave(self): + # Override base class method -- don't ask any questions + if self.get_saved(): + return "yes" + else: + return "no" + + # Act as output file + + def write(self, s, tags=(), mark="insert"): + self.text.insert(mark, str(s), tags) + self.text.see(mark) + self.text.update() + + def writelines(self, l): + map(self.write, l) + + # Our own right-button menu + + rmenu_specs = [ + ("Go to file/line", "<<goto-file-line>>"), + ] + + file_line_pats = [ + r'file "([^"]*)", line (\d+)', + r'([^\s]+)\((\d+)\)', + r'([^\s]+):\s*(\d+):', + ] + + file_line_progs = None + + def goto_file_line(self, event=None): + if self.file_line_progs is None: + l = [] + for pat in self.file_line_pats: + l.append(re.compile(pat, re.IGNORECASE)) + self.file_line_progs = l + # x, y = self.event.x, self.event.y + # self.text.mark_set("insert", "@%d,%d" % (x, y)) + line = self.text.get("insert linestart", "insert lineend") + for prog in self.file_line_progs: + m = prog.search(line) + if m: + break + else: + tkMessageBox.showerror("No special line", + "The line you point at doesn't look like " + "a file name followed by a line number.", + master=self.text) + return + filename, lineno = m.group(1, 2) + try: + f = open(filename, "r") + f.close() + except IOError, msg: + self.text.bell() + return + edit = self.flist.open(filename) + try: + lineno = int(lineno) + except ValueError, msg: + self.text.bell() + return + edit.gotoline(lineno) diff --git a/Tools/idle/PopupMenu.py b/Tools/idle/PopupMenu.py deleted file mode 100644 index edda3a3..0000000 --- a/Tools/idle/PopupMenu.py +++ /dev/null @@ -1,86 +0,0 @@ -import sys -import re - -from Tkinter import * - -class PopupMenu: - - def __init__(self, text, flist): - self.text = text - self.flist = flist - self.text.bind("<3>", self.right_menu_event) - - rmenu = None - - def right_menu_event(self, event): - if not self.rmenu: - self.make_menu() - rmenu = self.rmenu - self.event = event - iswin = sys.platform[:3] == 'win' - if iswin: - self.text.config(cursor="arrow") - rmenu.tk_popup(event.x_root, event.y_root) - if iswin: - self.text.config(cursor="ibeam") - - def make_menu(self): - rmenu = Menu(self.text, tearoff=0) - rmenu.add_command(label="Go to line from traceback", - command=self.goto_traceback_line) - #rmenu.add_command(label="Open stack viewer", - # command=self.open_stack_viewer) - #rmenu.add_command(label="Help", command=self.help) - self.rmenu = rmenu - - file_line_pats = [ - r'File "([^"]*)", line (\d+)', - r'([^\s]+)\((\d+)\)', - r'([^\s]+):\s*(\d+):', - ] - - file_line_progs = None - - def goto_traceback_line(self): - if self.file_line_progs is None: - l = [] - for pat in self.file_line_pats: - l.append(re.compile(pat)) - self.file_line_progs = l - x, y = self.event.x, self.event.y - self.text.mark_set("insert", "@%d,%d" % (x, y)) - line = self.text.get("insert linestart", "insert lineend") - for prog in self.file_line_progs: - m = prog.search(line) - if m: - break - else: - self.text.bell() - return - filename, lineno = m.group(1, 2) - try: - f = open(filename, "r") - f.close() - except IOError, msg: - self.text.bell() - return - edit = self.flist.open(filename) - try: - lineno = int(lineno) - except ValueError, msg: - self.text.bell() - return - edit.gotoline(lineno) - - def open_stack_viewer(self): - try: - sys.last_traceback - except: - print "No stack trace yet" - return - from StackViewer import StackBrowser - sv = StackBrowser(self.text._root(), self.flist) - - def help(self): - from HelpWindow import HelpWindow - HelpWindow(root=self.flist.root) diff --git a/Tools/idle/PyShell.py b/Tools/idle/PyShell.py index 887da1e..6df38c3 100644 --- a/Tools/idle/PyShell.py +++ b/Tools/idle/PyShell.py @@ -12,9 +12,10 @@ from code import InteractiveInterpreter from Tkinter import * import tkMessageBox -from EditorWindow import fixwordbreaks -from FileList import FileList, MultiEditorWindow, MultiIOBinding +from EditorWindow import EditorWindow, fixwordbreaks +from FileList import FileList from ColorDelegator import ColorDelegator +from OutputWindow import OutputWindow # We need to patch linecache.checkcache, because we don't want it # to throw away our <pyshell#...> entries. @@ -31,36 +32,54 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache): linecache.checkcache = linecache_checkcache -class PyShellEditorWindow(MultiEditorWindow): - +# Note: <<newline-and-indent>> event is defined in AutoIndent.py + +#$ event <<plain-newline-and-indent>> +#$ win <Control-j> +#$ unix <Control-j> + +#$ event <<beginning-of-line>> +#$ win <Control-a> +#$ win <Home> +#$ unix <Control-a> +#$ unix <Home> + +#$ event <<history-next>> +#$ win <Alt-n> +#$ unix <Alt-n> + +#$ event <<history-previous>> +#$ win <Alt-p> +#$ unix <Alt-p> + +#$ event <<interrupt-execution>> +#$ win <Control-c> +#$ unix <Control-c> + +#$ event <<end-of-file>> +#$ win <Control-d> +#$ unix <Control-d> + +#$ event <<open-stack-viewer>> + +#$ event <<toggle-debugger>> + + +class PyShellEditorWindow(EditorWindow): + + # Regular text edit window when a shell is present + # XXX ought to merge with regular editor window + def __init__(self, *args): - apply(MultiEditorWindow.__init__, (self,) + args) - self.text.bind("<3>", self.right_menu_event) - - def fixedwindowsmenu(self, wmenu): - wmenu.add_command(label="Python Shell", command=self.flist.open_shell) - wmenu.add_separator() - - menu = None - - def right_menu_event(self, event): - self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) - if not self.menu: - self.make_menu() - menu = self.menu - iswin = sys.platform[:3] == 'win' - if iswin: - self.text.config(cursor="arrow") - menu.tk_popup(event.x_root, event.y_root) - if iswin: - self.text.config(cursor="ibeam") - - def make_menu(self): - self.menu = menu = Menu(self.text, tearoff=0) - menu.add_command(label="Set breakpoint here", - command=self.set_breakpoint_here) - - def set_breakpoint_here(self): + apply(EditorWindow.__init__, (self,) + args) + self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here) + self.text.bind("<<open-python-shell>>", self.flist.open_shell) + + rmenu_specs = [ + ("Set breakpoint here", "<<set-breakpoint-here>>"), + ] + + def set_breakpoint_here(self, event=None): if not self.flist.pyshell or not self.flist.pyshell.interp.debugger: self.text.bell() return @@ -68,12 +87,14 @@ class PyShellEditorWindow(MultiEditorWindow): class PyShellFileList(FileList): - + + # File list when a shell is present + EditorWindow = PyShellEditorWindow - + pyshell = None - def open_shell(self): + def open_shell(self, event=None): if self.pyshell: self.pyshell.wakeup() else: @@ -82,43 +103,29 @@ class PyShellFileList(FileList): return self.pyshell -class ModifiedIOBinding(MultiIOBinding): - - def defaultfilename(self, mode="open"): - if self.filename: - return MultiIOBinding.defaultfilename(self, mode) - else: - try: - pwd = os.getcwd() - except os.error: - pwd = "" - return pwd, "" - - def open(self, event): - # Override base class method -- don't allow reusing this window - filename = self.askopenfile() - if filename: - self.flist.open(filename) - return "break" - - def maybesave(self): - # Override base class method -- don't ask any questions - if self.text.get_saved(): - return "yes" - else: - return "no" +class ModifiedColorDelegator(ColorDelegator): + # Colorizer for the shell window itself -class ModifiedColorDelegator(ColorDelegator): - def recolorize_main(self): self.tag_remove("TODO", "1.0", "iomark") self.tag_add("SYNC", "1.0", "iomark") ColorDelegator.recolorize_main(self) + tagdefs = ColorDelegator.tagdefs.copy() + + tagdefs.update({ + ##"stdin": {"background": "yellow"}, + "stdout": {"foreground": "blue"}, + "stderr": {"foreground": "#007700"}, + "console": {"foreground": "#770000"}, + "ERROR": {"background": "#FF7777"}, + None: {"foreground": "purple"}, # default + }) + class ModifiedInterpreter(InteractiveInterpreter): - + def __init__(self, tkconsole): self.tkconsole = tkconsole InteractiveInterpreter.__init__(self) @@ -176,7 +183,7 @@ class ModifiedInterpreter(InteractiveInterpreter): self.tkconsole.resetoutput() self.checklinecache() InteractiveInterpreter.showtraceback(self) - + def checklinecache(self): c = linecache.cache for key in c.keys(): @@ -184,10 +191,10 @@ class ModifiedInterpreter(InteractiveInterpreter): del c[key] debugger = None - + def setdebugger(self, debugger): self.debugger = debugger - + def getdebugger(self): return self.debugger @@ -214,25 +221,23 @@ class ModifiedInterpreter(InteractiveInterpreter): self.showtraceback() finally: self.tkconsole.endexecuting() - + def write(self, s): # Override base class write self.tkconsole.console.write(s) - -class PyShell(PyShellEditorWindow): + +class PyShell(OutputWindow): # Override classes ColorDelegator = ModifiedColorDelegator - IOBinding = ModifiedIOBinding - + # Override menu bar specs menu_specs = PyShellEditorWindow.menu_specs[:] - menu_specs.insert(len(menu_specs)-1, ("debug", "Debug")) - + menu_specs.insert(len(menu_specs)-2, ("debug", "_Debug")) + # New classes from History import History - from PopupMenu import PopupMenu def __init__(self, flist=None): self.interp = ModifiedInterpreter(self) @@ -242,23 +247,24 @@ class PyShell(PyShellEditorWindow): root.withdraw() flist = PyShellFileList(root) - PyShellEditorWindow.__init__(self, flist, None, None) - self.config_colors() + OutputWindow.__init__(self, flist, None, None) import __builtin__ __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D." + self.auto = self.extensions["AutoIndent"] # Required extension self.auto.config(prefertabs=1) text = self.text + text.configure(wrap="char") text.bind("<<newline-and-indent>>", self.enter_callback) text.bind("<<plain-newline-and-indent>>", self.linefeed_callback) text.bind("<<interrupt-execution>>", self.cancel_callback) text.bind("<<beginning-of-line>>", self.home_callback) text.bind("<<end-of-file>>", self.eof_callback) - text.bind("<<goto-traceback-line>>", self.goto_traceback_line) text.bind("<<open-stack-viewer>>", self.open_stack_viewer) text.bind("<<toggle-debugger>>", self.toggle_debugger) + text.bind("<<open-python-shell>>", self.flist.open_shell) sys.stdout = PseudoFile(self, "stdout") sys.stderr = PseudoFile(self, "stderr") @@ -266,31 +272,12 @@ class PyShell(PyShellEditorWindow): self.console = PseudoFile(self, "console") self.history = self.History(self.text) - self.popup = self.PopupMenu(self.text, self.flist) - - tagdefs = { - ##"stdin": {"background": "yellow"}, - "stdout": {"foreground": "blue"}, - "stderr": {"foreground": "#007700"}, - "console": {"foreground": "red"}, - "ERROR": {"background": "#FF7777"}, - None: {"foreground": "purple"}, # default - } - - def config_colors(self): - for tag, cnf in self.tagdefs.items(): - if cnf: - if not tag: - apply(self.text.configure, (), cnf) - else: - apply(self.text.tag_configure, (tag,), cnf) - self.text.tag_raise("sel") reading = 0 executing = 0 canceled = 0 endoffile = 0 - + def toggle_debugger(self, event=None): if self.executing: tkMessageBox.showerror("Don't debug now", @@ -362,14 +349,8 @@ class PyShell(PyShellEditorWindow): # Override this so EditorWindow never removes the colorizer return 1 - def saved_change_hook(self): - # Override this to get the title right - title = "Python Shell" - if self.io.filename: - title = title + ": " + self.io.filename - if not self.undo.get_saved(): - title = title + " *" - self.top.wm_title(title) + def short_title(self): + return "Python Shell" def begin(self): self.resetoutput() @@ -382,7 +363,7 @@ class PyShell(PyShellEditorWindow): self.showprompt() import Tkinter Tkinter._default_root = None - + def interact(self): self.begin() self.top.mainloop() @@ -457,7 +438,7 @@ class PyShell(PyShellEditorWindow): self.text.insert("insert", "\n") self.text.see("insert") else: - self.auto.autoindent(event) + self.auto.auto_indent(event) return "break" def enter_callback(self, event): @@ -468,7 +449,7 @@ class PyShell(PyShellEditorWindow): try: sel = self.text.get("sel.first", "sel.last") if sel: - if self.text.compare("self.last", "<=", "iomark"): + if self.text.compare("sel.last", "<=", "iomark"): self.recall(sel) return "break" except: @@ -492,7 +473,7 @@ class PyShell(PyShellEditorWindow): # If we're in the current input before its last line, # insert a newline right at the insert point if self.text.compare("insert", "<", "end-1c linestart"): - self.auto.autoindent(event) + self.auto.auto_indent(event) return "break" # We're in the last line; append a newline and submit it self.text.mark_set("insert", "end-1c") @@ -500,7 +481,7 @@ class PyShell(PyShellEditorWindow): self.text.insert("insert", "\n") self.text.see("insert") else: - self.auto.autoindent(event) + self.auto.auto_indent(event) self.text.tag_add("stdin", "iomark", "end-1c") self.text.update_idletasks() if self.reading: @@ -545,49 +526,7 @@ class PyShell(PyShellEditorWindow): self.canceled = 0 raise KeyboardInterrupt return self._cancel_check - - file_line_pats = [ - r'File "([^"]*)", line (\d+)', - r'([^\s]+)\((\d+)\)', - r'([^\s]+):\s*(\d+):', - ] - - file_line_progs = None - - def goto_traceback_line(self, event=None): - if self.file_line_progs is None: - l = [] - for pat in self.file_line_pats: - l.append(re.compile(pat)) - self.file_line_progs = l - # x, y = self.event.x, self.event.y - # self.text.mark_set("insert", "@%d,%d" % (x, y)) - line = self.text.get("insert linestart", "insert lineend") - for prog in self.file_line_progs: - m = prog.search(line) - if m: - break - else: - tkMessageBox.showerror("No traceback line", - "The line you point at doesn't look " - "like an error message.", - master=self.text) - return - filename, lineno = m.group(1, 2) - try: - f = open(filename, "r") - f.close() - except IOError, msg: - self.text.bell() - return - edit = self.flist.open(filename) - try: - lineno = int(lineno) - except ValueError, msg: - self.text.bell() - return - edit.gotoline(lineno) - + def open_stack_viewer(self, event=None): try: sys.last_traceback @@ -618,26 +557,22 @@ class PyShell(PyShellEditorWindow): self.text.mark_set("iomark", "end-1c") sys.stdout.softspace = 0 - def write(self, s): - # Overrides base class write - self.console.write(s) + def write(self, s, tags=()): + self.text.mark_gravity("iomark", "right") + OutputWindow.write(self, s, tags, "iomark") + self.text.mark_gravity("iomark", "left") + if self.canceled: + self.canceled = 0 + raise KeyboardInterrupt class PseudoFile: - def __init__(self, interp, tags): - self.interp = interp - self.text = interp.text + def __init__(self, shell, tags): + self.shell = shell self.tags = tags def write(self, s): - self.text.mark_gravity("iomark", "right") - self.text.insert("iomark", str(s), self.tags) - self.text.mark_gravity("iomark", "left") - self.text.see("iomark") - self.text.update() - if self.interp.canceled: - self.interp.canceled = 0 - raise KeyboardInterrupt + self.shell.write(s, self.tags) def writelines(self, l): map(self.write, l) diff --git a/Tools/idle/README b/Tools/idle/README.txt index 1a835f6..62b34cd 100644 --- a/Tools/idle/README +++ b/Tools/idle/README.txt @@ -1,23 +1,22 @@ -IDLE 0.1 - 10/16/98 +IDLE 0.2 - 01/01/99 ------------------- This is a *very* early preliminary release of IDLE, my own attempt at -a Tkinter-based IDE for Python. It currently has the following -features: +a Tkinter-based IDE for Python. It has the following features: - multi-window text editor with multiple undo and Python colorizing - Python shell (a.k.a. interactive interpreter) window subclass - debugger - 100% pure Python -- works on Windows and Unix (should work on Mac too) +- works on Windows and Unix (probably works on Mac too) The main program is in the file "idle"; on Windows you can use idle.pyw to avoid popping up a DOS console. Any arguments passed are interpreted as files that will be opened for editing. -IDLE requires Python 1.5.2, so it is currently only usable for PSA -members who have the latest 1.5.2 alpha release (a public beta release -is due shortly). +IDLE requires Python 1.5.2, so it is currently only usable with the +Python 1.5.2 beta distribution (luckily, IDLE is bundled with Python +1.5.2). Please send feedback to the Python newsgroup, comp.lang.python. @@ -27,46 +26,71 @@ Please send feedback to the Python newsgroup, comp.lang.python. TO DO: +- "GO" command +- "Modularize" command +- command expansion from keywords, module contents, other buffers, etc. - "Recent documents" menu item -- use platform specific default bindings -- title and Windows menu should have base filename first -- restructure state sensitive code to avoid testing flags all the time -- integrated debugger -- object browser instead of current stack viewer -- save some user state (e.g. window and cursor positions, bindings) -- make backups when saving -- check file mtimes at various points -- interface with RCS/CVS/Perforce ??? -- more search options: case [in]sensitive, fwd/back, string/regex -- global query replace -- incremental search - more emacsisms: + - parentheses matching - reindent, reformat text etc. - M-[, M-] to move by paragraphs - smart stuff with whitespace around Return - filter region? - - grep? + - incremental search? + - ^K should cut to buffer + - command to fill text paragraphs +- restructure state sensitive code to avoid testing flags all the time +- finish debugger +- object browser instead of current stack viewer +- persistent user state (e.g. window and cursor positions, bindings) +- make backups when saving +- check file mtimes at various points +- interface with RCS/CVS/Perforce ??? - status bar? - better help? +- don't open second class browser on same module Details: - when there's a selection, left/right arrow should go to either end of the selection -- ^O should honor autoindent +- ^O (on Unix -- open-line) should honor autoindent +- after paste, show end of pasted text +- on Windows, should turn short filename to long filename (not only in argv!) + (shouldn't this be done -- or undone -- by ntpath.normpath?) Structural problems: - too much knowledge in FileList about EditorWindow (for example) - Several occurrences of scrollable listbox with title and certain behavior; should create base class to generalize this -- class browser could become an outline? ====================================================================== Comparison to PTUI ------------------ ++ PTUI has a status line + ++ PTUI's help is better (HTML!) + ++ PTUI can attach a shell to any module + ++ PTUI's auto indent is better + (understands that "if a: # blah, blah" opens a block) + ++ IDLE requires 4x backspace to dedent a line + ++ PTUI has more bells and whistles: + open multiple + append + modularize + examine + go + +? PTUI's fontify is faster but synchronous (and still too slow); + does a lousy job if editing affects lines below + - PTUI's shell is worse: no coloring; no editing of multi-line commands; @@ -76,34 +100,18 @@ Comparison to PTUI no redo; one char at a time -- PTUI's framework is better: - status line - (not sure if I like the toolbar) - - PTUI's GUI is a tad ugly: - I don't like the multiple buffers in one window model - -- PTUI's help is better (HTML!) + I don't like the multiple buffers in one window model; + I don't like the big buttons at the top of the widow -- PTUI's search/replace is better (more features) +- PTUI lacks an integrated debugger -- PTUI's auto indent is better - (understands that "if a: # blah, blah" opens a block) - -- PTUI's key bindings are a bit weird (DEL to dedent a line!?!?!?) +- PTUI lacks a class browser -- PTUI's fontify is faster but synchronous (and still too slow); - also doesn't do as good a job if editing affects lines far below - -- PTUI has more bells and whistles: - open multiple - append - zap tabs - fontify (you could argue it's not needed in my code) - comment/uncomment - modularize - examine - go +- PTUI lacks many of IDLE's features: + - expand word + - regular expression search + - search files (grep) ====================================================================== diff --git a/Tools/idle/ReplaceDialog.py b/Tools/idle/ReplaceDialog.py new file mode 100644 index 0000000..3bff8b5 --- /dev/null +++ b/Tools/idle/ReplaceDialog.py @@ -0,0 +1,168 @@ +import string +import os +import re +import fnmatch +from Tkinter import * +import tkMessageBox +import SearchEngine +from SearchDialogBase import SearchDialogBase + +def replace(text): + root = text._root() + engine = SearchEngine.get(root) + if not hasattr(engine, "_replacedialog"): + engine._replacedialog = ReplaceDialog(root, engine) + dialog = engine._replacedialog + dialog.open(text) + +class ReplaceDialog(SearchDialogBase): + + title = "Replace Dialog" + icon = "Replace" + + def __init__(self, root, engine): + SearchDialogBase.__init__(self, root, engine) + self.replvar = StringVar(root) + + def open(self, text): + SearchDialogBase.open(self, text) + try: + first = text.index("sel.first") + except TclError: + first = None + try: + last = text.index("sel.last") + except TclError: + last = None + first = first or text.index("insert") + last = last or first + self.show_hit(first, last) + self.ok = 1 + + def create_entries(self): + SearchDialogBase.create_entries(self) + self.replent = self.make_entry("Replace with:", self.replvar) + + def create_command_buttons(self): + SearchDialogBase.create_command_buttons(self) + self.make_button("Find", self.find_it) + self.make_button("Replace", self.replace_it) + self.make_button("Replace+Find", self.default_command, 1) + self.make_button("Replace All", self.replace_all) + + def find_it(self, event=None): + self.do_find(0) + + def replace_it(self, event=None): + if self.do_find(self.ok): + self.do_replace() + + def default_command(self, event=None): + if self.do_find(self.ok): + self.do_replace() + self.do_find(0) + + def replace_all(self, event=None): + prog = self.engine.getprog() + if not prog: + return + repl = self.replvar.get() + text = self.text + res = self.engine.search_text(text, prog) + if not res: + text.bell() + return + text.tag_remove("sel", "1.0", "end") + text.tag_remove("hit", "1.0", "end") + line = res[0] + col = res[1].start() + if self.engine.iswrap(): + line = 1 + col = 0 + ok = 1 + first = last = None + # XXX ought to replace circular instead of top-to-bottom when wrapping + while 1: + res = self.engine.search_forward(text, prog, line, col, 0, ok) + if not res: + break + line, m = res + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + orig = m.group() + new = re.pcre_expand(m, repl) + i, j = m.span() + first = "%d.%d" % (line, i) + last = "%d.%d" % (line, j) + if new == orig: + text.mark_set("insert", last) + else: + text.mark_set("insert", first) + if first != last: + text.delete(first, last) + if new: + text.insert(first, new) + col = i + len(new) + ok = 0 + if first and last: + self.show_hit(first, last) + self.close() + + def do_find(self, ok=0): + if not self.engine.getprog(): + return 0 + text = self.text + res = self.engine.search_text(text, None, ok) + if not res: + text.bell() + return 0 + line, m = res + i, j = m.span() + first = "%d.%d" % (line, i) + last = "%d.%d" % (line, j) + self.show_hit(first, last) + self.ok = 1 + return 1 + + def do_replace(self): + prog = self.engine.getprog() + if not prog: + return 0 + text = self.text + try: + first = pos = text.index("sel.first") + last = text.index("sel.last") + except TclError: + pos = None + if not pos: + first = last = pos = text.index("insert") + line, col = SearchEngine.get_line_col(pos) + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + m = prog.match(chars, col) + if not prog: + return 0 + new = re.pcre_expand(m, self.replvar.get()) + text.mark_set("insert", first) + if m.group(): + text.delete(first, last) + if new: + text.insert(first, new) + self.show_hit(first, text.index("insert")) + self.ok = 0 + return 1 + + def show_hit(self, first, last): + text = self.text + text.mark_set("insert", first) + text.tag_remove("sel", "1.0", "end") + text.tag_add("sel", first, last) + text.tag_remove("hit", "1.0", "end") + if first == last: + text.tag_add("hit", first) + else: + text.tag_add("hit", first, last) + text.see("insert") + text.update_idletasks() + + def close(self, event=None): + SearchDialogBase.close(self, event) + self.text.tag_remove("hit", "1.0", "end") diff --git a/Tools/idle/ScriptBinding.py b/Tools/idle/ScriptBinding.py new file mode 100644 index 0000000..a112fc5 --- /dev/null +++ b/Tools/idle/ScriptBinding.py @@ -0,0 +1,38 @@ +import tkMessageBox +import os +import imp +import sys + +class ScriptBinding: + + def __init__(self, editwin): + self.editwin = editwin + text = editwin.text + text.bind("<<run-module>>", self.run_module) + text.bind("<<run-script>>", self.run_script) + text.bind("<<new-shell>>", self.new_shell) + + def run_module(self, event=None): + filename = self.editwin.io.filename + if not filename: + tkMessageBox.showerror("No file name", + "This window has no file name", + master=self.editwin.text) + return + modname, ext = os.path.splitext(os.path.basename(filename)) + try: + mod = sys.modules[modname] + except KeyError: + mod = imp.new_module(modname) + sys.modules[modname] = mod + source = self.editwin.text.get("1.0", "end") + exec source in mod.__dict__ + + def run_script(self, event=None): + pass + + def new_shell(self, event=None): + import PyShell + # XXX Not enough: each shell takes over stdin/stdout/stderr... + pyshell = PyShell.PyShell(self.editwin.flist) + pyshell.begin() diff --git a/Tools/idle/ScrolledList.py b/Tools/idle/ScrolledList.py index ef2fde4..a5f9a29 100644 --- a/Tools/idle/ScrolledList.py +++ b/Tools/idle/ScrolledList.py @@ -1,7 +1,7 @@ from Tkinter import * class ScrolledList: - + def __init__(self, master, **options): # Create top frame, with scrollbar and listbox self.master = master @@ -18,22 +18,22 @@ class ScrolledList: listbox["yscrollcommand"] = vbar.set # Bind events to the list box listbox.bind("<ButtonRelease-1>", self.click_event) - listbox.bind("<Double-ButtonRelease-1>", self.double_click_event) + listbox.bind("<Double-ButtonRelease-1>", self.double_click_event) listbox.bind("<ButtonPress-3>", self.popup_event) listbox.bind("<Key-Up>", self.up_event) listbox.bind("<Key-Down>", self.down_event) # Set the focus listbox.focus_set() - + def close(self): self.frame.destroy() - + def clear(self): self.listbox.delete(0, "end") - + def append(self, item): self.listbox.insert("end", str(item)) - + def get(self, index): return self.listbox.get(index) @@ -49,9 +49,9 @@ class ScrolledList: self.select(index) self.on_double(index) return "break" - + menu = None - + def popup_event(self, event): if not self.menu: self.make_menu() @@ -65,7 +65,7 @@ class ScrolledList: menu = Menu(self.listbox, tearoff=0) self.menu = menu self.fill_menu() - + def up_event(self, event): index = self.listbox.index("active") if self.listbox.selection_includes(index): @@ -78,7 +78,7 @@ class ScrolledList: self.select(index) self.on_select(index) return "break" - + def down_event(self, event): index = self.listbox.index("active") if self.listbox.selection_includes(index): @@ -91,22 +91,22 @@ class ScrolledList: self.select(index) self.on_select(index) return "break" - + def select(self, index): self.listbox.focus_set() self.listbox.activate(index) self.listbox.selection_clear(0, "end") self.listbox.selection_set(index) self.listbox.see(index) - + # Methods to override for specific actions - + def fill_menu(self): pass - + def on_select(self, index): pass - + def on_double(self, index): pass diff --git a/Tools/idle/SearchBinding.py b/Tools/idle/SearchBinding.py index ccbdc6c..d73db13 100644 --- a/Tools/idle/SearchBinding.py +++ b/Tools/idle/SearchBinding.py @@ -1,89 +1,96 @@ -import string -import re import tkSimpleDialog -import tkMessageBox + +###$ event <<find>> +###$ win <Control-f> +###$ unix <Control-u><Control-u><Control-s> + +###$ event <<find-again>> +###$ win <Control-g> +###$ win <F3> +###$ unix <Control-u><Control-s> + +###$ event <<find-selection>> +###$ win <Control-F3> +###$ unix <Control-s> + +###$ event <<find-in-files>> +###$ win <Alt-F3> + +###$ event <<replace>> +###$ win <Control-h> + +###$ event <<goto-line>> +###$ win <Alt-g> +###$ unix <Alt-g> class SearchBinding: - - def __init__(self, text): - self.text = text - self.pat = "" - self.prog = None - self.text.bind("<<find>>", self.find_event) - self.text.bind("<<find-next>>", self.find_next_event) - self.text.bind("<<find-same>>", self.find_same_event) - self.text.bind("<<goto-line>>", self.goto_line_event) - - def find_event(self, event): - default = self.text.get("self.first", "sel.last") or self.pat - new = tkSimpleDialog.askstring("Find", - "Regular Expression:", - initialvalue=default, - parent=self.text) - if not new: - return "break" - self.pat = new - try: - self.prog = re.compile(self.pat) - except re.error, msg: - tkMessageBox.showerror("RE error", str(msg), - master=self.text) - return "break" - return self.find_next_event(event) - - def find_same_event(self, event): - pat = self.text.get("sel.first", "sel.last") - if not pat: - return self.find_event(event) - self.pat = re.escape(pat) - self.prog = None - try: - self.prog = re.compile(self.pat) - except re.error, msg: - tkMessageBox.showerror("RE error", str(message), - master=self.text) - return "break" - self.text.mark_set("insert", "sel.last") - return self.find_next_event(event) - - def find_next_event(self, event): - if not self.pat: - return self.find_event(event) - if not self.prog: - self.text.bell() - ##print "No program" - return "break" - line, col = map(int, - string.split(self.text.index("insert"), ".")) - chars = self.text.get("%d.0" % line, "%d.0" % (line+1)) - while chars: - m = self.prog.search(chars, col) - if m: - i, j = m.span() - self.text.mark_set("insert", - "%d.%d" % (line, j)) - self.text.tag_remove("sel", "1.0", "end") - self.text.tag_add("sel", - "%d.%d" % (line, i), - "%d.%d" % (line, j)) - self.text.see("insert") - break - line = line + 1 - col = 0 - chars = self.text.get("%d.0" % line, "%d.0" % (line+1)) - else: - # Not found - self.text.bell() - return "break" - - def goto_line_event(self, event): - lineno = tkSimpleDialog.askinteger("Goto", - "Go to line number:", - parent=self.text) - if lineno is None: - return "break" - if lineno <= 0: - self.text.bell() - return "break" - self.text.mark_set("insert", "%d.0" % lineno) - self.text.see("insert") + + windows_keydefs = { + '<<find-again>>': ['<Control-g>', '<F3>'], + '<<find-in-files>>': ['<Alt-F3>'], + '<<find-selection>>': ['<Control-F3>'], + '<<find>>': ['<Control-f>'], + '<<replace>>': ['<Control-h>'], + '<<goto-line>>': ['<Alt-g>'], + } + + unix_keydefs = { + '<<find-again>>': ['<Control-u><Control-s>'], + '<<find-selection>>': ['<Control-s>'], + '<<find>>': ['<Control-u><Control-u><Control-s>'], + '<<goto-line>>': ['<Alt-g>', '<Meta-g>'], + } + + menudefs = [ + ('edit', [ + None, + ('_Find...', '<<find>>'), + ('Find a_gain', '<<find-again>>'), + ('Find _selection', '<<find-selection>>'), + ('Find in Files...', '<<find-in-files>>'), + ('R_eplace...', '<<replace>>'), + ('Go to _line', '<<goto-line>>'), + ]), + ] + + def __init__(self, editwin): + self.editwin = editwin + + def find_event(self, event): + import SearchDialog + SearchDialog.find(self.editwin.text) + return "break" + + def find_again_event(self, event): + import SearchDialog + SearchDialog.find_again(self.editwin.text) + return "break" + + def find_selection_event(self, event): + import SearchDialog + SearchDialog.find_selection(self.editwin.text) + return "break" + + def find_in_files_event(self, event): + import GrepDialog + GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist) + return "break" + + def replace_event(self, event): + import ReplaceDialog + ReplaceDialog.replace(self.editwin.text) + return "break" + + def goto_line_event(self, event): + print event + text = self.editwin.text + lineno = tkSimpleDialog.askinteger("Goto", + "Go to line number:", + parent=text) + if lineno is None: + return "break" + if lineno <= 0: + text.bell() + return "break" + text.mark_set("insert", "%d.0" % lineno) + text.see("insert") diff --git a/Tools/idle/SearchDialog.py b/Tools/idle/SearchDialog.py new file mode 100644 index 0000000..501b6d0 --- /dev/null +++ b/Tools/idle/SearchDialog.py @@ -0,0 +1,59 @@ +from Tkinter import * +import SearchEngine +from SearchDialogBase import SearchDialogBase + + +def _setup(text): + root = text._root() + engine = SearchEngine.get(root) + if not hasattr(engine, "_searchdialog"): + engine._searchdialog = SearchDialog(root, engine) + return engine._searchdialog + +def find(text): + return _setup(text).open(text) + +def find_again(text): + return _setup(text).find_again(text) + +def find_selection(text): + return _setup(text).find_selection(text) + +class SearchDialog(SearchDialogBase): + + def create_widgets(self): + f = SearchDialogBase.create_widgets(self) + self.make_button("Find", self.default_command, 1) + + def default_command(self, event=None): + if not self.engine.getprog(): + return + if self.find_again(self.text): + self.close() + + def find_again(self, text): + if not self.engine.getpat(): + self.open(text) + return 0 + if not self.engine.getprog(): + return 0 + res = self.engine.search_text(text) + if res: + line, m = res + i, j = m.span() + first = "%d.%d" % (line, i) + last = "%d.%d" % (line, j) + text.tag_remove("sel", "1.0", "end") + text.tag_add("sel", first, last) + text.mark_set("insert", self.engine.isback() and first or last) + text.see("insert") + return 1 + else: + text.bell() + return 0 + + def find_selection(self, text): + pat = text.get("sel.first", "sel.last") + if pat: + self.engine.setcookedpat(pat) + return self.find_again(text) diff --git a/Tools/idle/SearchDialogBase.py b/Tools/idle/SearchDialogBase.py new file mode 100644 index 0000000..faf5269 --- /dev/null +++ b/Tools/idle/SearchDialogBase.py @@ -0,0 +1,129 @@ +import string +from Tkinter import * + +class SearchDialogBase: + + title = "Search Dialog" + icon = "Search" + needwrapbutton = 1 + + def __init__(self, root, engine): + self.root = root + self.engine = engine + self.top = None + + def open(self, text): + self.text = text + if not self.top: + self.create_widgets() + else: + self.top.deiconify() + self.top.tkraise() + self.ent.focus_set() + self.ent.selection_range(0, "end") + self.ent.icursor(0) + self.top.grab_set() + + def close(self, event=None): + if self.top: + self.top.grab_release() + self.top.withdraw() + + def create_widgets(self): + top = Toplevel(self.root) + top.bind("<Return>", self.default_command) + top.bind("<Escape>", self.close) + top.protocol("WM_DELETE_WINDOW", self.close) + top.wm_title(self.title) + top.wm_iconname(self.icon) + self.top = top + + self.row = 0 + self.top.grid_columnconfigure(0, weight=0) + self.top.grid_columnconfigure(1, weight=100) + + self.create_entries() + self.create_option_buttons() + self.create_other_buttons() + return self.create_command_buttons() + + def make_entry(self, label, var): + l = Label(self.top, text=label) + l.grid(row=self.row, col=0, sticky="w") + e = Entry(self.top, textvariable=var, exportselection=0) + e.grid(row=self.row, col=1, sticky="we") + self.row = self.row + 1 + return e + + def make_frame(self): + f = Frame(self.top) + f.grid(row=self.row, col=0, columnspan=2, sticky="we") + self.row = self.row + 1 + return f + + def make_button(self, label, command, isdef=0, side="left"): + b = Button(self.buttonframe, + text=label, command=command, + default=isdef and "active" or "normal") + b.pack(side=side) + return b + + def create_entries(self): + self.ent = self.make_entry("Find:", self.engine.patvar) + + def create_option_buttons(self): + f = self.make_frame() + + btn = Checkbutton(f, anchor="w", + variable=self.engine.revar, + text="Regular expression") + btn.pack(side="left", fill="both") + if self.engine.isre(): + btn.select() + + btn = Checkbutton(f, anchor="w", + variable=self.engine.casevar, + text="Match case") + btn.pack(side="left", fill="both") + if self.engine.iscase(): + btn.select() + + btn = Checkbutton(f, anchor="w", + variable=self.engine.wordvar, + text="Whole word") + btn.pack(side="left", fill="both") + if self.engine.isword(): + btn.select() + + if self.needwrapbutton: + btn = Checkbutton(f, anchor="w", + variable=self.engine.wrapvar, + text="Wrap around") + btn.pack(side="left", fill="both") + if self.engine.iswrap(): + btn.select() + + def create_other_buttons(self): + f = self.make_frame() + + lbl = Label(f, text="Direction: ") + lbl.pack(side="left") + + btn = Radiobutton(f, anchor="w", + variable=self.engine.backvar, value=1, + text="Up") + btn.pack(side="left", fill="both") + if self.engine.isback(): + btn.select() + + btn = Radiobutton(f, anchor="w", + variable=self.engine.backvar, value=0, + text="Down") + btn.pack(side="left", fill="both") + if not self.engine.isback(): + btn.select() + + def create_command_buttons(self): + f = self.buttonframe = self.make_frame() + b = self.make_button("close", self.close, side="right") + b.lower() diff --git a/Tools/idle/SearchEngine.py b/Tools/idle/SearchEngine.py new file mode 100644 index 0000000..d9361d0 --- /dev/null +++ b/Tools/idle/SearchEngine.py @@ -0,0 +1,214 @@ +import string +import re +from Tkinter import * +import tkMessageBox + +def get(root): + if not hasattr(root, "_searchengine"): + root._searchengine = SearchEngine(root) + # XXX This will never garbage-collect -- who cares + return root._searchengine + +class SearchEngine: + + def __init__(self, root): + self.root = root + # State shared by search, replace, and grep; + # the search dialogs bind these to UI elements. + self.patvar = StringVar(root) # search pattern + self.revar = BooleanVar(root) # regular expression? + self.casevar = BooleanVar(root) # match case? + self.wordvar = BooleanVar(root) # match whole word? + self.wrapvar = BooleanVar(root) # wrap around buffer? + self.wrapvar.set(1) # (on by default) + self.backvar = BooleanVar(root) # search backwards? + + # Access methods + + def getpat(self): + return self.patvar.get() + + def setpat(self, pat): + self.patvar.set(pat) + + def isre(self): + return self.revar.get() + + def iscase(self): + return self.casevar.get() + + def isword(self): + return self.wordvar.get() + + def iswrap(self): + return self.wrapvar.get() + + def isback(self): + return self.backvar.get() + + # Higher level access methods + + def getcookedpat(self): + pat = self.getpat() + if not self.isre(): + pat = re.escape(pat) + if self.isword(): + pat = r"\b%s\b" % pat + return pat + + def getprog(self): + pat = self.getpat() + if not pat: + self.report_error(pat, "Empty regular expression") + return None + pat = self.getcookedpat() + flags = 0 + if not self.iscase(): + flags = flags | re.IGNORECASE + try: + prog = re.compile(pat, flags) + except re.error, what: + try: + msg, col = what + except: + msg = str(what) + col = -1 + self.report_error(pat, msg, col) + return None + return prog + + def report_error(self, pat, msg, col=-1): + # Derived class could overrid this with something fancier + msg = "Error: " + str(msg) + if pat: + msg = msg + "\np\Pattern: " + str(pat) + if col >= 0: + msg = msg + "\nOffset: " + str(col) + tkMessageBox.showerror("Regular expression error", + msg, master=self.root) + + def setcookedpat(self, pat): + if self.isre(): + pat = re.escape(pat) + self.setpat(pat) + + def search_text(self, text, prog=None, ok=0): + """Search a text widget for the pattern. + + If prog is given, it should be the precompiled pattern. + Return a tuple (lineno, matchobj); None if not found. + + This obeys the wrap and direction (back) settings. + + The search starts at the selection (if there is one) or + at the insert mark (otherwise). If the search is forward, + it starts at the right of the selection; for a backward + search, it starts at the left end. An empty match exactly + at either end of the selection (or at the insert mark if + there is no selection) is ignored unless the ok flag is true + -- this is done to guarantee progress. + + If the search is allowed to wrap around, it will return the + original selection if (and only if) it is the only match. + + XXX When wrapping around and failing to find anything, the + portion of the text after the selection is searched twice :-( + """ + if not prog: + prog = self.getprog() + if not prog: + return None # Compilation failed -- stop + wrap = self.wrapvar.get() + first, last = get_selection(text) + if self.isback(): + if ok: + start = last + else: + start = first + line, col = get_line_col(start) + res = self.search_backward(text, prog, line, col, wrap, ok) + else: + if ok: + start = first + else: + start = last + line, col = get_line_col(start) + res = self.search_forward(text, prog, line, col, wrap, ok) + return res + + def search_forward(self, text, prog, line, col, wrap, ok=0): + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + while chars: + m = prog.search(chars[:-1], col) + if m: + if ok or m.end() > col: + return line, m + line = line + 1 + col = 0 + ok = 1 + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + if not chars and wrap: + wrap = 0 + line = 1 + chars = text.get("1.0", "2.0") + return None + + def search_backward(self, text, prog, line, col, wrap, ok=0): + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + while 1: + m = search_reverse(prog, chars[:-1], col) + if m: + i, j = m.span() + if ok or m.start() < col: + return line, m + line = line - 1 + ok = 1 + if line <= 0: + if not wrap: + break + wrap = 0 + pos = text.index("end-1c") + line, col = map(int, string.split(pos, ".")) + chars = text.get("%d.0" % line, "%d.0" % (line+1)) + col = len(chars) - 1 + return None + +# Helper to search backwards in a string. +# (Optimized for the case where the pattern isn't found.) + +def search_reverse(prog, chars, col): + m = prog.search(chars) + if not m: + return None + found = None + i, j = m.span() + while i < col and j <= col: + found = m + if i == j: + j = j+1 + m = prog.search(chars, j) + if not m: + break + i, j = m.span() + return found + +# Helper to get selection end points, defaulting to insert mark. +# Return a tuple of indices ("line.col" strings). + +def get_selection(text): + try: + first = text.index("sel.first") + last = text.index("sel.last") + except TclError: + first = last = None + if not first: + first = text.index("insert") + if not last: + last = first + return first, last + +# Helper to parse a text index into a (line, col) tuple. + +def get_line_col(index): + line, col = map(int, string.split(index, ".")) # Fails on invalid index + return line, col diff --git a/Tools/idle/StackViewer.py b/Tools/idle/StackViewer.py index 688c1b4..93923f9 100644 --- a/Tools/idle/StackViewer.py +++ b/Tools/idle/StackViewer.py @@ -4,16 +4,18 @@ import os from Tkinter import * import linecache from repr import Repr +from WindowList import ListedToplevel from ScrolledList import ScrolledList class StackBrowser: - + def __init__(self, root, flist, stack=None): - self.top = top = Toplevel(root) + self.top = top = ListedToplevel(root) top.protocol("WM_DELETE_WINDOW", self.close) top.wm_title("Stack viewer") + top.wm_iconname("Stack") # Create help label self.helplabel = Label(top, text="Click once to view variables; twice for source", @@ -24,7 +26,7 @@ class StackBrowser: if stack is None: stack = get_stack() self.sv.load_stack(stack) - + def close(self): self.top.destroy() @@ -44,7 +46,7 @@ class StackBrowser: self.show_globals(frame) self.show_locals(frame) self.curframe = frame - + def show_globals(self, frame): title = "Global Variables" if frame.f_globals.has_key("__name__"): @@ -66,7 +68,7 @@ class StackBrowser: title, self.globalsdict) self.globalsframe.pack(fill="both", side="bottom") - + def show_locals(self, frame): self.localsdict = None if self.localsviewer: @@ -92,7 +94,7 @@ class StackBrowser: class StackViewer(ScrolledList): - + def __init__(self, master, flist, browser): ScrolledList.__init__(self, master) self.flist = flist @@ -149,7 +151,7 @@ class StackViewer(ScrolledList): def show_stack_frame(self): index = self.listbox.index("active") self.browser.show_frame(self.stack[index]) - + def show_source(self, index): frame, lineno = self.stack[index] code = frame.f_code @@ -169,7 +171,7 @@ def get_stack(t=None, f=None): while f is not None: stack.append((f, f.f_lineno)) if f is self.botframe: - break + break f = f.f_back stack.reverse() while t is not None: @@ -191,7 +193,7 @@ def getexception(type=None, value=None): class NamespaceViewer: - + def __init__(self, master, title, dict=None): width = 0 height = 40 @@ -217,9 +219,9 @@ class NamespaceViewer: self.subframe = subframe = Frame(canvas) self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw") self.load_dict(dict) - + dict = -1 - + def load_dict(self, dict, force=0): if dict is self.dict and not force: return diff --git a/Tools/idle/UndoDelegator.py b/Tools/idle/UndoDelegator.py index ee49651..39c8e63 100644 --- a/Tools/idle/UndoDelegator.py +++ b/Tools/idle/UndoDelegator.py @@ -3,6 +3,18 @@ import string from Tkinter import * from Delegator import Delegator +#$ event <<redo>> +#$ win <Control-y> +#$ unix <Alt-z> + +#$ event <<undo>> +#$ win <Control-z> +#$ unix <Control-z> + +#$ event <<dump-undo-state>> +#$ win <Control-backslash> +#$ unix <Control-backslash> + class UndoDelegator(Delegator): @@ -11,7 +23,7 @@ class UndoDelegator(Delegator): def __init__(self): Delegator.__init__(self) self.reset_undo() - + def setdelegate(self, delegate): if self.delegate is not None: self.unbind("<<undo>>") diff --git a/Tools/idle/WindowList.py b/Tools/idle/WindowList.py new file mode 100644 index 0000000..b9b0bb1 --- /dev/null +++ b/Tools/idle/WindowList.py @@ -0,0 +1,53 @@ +from Tkinter import * + +class WindowList: + + def __init__(self): + self.dict = {} + + def add(self, window): + self.dict[str(window)] = window + + def delete(self, window): + try: + del self.dict[str(window)] + except KeyError: + # Sometimes, destroy() is called twice + pass + + def add_windows_to_menu(self, menu): + list = [] + for key in self.dict.keys(): + window = self.dict[key] + title = window.get_title() + list.append((title, window)) + list.sort() + for title, window in list: + if title == "Python Shell": + # Hack -- until we have a better way to this + continue + menu.add_command(label=title, command=window.wakeup) + +registry = WindowList() + +def add_windows_to_menu(menu): + registry.add_windows_to_menu(menu) + +class ListedToplevel(Toplevel): + + def __init__(self, master, **kw): + Toplevel.__init__(self, master, kw) + registry.add(self) + + def destroy(self): + registry.delete(self) + Toplevel.destroy(self) + + def get_title(self): + # Subclass can override + return self.wm_title() + + def wakeup(self): + self.tkraise() + self.wm_deiconify() + self.focus_set() diff --git a/Tools/idle/ZoomHeight.py b/Tools/idle/ZoomHeight.py new file mode 100644 index 0000000..eee901c --- /dev/null +++ b/Tools/idle/ZoomHeight.py @@ -0,0 +1,35 @@ +# Sample extension: zoom a window to maximum height + +import re + +class ZoomHeight: + + menudefs = [ + ('windows', [ + ('_Zoom Height', '<<zoom-height>>'), + ]) + ] + + windows_keydefs = { + '<<zoom-height>>': ['<Alt-F2>'], + } + unix_keydefs = { + '<<zoom-height>>': ['<Control-z><Control-z>'], + } + + def __init__(self, editwin): + self.editwin = editwin + + def zoom_height_event(self, event): + top = self.editwin.top + geom = top.wm_geometry() + m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom) + if not m: + top.bell() + return + width, height, x, y = map(int, m.groups()) + height = top.winfo_screenheight() - 72 + newgeom = "%dx%d+%d+%d" % (width, height, x, 0) + if geom == newgeom: + newgeom = "" + top.wm_geometry(newgeom) diff --git a/Tools/idle/eventparse.py b/Tools/idle/eventparse.py new file mode 100644 index 0000000..cb2028d --- /dev/null +++ b/Tools/idle/eventparse.py @@ -0,0 +1,93 @@ +#! /usr/bin/env python + +"""Parse event definitions out of comments in source files.""" + +import re +import sys +import os +import string +import getopt +import glob +import fileinput +import pprint + +def main(): + hits = [] + sublist = [] + args = sys.argv[1:] + if not args: + args = filter(lambda s: 'A' <= s[0] <= 'Z', glob.glob("*.py")) + if not args: + print "No arguments, no [A-Z]*.py files." + return 1 + for line in fileinput.input(args): + if line[:2] == '#$': + if not sublist: + sublist.append('file %s' % fileinput.filename()) + sublist.append('line %d' % fileinput.lineno()) + sublist.append(string.strip(line[2:-1])) + else: + if sublist: + hits.append(sublist) + sublist = [] + if sublist: + hits.append(sublist) + sublist = [] + dd = {} + for sublist in hits: + d = {} + for line in sublist: + words = string.split(line, None, 1) + if len(words) != 2: + continue + tag = words[0] + l = d.get(tag, []) + l.append(words[1]) + d[tag] = l + if d.has_key('event'): + keys = d['event'] + if len(keys) != 1: + print "Multiple event keys in", d + print 'File "%s", line %d' % (d['file'], d['line']) + key = keys[0] + if dd.has_key(key): + print "Duplicate event in", d + print 'File "%s", line %d' % (d['file'], d['line']) + return + dd[key] = d + else: + print "No event key in", d + print 'File "%s", line %d' % (d['file'], d['line']) + winevents = getevents(dd, "win") + unixevents = getevents(dd, "unix") + save = sys.stdout + f = open("keydefs.py", "w") + try: + sys.stdout = f + print "windows_keydefs = \\" + pprint.pprint(winevents) + print + print "unix_keydefs = \\" + pprint.pprint(unixevents) + finally: + sys.stdout = save + f.close() + +def getevents(dd, key): + res = {} + events = dd.keys() + events.sort() + for e in events: + d = dd[e] + if d.has_key(key) or d.has_key("all"): + list = [] + for x in d.get(key, []) + d.get("all", []): + list.append(x) + if key == "unix" and x[:5] == "<Alt-": + x = "<Meta-" + x[5:] + list.append(x) + res[e] = list + return res + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Tools/idle/extend.py b/Tools/idle/extend.py new file mode 100644 index 0000000..7d8117d --- /dev/null +++ b/Tools/idle/extend.py @@ -0,0 +1,9 @@ +# IDLE extensions to be loaded by default (see extend.txt). +# Edit this file to configure your set of IDLE extensions. + +standard = [ + "SearchBinding", + "AutoIndent", + "AutoExpand", + "ZoomHeight", +] diff --git a/Tools/idle/extend.txt b/Tools/idle/extend.txt new file mode 100644 index 0000000..83b6428 --- /dev/null +++ b/Tools/idle/extend.txt @@ -0,0 +1,105 @@ +Writing an IDLE extension + +An IDLE extension can define new key bindings and menu entries for +IDLE edit windows. There is a simple mechanism to load extensions +when IDLE starts up and to attach them to each edit window. +(It is also possible to make other changes to IDLE, but this must +be done by editing the IDLE source code.) + +The list of extensions loaded at startup time is configured by editing +the file extend.py; see below for details. + +An IDLE extension is defined by a class. Methods of the class define +actions that are invoked by those bindings or menu entries. +Class (or instance) variables define the bindings and menu additions; +these are automatically applied by IDLE when the extension is linked +to an edit window. + +An IDLE extension class is instantiated with a single argument, +`editwin', an EditorWindow instance. +The extension cannot assume much about this argument, but it +is guarateed to have the following instance variables: + + text a Text instance (a widget) + io an IOBinding instance (more about this later) + flist the FileList instance (shared by all edit windows) + +(There are a few more, but they are rarely useful.) + +The extension class must not bind key events. Rather, it must define +one or more virtual events, e.g. <<zoom-height>>, and corresponding +methods, e.g. zoom_height(), and have one or more class (or instance) +variables that define mappings between virtual events and key sequences, +e.g. <Alt-F2>. When the extension is loaded, these key sequences will +be bound to the corresponding virtual events, and the virtual events +will be bound to the corresponding methods. (This indirection is done +so that the key bindings can easily be changed, and so that other sources +of virtual events can exist, such as menu entries.) + +The following class or instance variables are used to define key +bindings for virtual events: + + keydefs for all platforms + mac_keydefs for Macintosh + windows_keydefs for Windows + unix_keydefs for Unix (and other platforms) + +Each of these variables, if it exists, must be a dictionary whose +keys are virtual events, and whose values are lists of key sequences. + +An extension can define menu entries in a similar fashion. This is done +with a class or instance variable named menudefs; it should be a list of +pair, where each pair is a menu name (lowercase) and a list of menu entries. +Each menu entry is either None (to insert a separator entry) or a pair of +strings (menu_label, virtual_event). Here, menu_label is the label of the +menu entry, and virtual_event is the virtual event to be generated when the +entry is selected. An underscore in the menu label is removed; the +character following the underscore is displayed underlined, to indicate the +shortcut character (for Windows). + +At the moment, extensions cannot define whole new menus; they must define +entries in existing menus. Some menus are not present on some windows; +such entry definitions are then ignored, but the key bindings are still +applied. (This should probably be refined in the future.) + +Here is a complete example example: + +class ZoomHeight: + + menudefs = [ + ('edit', [ + None, # Separator + ('_Zoom Height', '<<zoom-height>>'), + ]) + ] + + windows_keydefs = { + '<<zoom-height>>': ['<Alt-F2>'], + } + unix_keydefs = { + '<<zoom-height>>': ['<Control-z><Control-z>'], + } + + def __init__(self, editwin): + self.editwin = editwin + + def zoom_height(self, event): + "...Do what you want here..." + +The final piece of the puzzle is the file "extend.py", which contains a +simple table used to configure the loading of extensions. This file currently +contains a single list variable named "standard", which is a list of extension +names that are to be loaded. (In the future, other configuration variables +may be added to this module.) + +Extensions can define key bindings and menu entries that reference events they +don't implement (including standard events); however this is not recommended +(and may be forbidden in the future). + +Extensions are not required to define menu entries for all events +they implement. + +Note: in order to change key bindings, you must currently edit the file +keydefs. It contains two dictionaries named and formatted like the +keydefs dictionaries described above, one for the Unix bindings and one for +the Windows bindings. In the future, a better mechanism will be provided. diff --git a/Tools/idle/help.txt b/Tools/idle/help.txt index beafc42..5307fa6 100644 --- a/Tools/idle/help.txt +++ b/Tools/idle/help.txt @@ -1,3 +1,5 @@ +[See end for tips.] + File menu: New window -- create a new editing window @@ -75,9 +77,19 @@ Python syntax colors: the coloring is applied in a background thread Comments red Definitions blue -Console colors: +Shell colors: - Console output red + Console output dark red stdout blue stderr dark green - stdin purple + stdin black + +Tips: + To change the font on Windows, open EditorWindow.py and change + text['font'] = ("verdana", 8) + to, e.g., + text['font'] = ("courier new", 10) + + To change the Python syntax colors, edit the tagdefs table + in ColorDelegator.py; to change the shell colors, edit the + tagdefs table in PyShell.py. diff --git a/Tools/idle/idle.bat b/Tools/idle/idle.bat new file mode 100644 index 0000000..a416001 --- /dev/null +++ b/Tools/idle/idle.bat @@ -0,0 +1,3 @@ +rem idle.bat + +"C:\Program Files\Python\python.exe" "idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9 diff --git a/Tools/idle/idle.pyw b/Tools/idle/idle.pyw index 3c06e05..a1fc021 100644 --- a/Tools/idle/idle.pyw +++ b/Tools/idle/idle.pyw @@ -1,3 +1,9 @@ -#! /usr/bin/env python -import PyShell -PyShell.main() +try: + import PyShell + PyShell.main() +except SystemExit: + raise +except: + import traceback + traceback.print_exc() + raw_input("Hit return to exit...") diff --git a/Tools/idle/idlever.py b/Tools/idle/idlever.py new file mode 100644 index 0000000..7465590 --- /dev/null +++ b/Tools/idle/idlever.py @@ -0,0 +1 @@ +IDLE_VERSION = "0.2" diff --git a/Tools/idle/keydefs.py b/Tools/idle/keydefs.py new file mode 100644 index 0000000..1e94904 --- /dev/null +++ b/Tools/idle/keydefs.py @@ -0,0 +1,59 @@ +windows_keydefs = \ +{'<<Copy>>': ['<Control-c>'], + '<<Cut>>': ['<Control-x>'], + '<<Paste>>': ['<Control-v>'], + '<<beginning-of-line>>': ['<Control-a>', '<Home>'], + '<<center-insert>>': ['<Control-l>'], + '<<close-all-windows>>': ['<Control-q>'], + '<<close-window>>': ['<Alt-F4>'], + '<<dump-undo-state>>': ['<Control-backslash>'], + '<<end-of-file>>': ['<Control-d>'], + '<<expand-word>>': ['<Alt-slash>'], + '<<help>>': ['<F1>'], + '<<history-next>>': ['<Alt-n>'], + '<<history-previous>>': ['<Alt-p>'], + '<<interrupt-execution>>': ['<Control-c>'], + '<<open-class-browser>>': ['<Alt-c>'], + '<<open-module>>': ['<Alt-m>'], + '<<open-new-window>>': ['<Control-n>'], + '<<open-window-from-file>>': ['<Control-o>'], + '<<plain-newline-and-indent>>': ['<Control-j>'], + '<<redo>>': ['<Control-y>'], + '<<remove-selection>>': ['<Escape>'], + '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'], + '<<save-window-as-file>>': ['<Alt-s>'], + '<<save-window>>': ['<Control-s>'], + '<<select-all>>': ['<Alt-a>'], + '<<toggle-auto-coloring>>': ['<Control-slash>'], + '<<undo>>': ['<Control-z>'], +} + +unix_keydefs = \ +{'<<Copy>>': ['<Alt-w>', '<Meta-w>'], + '<<Cut>>': ['<Control-w>'], + '<<Paste>>': ['<Control-y>'], + '<<beginning-of-line>>': ['<Control-a>', '<Home>'], + '<<center-insert>>': ['<Control-l>'], + '<<close-all-windows>>': ['<Control-x><Control-c>'], + '<<close-window>>': ['<Control-x><Control-0>', '<Control-x><Key-0>'], + '<<do-nothing>>': ['<Control-x>'], + '<<dump-undo-state>>': ['<Control-backslash>'], + '<<end-of-file>>': ['<Control-d>'], + '<<expand-word>>': ['<Alt-slash>', '<Meta-slash>'], + '<<help>>': ['<F1>'], + '<<history-next>>': ['<Alt-n>', '<Meta-n>'], + '<<history-previous>>': ['<Alt-p>', '<Meta-p>'], + '<<interrupt-execution>>': ['<Control-c>'], + '<<open-class-browser>>': ['<Control-x><Control-b>'], + '<<open-module>>': ['<Control-x><Control-m>'], + '<<open-new-window>>': ['<Control-x><Control-n>'], + '<<open-window-from-file>>': ['<Control-x><Control-f>'], + '<<plain-newline-and-indent>>': ['<Control-j>'], + '<<redo>>': ['<Alt-z>', '<Meta-z>'], + '<<save-copy-of-window-as-file>>': ['<Control-x><w>'], + '<<save-window-as-file>>': ['<Control-x><Control-w>'], + '<<save-window>>': ['<Control-x><Control-s>'], + '<<select-all>>': ['<Alt-a>', '<Meta-a>'], + '<<toggle-auto-coloring>>': ['<Control-slash>'], + '<<undo>>': ['<Control-z>'], +} |