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