Commit febd5f8e authored by Guido van Rossum's avatar Guido van Rossum

Checking in IDLE 0.2.

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

This is exactly as downloaded from my laptop after returning
from the holidays -- it hasn't even been tested on Unix yet.
parent b2eec350
import string import string
import re import re
###$ event <<expand-word>>
###$ win <Alt-slash>
###$ unix <Alt-slash>
class AutoExpand: class AutoExpand:
keydefs = {
'<<expand-word>>': ['<Alt-slash>'],
}
menudefs = [
('edit', [
('E_xpand word', '<<expand-word>>'),
]),
]
wordchars = string.letters + string.digits + "_" wordchars = string.letters + string.digits + "_"
def __init__(self, text): def __init__(self, editwin):
self.text = text self.text = editwin.text
self.text.wordlist = None self.text.wordlist = None # XXX what is this?
self.state = None self.state = None
self.text.bind("<<expand-word>>", self.autoexpand)
def expand_word_event(self, event):
def autoexpand(self, event):
curinsert = self.text.index("insert") curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend") curline = self.text.get("insert linestart", "insert lineend")
if not self.state: if not self.state:
...@@ -36,7 +49,7 @@ class AutoExpand: ...@@ -36,7 +49,7 @@ class AutoExpand:
curline = self.text.get("insert linestart", "insert lineend") curline = self.text.get("insert linestart", "insert lineend")
self.state = words, index, curinsert, curline self.state = words, index, curinsert, curline
return "break" return "break"
def getwords(self): def getwords(self):
word = self.getprevword() word = self.getprevword()
if not word: if not word:
...@@ -66,7 +79,7 @@ class AutoExpand: ...@@ -66,7 +79,7 @@ class AutoExpand:
dict[w] = w dict[w] = w
words.append(word) words.append(word)
return words return words
def getprevword(self): def getprevword(self):
line = self.text.get("insert linestart", "insert") line = self.text.get("insert linestart", "insert")
i = len(line) i = len(line)
......
import string 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: class AutoIndent:
def __init__(self, text, prefertabs=0, spaceindent=4*" "): menudefs = [
self.text = text ('edit', [
self.prefertabs = prefertabs None,
self.spaceindent = spaceindent ('_Indent region', '<<indent-region>>'),
text.bind("<<newline-and-indent>>", self.autoindent) ('_Dedent region', '<<dedent-region>>'),
text.bind("<<indent-region>>", self.indentregion) ('Comment _out region', '<<comment-region>>'),
text.bind("<<dedent-region>>", self.dedentregion) ('U_ncomment region', '<<uncomment-region>>'),
text.bind("<<comment-region>>", self.commentregion) ('Tabify region', '<<tabify-region>>'),
text.bind("<<uncomment-region>>", self.uncommentregion) ('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): def config(self, **options):
for key, value in options.items(): for key, value in options.items():
...@@ -21,8 +86,16 @@ class AutoIndent: ...@@ -21,8 +86,16 @@ class AutoIndent:
else: else:
raise KeyError, "bad option name: %s" % `key` raise KeyError, "bad option name: %s" % `key`
def autoindent(self, event): def newline_and_indent_event(self, event):
text = self.text 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") line = text.get("insert linestart", "insert")
i, n = 0, len(line) i, n = 0, len(line)
while i < n and line[i] in " \t": while i < n and line[i] in " \t":
...@@ -43,8 +116,10 @@ class AutoIndent: ...@@ -43,8 +116,10 @@ class AutoIndent:
text.see("insert") text.see("insert")
return "break" return "break"
def indentregion(self, event): auto_indent = newline_and_indent_event
head, tail, chars, lines = self.getregion()
def indent_region_event(self, event):
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)): for pos in range(len(lines)):
line = lines[pos] line = lines[pos]
if line: if line:
...@@ -53,11 +128,11 @@ class AutoIndent: ...@@ -53,11 +128,11 @@ class AutoIndent:
i = i+1 i = i+1
line = line[:i] + " " + line[i:] line = line[:i] + " " + line[i:]
lines[pos] = line lines[pos] = line
self.setregion(head, tail, chars, lines) self.set_region(head, tail, chars, lines)
return "break" return "break"
def dedentregion(self, event): def dedent_region_event(self, event):
head, tail, chars, lines = self.getregion() head, tail, chars, lines = self.get_region()
for pos in range(len(lines)): for pos in range(len(lines)):
line = lines[pos] line = lines[pos]
if line: if line:
...@@ -75,20 +150,20 @@ class AutoIndent: ...@@ -75,20 +150,20 @@ class AutoIndent:
indent = indent[:-4] indent = indent[:-4]
line = indent + line line = indent + line
lines[pos] = line lines[pos] = line
self.setregion(head, tail, chars, lines) self.set_region(head, tail, chars, lines)
return "break" return "break"
def commentregion(self, event): def comment_region_event(self, event):
head, tail, chars, lines = self.getregion() head, tail, chars, lines = self.get_region()
for pos in range(len(lines)): for pos in range(len(lines)):
line = lines[pos] line = lines[pos]
if not line: if not line:
continue continue
lines[pos] = '##' + line lines[pos] = '##' + line
self.setregion(head, tail, chars, lines) self.set_region(head, tail, chars, lines)
def uncommentregion(self, event): def uncomment_region_event(self, event):
head, tail, chars, lines = self.getregion() head, tail, chars, lines = self.get_region()
for pos in range(len(lines)): for pos in range(len(lines)):
line = lines[pos] line = lines[pos]
if not line: if not line:
...@@ -98,9 +173,19 @@ class AutoIndent: ...@@ -98,9 +173,19 @@ class AutoIndent:
elif line[:1] == '#': elif line[:1] == '#':
line = line[1:] line = line[1:]
lines[pos] = line 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 text = self.text
head = text.index("sel.first linestart") head = text.index("sel.first linestart")
tail = text.index("sel.last -1c lineend +1c") tail = text.index("sel.last -1c lineend +1c")
...@@ -111,7 +196,7 @@ class AutoIndent: ...@@ -111,7 +196,7 @@ class AutoIndent:
lines = string.split(chars, "\n") lines = string.split(chars, "\n")
return head, tail, chars, lines return head, tail, chars, lines
def setregion(self, head, tail, chars, lines): def set_region(self, head, tail, chars, lines):
text = self.text text = self.text
newchars = string.join(lines, "\n") newchars = string.join(lines, "\n")
if newchars == chars: if newchars == chars:
...@@ -122,3 +207,12 @@ class AutoIndent: ...@@ -122,3 +207,12 @@ class AutoIndent:
text.delete(head, tail) text.delete(head, tail)
text.insert(head, newchars) text.insert(head, newchars)
text.tag_add("sel", head, "insert") 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:]
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import sys import sys
import string import string
import re import re
from keydefs import *
menudefs = [ menudefs = [
# underscore prefixes character to underscore # underscore prefixes character to underscore
...@@ -15,7 +16,8 @@ menudefs = [ ...@@ -15,7 +16,8 @@ menudefs = [
('_New window', '<<open-new-window>>'), ('_New window', '<<open-new-window>>'),
('_Open...', '<<open-window-from-file>>'), ('_Open...', '<<open-window-from-file>>'),
('Open _module...', '<<open-module>>'), ('Open _module...', '<<open-module>>'),
('Class _browser...', '<<open-class-browser>>'), ('Class _browser', '<<open-class-browser>>'),
('Python shell', '<<open-python-shell>>'),
None, None,
('_Save', '<<save-window>>'), ('_Save', '<<save-window>>'),
('Save _As...', '<<save-window-as-file>>'), ('Save _As...', '<<save-window-as-file>>'),
...@@ -31,19 +33,15 @@ menudefs = [ ...@@ -31,19 +33,15 @@ menudefs = [
('Cu_t', '<<Cut>>'), ('Cu_t', '<<Cut>>'),
('_Copy', '<<Copy>>'), ('_Copy', '<<Copy>>'),
('_Paste', '<<Paste>>'), ('_Paste', '<<Paste>>'),
None, ('Select _All', '<<select-all>>'),
('_Find...', '<<find>>'), ]),
('Find _next', '<<find-next>>'), ('script', [
('Find _same', '<<find-same>>'), ('Run module', '<<run-module>>'),
('_Go to line', '<<goto-line>>'), ('Run script', '<<run-script>>'),
None, ('New shell', '<<new-shell>>'),
('_Dedent region', '<<dedent-region>>'),
('_Indent region', '<<indent-region>>'),
('Comment _out region', '<<comment-region>>'),
('U_ncomment region', '<<uncomment-region>>'),
]), ]),
('debug', [ ('debug', [
('_Go to line from traceback', '<<goto-traceback-line>>'), ('_Go to file/line', '<<goto-file-line>>'),
('_Open stack viewer', '<<open-stack-viewer>>'), ('_Open stack viewer', '<<open-stack-viewer>>'),
('_Debugger toggle', '<<toggle-debugger>>'), ('_Debugger toggle', '<<toggle-debugger>>'),
]), ]),
...@@ -54,81 +52,6 @@ menudefs = [ ...@@ -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): def prepstr(s):
# Helper to extract the underscore from a string, # Helper to extract the underscore from a string,
# e.g. prepstr("Co_py") returns (2, "Copy"). # e.g. prepstr("Co_py") returns (2, "Copy").
...@@ -140,18 +63,14 @@ def prepstr(s): ...@@ -140,18 +63,14 @@ def prepstr(s):
keynames = { keynames = {
'bracketleft': '[', 'bracketleft': '[',
'bracketright': ']', 'bracketright': ']',
'slash': '/',
} }
def getaccelerator(keydefs, event): def get_accelerator(keydefs, event):
keylist = keydefs.get(event) keylist = keydefs.get(event)
if not keylist: if not keylist:
return "" return ""
s = keylist[0] 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"-[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(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
s = re.sub("Key-", "", s) s = re.sub("Key-", "", s)
...@@ -165,7 +84,7 @@ def getaccelerator(keydefs, event): ...@@ -165,7 +84,7 @@ def getaccelerator(keydefs, event):
if sys.platform == 'win32': if sys.platform == 'win32':
default_keydefs = windows_keydefs default_keydefs = windows_keydefs
else: else:
default_keydefs = emacs_keydefs default_keydefs = unix_keydefs
def apply_bindings(text, keydefs=default_keydefs): def apply_bindings(text, keydefs=default_keydefs):
text.keydefs = keydefs text.keydefs = keydefs
...@@ -173,14 +92,10 @@ def apply_bindings(text, keydefs=default_keydefs): ...@@ -173,14 +92,10 @@ def apply_bindings(text, keydefs=default_keydefs):
if keylist: if keylist:
apply(text.event_add, (event,) + tuple(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 # Fill the menus for the given text widget. The menudict argument is
# a dictionary containing the menus, keyed by their lowercased name. # a dictionary containing the menus, keyed by their lowercased name.
# Menus that are absent or None are ignored. # Menus that are absent or None are ignored.
if hasattr(text, "keydefs"):
keydefs = text.keydefs
else:
keydefs = default_keydefs
for mname, itemlist in defs: for mname, itemlist in defs:
menu = menudict.get(mname) menu = menudict.get(mname)
if not menu: if not menu:
...@@ -191,7 +106,7 @@ def fill_menus(text, menudict, defs=menudefs): ...@@ -191,7 +106,7 @@ def fill_menus(text, menudict, defs=menudefs):
else: else:
label, event = item label, event = item
underline, label = prepstr(label) underline, label = prepstr(label)
accelerator = getaccelerator(keydefs, event) accelerator = get_accelerator(keydefs, event)
def command(text=text, event=event): def command(text=text, event=event):
text.event_generate(event) text.event_generate(event)
menu.add_command(label=label, underline=underline, menu.add_command(label=label, underline=underline,
......
"""Primitive class browser. """Primitive class browser.
XXX TO DO: XXX TO DO:
- generalize the scrolling listbox with some behavior into a base class - generalize the scrolling listbox with some behavior into a base class
- add popup menu with more options (e.g. doc strings, base classes, imports) - add popup menu with more options (e.g. doc strings, base classes, imports)
- show function argument list (have to do pattern matching on source) - show function argument list (have to do pattern matching on source)
...@@ -14,12 +14,13 @@ import string ...@@ -14,12 +14,13 @@ import string
import pyclbr import pyclbr
from Tkinter import * from Tkinter import *
import tkMessageBox import tkMessageBox
from WindowList import ListedToplevel
from ScrolledList import ScrolledList from ScrolledList import ScrolledList
class ClassBrowser: class ClassBrowser:
def __init__(self, flist, name, path=[]): def __init__(self, flist, name, path=[]):
root = flist.root root = flist.root
try: try:
...@@ -34,9 +35,10 @@ class ClassBrowser: ...@@ -34,9 +35,10 @@ class ClassBrowser:
self.flist = flist self.flist = flist
self.dict = dict self.dict = dict
self.root = root self.root = root
self.top = top = Toplevel(root) self.top = top = ListedToplevel(root)
self.top.protocol("WM_DELETE_WINDOW", self.close) 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 = leftframe = Frame(top)
self.leftframe.pack(side="left", fill="both", expand=1) self.leftframe.pack(side="left", fill="both", expand=1)
# Create help label # Create help label
...@@ -48,12 +50,12 @@ class ClassBrowser: ...@@ -48,12 +50,12 @@ class ClassBrowser:
self.leftframe, self.flist, self) self.leftframe, self.flist, self)
# Load the classes # Load the classes
self.load_classes(dict, name) self.load_classes(dict, name)
def close(self): def close(self):
self.classviewer = None self.classviewer = None
self.methodviewer = None self.methodviewer = None
self.top.destroy() self.top.destroy()
def load_classes(self, dict, module): def load_classes(self, dict, module):
self.classviewer.load_classes(dict, module) self.classviewer.load_classes(dict, module)
if self.botframe: if self.botframe:
...@@ -64,7 +66,7 @@ class ClassBrowser: ...@@ -64,7 +66,7 @@ class ClassBrowser:
botframe = None botframe = None
methodhelplabel = None methodhelplabel = None
methodviewer = None methodviewer = None
def show_methods(self, cl): def show_methods(self, cl):
if not self.botframe: if not self.botframe:
self.botframe = Frame(self.top) self.botframe = Frame(self.top)
...@@ -78,12 +80,12 @@ class ClassBrowser: ...@@ -78,12 +80,12 @@ class ClassBrowser:
class ClassViewer(ScrolledList): class ClassViewer(ScrolledList):
def __init__(self, master, flist, browser): def __init__(self, master, flist, browser):
ScrolledList.__init__(self, master) ScrolledList.__init__(self, master)
self.flist = flist self.flist = flist
self.browser = browser self.browser = browser
def load_classes(self, dict, module): def load_classes(self, dict, module):
self.clear() self.clear()
self.dict = dict self.dict = dict
...@@ -103,7 +105,7 @@ class ClassViewer(ScrolledList): ...@@ -103,7 +105,7 @@ class ClassViewer(ScrolledList):
super.append(name) super.append(name)
s = s + "(%s)" % string.join(super, ", ") s = s + "(%s)" % string.join(super, ", ")
self.append(s) self.append(s)
def getname(self, index): def getname(self, index):
name = self.listbox.get(index) name = self.listbox.get(index)
i = string.find(name, '(') i = string.find(name, '(')
...@@ -113,13 +115,13 @@ class ClassViewer(ScrolledList): ...@@ -113,13 +115,13 @@ class ClassViewer(ScrolledList):
def getclass(self, index): def getclass(self, index):
return self.dict[self.getname(index)] return self.dict[self.getname(index)]
def on_select(self, index): def on_select(self, index):
self.show_methods(index) self.show_methods(index)
def on_double(self, index): def on_double(self, index):
self.show_source(index) self.show_source(index)
def show_methods(self, index): def show_methods(self, index):
cl = self.getclass(index) cl = self.getclass(index)
self.browser.show_methods(cl) self.browser.show_methods(cl)
...@@ -132,13 +134,13 @@ class ClassViewer(ScrolledList): ...@@ -132,13 +134,13 @@ class ClassViewer(ScrolledList):
class MethodViewer(ScrolledList): class MethodViewer(ScrolledList):
def __init__(self, master, flist): def __init__(self, master, flist):
ScrolledList.__init__(self, master) ScrolledList.__init__(self, master)
self.flist = flist self.flist = flist
classinfo = None classinfo = None
def load_methods(self, cl): def load_methods(self, cl):
self.classinfo = cl self.classinfo = cl
self.clear() self.clear()
...@@ -151,10 +153,10 @@ class MethodViewer(ScrolledList): ...@@ -151,10 +153,10 @@ class MethodViewer(ScrolledList):
def click_event(self, event): def click_event(self, event):
pass pass
def on_double(self, index): def on_double(self, index):
self.show_source(self.get(index)) self.show_source(self.get(index))
def show_source(self, name): def show_source(self, name):
if os.path.isfile(self.classinfo.file): if os.path.isfile(self.classinfo.file):
edit = self.flist.open(self.classinfo.file) edit = self.flist.open(self.classinfo.file)
......
...@@ -5,6 +5,10 @@ import keyword ...@@ -5,6 +5,10 @@ import keyword
from Tkinter import * from Tkinter import *
from Delegator import Delegator from Delegator import Delegator
#$ event <<toggle-auto-coloring>>
#$ win <Control-slash>
#$ unix <Control-slash>
__debug__ = 0 __debug__ = 0
...@@ -29,10 +33,10 @@ class ColorDelegator(Delegator): ...@@ -29,10 +33,10 @@ class ColorDelegator(Delegator):
def __init__(self): def __init__(self):
Delegator.__init__(self) Delegator.__init__(self)
self.prog = prog self.prog = prog
self.idprog = idprog self.idprog = idprog
def setdelegate(self, delegate): def setdelegate(self, delegate):
if self.delegate is not None: if self.delegate is not None:
self.unbind("<<toggle-auto-coloring>>") self.unbind("<<toggle-auto-coloring>>")
Delegator.setdelegate(self, delegate) Delegator.setdelegate(self, delegate)
if delegate is not None: if delegate is not None:
...@@ -54,8 +58,11 @@ class ColorDelegator(Delegator): ...@@ -54,8 +58,11 @@ class ColorDelegator(Delegator):
"SYNC": {}, #{"background": "#ffff00"}, "SYNC": {}, #{"background": "#ffff00"},
"TODO": {}, #{"background": "#cccccc"}, "TODO": {}, #{"background": "#cccccc"},
"BREAK": {"background": "#FF7777"}, "BREAK": {"background": "#FF7777"},
# The following is used by ReplaceDialog:
"hit": {"foreground": "#FFFFFF", "background": "#000000"},
} }
def insert(self, index, chars, tags=None): def insert(self, index, chars, tags=None):
...@@ -79,9 +86,9 @@ class ColorDelegator(Delegator): ...@@ -79,9 +86,9 @@ class ColorDelegator(Delegator):
return return
if self.colorizing: if self.colorizing:
self.stop_colorizing = 1 self.stop_colorizing = 1
if __debug__: print "stop colorizing" if __debug__: print "stop colorizing"
if self.allow_colorizing: if self.allow_colorizing:
if __debug__: print "schedule colorizing" if __debug__: print "schedule colorizing"
self.after_id = self.after(1, self.recolorize) self.after_id = self.after(1, self.recolorize)
def close(self): def close(self):
...@@ -99,29 +106,29 @@ class ColorDelegator(Delegator): ...@@ -99,29 +106,29 @@ class ColorDelegator(Delegator):
self.after_id = None self.after_id = None
if __debug__: print "cancel scheduled recolorizer" if __debug__: print "cancel scheduled recolorizer"
self.after_cancel(after_id) self.after_cancel(after_id)
if self.allow_colorizing and self.colorizing: if self.allow_colorizing and self.colorizing:
if __debug__: print "stop colorizing" if __debug__: print "stop colorizing"
self.stop_colorizing = 1 self.stop_colorizing = 1
self.allow_colorizing = not self.allow_colorizing self.allow_colorizing = not self.allow_colorizing
if self.allow_colorizing and not self.colorizing: if self.allow_colorizing and not self.colorizing:
self.after_id = self.after(1, self.recolorize) self.after_id = self.after(1, self.recolorize)
if __debug__: if __debug__:
print "auto colorizing turned", self.allow_colorizing and "on" or "off" print "auto colorizing turned", self.allow_colorizing and "on" or "off"
return "break" return "break"
def recolorize(self): def recolorize(self):
self.after_id = None self.after_id = None
if not self.delegate: if not self.delegate:
if __debug__: print "no delegate" if __debug__: print "no delegate"
return return
if not self.allow_colorizing: if not self.allow_colorizing:
if __debug__: print "auto colorizing is off" if __debug__: print "auto colorizing is off"
return return
if self.colorizing: if self.colorizing:
if __debug__: print "already colorizing" if __debug__: print "already colorizing"
return return
try: try:
self.stop_colorizing = 0 self.stop_colorizing = 0
self.colorizing = 1 self.colorizing = 1
if __debug__: print "colorizing..." if __debug__: print "colorizing..."
t0 = time.clock() t0 = time.clock()
...@@ -131,63 +138,63 @@ class ColorDelegator(Delegator): ...@@ -131,63 +138,63 @@ class ColorDelegator(Delegator):
finally: finally:
self.colorizing = 0 self.colorizing = 0
if self.allow_colorizing and self.tag_nextrange("TODO", "1.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) self.after_id = self.after(1, self.recolorize)
def recolorize_main(self): def recolorize_main(self):
next = "1.0" next = "1.0"
was_ok = is_ok = 0 was_ok = is_ok = 0
while 1: while 1:
item = self.tag_nextrange("TODO", next) item = self.tag_nextrange("TODO", next)
if not item: if not item:
break break
head, tail = item head, tail = item
self.tag_remove("SYNC", head, tail) self.tag_remove("SYNC", head, tail)
item = self.tag_prevrange("SYNC", head) item = self.tag_prevrange("SYNC", head)
if item: if item:
head = item[1] head = item[1]
else: else:
head = "1.0" head = "1.0"
chars = "" chars = ""
mark = head mark = head
is_ok = was_ok = 0 is_ok = was_ok = 0
while not (was_ok and is_ok): while not (was_ok and is_ok):
next = self.index(mark + " lineend +1c") next = self.index(mark + " lineend +1c")
was_ok = "SYNC" in self.tag_names(next + "-1c") was_ok = "SYNC" in self.tag_names(next + "-1c")
line = self.get(mark, next) line = self.get(mark, next)
##print head, "get", mark, next, "->", `line` ##print head, "get", mark, next, "->", `line`
if not line: if not line:
return return
for tag in self.tagdefs.keys(): for tag in self.tagdefs.keys():
self.tag_remove(tag, mark, next) self.tag_remove(tag, mark, next)
chars = chars + line chars = chars + line
m = self.prog.search(chars) m = self.prog.search(chars)
while m: while m:
i, j = m.span() i, j = m.span()
for key, value in m.groupdict().items(): for key, value in m.groupdict().items():
if value: if value:
a, b = m.span(key) a, b = m.span(key)
self.tag_add(key, self.tag_add(key,
head + "+%dc" % a, head + "+%dc" % a,
head + "+%dc" % b) head + "+%dc" % b)
if value in ("def", "class"): if value in ("def", "class"):
m1 = self.idprog.match(chars, b) m1 = self.idprog.match(chars, b)
if m1: if m1:
a, b = m1.span(1) a, b = m1.span(1)
self.tag_add("DEFINITION", self.tag_add("DEFINITION",
head + "+%dc" % a, head + "+%dc" % a,
head + "+%dc" % b) head + "+%dc" % b)
m = self.prog.search(chars, j) m = self.prog.search(chars, j)
is_ok = "SYNC" in self.tag_names(next + "-1c") is_ok = "SYNC" in self.tag_names(next + "-1c")
mark = next mark = next
if is_ok: if is_ok:
head = mark head = mark
chars = "" chars = ""
self.update() self.update()
if self.stop_colorizing: if self.stop_colorizing:
if __debug__: print "colorizing stopped" if __debug__: print "colorizing stopped"
return return
def main(): def main():
......
...@@ -2,28 +2,29 @@ import os ...@@ -2,28 +2,29 @@ import os
import bdb import bdb
import traceback import traceback
from Tkinter import * from Tkinter import *
from WindowList import ListedToplevel
import StackViewer import StackViewer
class Debugger(bdb.Bdb): class Debugger(bdb.Bdb):
interacting = 0 interacting = 0
vstack = vsource = vlocals = vglobals = None vstack = vsource = vlocals = vglobals = None
def __init__(self, pyshell): def __init__(self, pyshell):
bdb.Bdb.__init__(self) bdb.Bdb.__init__(self)
self.pyshell = pyshell self.pyshell = pyshell
self.make_gui() self.make_gui()
def close(self): def close(self):
if self.interacting: if self.interacting:
self.top.bell() self.top.bell()
return return
self.pyshell.close_debugger() self.pyshell.close_debugger()
self.top.destroy() self.top.destroy()
def run(self, *args): def run(self, *args):
try: try:
self.interacting = 1 self.interacting = 1
...@@ -41,12 +42,14 @@ class Debugger(bdb.Bdb): ...@@ -41,12 +42,14 @@ class Debugger(bdb.Bdb):
def user_exception(self, frame, info): def user_exception(self, frame, info):
self.interaction(frame, info) self.interaction(frame, info)
def make_gui(self): def make_gui(self):
pyshell = self.pyshell pyshell = self.pyshell
self.flist = pyshell.flist self.flist = pyshell.flist
self.root = root = pyshell.root 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) top.wm_protocol("WM_DELETE_WINDOW", self.close)
# #
self.bframe = bframe = Frame(top) self.bframe = bframe = Frame(top)
...@@ -113,9 +116,9 @@ class Debugger(bdb.Bdb): ...@@ -113,9 +116,9 @@ class Debugger(bdb.Bdb):
self.show_locals() self.show_locals()
if self.vglobals.get(): if self.vglobals.get():
self.show_globals() self.show_globals()
frame = None frame = None
def interaction(self, frame, info=None): def interaction(self, frame, info=None):
self.frame = frame self.frame = frame
code = frame.f_code code = frame.f_code
...@@ -167,7 +170,7 @@ class Debugger(bdb.Bdb): ...@@ -167,7 +170,7 @@ class Debugger(bdb.Bdb):
self.status.configure(text="") self.status.configure(text="")
self.error.configure(text="", background=self.errorbg) self.error.configure(text="", background=self.errorbg)
self.frame = None self.frame = None
def sync_source_line(self): def sync_source_line(self):
frame = self.frame frame = self.frame
if not frame: if not frame:
...@@ -179,19 +182,19 @@ class Debugger(bdb.Bdb): ...@@ -179,19 +182,19 @@ class Debugger(bdb.Bdb):
edit = self.flist.open(file) edit = self.flist.open(file)
if edit: if edit:
edit.gotoline(lineno) edit.gotoline(lineno)
def cont(self): def cont(self):
self.set_continue() self.set_continue()
self.root.quit() self.root.quit()
def step(self): def step(self):
self.set_step() self.set_step()
self.root.quit() self.root.quit()
def next(self): def next(self):
self.set_next(self.frame) self.set_next(self.frame)
self.root.quit() self.root.quit()
def ret(self): def ret(self):
self.set_return(self.frame) self.set_return(self.frame)
self.root.quit() self.root.quit()
...@@ -211,7 +214,7 @@ class Debugger(bdb.Bdb): ...@@ -211,7 +214,7 @@ class Debugger(bdb.Bdb):
self.stackviewer = None self.stackviewer = None
sv.close() sv.close()
self.fstack['height'] = 1 self.fstack['height'] = 1
def show_source(self): def show_source(self):
if self.vsource.get(): if self.vsource.get():
self.sync_source_line() self.sync_source_line()
...@@ -277,16 +280,16 @@ class Debugger(bdb.Bdb): ...@@ -277,16 +280,16 @@ class Debugger(bdb.Bdb):
text.bell() text.bell()
return return
text.tag_add("BREAK", "insert linestart", "insert lineend +1char") text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
# A literal copy of Bdb.set_break() without the print statement at the end # A literal copy of Bdb.set_break() without the print statement at the end
def set_break(self, filename, lineno, temporary=0, cond = None): def set_break(self, filename, lineno, temporary=0, cond = None):
import linecache # Import as late as possible import linecache # Import as late as possible
line = linecache.getline(filename, lineno) line = linecache.getline(filename, lineno)
if not line: if not line:
return 'That line does not exist!' return 'That line does not exist!'
if not self.breaks.has_key(filename): if not self.breaks.has_key(filename):
self.breaks[filename] = [] self.breaks[filename] = []
list = self.breaks[filename] list = self.breaks[filename]
if not lineno in list: if not lineno in list:
list.append(lineno) list.append(lineno)
bp = bdb.Breakpoint(filename, lineno, temporary, cond) bp = bdb.Breakpoint(filename, lineno, temporary, cond)
class Delegator: class Delegator:
# The cache is only used to be able to change delegates! # The cache is only used to be able to change delegates!
......
...@@ -5,15 +5,70 @@ import imp ...@@ -5,15 +5,70 @@ import imp
from Tkinter import * from Tkinter import *
import tkSimpleDialog import tkSimpleDialog
import tkMessageBox 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_title = "About IDLE"
about_text = """\ 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 by Guido van Rossum
""" """ % idlever.IDLE_VERSION
class EditorWindow: class EditorWindow:
...@@ -21,44 +76,52 @@ class EditorWindow: ...@@ -21,44 +76,52 @@ class EditorWindow:
from ColorDelegator import ColorDelegator from ColorDelegator import ColorDelegator
from UndoDelegator import UndoDelegator from UndoDelegator import UndoDelegator
from IOBinding import IOBinding from IOBinding import IOBinding
from SearchBinding import SearchBinding
from AutoIndent import AutoIndent
from AutoExpand import AutoExpand
import Bindings import Bindings
from Tkinter import Toplevel
about_title = about_title 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.root = root
self.menubar = Menu(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.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.createmenubar()
self.Bindings.apply_bindings(text) self.Bindings.apply_bindings(text)
self.top.protocol("WM_DELETE_WINDOW", self.close) self.top.protocol("WM_DELETE_WINDOW", self.close)
self.top.bind("<<close-window>>", self.close_event) self.top.bind("<<close-window>>", self.close_event)
self.text.bind("<<center-insert>>", self.center_insert_event) text.bind("<<center-insert>>", self.center_insert_event)
self.text.bind("<<help>>", self.help_dialog) text.bind("<<help>>", self.help_dialog)
self.text.bind("<<about-idle>>", self.about_dialog) text.bind("<<about-idle>>", self.about_dialog)
self.text.bind("<<open-module>>", self.open_module) text.bind("<<open-module>>", self.open_module)
self.text.bind("<<do-nothing>>", lambda event: "break") 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['command'] = text.yview
vbar.pack(side=RIGHT, fill=Y) vbar.pack(side=RIGHT, fill=Y)
text['yscrollcommand'] = vbar.set text['yscrollcommand'] = vbar.set
text['background'] = 'white'
if sys.platform[:3] == 'win': if sys.platform[:3] == 'win':
text['font'] = ("lucida console", 8) text['font'] = ("lucida console", 8)
text.pack(side=LEFT, fill=BOTH, expand=1) text.pack(side=LEFT, fill=BOTH, expand=1)
text.focus_set() text.focus_set()
self.auto = auto = self.AutoIndent(text)
self.autoex = self.AutoExpand(text)
self.per = per = self.Percolator(text) self.per = per = self.Percolator(text)
if self.ispythonsource(filename): if self.ispythonsource(filename):
self.color = color = self.ColorDelegator(); per.insertfilter(color) self.color = color = self.ColorDelegator(); per.insertfilter(color)
...@@ -67,8 +130,7 @@ class EditorWindow: ...@@ -67,8 +130,7 @@ class EditorWindow:
##print "No initial colorizer" ##print "No initial colorizer"
self.color = None self.color = None
self.undo = undo = self.UndoDelegator(); per.insertfilter(undo) self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
self.search = search = self.SearchBinding(undo) self.io = io = self.IOBinding(self)
self.io = io = self.IOBinding(undo)
undo.set_saved_change_hook(self.saved_change_hook) undo.set_saved_change_hook(self.saved_change_hook)
io.set_filename_change_hook(self.filename_change_hook) io.set_filename_change_hook(self.filename_change_hook)
...@@ -81,9 +143,29 @@ class EditorWindow: ...@@ -81,9 +143,29 @@ class EditorWindow:
self.saved_change_hook() 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 = [ menu_specs = [
("file", "_File"), ("file", "_File"),
("edit", "_Edit"), ("edit", "_Edit"),
("windows", "_Windows"),
("help", "_Help"), ("help", "_Help"),
] ]
...@@ -96,15 +178,78 @@ class EditorWindow: ...@@ -96,15 +178,78 @@ class EditorWindow:
mbar.add_cascade(label=label, menu=menu, underline=underline) mbar.add_cascade(label=label, menu=menu, underline=underline)
self.Bindings.fill_menus(self.text, mdict) 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): def about_dialog(self, event=None):
tkMessageBox.showinfo(self.about_title, self.about_text, tkMessageBox.showinfo(self.about_title, self.about_text,
master=self.text) master=self.text)
helpfile = "help.txt"
def help_dialog(self, event=None): def help_dialog(self, event=None):
from HelpWindow import HelpWindow helpfile = self.helpfile
HelpWindow(root=self.root) 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): def open_module(self, event=None):
# XXX Shouldn't this be in IOBinding or in FileList?
try: try:
name = self.text.get("sel.first", "sel.last") name = self.text.get("sel.first", "sel.last")
except TclError: except TclError:
...@@ -120,6 +265,8 @@ class EditorWindow: ...@@ -120,6 +265,8 @@ class EditorWindow:
name = string.strip(name) name = string.strip(name)
if not name: if not name:
return return
# XXX Ought to support package syntax
# XXX Ought to insert current file's directory in front of path
try: try:
(f, file, (suffix, mode, type)) = imp.find_module(name) (f, file, (suffix, mode, type)) = imp.find_module(name)
except ImportError, msg: except ImportError, msg:
...@@ -131,7 +278,26 @@ class EditorWindow: ...@@ -131,7 +278,26 @@ class EditorWindow:
return return
if f: if f:
f.close() 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): def gotoline(self, lineno):
if lineno is not None and lineno > 0: if lineno is not None and lineno > 0:
...@@ -143,7 +309,8 @@ class EditorWindow: ...@@ -143,7 +309,8 @@ class EditorWindow:
def ispythonsource(self, filename): def ispythonsource(self, filename):
if not filename: if not filename:
return 1 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 return 1
try: try:
f = open(filename) f = open(filename)
...@@ -153,12 +320,16 @@ class EditorWindow: ...@@ -153,12 +320,16 @@ class EditorWindow:
return 0 return 0
return line[:2] == '#!' and string.find(line, 'python') >= 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): def set_close_hook(self, close_hook):
self.close_hook = close_hook self.close_hook = close_hook
def filename_change_hook(self): def filename_change_hook(self):
if self.flist:
self.flist.filename_changed_edit(self)
self.saved_change_hook() self.saved_change_hook()
if self.ispythonsource(self.io.filename): if self.ispythonsource(self.io.filename):
self.addcolorizer() self.addcolorizer()
...@@ -184,13 +355,40 @@ class EditorWindow: ...@@ -184,13 +355,40 @@ class EditorWindow:
self.per.insertfilter(self.undo) self.per.insertfilter(self.undo)
def saved_change_hook(self): def saved_change_hook(self):
if self.io.filename: short = self.short_title()
title = self.io.filename long = self.long_title()
if short and long:
title = short + " - " + long
elif short:
title = short
elif long:
title = long
else: else:
title = "(Untitled)" title = "Untitled"
if not self.undo.get_saved(): icon = short or long or title
title = title + " *" if not self.get_saved():
title = "*%s*" % title
icon = "*%s" % icon
self.top.wm_title(title) 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): def center_insert_event(self, event):
self.center() self.center()
...@@ -207,10 +405,14 @@ class EditorWindow: ...@@ -207,10 +405,14 @@ class EditorWindow:
def close_event(self, event): def close_event(self, event):
self.close() self.close()
def maybesave(self):
if self.io:
return self.io.maybesave()
def close(self): def close(self):
self.top.wm_deiconify() self.top.wm_deiconify()
self.top.tkraise() self.top.tkraise()
reply = self.io.maybesave() reply = self.maybesave()
if reply != "cancel": if reply != "cancel":
if self.color and self.color.colorizing: if self.color and self.color.colorizing:
self.color.close() self.color.close()
...@@ -223,8 +425,59 @@ class EditorWindow: ...@@ -223,8 +425,59 @@ class EditorWindow:
self.top.destroy() self.top.destroy()
return reply 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): 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 = root.tk
tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]') tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
...@@ -239,7 +492,7 @@ def test(): ...@@ -239,7 +492,7 @@ def test():
filename = sys.argv[1] filename = sys.argv[1]
else: else:
filename = None filename = None
edit = EditorWindow(root, filename) edit = EditorWindow(root=root, filename=filename)
edit.set_close_hook(root.quit) edit.set_close_hook(root.quit)
root.mainloop() root.mainloop()
root.destroy() root.destroy()
......
...@@ -2,125 +2,59 @@ import os ...@@ -2,125 +2,59 @@ import os
from Tkinter import * from Tkinter import *
import tkMessageBox import tkMessageBox
from EditorWindow import EditorWindow, fixwordbreaks import WindowList
from IOBinding import IOBinding
#$ 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): class FileList:
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])
from EditorWindow import EditorWindow
EditorWindow.Toplevel = WindowList.ListedToplevel # XXX Patch it!
class FileList:
EditorWindow = MultiEditorWindow
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
self.dict = {} self.dict = {}
self.inversedict = {} self.inversedict = {}
def new(self):
return self.open(None)
def open(self, filename, edit=None): def goodname(self, filename):
if filename:
filename = self.canonize(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) key = os.path.normcase(filename)
if self.dict.has_key(key): if self.dict.has_key(key):
edit = self.dict[key] edit = self.dict[key]
edit.wakeup() filename = edit.io.filename or filename
return edit return filename
if not os.path.exists(filename):
tkMessageBox.showinfo( def open(self, filename):
"New File", assert filename
"Opening non-existent file %s" % `filename`, filename = self.canonize(filename)
master=self.root) if os.path.isdir(filename):
if edit and not edit.io.filename and edit.undo.get_saved(): tkMessageBox.showerror(
# Reuse existing Untitled window for new file "Is A Directory",
edit.io.loadfile(filename) "The path %s is a directory." % `filename`,
self.dict[key] = edit master=self.root)
self.inversedict[edit] = key return None
edit.wakeup() key = os.path.normcase(filename)
return edit if self.dict.has_key(key):
else: edit = self.dict[key]
key = None edit.wakeup()
edit = self.EditorWindow(self, filename, key) return edit
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): def new_callback(self, event):
self.new() self.new()
...@@ -189,6 +123,7 @@ class FileList: ...@@ -189,6 +123,7 @@ class FileList:
def test(): def test():
from EditorWindow import fixwordbreaks
import sys import sys
root = Tk() root = Tk()
fixwordbreaks(root) fixwordbreaks(root)
......
...@@ -10,7 +10,7 @@ class FrameViewer: ...@@ -10,7 +10,7 @@ class FrameViewer:
self.repr = Repr() self.repr = Repr()
self.repr.maxstring = 60 self.repr.maxstring = 60
self.load_variables() self.load_variables()
def load_variables(self): def load_variables(self):
row = 0 row = 0
if self.frame.f_locals is not self.frame.f_globals: if self.frame.f_locals is not self.frame.f_globals:
...@@ -22,7 +22,7 @@ class FrameViewer: ...@@ -22,7 +22,7 @@ class FrameViewer:
borderwidth=2, relief="raised") borderwidth=2, relief="raised")
l.grid(row=row, column=0, columnspan=2, sticky="ew") l.grid(row=row, column=0, columnspan=2, sticky="ew")
row = self.load_names(self.frame.f_globals, row+1) row = self.load_names(self.frame.f_globals, row+1)
def load_names(self, dict, row): def load_names(self, dict, row):
names = dict.keys() names = dict.keys()
names.sort() names.sort()
......
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()
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()
import string import string
class History: class History:
def __init__(self, text): def __init__(self, text):
self.text = text self.text = text
self.history = [] self.history = []
......
...@@ -2,20 +2,42 @@ import os ...@@ -2,20 +2,42 @@ import os
import tkFileDialog import tkFileDialog
import tkMessageBox 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): class IOBinding:
self.text = text
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.text.bind("<<open-window-from-file>>", self.open) self.text.bind("<<open-window-from-file>>", self.open)
self.text.bind("<<save-window>>", self.save) self.text.bind("<<save-window>>", self.save)
self.text.bind("<<save-window-as-file>>", self.save_as) self.text.bind("<<save-window-as-file>>", self.save_as)
self.text.bind("<<save-copy-of-window-as-file>>", self.save_a_copy) 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 filename_change_hook = None
def set_filename_change_hook(self, hook): def set_filename_change_hook(self, hook):
...@@ -25,18 +47,29 @@ class IOBinding: ...@@ -25,18 +47,29 @@ class IOBinding:
def set_filename(self, filename): def set_filename(self, filename):
self.filename = filename self.filename = filename
self.text.set_saved(1) self.set_saved(1)
if self.filename_change_hook: if self.filename_change_hook:
self.filename_change_hook() self.filename_change_hook()
def open(self, event): 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() reply = self.maybesave()
if reply == "cancel": if reply == "cancel":
self.text.focus_set()
return "break" return "break"
filename = self.askopenfile() filename = self.askopenfile()
if filename: if filename:
self.loadfile(filename) self.loadfile(filename)
else:
self.text.focus_set()
return "break" return "break"
def loadfile(self, filename): def loadfile(self, filename):
...@@ -50,14 +83,14 @@ class IOBinding: ...@@ -50,14 +83,14 @@ class IOBinding:
self.text.delete("1.0", "end") self.text.delete("1.0", "end")
self.set_filename(None) self.set_filename(None)
self.text.insert("1.0", chars) self.text.insert("1.0", chars)
self.text.reset_undo() self.reset_undo()
self.set_filename(filename) self.set_filename(filename)
self.text.mark_set("insert", "1.0") self.text.mark_set("insert", "1.0")
self.text.see("insert") self.text.see("insert")
return 1 return 1
def maybesave(self): def maybesave(self):
if self.text.get_saved(): if self.get_saved():
return "yes" return "yes"
message = "Do you want to save %s before closing?" % ( message = "Do you want to save %s before closing?" % (
self.filename or "this untitled document") self.filename or "this untitled document")
...@@ -70,8 +103,9 @@ class IOBinding: ...@@ -70,8 +103,9 @@ class IOBinding:
reply = m.show() reply = m.show()
if reply == "yes": if reply == "yes":
self.save(None) self.save(None)
if not self.text.get_saved(): if not self.get_saved():
reply = "cancel" reply = "cancel"
self.text.focus_set()
return reply return reply
def save(self, event): def save(self, event):
...@@ -79,7 +113,8 @@ class IOBinding: ...@@ -79,7 +113,8 @@ class IOBinding:
self.save_as(event) self.save_as(event)
else: else:
if self.writefile(self.filename): if self.writefile(self.filename):
self.text.set_saved(1) self.set_saved(1)
self.text.focus_set()
return "break" return "break"
def save_as(self, event): def save_as(self, event):
...@@ -87,22 +122,23 @@ class IOBinding: ...@@ -87,22 +122,23 @@ class IOBinding:
if filename: if filename:
if self.writefile(filename): if self.writefile(filename):
self.set_filename(filename) self.set_filename(filename)
self.text.set_saved(1) self.set_saved(1)
self.text.focus_set()
return "break" return "break"
def save_a_copy(self, event): def save_a_copy(self, event):
filename = self.asksavefile() filename = self.asksavefile()
if filename: if filename:
self.writefile(filename) self.writefile(filename)
self.text.focus_set()
return "break" return "break"
def writefile(self, filename): def writefile(self, filename):
self.fixlastline()
try: try:
f = open(filename, "w") f = open(filename, "w")
chars = self.text.get("1.0", "end-1c") chars = self.text.get("1.0", "end-1c")
f.write(chars) f.write(chars)
if chars and chars[-1] != "\n":
f.write("\n")
f.close() f.close()
## print "saved to", `filename` ## print "saved to", `filename`
return 1 return 1
...@@ -111,11 +147,16 @@ class IOBinding: ...@@ -111,11 +147,16 @@ class IOBinding:
master=self.text) master=self.text)
return 0 return 0
def fixlastline(self):
c = self.text.get("end-2c")
if c != '\n':
self.text.insert("end-1c", "\n")
opendialog = None opendialog = None
savedialog = None savedialog = None
filetypes = [ filetypes = [
("Python files", "*.py", "TEXT"), ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
("All text files", "*", "TEXT"), ("All text files", "*", "TEXT"),
("All files", "*"), ("All files", "*"),
] ]
...@@ -129,10 +170,13 @@ class IOBinding: ...@@ -129,10 +170,13 @@ class IOBinding:
def defaultfilename(self, mode="open"): def defaultfilename(self, mode="open"):
if self.filename: if self.filename:
dir, base = os.path.split(self.filename) return os.path.split(self.filename)
else: else:
dir = base = "" try:
return dir, base pwd = os.getcwd()
except os.error:
pwd = ""
return pwd, ""
def asksavefile(self): def asksavefile(self):
dir, base = self.defaultfilename("save") dir, base = self.defaultfilename("save")
...@@ -145,13 +189,30 @@ class IOBinding: ...@@ -145,13 +189,30 @@ class IOBinding:
def test(): def test():
from Tkinter import * from Tkinter import *
root = Tk() root = Tk()
class MyText(Text): class MyEditWin:
def reset_undo(self): pass 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 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.pack()
text.focus_set() text.focus_set()
io = IOBinding(text) editwin = MyEditWin(text)
io = IOBinding(editwin)
root.mainloop() root.mainloop()
if __name__ == "__main__": if __name__ == "__main__":
......
import string import string
class History: class History:
def __init__(self, text): def __init__(self, text):
self.text = text self.text = text
self.history = [] self.history = []
......
import sys from Tkinter import *
from EditorWindow import EditorWindow
import re import re
import tkMessageBox
from Tkinter import * 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>>"),
]
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 = [ file_line_pats = [
r'File "([^"]*)", line (\d+)', r'file "([^"]*)", line (\d+)',
r'([^\s]+)\((\d+)\)', r'([^\s]+)\((\d+)\)',
r'([^\s]+):\s*(\d+):', r'([^\s]+):\s*(\d+):',
] ]
file_line_progs = None file_line_progs = None
def goto_traceback_line(self): def goto_file_line(self, event=None):
if self.file_line_progs is None: if self.file_line_progs is None:
l = [] l = []
for pat in self.file_line_pats: for pat in self.file_line_pats:
l.append(re.compile(pat)) l.append(re.compile(pat, re.IGNORECASE))
self.file_line_progs = l self.file_line_progs = l
x, y = self.event.x, self.event.y # x, y = self.event.x, self.event.y
self.text.mark_set("insert", "@%d,%d" % (x, y)) # self.text.mark_set("insert", "@%d,%d" % (x, y))
line = self.text.get("insert linestart", "insert lineend") line = self.text.get("insert linestart", "insert lineend")
for prog in self.file_line_progs: for prog in self.file_line_progs:
m = prog.search(line) m = prog.search(line)
if m: if m:
break break
else: else:
self.text.bell() 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 return
filename, lineno = m.group(1, 2) filename, lineno = m.group(1, 2)
try: try:
...@@ -71,16 +88,3 @@ class PopupMenu: ...@@ -71,16 +88,3 @@ class PopupMenu:
self.text.bell() self.text.bell()
return return
edit.gotoline(lineno) 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)
...@@ -12,9 +12,10 @@ from code import InteractiveInterpreter ...@@ -12,9 +12,10 @@ from code import InteractiveInterpreter
from Tkinter import * from Tkinter import *
import tkMessageBox import tkMessageBox
from EditorWindow import fixwordbreaks from EditorWindow import EditorWindow, fixwordbreaks
from FileList import FileList, MultiEditorWindow, MultiIOBinding from FileList import FileList
from ColorDelegator import ColorDelegator from ColorDelegator import ColorDelegator
from OutputWindow import OutputWindow
# We need to patch linecache.checkcache, because we don't want it # We need to patch linecache.checkcache, because we don't want it
# to throw away our <pyshell#...> entries. # to throw away our <pyshell#...> entries.
...@@ -31,36 +32,54 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache): ...@@ -31,36 +32,54 @@ def linecache_checkcache(orig_checkcache=linecache.checkcache):
linecache.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): def __init__(self, *args):
apply(MultiEditorWindow.__init__, (self,) + args) apply(EditorWindow.__init__, (self,) + args)
self.text.bind("<3>", self.right_menu_event) self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
self.text.bind("<<open-python-shell>>", self.flist.open_shell)
def fixedwindowsmenu(self, wmenu):
wmenu.add_command(label="Python Shell", command=self.flist.open_shell) rmenu_specs = [
wmenu.add_separator() ("Set breakpoint here", "<<set-breakpoint-here>>"),
]
menu = None
def set_breakpoint_here(self, event=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):
if not self.flist.pyshell or not self.flist.pyshell.interp.debugger: if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
self.text.bell() self.text.bell()
return return
...@@ -68,12 +87,14 @@ class PyShellEditorWindow(MultiEditorWindow): ...@@ -68,12 +87,14 @@ class PyShellEditorWindow(MultiEditorWindow):
class PyShellFileList(FileList): class PyShellFileList(FileList):
# File list when a shell is present
EditorWindow = PyShellEditorWindow EditorWindow = PyShellEditorWindow
pyshell = None pyshell = None
def open_shell(self): def open_shell(self, event=None):
if self.pyshell: if self.pyshell:
self.pyshell.wakeup() self.pyshell.wakeup()
else: else:
...@@ -82,43 +103,29 @@ class PyShellFileList(FileList): ...@@ -82,43 +103,29 @@ class PyShellFileList(FileList):
return self.pyshell return self.pyshell
class ModifiedIOBinding(MultiIOBinding): class ModifiedColorDelegator(ColorDelegator):
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"
# Colorizer for the shell window itself
class ModifiedColorDelegator(ColorDelegator):
def recolorize_main(self): def recolorize_main(self):
self.tag_remove("TODO", "1.0", "iomark") self.tag_remove("TODO", "1.0", "iomark")
self.tag_add("SYNC", "1.0", "iomark") self.tag_add("SYNC", "1.0", "iomark")
ColorDelegator.recolorize_main(self) 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): class ModifiedInterpreter(InteractiveInterpreter):
def __init__(self, tkconsole): def __init__(self, tkconsole):
self.tkconsole = tkconsole self.tkconsole = tkconsole
InteractiveInterpreter.__init__(self) InteractiveInterpreter.__init__(self)
...@@ -176,7 +183,7 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -176,7 +183,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.tkconsole.resetoutput() self.tkconsole.resetoutput()
self.checklinecache() self.checklinecache()
InteractiveInterpreter.showtraceback(self) InteractiveInterpreter.showtraceback(self)
def checklinecache(self): def checklinecache(self):
c = linecache.cache c = linecache.cache
for key in c.keys(): for key in c.keys():
...@@ -184,10 +191,10 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -184,10 +191,10 @@ class ModifiedInterpreter(InteractiveInterpreter):
del c[key] del c[key]
debugger = None debugger = None
def setdebugger(self, debugger): def setdebugger(self, debugger):
self.debugger = debugger self.debugger = debugger
def getdebugger(self): def getdebugger(self):
return self.debugger return self.debugger
...@@ -214,25 +221,23 @@ class ModifiedInterpreter(InteractiveInterpreter): ...@@ -214,25 +221,23 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.showtraceback() self.showtraceback()
finally: finally:
self.tkconsole.endexecuting() self.tkconsole.endexecuting()
def write(self, s): def write(self, s):
# Override base class write # Override base class write
self.tkconsole.console.write(s) self.tkconsole.console.write(s)
class PyShell(PyShellEditorWindow):
class PyShell(OutputWindow):
# Override classes # Override classes
ColorDelegator = ModifiedColorDelegator ColorDelegator = ModifiedColorDelegator
IOBinding = ModifiedIOBinding
# Override menu bar specs # Override menu bar specs
menu_specs = PyShellEditorWindow.menu_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 # New classes
from History import History from History import History
from PopupMenu import PopupMenu
def __init__(self, flist=None): def __init__(self, flist=None):
self.interp = ModifiedInterpreter(self) self.interp = ModifiedInterpreter(self)
...@@ -242,23 +247,24 @@ class PyShell(PyShellEditorWindow): ...@@ -242,23 +247,24 @@ class PyShell(PyShellEditorWindow):
root.withdraw() root.withdraw()
flist = PyShellFileList(root) flist = PyShellFileList(root)
PyShellEditorWindow.__init__(self, flist, None, None) OutputWindow.__init__(self, flist, None, None)
self.config_colors()
import __builtin__ import __builtin__
__builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D." __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
self.auto = self.extensions["AutoIndent"] # Required extension
self.auto.config(prefertabs=1) self.auto.config(prefertabs=1)
text = self.text text = self.text
text.configure(wrap="char")
text.bind("<<newline-and-indent>>", self.enter_callback) text.bind("<<newline-and-indent>>", self.enter_callback)
text.bind("<<plain-newline-and-indent>>", self.linefeed_callback) text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
text.bind("<<interrupt-execution>>", self.cancel_callback) text.bind("<<interrupt-execution>>", self.cancel_callback)
text.bind("<<beginning-of-line>>", self.home_callback) text.bind("<<beginning-of-line>>", self.home_callback)
text.bind("<<end-of-file>>", self.eof_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("<<open-stack-viewer>>", self.open_stack_viewer)
text.bind("<<toggle-debugger>>", self.toggle_debugger) text.bind("<<toggle-debugger>>", self.toggle_debugger)
text.bind("<<open-python-shell>>", self.flist.open_shell)
sys.stdout = PseudoFile(self, "stdout") sys.stdout = PseudoFile(self, "stdout")
sys.stderr = PseudoFile(self, "stderr") sys.stderr = PseudoFile(self, "stderr")
...@@ -266,31 +272,12 @@ class PyShell(PyShellEditorWindow): ...@@ -266,31 +272,12 @@ class PyShell(PyShellEditorWindow):
self.console = PseudoFile(self, "console") self.console = PseudoFile(self, "console")
self.history = self.History(self.text) 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 reading = 0
executing = 0 executing = 0
canceled = 0 canceled = 0
endoffile = 0 endoffile = 0
def toggle_debugger(self, event=None): def toggle_debugger(self, event=None):
if self.executing: if self.executing:
tkMessageBox.showerror("Don't debug now", tkMessageBox.showerror("Don't debug now",
...@@ -362,14 +349,8 @@ class PyShell(PyShellEditorWindow): ...@@ -362,14 +349,8 @@ class PyShell(PyShellEditorWindow):
# Override this so EditorWindow never removes the colorizer # Override this so EditorWindow never removes the colorizer
return 1 return 1
def saved_change_hook(self): def short_title(self):
# Override this to get the title right return "Python Shell"
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 begin(self): def begin(self):
self.resetoutput() self.resetoutput()
...@@ -382,7 +363,7 @@ class PyShell(PyShellEditorWindow): ...@@ -382,7 +363,7 @@ class PyShell(PyShellEditorWindow):
self.showprompt() self.showprompt()
import Tkinter import Tkinter
Tkinter._default_root = None Tkinter._default_root = None
def interact(self): def interact(self):
self.begin() self.begin()
self.top.mainloop() self.top.mainloop()
...@@ -457,7 +438,7 @@ class PyShell(PyShellEditorWindow): ...@@ -457,7 +438,7 @@ class PyShell(PyShellEditorWindow):
self.text.insert("insert", "\n") self.text.insert("insert", "\n")
self.text.see("insert") self.text.see("insert")
else: else:
self.auto.autoindent(event) self.auto.auto_indent(event)
return "break" return "break"
def enter_callback(self, event): def enter_callback(self, event):
...@@ -468,7 +449,7 @@ class PyShell(PyShellEditorWindow): ...@@ -468,7 +449,7 @@ class PyShell(PyShellEditorWindow):
try: try:
sel = self.text.get("sel.first", "sel.last") sel = self.text.get("sel.first", "sel.last")
if sel: if sel:
if self.text.compare("self.last", "<=", "iomark"): if self.text.compare("sel.last", "<=", "iomark"):
self.recall(sel) self.recall(sel)
return "break" return "break"
except: except:
...@@ -492,7 +473,7 @@ class PyShell(PyShellEditorWindow): ...@@ -492,7 +473,7 @@ class PyShell(PyShellEditorWindow):
# If we're in the current input before its last line, # If we're in the current input before its last line,
# insert a newline right at the insert point # insert a newline right at the insert point
if self.text.compare("insert", "<", "end-1c linestart"): if self.text.compare("insert", "<", "end-1c linestart"):
self.auto.autoindent(event) self.auto.auto_indent(event)
return "break" return "break"
# We're in the last line; append a newline and submit it # We're in the last line; append a newline and submit it
self.text.mark_set("insert", "end-1c") self.text.mark_set("insert", "end-1c")
...@@ -500,7 +481,7 @@ class PyShell(PyShellEditorWindow): ...@@ -500,7 +481,7 @@ class PyShell(PyShellEditorWindow):
self.text.insert("insert", "\n") self.text.insert("insert", "\n")
self.text.see("insert") self.text.see("insert")
else: else:
self.auto.autoindent(event) self.auto.auto_indent(event)
self.text.tag_add("stdin", "iomark", "end-1c") self.text.tag_add("stdin", "iomark", "end-1c")
self.text.update_idletasks() self.text.update_idletasks()
if self.reading: if self.reading:
...@@ -545,49 +526,7 @@ class PyShell(PyShellEditorWindow): ...@@ -545,49 +526,7 @@ class PyShell(PyShellEditorWindow):
self.canceled = 0 self.canceled = 0
raise KeyboardInterrupt raise KeyboardInterrupt
return self._cancel_check 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): def open_stack_viewer(self, event=None):
try: try:
sys.last_traceback sys.last_traceback
...@@ -618,26 +557,22 @@ class PyShell(PyShellEditorWindow): ...@@ -618,26 +557,22 @@ class PyShell(PyShellEditorWindow):
self.text.mark_set("iomark", "end-1c") self.text.mark_set("iomark", "end-1c")
sys.stdout.softspace = 0 sys.stdout.softspace = 0
def write(self, s): def write(self, s, tags=()):
# Overrides base class write self.text.mark_gravity("iomark", "right")
self.console.write(s) OutputWindow.write(self, s, tags, "iomark")
self.text.mark_gravity("iomark", "left")
if self.canceled:
self.canceled = 0
raise KeyboardInterrupt
class PseudoFile: class PseudoFile:
def __init__(self, interp, tags): def __init__(self, shell, tags):
self.interp = interp self.shell = shell
self.text = interp.text
self.tags = tags self.tags = tags
def write(self, s): def write(self, s):
self.text.mark_gravity("iomark", "right") self.shell.write(s, self.tags)
self.text.insert("iomark", str(s), self.tags)
self.text.mark_gravity("iomark", "left")
self.text.see("iomark")
self.text.update()
if self.interp.canceled:
self.interp.canceled = 0
raise KeyboardInterrupt
def writelines(self, l): def writelines(self, l):
map(self.write, l) map(self.write, l)
......
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 This is a *very* early preliminary release of IDLE, my own attempt at
a Tkinter-based IDE for Python. It currently has the following a Tkinter-based IDE for Python. It has the following features:
features:
- multi-window text editor with multiple undo and Python colorizing - multi-window text editor with multiple undo and Python colorizing
- Python shell (a.k.a. interactive interpreter) window subclass - Python shell (a.k.a. interactive interpreter) window subclass
- debugger - debugger
- 100% pure Python - 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 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 idle.pyw to avoid popping up a DOS console. Any arguments passed are
interpreted as files that will be opened for editing. interpreted as files that will be opened for editing.
IDLE requires Python 1.5.2, so it is currently only usable for PSA IDLE requires Python 1.5.2, so it is currently only usable with the
members who have the latest 1.5.2 alpha release (a public beta release Python 1.5.2 beta distribution (luckily, IDLE is bundled with Python
is due shortly). 1.5.2).
Please send feedback to the Python newsgroup, comp.lang.python. Please send feedback to the Python newsgroup, comp.lang.python.
...@@ -27,46 +26,71 @@ 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: TO DO:
- "GO" command
- "Modularize" command
- command expansion from keywords, module contents, other buffers, etc.
- "Recent documents" menu item - "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: - more emacsisms:
- parentheses matching
- reindent, reformat text etc. - reindent, reformat text etc.
- M-[, M-] to move by paragraphs - M-[, M-] to move by paragraphs
- smart stuff with whitespace around Return - smart stuff with whitespace around Return
- filter region? - 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? - status bar?
- better help? - better help?
- don't open second class browser on same module
Details: Details:
- when there's a selection, left/right arrow should go to either - when there's a selection, left/right arrow should go to either
end of the selection 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: Structural problems:
- too much knowledge in FileList about EditorWindow (for example) - too much knowledge in FileList about EditorWindow (for example)
- Several occurrences of scrollable listbox with title and certain - Several occurrences of scrollable listbox with title and certain
behavior; should create base class to generalize this behavior; should create base class to generalize this
- class browser could become an outline?
====================================================================== ======================================================================
Comparison to PTUI 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: - PTUI's shell is worse:
no coloring; no coloring;
no editing of multi-line commands; no editing of multi-line commands;
...@@ -76,34 +100,18 @@ Comparison to PTUI ...@@ -76,34 +100,18 @@ Comparison to PTUI
no redo; no redo;
one char at a time 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: - PTUI's GUI is a tad ugly:
I don't like the multiple buffers in one window model I don't like the multiple buffers in one window model;
I don't like the big buttons at the top of the widow
- PTUI's help is better (HTML!)
- PTUI's search/replace is better (more features) - PTUI lacks an integrated debugger
- PTUI's auto indent is better - PTUI lacks a class browser
(understands that "if a: # blah, blah" opens a block)
- PTUI's key bindings are a bit weird (DEL to dedent a line!?!?!?)
- PTUI's fontify is faster but synchronous (and still too slow); - PTUI lacks many of IDLE's features:
also doesn't do as good a job if editing affects lines far below - expand word
- regular expression search
- PTUI has more bells and whistles: - search files (grep)
open multiple
append
zap tabs
fontify (you could argue it's not needed in my code)
comment/uncomment
modularize
examine
go
====================================================================== ======================================================================
......
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")
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()
from Tkinter import * from Tkinter import *
class ScrolledList: class ScrolledList:
def __init__(self, master, **options): def __init__(self, master, **options):
# Create top frame, with scrollbar and listbox # Create top frame, with scrollbar and listbox
self.master = master self.master = master
...@@ -18,22 +18,22 @@ class ScrolledList: ...@@ -18,22 +18,22 @@ class ScrolledList:
listbox["yscrollcommand"] = vbar.set listbox["yscrollcommand"] = vbar.set
# Bind events to the list box # Bind events to the list box
listbox.bind("<ButtonRelease-1>", self.click_event) 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("<ButtonPress-3>", self.popup_event)
listbox.bind("<Key-Up>", self.up_event) listbox.bind("<Key-Up>", self.up_event)
listbox.bind("<Key-Down>", self.down_event) listbox.bind("<Key-Down>", self.down_event)
# Set the focus # Set the focus
listbox.focus_set() listbox.focus_set()
def close(self): def close(self):
self.frame.destroy() self.frame.destroy()
def clear(self): def clear(self):
self.listbox.delete(0, "end") self.listbox.delete(0, "end")
def append(self, item): def append(self, item):
self.listbox.insert("end", str(item)) self.listbox.insert("end", str(item))
def get(self, index): def get(self, index):
return self.listbox.get(index) return self.listbox.get(index)
...@@ -49,9 +49,9 @@ class ScrolledList: ...@@ -49,9 +49,9 @@ class ScrolledList:
self.select(index) self.select(index)
self.on_double(index) self.on_double(index)
return "break" return "break"
menu = None menu = None
def popup_event(self, event): def popup_event(self, event):
if not self.menu: if not self.menu:
self.make_menu() self.make_menu()
...@@ -65,7 +65,7 @@ class ScrolledList: ...@@ -65,7 +65,7 @@ class ScrolledList:
menu = Menu(self.listbox, tearoff=0) menu = Menu(self.listbox, tearoff=0)
self.menu = menu self.menu = menu
self.fill_menu() self.fill_menu()
def up_event(self, event): def up_event(self, event):
index = self.listbox.index("active") index = self.listbox.index("active")
if self.listbox.selection_includes(index): if self.listbox.selection_includes(index):
...@@ -78,7 +78,7 @@ class ScrolledList: ...@@ -78,7 +78,7 @@ class ScrolledList:
self.select(index) self.select(index)
self.on_select(index) self.on_select(index)
return "break" return "break"
def down_event(self, event): def down_event(self, event):
index = self.listbox.index("active") index = self.listbox.index("active")
if self.listbox.selection_includes(index): if self.listbox.selection_includes(index):
...@@ -91,22 +91,22 @@ class ScrolledList: ...@@ -91,22 +91,22 @@ class ScrolledList:
self.select(index) self.select(index)
self.on_select(index) self.on_select(index)
return "break" return "break"
def select(self, index): def select(self, index):
self.listbox.focus_set() self.listbox.focus_set()
self.listbox.activate(index) self.listbox.activate(index)
self.listbox.selection_clear(0, "end") self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index) self.listbox.selection_set(index)
self.listbox.see(index) self.listbox.see(index)
# Methods to override for specific actions # Methods to override for specific actions
def fill_menu(self): def fill_menu(self):
pass pass
def on_select(self, index): def on_select(self, index):
pass pass
def on_double(self, index): def on_double(self, index):
pass pass
......
import string
import re
import tkSimpleDialog 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: class SearchBinding:
def __init__(self, text): windows_keydefs = {
self.text = text '<<find-again>>': ['<Control-g>', '<F3>'],
self.pat = "" '<<find-in-files>>': ['<Alt-F3>'],
self.prog = None '<<find-selection>>': ['<Control-F3>'],
self.text.bind("<<find>>", self.find_event) '<<find>>': ['<Control-f>'],
self.text.bind("<<find-next>>", self.find_next_event) '<<replace>>': ['<Control-h>'],
self.text.bind("<<find-same>>", self.find_same_event) '<<goto-line>>': ['<Alt-g>'],
self.text.bind("<<goto-line>>", self.goto_line_event) }
def find_event(self, event): unix_keydefs = {
default = self.text.get("self.first", "sel.last") or self.pat '<<find-again>>': ['<Control-u><Control-s>'],
new = tkSimpleDialog.askstring("Find", '<<find-selection>>': ['<Control-s>'],
"Regular Expression:", '<<find>>': ['<Control-u><Control-u><Control-s>'],
initialvalue=default, '<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
parent=self.text) }
if not new:
return "break" menudefs = [
self.pat = new ('edit', [
try: None,
self.prog = re.compile(self.pat) ('_Find...', '<<find>>'),
except re.error, msg: ('Find a_gain', '<<find-again>>'),
tkMessageBox.showerror("RE error", str(msg), ('Find _selection', '<<find-selection>>'),
master=self.text) ('Find in Files...', '<<find-in-files>>'),
return "break" ('R_eplace...', '<<replace>>'),
return self.find_next_event(event) ('Go to _line', '<<goto-line>>'),
]),
def find_same_event(self, event): ]
pat = self.text.get("sel.first", "sel.last")
if not pat: def __init__(self, editwin):
return self.find_event(event) self.editwin = editwin
self.pat = re.escape(pat)
self.prog = None def find_event(self, event):
try: import SearchDialog
self.prog = re.compile(self.pat) SearchDialog.find(self.editwin.text)
except re.error, msg: return "break"
tkMessageBox.showerror("RE error", str(message),
master=self.text) def find_again_event(self, event):
return "break" import SearchDialog
self.text.mark_set("insert", "sel.last") SearchDialog.find_again(self.editwin.text)
return self.find_next_event(event) return "break"
def find_next_event(self, event): def find_selection_event(self, event):
if not self.pat: import SearchDialog
return self.find_event(event) SearchDialog.find_selection(self.editwin.text)
if not self.prog: return "break"
self.text.bell()
##print "No program" def find_in_files_event(self, event):
return "break" import GrepDialog
line, col = map(int, GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist)
string.split(self.text.index("insert"), ".")) return "break"
chars = self.text.get("%d.0" % line, "%d.0" % (line+1))
while chars: def replace_event(self, event):
m = self.prog.search(chars, col) import ReplaceDialog
if m: ReplaceDialog.replace(self.editwin.text)
i, j = m.span() return "break"
self.text.mark_set("insert",
"%d.%d" % (line, j)) def goto_line_event(self, event):
self.text.tag_remove("sel", "1.0", "end") print event
self.text.tag_add("sel", text = self.editwin.text
"%d.%d" % (line, i), lineno = tkSimpleDialog.askinteger("Goto",
"%d.%d" % (line, j)) "Go to line number:",
self.text.see("insert") parent=text)
break if lineno is None:
line = line + 1 return "break"
col = 0 if lineno <= 0:
chars = self.text.get("%d.0" % line, "%d.0" % (line+1)) text.bell()
else: return "break"
# Not found text.mark_set("insert", "%d.0" % lineno)
self.text.bell() text.see("insert")
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")
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)
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()
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
...@@ -4,16 +4,18 @@ import os ...@@ -4,16 +4,18 @@ import os
from Tkinter import * from Tkinter import *
import linecache import linecache
from repr import Repr from repr import Repr
from WindowList import ListedToplevel
from ScrolledList import ScrolledList from ScrolledList import ScrolledList
class StackBrowser: class StackBrowser:
def __init__(self, root, flist, stack=None): 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.protocol("WM_DELETE_WINDOW", self.close)
top.wm_title("Stack viewer") top.wm_title("Stack viewer")
top.wm_iconname("Stack")
# Create help label # Create help label
self.helplabel = Label(top, self.helplabel = Label(top,
text="Click once to view variables; twice for source", text="Click once to view variables; twice for source",
...@@ -24,7 +26,7 @@ class StackBrowser: ...@@ -24,7 +26,7 @@ class StackBrowser:
if stack is None: if stack is None:
stack = get_stack() stack = get_stack()
self.sv.load_stack(stack) self.sv.load_stack(stack)
def close(self): def close(self):
self.top.destroy() self.top.destroy()
...@@ -44,7 +46,7 @@ class StackBrowser: ...@@ -44,7 +46,7 @@ class StackBrowser:
self.show_globals(frame) self.show_globals(frame)
self.show_locals(frame) self.show_locals(frame)
self.curframe = frame self.curframe = frame
def show_globals(self, frame): def show_globals(self, frame):
title = "Global Variables" title = "Global Variables"
if frame.f_globals.has_key("__name__"): if frame.f_globals.has_key("__name__"):
...@@ -66,7 +68,7 @@ class StackBrowser: ...@@ -66,7 +68,7 @@ class StackBrowser:
title, title,
self.globalsdict) self.globalsdict)
self.globalsframe.pack(fill="both", side="bottom") self.globalsframe.pack(fill="both", side="bottom")
def show_locals(self, frame): def show_locals(self, frame):
self.localsdict = None self.localsdict = None
if self.localsviewer: if self.localsviewer:
...@@ -92,7 +94,7 @@ class StackBrowser: ...@@ -92,7 +94,7 @@ class StackBrowser:
class StackViewer(ScrolledList): class StackViewer(ScrolledList):
def __init__(self, master, flist, browser): def __init__(self, master, flist, browser):
ScrolledList.__init__(self, master) ScrolledList.__init__(self, master)
self.flist = flist self.flist = flist
...@@ -149,7 +151,7 @@ class StackViewer(ScrolledList): ...@@ -149,7 +151,7 @@ class StackViewer(ScrolledList):
def show_stack_frame(self): def show_stack_frame(self):
index = self.listbox.index("active") index = self.listbox.index("active")
self.browser.show_frame(self.stack[index]) self.browser.show_frame(self.stack[index])
def show_source(self, index): def show_source(self, index):
frame, lineno = self.stack[index] frame, lineno = self.stack[index]
code = frame.f_code code = frame.f_code
...@@ -169,7 +171,7 @@ def get_stack(t=None, f=None): ...@@ -169,7 +171,7 @@ def get_stack(t=None, f=None):
while f is not None: while f is not None:
stack.append((f, f.f_lineno)) stack.append((f, f.f_lineno))
if f is self.botframe: if f is self.botframe:
break break
f = f.f_back f = f.f_back
stack.reverse() stack.reverse()
while t is not None: while t is not None:
...@@ -191,7 +193,7 @@ def getexception(type=None, value=None): ...@@ -191,7 +193,7 @@ def getexception(type=None, value=None):
class NamespaceViewer: class NamespaceViewer:
def __init__(self, master, title, dict=None): def __init__(self, master, title, dict=None):
width = 0 width = 0
height = 40 height = 40
...@@ -217,9 +219,9 @@ class NamespaceViewer: ...@@ -217,9 +219,9 @@ class NamespaceViewer:
self.subframe = subframe = Frame(canvas) self.subframe = subframe = Frame(canvas)
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw") self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
self.load_dict(dict) self.load_dict(dict)
dict = -1 dict = -1
def load_dict(self, dict, force=0): def load_dict(self, dict, force=0):
if dict is self.dict and not force: if dict is self.dict and not force:
return return
......
...@@ -3,6 +3,18 @@ import string ...@@ -3,6 +3,18 @@ import string
from Tkinter import * from Tkinter import *
from Delegator import Delegator 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): class UndoDelegator(Delegator):
...@@ -11,7 +23,7 @@ class UndoDelegator(Delegator): ...@@ -11,7 +23,7 @@ class UndoDelegator(Delegator):
def __init__(self): def __init__(self):
Delegator.__init__(self) Delegator.__init__(self)
self.reset_undo() self.reset_undo()
def setdelegate(self, delegate): def setdelegate(self, delegate):
if self.delegate is not None: if self.delegate is not None:
self.unbind("<<undo>>") self.unbind("<<undo>>")
......
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()
# 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)
#! /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())
# 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",
]
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.
[See end for tips.]
File menu: File menu:
New window -- create a new editing window New window -- create a new editing window
...@@ -75,9 +77,19 @@ Python syntax colors: the coloring is applied in a background thread ...@@ -75,9 +77,19 @@ Python syntax colors: the coloring is applied in a background thread
Comments red Comments red
Definitions blue Definitions blue
Console colors: Shell colors:
Console output red Console output dark red
stdout blue stdout blue
stderr dark green 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.
rem idle.bat
"C:\Program Files\Python\python.exe" "idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9
#! /usr/bin/env python try:
import PyShell import PyShell
PyShell.main() PyShell.main()
except SystemExit:
raise
except:
import traceback
traceback.print_exc()
raw_input("Hit return to exit...")
IDLE_VERSION = "0.2"
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>'],
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment