mirror of https://github.com/python/cpython
Initial checking of Tk-based Python IDE.
Features: text editor with syntax coloring and undo; subclassed into interactive Python shell which adds history.
This commit is contained in:
parent
dc1adabcb8
commit
3b4ca0ddad
|
@ -0,0 +1,75 @@
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
|
||||||
|
class AutoExpand:
|
||||||
|
|
||||||
|
wordchars = string.letters + string.digits + "_"
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
self.text.wordlist = None
|
||||||
|
self.state = None
|
||||||
|
self.text.bind("<<expand-word>>", self.autoexpand)
|
||||||
|
|
||||||
|
def autoexpand(self, event):
|
||||||
|
curinsert = self.text.index("insert")
|
||||||
|
curline = self.text.get("insert linestart", "insert lineend")
|
||||||
|
if not self.state:
|
||||||
|
words = self.getwords()
|
||||||
|
index = 0
|
||||||
|
else:
|
||||||
|
words, index, insert, line = self.state
|
||||||
|
if insert != curinsert or line != curline:
|
||||||
|
words = self.getwords()
|
||||||
|
index = 0
|
||||||
|
if not words:
|
||||||
|
self.text.bell()
|
||||||
|
return "break"
|
||||||
|
word = self.getprevword()
|
||||||
|
self.text.delete("insert - %d chars" % len(word), "insert")
|
||||||
|
newword = words[index]
|
||||||
|
index = (index + 1) % len(words)
|
||||||
|
if index == 0:
|
||||||
|
self.text.bell() # Warn we cycled around
|
||||||
|
self.text.insert("insert", newword)
|
||||||
|
curinsert = self.text.index("insert")
|
||||||
|
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:
|
||||||
|
return []
|
||||||
|
before = self.text.get("1.0", "insert wordstart")
|
||||||
|
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
|
||||||
|
del before
|
||||||
|
after = self.text.get("insert wordend", "end")
|
||||||
|
wafter = re.findall(r"\b" + word + r"\w+\b", after)
|
||||||
|
del after
|
||||||
|
if not wbefore and not wafter:
|
||||||
|
return []
|
||||||
|
words = []
|
||||||
|
dict = {}
|
||||||
|
# search backwards through words before
|
||||||
|
wbefore.reverse()
|
||||||
|
for w in wbefore:
|
||||||
|
if dict.get(w):
|
||||||
|
continue
|
||||||
|
words.append(w)
|
||||||
|
dict[w] = w
|
||||||
|
# search onwards through words after
|
||||||
|
for w in wafter:
|
||||||
|
if dict.get(w):
|
||||||
|
continue
|
||||||
|
words.append(w)
|
||||||
|
dict[w] = w
|
||||||
|
words.append(word)
|
||||||
|
return words
|
||||||
|
|
||||||
|
def getprevword(self):
|
||||||
|
line = self.text.get("insert linestart", "insert")
|
||||||
|
i = len(line)
|
||||||
|
while i > 0 and line[i-1] in self.wordchars:
|
||||||
|
i = i-1
|
||||||
|
return line[i:]
|
|
@ -0,0 +1,124 @@
|
||||||
|
import string
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def config(self, **options):
|
||||||
|
for key, value in options.items():
|
||||||
|
if key == 'prefertabs':
|
||||||
|
self.prefertabs = value
|
||||||
|
elif key == 'spaceindent':
|
||||||
|
self.spaceindent = value
|
||||||
|
else:
|
||||||
|
raise KeyError, "bad option name: %s" % `key`
|
||||||
|
|
||||||
|
def autoindent(self, event):
|
||||||
|
text = self.text
|
||||||
|
line = text.get("insert linestart", "insert")
|
||||||
|
i, n = 0, len(line)
|
||||||
|
while i < n and line[i] in " \t":
|
||||||
|
i = i+1
|
||||||
|
indent = line[:i]
|
||||||
|
lastchar = text.get("insert -1c")
|
||||||
|
if lastchar == ":":
|
||||||
|
if not indent:
|
||||||
|
if self.prefertabs:
|
||||||
|
indent = "\t"
|
||||||
|
else:
|
||||||
|
indent = self.spaceindent
|
||||||
|
elif indent[-1] == "\t":
|
||||||
|
indent = indent + "\t"
|
||||||
|
else:
|
||||||
|
indent = indent + self.spaceindent
|
||||||
|
text.insert("insert", "\n" + indent)
|
||||||
|
text.see("insert")
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def indentregion(self, event):
|
||||||
|
head, tail, chars, lines = self.getregion()
|
||||||
|
for pos in range(len(lines)):
|
||||||
|
line = lines[pos]
|
||||||
|
if line:
|
||||||
|
i, n = 0, len(line)
|
||||||
|
while i < n and line[i] in " \t":
|
||||||
|
i = i+1
|
||||||
|
line = line[:i] + " " + line[i:]
|
||||||
|
lines[pos] = line
|
||||||
|
self.setregion(head, tail, chars, lines)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def dedentregion(self, event):
|
||||||
|
head, tail, chars, lines = self.getregion()
|
||||||
|
for pos in range(len(lines)):
|
||||||
|
line = lines[pos]
|
||||||
|
if line:
|
||||||
|
i, n = 0, len(line)
|
||||||
|
while i < n and line[i] in " \t":
|
||||||
|
i = i+1
|
||||||
|
indent, line = line[:i], line[i:]
|
||||||
|
if indent:
|
||||||
|
if indent == "\t" or indent[-2:] == "\t\t":
|
||||||
|
indent = indent[:-1] + " "
|
||||||
|
elif indent[-4:] == " ":
|
||||||
|
indent = indent[:-4]
|
||||||
|
else:
|
||||||
|
indent = string.expandtabs(indent, 8)
|
||||||
|
indent = indent[:-4]
|
||||||
|
line = indent + line
|
||||||
|
lines[pos] = line
|
||||||
|
self.setregion(head, tail, chars, lines)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def commentregion(self, event):
|
||||||
|
head, tail, chars, lines = self.getregion()
|
||||||
|
for pos in range(len(lines)):
|
||||||
|
line = lines[pos]
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
lines[pos] = '##' + line
|
||||||
|
self.setregion(head, tail, chars, lines)
|
||||||
|
|
||||||
|
def uncommentregion(self, event):
|
||||||
|
head, tail, chars, lines = self.getregion()
|
||||||
|
for pos in range(len(lines)):
|
||||||
|
line = lines[pos]
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line[:2] == '##':
|
||||||
|
line = line[2:]
|
||||||
|
elif line[:1] == '#':
|
||||||
|
line = line[1:]
|
||||||
|
lines[pos] = line
|
||||||
|
self.setregion(head, tail, chars, lines)
|
||||||
|
|
||||||
|
def getregion(self):
|
||||||
|
text = self.text
|
||||||
|
head = text.index("sel.first linestart")
|
||||||
|
tail = text.index("sel.last -1c lineend +1c")
|
||||||
|
if not (head and tail):
|
||||||
|
head = text.index("insert linestart")
|
||||||
|
tail = text.index("insert lineend +1c")
|
||||||
|
chars = text.get(head, tail)
|
||||||
|
lines = string.split(chars, "\n")
|
||||||
|
return head, tail, chars, lines
|
||||||
|
|
||||||
|
def setregion(self, head, tail, chars, lines):
|
||||||
|
text = self.text
|
||||||
|
newchars = string.join(lines, "\n")
|
||||||
|
if newchars == chars:
|
||||||
|
text.bell()
|
||||||
|
return
|
||||||
|
text.tag_remove("sel", "1.0", "end")
|
||||||
|
text.mark_set("insert", head)
|
||||||
|
text.delete(head, tail)
|
||||||
|
text.insert(head, newchars)
|
||||||
|
text.tag_add("sel", head, "insert")
|
|
@ -0,0 +1,92 @@
|
||||||
|
# The first item of each tuple is the virtual event;
|
||||||
|
# each of the remaining items is an actual key binding for the event.
|
||||||
|
# (This conveniently forms an argument list for event_add().)
|
||||||
|
|
||||||
|
win_bindings = [
|
||||||
|
("<<beginning-of-line>>", "<Control-a>", "<Home>"),
|
||||||
|
|
||||||
|
("<<expand-word>>", "<Meta-slash>", "<Alt-slash>"),
|
||||||
|
|
||||||
|
("<<newline-and-indent>>", "<Key-Return>", "<KP_Enter>"),
|
||||||
|
("<<plain-newline-and-indent>>", "<Control-j>"),
|
||||||
|
|
||||||
|
("<<interrupt-execution>>", "<Control-c>"),
|
||||||
|
("<<end-of-file>>", "<Control-d>"),
|
||||||
|
|
||||||
|
("<<dedent-region>>", "<Control-bracketleft>"),
|
||||||
|
("<<indent-region>>", "<Control-bracketright>"),
|
||||||
|
|
||||||
|
("<<comment-region>>", "<Meta-Key-3>", "<Alt-Key-3>"),
|
||||||
|
("<<uncomment-region>>", "<Meta-Key-4>", "<Alt-Key-4>"),
|
||||||
|
|
||||||
|
("<<history-previous>>", "<Meta-p>", "<Alt-p>"),
|
||||||
|
("<<history-next>>", "<Meta-n>", "<Alt-n>"),
|
||||||
|
|
||||||
|
("<<toggle-auto-coloring>>", "<Control-slash>"),
|
||||||
|
|
||||||
|
("<<close-all-windows>>", "<Control-q>"),
|
||||||
|
("<<open-new-window>>", "<Control-n>"),
|
||||||
|
("<<open-window-from-file>>", "<Control-o>"),
|
||||||
|
("<<save-window>>", "<Control-s>"),
|
||||||
|
("<<save-window-as-file>>", "<Control-w>"),
|
||||||
|
("<<save-copy-of-window-as-file>>", "<Meta-w>"),
|
||||||
|
|
||||||
|
("<<find>>", "<Control-f>"),
|
||||||
|
("<<find-next>>", "<F3>"),
|
||||||
|
("<<find-same>>", "<Control-F3>"),
|
||||||
|
("<<goto-line>>", "<Alt-g>", "<Meta-g>"),
|
||||||
|
|
||||||
|
("<<undo>>", "<Control-z>"),
|
||||||
|
("<<redo>>", "<Control-y>"),
|
||||||
|
("<<dump-undo-state>>", "<Control-backslash>"),
|
||||||
|
]
|
||||||
|
|
||||||
|
emacs_bindings = [
|
||||||
|
("<<beginning-of-line>>", "<Control-a>", "<Home>"),
|
||||||
|
("<<center-insert>>", "<Control-l>"),
|
||||||
|
|
||||||
|
("<<expand-word>>", "<Meta-slash>", "<Alt-slash>"),
|
||||||
|
|
||||||
|
("<<newline-and-indent>>", "<Key-Return>", "<KP_Enter>"),
|
||||||
|
("<<plain-newline-and-indent>>", "<Control-j>"),
|
||||||
|
|
||||||
|
("<<interrupt-execution>>", "<Control-c>"),
|
||||||
|
("<<end-of-file>>", "<Control-d>"),
|
||||||
|
|
||||||
|
("<<dedent-region>>",
|
||||||
|
"<Meta-bracketleft>", "<Alt-bracketleft>", "<Control-bracketleft>"),
|
||||||
|
("<<indent-region>>",
|
||||||
|
"<Meta-bracketright>", "<Alt-bracketright>", "<Control-bracketright>"),
|
||||||
|
|
||||||
|
("<<comment-region>>", "<Meta-Key-3>", "<Alt-Key-3>"),
|
||||||
|
("<<uncomment-region>>", "<Meta-Key-4>", "<Alt-Key-4>"),
|
||||||
|
|
||||||
|
("<<history-previous>>", "<Meta-p>", "<Alt-p>"),
|
||||||
|
("<<history-next>>", "<Meta-n>", "<Alt-n>"),
|
||||||
|
|
||||||
|
("<<toggle-auto-coloring>>", "<Control-slash>"),
|
||||||
|
|
||||||
|
("<<close-all-windows>>", "<Control-x><Control-c>"),
|
||||||
|
("<<close-window>>", "<Control-x><Control-0>"),
|
||||||
|
("<<open-new-window>>", "<Control-x><Control-n>"),
|
||||||
|
("<<open-window-from-file>>", "<Control-x><Control-f>"),
|
||||||
|
("<<save-window>>", "<Control-x><Control-s>"),
|
||||||
|
("<<save-window-as-file>>", "<Control-x><Control-w>"),
|
||||||
|
("<<save-copy-of-window-as-file>>", "<Control-x><w>"),
|
||||||
|
|
||||||
|
("<<find>>", "<Control-u><Control-u><Control-s>"),
|
||||||
|
("<<find-next>>", "<Control-u><Control-s>"),
|
||||||
|
("<<find-same>>", "<Control-s>"),
|
||||||
|
("<<goto-line>>", "<Alt-g>", "<Meta-g>"),
|
||||||
|
|
||||||
|
("<<undo>>", "<Control-z>"),
|
||||||
|
("<<redo>>", "<Alt-z>", "<Meta-z>"),
|
||||||
|
("<<dump-undo-state>>", "<Control-backslash>"),
|
||||||
|
]
|
||||||
|
|
||||||
|
default_bindings = emacs_bindings
|
||||||
|
|
||||||
|
def apply_bindings(text, bindings=default_bindings):
|
||||||
|
event_add = text.event_add
|
||||||
|
for args in bindings:
|
||||||
|
apply(event_add, args)
|
|
@ -0,0 +1,203 @@
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
import keyword
|
||||||
|
from Tkinter import *
|
||||||
|
from Delegator import Delegator
|
||||||
|
|
||||||
|
__debug__ = 0
|
||||||
|
|
||||||
|
|
||||||
|
def any(name, list):
|
||||||
|
return "(?P<%s>" % name + string.join(list, "|") + ")"
|
||||||
|
|
||||||
|
def make_pat():
|
||||||
|
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
|
||||||
|
comment = any("COMMENT", [r"#[^\n]*"])
|
||||||
|
sqstring = r"(\b[rR])?'([^'\\\n]|\\.)*'?"
|
||||||
|
dqstring = r'(\b[rR])?"([^"\\\n]|\\.)*"?'
|
||||||
|
sq3string = r"(\b[rR])?'''([^'\\]|\\.|'(?!''))*(''')?"
|
||||||
|
dq3string = r'(\b[rR])?"""([^"\\]|\\.|"(?!""))*(""")?'
|
||||||
|
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
|
||||||
|
return kw + "|" + comment + "|" + string + "|" + any("SYNC", [r"\n"])
|
||||||
|
|
||||||
|
prog = re.compile(make_pat(), re.S)
|
||||||
|
idprog = re.compile(r"\s+(\w+)", re.S)
|
||||||
|
|
||||||
|
class ColorDelegator(Delegator):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Delegator.__init__(self)
|
||||||
|
self.prog = prog
|
||||||
|
self.idprog = idprog
|
||||||
|
|
||||||
|
def setdelegate(self, delegate):
|
||||||
|
if self.delegate is not None:
|
||||||
|
self.unbind("<<toggle-auto-coloring>>")
|
||||||
|
Delegator.setdelegate(self, delegate)
|
||||||
|
if delegate is not None:
|
||||||
|
self.config_colors()
|
||||||
|
self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
|
||||||
|
self.notify_range("1.0", "end")
|
||||||
|
|
||||||
|
def config_colors(self):
|
||||||
|
for tag, cnf in self.tagdefs.items():
|
||||||
|
if cnf:
|
||||||
|
apply(self.tag_configure, (tag,), cnf)
|
||||||
|
|
||||||
|
tagdefs = {
|
||||||
|
"COMMENT": {"foreground": "#dd0000"},
|
||||||
|
"KEYWORD": {"foreground": "#ff7700"},
|
||||||
|
"STRING": {"foreground": "#00aa00"},
|
||||||
|
"DEFINITION": {"foreground": "#0000ff"},
|
||||||
|
|
||||||
|
"SYNC": {}, #{"background": "#ffff00"},
|
||||||
|
"TODO": {}, #{"background": "#cccccc"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def insert(self, index, chars, tags=None):
|
||||||
|
index = self.index(index)
|
||||||
|
self.delegate.insert(index, chars, tags)
|
||||||
|
self.notify_range(index, index + "+%dc" % len(chars))
|
||||||
|
|
||||||
|
def delete(self, index1, index2=None):
|
||||||
|
index1 = self.index(index1)
|
||||||
|
self.delegate.delete(index1, index2)
|
||||||
|
self.notify_range(index1)
|
||||||
|
|
||||||
|
after_id = None
|
||||||
|
allow_colorizing = 1
|
||||||
|
colorizing = 0
|
||||||
|
|
||||||
|
def notify_range(self, index1, index2=None):
|
||||||
|
self.tag_add("TODO", index1, index2)
|
||||||
|
if self.after_id:
|
||||||
|
if __debug__: print "colorizing already scheduled"
|
||||||
|
return
|
||||||
|
if self.colorizing:
|
||||||
|
self.stop_colorizing = 1
|
||||||
|
if __debug__: print "stop colorizing"
|
||||||
|
if self.allow_colorizing:
|
||||||
|
if __debug__: print "schedule colorizing"
|
||||||
|
self.after_id = self.after(1, self.recolorize)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.after_id:
|
||||||
|
after_id = self.after_id
|
||||||
|
self.after_id = None
|
||||||
|
if __debug__: print "cancel scheduled recolorizer"
|
||||||
|
self.after_cancel(after_id)
|
||||||
|
self.allow_colorizing = 0
|
||||||
|
self.stop_colorizing = 1
|
||||||
|
|
||||||
|
def toggle_colorize_event(self, event):
|
||||||
|
if self.after_id:
|
||||||
|
after_id = self.after_id
|
||||||
|
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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.stop_colorizing = 0
|
||||||
|
self.colorizing = 1
|
||||||
|
if __debug__: print "colorizing..."
|
||||||
|
t0 = time.clock()
|
||||||
|
self.recolorize_main()
|
||||||
|
t1 = time.clock()
|
||||||
|
if __debug__: print "%.3f seconds" % (t1-t0)
|
||||||
|
finally:
|
||||||
|
self.colorizing = 0
|
||||||
|
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
|
||||||
|
if __debug__: print "reschedule colorizing"
|
||||||
|
self.after_id = self.after(1, self.recolorize)
|
||||||
|
|
||||||
|
def recolorize_main(self):
|
||||||
|
next = "1.0"
|
||||||
|
was_ok = is_ok = 0
|
||||||
|
while 1:
|
||||||
|
item = self.tag_nextrange("TODO", next)
|
||||||
|
if not item:
|
||||||
|
break
|
||||||
|
head, tail = item
|
||||||
|
self.tag_remove("SYNC", head, tail)
|
||||||
|
item = self.tag_prevrange("SYNC", head)
|
||||||
|
if item:
|
||||||
|
head = item[1]
|
||||||
|
else:
|
||||||
|
head = "1.0"
|
||||||
|
|
||||||
|
chars = ""
|
||||||
|
mark = head
|
||||||
|
is_ok = was_ok = 0
|
||||||
|
while not (was_ok and is_ok):
|
||||||
|
next = self.index(mark + " lineend +1c")
|
||||||
|
was_ok = "SYNC" in self.tag_names(next + "-1c")
|
||||||
|
line = self.get(mark, next)
|
||||||
|
##print head, "get", mark, next, "->", `line`
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
for tag in self.tagdefs.keys():
|
||||||
|
self.tag_remove(tag, mark, next)
|
||||||
|
chars = chars + line
|
||||||
|
m = self.prog.search(chars)
|
||||||
|
while m:
|
||||||
|
i, j = m.span()
|
||||||
|
for key, value in m.groupdict().items():
|
||||||
|
if value:
|
||||||
|
a, b = m.span(key)
|
||||||
|
self.tag_add(key,
|
||||||
|
head + "+%dc" % a,
|
||||||
|
head + "+%dc" % b)
|
||||||
|
if value in ("def", "class"):
|
||||||
|
m1 = self.idprog.match(chars, b)
|
||||||
|
if m1:
|
||||||
|
a, b = m1.span(1)
|
||||||
|
self.tag_add("DEFINITION",
|
||||||
|
head + "+%dc" % a,
|
||||||
|
head + "+%dc" % b)
|
||||||
|
m = self.prog.search(chars, j)
|
||||||
|
is_ok = "SYNC" in self.tag_names(next + "-1c")
|
||||||
|
mark = next
|
||||||
|
if is_ok:
|
||||||
|
head = mark
|
||||||
|
chars = ""
|
||||||
|
self.update()
|
||||||
|
if self.stop_colorizing:
|
||||||
|
if __debug__: print "colorizing stopped"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
from Percolator import Percolator
|
||||||
|
root = Tk()
|
||||||
|
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||||
|
text = Text(background="white")
|
||||||
|
text.pack(expand=1, fill="both")
|
||||||
|
text.focus_set()
|
||||||
|
p = Percolator(text)
|
||||||
|
d = ColorDelegator()
|
||||||
|
p.insertfilter(d)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,33 @@
|
||||||
|
class Delegator:
|
||||||
|
|
||||||
|
# The cache is only used to be able to change delegates!
|
||||||
|
|
||||||
|
def __init__(self, delegate=None):
|
||||||
|
self.delegate = delegate
|
||||||
|
self.__cache = {}
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
attr = getattr(self.delegate, name) # May raise AttributeError
|
||||||
|
setattr(self, name, attr)
|
||||||
|
self.__cache[name] = attr
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def resetcache(self):
|
||||||
|
for key in self.__cache.keys():
|
||||||
|
try:
|
||||||
|
delattr(self, key)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.__cache.clear()
|
||||||
|
|
||||||
|
def cachereport(self):
|
||||||
|
keys = self.__cache.keys()
|
||||||
|
keys.sort()
|
||||||
|
print keys
|
||||||
|
|
||||||
|
def setdelegate(self, delegate):
|
||||||
|
self.resetcache()
|
||||||
|
self.delegate = delegate
|
||||||
|
|
||||||
|
def getdelegate(self):
|
||||||
|
return self.delegate
|
|
@ -0,0 +1,175 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
|
||||||
|
class EditorWindow:
|
||||||
|
|
||||||
|
from Percolator import Percolator
|
||||||
|
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
|
||||||
|
|
||||||
|
def __init__(self, root, filename=None):
|
||||||
|
self.top = top = Toplevel(root)
|
||||||
|
self.vbar = vbar = Scrollbar(top, name='vbar')
|
||||||
|
self.text = text = Text(top, name='text')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
##print "Initial colorizer"
|
||||||
|
else:
|
||||||
|
##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)
|
||||||
|
|
||||||
|
undo.set_saved_change_hook(self.saved_change_hook)
|
||||||
|
io.set_filename_change_hook(self.filename_change_hook)
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
if os.path.exists(filename):
|
||||||
|
io.loadfile(filename)
|
||||||
|
else:
|
||||||
|
io.set_filename(filename)
|
||||||
|
|
||||||
|
self.saved_change_hook()
|
||||||
|
|
||||||
|
def gotoline(self, lineno):
|
||||||
|
if lineno is not None and lineno > 0:
|
||||||
|
self.text.mark_set("insert", "%d.0" % lineno)
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.text.tag_add("sel", "insert", "insert +1l")
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def ispythonsource(self, filename):
|
||||||
|
if not filename:
|
||||||
|
return 1
|
||||||
|
if os.path.normcase(filename[-3:]) == ".py":
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
line = f.readline()
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
return 0
|
||||||
|
return line[:2] == '#!' and string.find(line, 'python') >= 0
|
||||||
|
|
||||||
|
close_hook = None
|
||||||
|
|
||||||
|
def set_close_hook(self, close_hook):
|
||||||
|
self.close_hook = close_hook
|
||||||
|
|
||||||
|
def filename_change_hook(self):
|
||||||
|
self.saved_change_hook()
|
||||||
|
if self.ispythonsource(self.io.filename):
|
||||||
|
self.addcolorizer()
|
||||||
|
else:
|
||||||
|
self.rmcolorizer()
|
||||||
|
|
||||||
|
def addcolorizer(self):
|
||||||
|
if self.color:
|
||||||
|
return
|
||||||
|
##print "Add colorizer"
|
||||||
|
self.per.removefilter(self.undo)
|
||||||
|
self.color = self.ColorDelegator()
|
||||||
|
self.per.insertfilter(self.color)
|
||||||
|
self.per.insertfilter(self.undo)
|
||||||
|
|
||||||
|
def rmcolorizer(self):
|
||||||
|
if not self.color:
|
||||||
|
return
|
||||||
|
##print "Remove colorizer"
|
||||||
|
self.per.removefilter(self.undo)
|
||||||
|
self.per.removefilter(self.color)
|
||||||
|
self.color = None
|
||||||
|
self.per.insertfilter(self.undo)
|
||||||
|
|
||||||
|
def saved_change_hook(self):
|
||||||
|
if self.io.filename:
|
||||||
|
title = self.io.filename
|
||||||
|
else:
|
||||||
|
title = "(Untitled)"
|
||||||
|
if not self.undo.get_saved():
|
||||||
|
title = title + " *"
|
||||||
|
self.top.wm_title(title)
|
||||||
|
|
||||||
|
def center_insert_event(self, event):
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def center(self, mark="insert"):
|
||||||
|
insert = float(self.text.index(mark + " linestart"))
|
||||||
|
end = float(self.text.index("end"))
|
||||||
|
if insert > end-insert:
|
||||||
|
self.text.see("1.0")
|
||||||
|
else:
|
||||||
|
self.text.see("end")
|
||||||
|
self.text.see(mark)
|
||||||
|
|
||||||
|
def close_event(self, event):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.top.wm_deiconify()
|
||||||
|
self.top.tkraise()
|
||||||
|
reply = self.io.maybesave()
|
||||||
|
if reply != "cancel":
|
||||||
|
if self.color and self.color.colorizing:
|
||||||
|
self.color.close()
|
||||||
|
self.top.bell()
|
||||||
|
return "cancel"
|
||||||
|
if self.close_hook:
|
||||||
|
self.close_hook()
|
||||||
|
if self.color:
|
||||||
|
self.color.close() # Cancel colorization
|
||||||
|
self.top.destroy()
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def fixwordbreaks(root):
|
||||||
|
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_]')
|
||||||
|
tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
root = Tk()
|
||||||
|
fixwordbreaks(root)
|
||||||
|
root.withdraw()
|
||||||
|
if sys.argv[1:]:
|
||||||
|
filename = sys.argv[1]
|
||||||
|
else:
|
||||||
|
filename = None
|
||||||
|
edit = EditorWindow(root, filename)
|
||||||
|
edit.set_close_hook(root.quit)
|
||||||
|
root.mainloop()
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
|
@ -0,0 +1,169 @@
|
||||||
|
import os
|
||||||
|
from Tkinter import *
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
from EditorWindow import EditorWindow, fixwordbreaks
|
||||||
|
from IOBinding import IOBinding
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
from PopupMenu import PopupMenu
|
||||||
|
|
||||||
|
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.popup = self.PopupMenu(self.text, self.flist)
|
||||||
|
self.text.bind("<<open-new-window>>", self.flist.new_callback)
|
||||||
|
self.text.bind("<<close-all-windows>>", self.flist.close_all_callback)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class FileList:
|
||||||
|
|
||||||
|
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:
|
||||||
|
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.top.tkraise()
|
||||||
|
edit.top.wm_deiconify()
|
||||||
|
edit.text.focus_set()
|
||||||
|
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.top.tkraise()
|
||||||
|
edit.top.wm_deiconify()
|
||||||
|
edit.text.focus_set()
|
||||||
|
return edit
|
||||||
|
else:
|
||||||
|
key = None
|
||||||
|
edit = MultiEditorWindow(self, filename, key)
|
||||||
|
return edit
|
||||||
|
|
||||||
|
def new_callback(self, event):
|
||||||
|
self.new()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def close_all_callback(self, event):
|
||||||
|
for edit in self.inversedict.keys():
|
||||||
|
reply = edit.close()
|
||||||
|
if reply == "cancel":
|
||||||
|
break
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def close_edit(self, edit):
|
||||||
|
try:
|
||||||
|
key = self.inversedict[edit]
|
||||||
|
except KeyError:
|
||||||
|
print "Don't know this EditorWindow object. (close)"
|
||||||
|
return
|
||||||
|
if key:
|
||||||
|
del self.dict[key]
|
||||||
|
del self.inversedict[edit]
|
||||||
|
if not self.inversedict:
|
||||||
|
self.root.quit()
|
||||||
|
|
||||||
|
def filename_changed_edit(self, edit):
|
||||||
|
edit.saved_change_hook()
|
||||||
|
try:
|
||||||
|
key = self.inversedict[edit]
|
||||||
|
except KeyError:
|
||||||
|
print "Don't know this EditorWindow object. (rename)"
|
||||||
|
return
|
||||||
|
filename = edit.io.filename
|
||||||
|
if not filename:
|
||||||
|
if key:
|
||||||
|
del self.dict[key]
|
||||||
|
self.inversedict[edit] = None
|
||||||
|
return
|
||||||
|
filename = self.canonize(filename)
|
||||||
|
newkey = os.path.normcase(filename)
|
||||||
|
if newkey == key:
|
||||||
|
return
|
||||||
|
if self.dict.has_key(newkey):
|
||||||
|
conflict = self.dict[newkey]
|
||||||
|
self.inversedict[conflict] = None
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"Name Conflict",
|
||||||
|
"You now have multiple edit windows open for %s" % `filename`,
|
||||||
|
master=self.root)
|
||||||
|
self.dict[newkey] = edit
|
||||||
|
self.inversedict[edit] = newkey
|
||||||
|
if key:
|
||||||
|
try:
|
||||||
|
del self.dict[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def canonize(self, filename):
|
||||||
|
if not os.path.isabs(filename):
|
||||||
|
try:
|
||||||
|
pwd = os.getcwd()
|
||||||
|
except os.error:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
filename = os.path.join(pwd, filename)
|
||||||
|
return os.path.normpath(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
import sys
|
||||||
|
root = Tk()
|
||||||
|
fixwordbreaks(root)
|
||||||
|
root.withdraw()
|
||||||
|
flist = FileList(root)
|
||||||
|
if sys.argv[1:]:
|
||||||
|
for filename in sys.argv[1:]:
|
||||||
|
flist.open(filename)
|
||||||
|
else:
|
||||||
|
flist.new()
|
||||||
|
if flist.inversedict:
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
|
@ -0,0 +1,38 @@
|
||||||
|
from repr import Repr
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
class FrameViewer:
|
||||||
|
|
||||||
|
def __init__(self, root, frame):
|
||||||
|
self.root = root
|
||||||
|
self.frame = frame
|
||||||
|
self.top = Toplevel(self.root)
|
||||||
|
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:
|
||||||
|
l = Label(self.top, text="Local Variables",
|
||||||
|
borderwidth=2, relief="raised")
|
||||||
|
l.grid(row=row, column=0, columnspan=2, sticky="ew")
|
||||||
|
row = self.load_names(self.frame.f_locals, row+1)
|
||||||
|
l = Label(self.top, text="Global Variables",
|
||||||
|
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()
|
||||||
|
for name in names:
|
||||||
|
value = dict[name]
|
||||||
|
svalue = self.repr.repr(value)
|
||||||
|
l = Label(self.top, text=name)
|
||||||
|
l.grid(row=row, column=0, sticky="w")
|
||||||
|
l = Entry(self.top, width=60, borderwidth=0)
|
||||||
|
l.insert(0, svalue)
|
||||||
|
l.grid(row=row, column=1, sticky="w")
|
||||||
|
row = row+1
|
||||||
|
return row
|
|
@ -0,0 +1,65 @@
|
||||||
|
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()
|
|
@ -0,0 +1,73 @@
|
||||||
|
import string
|
||||||
|
|
||||||
|
class History:
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
self.history = []
|
||||||
|
self.history_prefix = None
|
||||||
|
self.history_pointer = None
|
||||||
|
text.bind("<<history-previous>>", self.history_prev)
|
||||||
|
text.bind("<<history-next>>", self.history_next)
|
||||||
|
|
||||||
|
def history_next(self, event):
|
||||||
|
self.history_do(0)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def history_prev(self, event):
|
||||||
|
self.history_do(1)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def history_do(self, reverse):
|
||||||
|
nhist = len(self.history)
|
||||||
|
pointer = self.history_pointer
|
||||||
|
prefix = self.history_prefix
|
||||||
|
if pointer is not None and prefix is not None:
|
||||||
|
if self.text.compare("insert", "!=", "end-1c") or \
|
||||||
|
self.text.get("iomark", "end-1c") != self.history[pointer]:
|
||||||
|
pointer = prefix = None
|
||||||
|
if pointer is None or prefix is None:
|
||||||
|
prefix = self.text.get("iomark", "end-1c")
|
||||||
|
if reverse:
|
||||||
|
pointer = nhist
|
||||||
|
else:
|
||||||
|
pointer = -1
|
||||||
|
nprefix = len(prefix)
|
||||||
|
while 1:
|
||||||
|
if reverse:
|
||||||
|
pointer = pointer - 1
|
||||||
|
else:
|
||||||
|
pointer = pointer + 1
|
||||||
|
if pointer < 0 or pointer >= nhist:
|
||||||
|
self.text.bell()
|
||||||
|
if self.text.get("iomark", "end-1c") != prefix:
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.insert("iomark", prefix)
|
||||||
|
pointer = prefix = None
|
||||||
|
break
|
||||||
|
item = self.history[pointer]
|
||||||
|
if item[:nprefix] == prefix and len(item) > nprefix:
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.insert("iomark", item)
|
||||||
|
break
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
self.text.see("insert")
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.history_pointer = pointer
|
||||||
|
self.history_prefix = prefix
|
||||||
|
|
||||||
|
def history_store(self, source):
|
||||||
|
source = string.strip(source)
|
||||||
|
if len(source) > 2:
|
||||||
|
self.history.append(source)
|
||||||
|
self.history_pointer = None
|
||||||
|
self.history_prefix = None
|
||||||
|
|
||||||
|
def recall(self, s):
|
||||||
|
s = string.strip(s)
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
self.text.insert("insert", s)
|
||||||
|
self.text.see("insert")
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import os
|
||||||
|
import tkFileDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
|
||||||
|
class IOBinding:
|
||||||
|
|
||||||
|
# Calls to non-standard text methods:
|
||||||
|
# reset_undo()
|
||||||
|
# set_saved(1)
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = 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)
|
||||||
|
|
||||||
|
filename_change_hook = None
|
||||||
|
|
||||||
|
def set_filename_change_hook(self, hook):
|
||||||
|
self.filename_change_hook = hook
|
||||||
|
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
def set_filename(self, filename):
|
||||||
|
self.filename = filename
|
||||||
|
self.text.set_saved(1)
|
||||||
|
if self.filename_change_hook:
|
||||||
|
self.filename_change_hook()
|
||||||
|
|
||||||
|
def open(self, event):
|
||||||
|
if not self.text.get_saved():
|
||||||
|
reply = self.maybesave()
|
||||||
|
if reply == "cancel":
|
||||||
|
return "break"
|
||||||
|
filename = self.askopenfile()
|
||||||
|
if filename:
|
||||||
|
self.loadfile(filename)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def loadfile(self, filename):
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
chars = f.read()
|
||||||
|
f.close()
|
||||||
|
except IOError, msg:
|
||||||
|
tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
|
||||||
|
return 0
|
||||||
|
self.text.delete("1.0", "end")
|
||||||
|
self.set_filename(None)
|
||||||
|
self.text.insert("1.0", chars)
|
||||||
|
self.text.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():
|
||||||
|
return "yes"
|
||||||
|
message = "Do you want to save %s before closing?" % (
|
||||||
|
self.filename or "this untitled document")
|
||||||
|
m = tkMessageBox.Message(
|
||||||
|
title="Save On Close",
|
||||||
|
message=message,
|
||||||
|
icon=tkMessageBox.QUESTION,
|
||||||
|
type=tkMessageBox.YESNOCANCEL,
|
||||||
|
master=self.text)
|
||||||
|
reply = m.show()
|
||||||
|
if reply == "yes":
|
||||||
|
self.save(None)
|
||||||
|
if not self.text.get_saved():
|
||||||
|
reply = "cancel"
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def save(self, event):
|
||||||
|
if not self.filename:
|
||||||
|
self.save_as(event)
|
||||||
|
else:
|
||||||
|
if self.writefile(self.filename):
|
||||||
|
self.text.set_saved(1)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def save_as(self, event):
|
||||||
|
filename = self.asksavefile()
|
||||||
|
if filename:
|
||||||
|
if self.writefile(filename):
|
||||||
|
self.set_filename(filename)
|
||||||
|
self.text.set_saved(1)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def save_a_copy(self, event):
|
||||||
|
filename = self.asksavefile()
|
||||||
|
if filename:
|
||||||
|
self.writefile(filename)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def writefile(self, filename):
|
||||||
|
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
|
||||||
|
except IOError, msg:
|
||||||
|
tkMessageBox.showerror("I/O Error", str(msg),
|
||||||
|
master=self.text)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
opendialog = None
|
||||||
|
savedialog = None
|
||||||
|
|
||||||
|
filetypes = [
|
||||||
|
("Python files", "*.py", "TEXT"),
|
||||||
|
("All text files", "*", "TEXT"),
|
||||||
|
("All files", "*"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def askopenfile(self):
|
||||||
|
dir, base = self.defaultfilename("open")
|
||||||
|
if not self.opendialog:
|
||||||
|
self.opendialog = tkFileDialog.Open(master=self.text,
|
||||||
|
filetypes=self.filetypes)
|
||||||
|
return self.opendialog.show(initialdir=dir, initialfile=base)
|
||||||
|
|
||||||
|
def defaultfilename(self, mode="open"):
|
||||||
|
if self.filename:
|
||||||
|
dir, base = os.path.split(self.filename)
|
||||||
|
else:
|
||||||
|
dir = base = ""
|
||||||
|
return dir, base
|
||||||
|
|
||||||
|
def asksavefile(self):
|
||||||
|
dir, base = self.defaultfilename("save")
|
||||||
|
if not self.savedialog:
|
||||||
|
self.savedialog = tkFileDialog.SaveAs(master=self.text,
|
||||||
|
filetypes=self.filetypes)
|
||||||
|
return self.savedialog.show(initialdir=dir, initialfile=base)
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
from Tkinter import *
|
||||||
|
root = Tk()
|
||||||
|
class MyText(Text):
|
||||||
|
def reset_undo(self): pass
|
||||||
|
def set_saved(self, flag): pass
|
||||||
|
text = MyText(root)
|
||||||
|
text.pack()
|
||||||
|
text.focus_set()
|
||||||
|
io = IOBinding(text)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test()
|
|
@ -0,0 +1,73 @@
|
||||||
|
import string
|
||||||
|
|
||||||
|
class History:
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
self.history = []
|
||||||
|
self.history_prefix = None
|
||||||
|
self.history_pointer = None
|
||||||
|
text.bind("<<history-previous>>", self.history_prev)
|
||||||
|
text.bind("<<history-next>>", self.history_next)
|
||||||
|
|
||||||
|
def history_next(self, event):
|
||||||
|
self.history_do(0)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def history_prev(self, event):
|
||||||
|
self.history_do(1)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def history_do(self, reverse):
|
||||||
|
nhist = len(self.history)
|
||||||
|
pointer = self.history_pointer
|
||||||
|
prefix = self.history_prefix
|
||||||
|
if pointer is not None and prefix is not None:
|
||||||
|
if self.text.compare("insert", "!=", "end-1c") or \
|
||||||
|
self.text.get("iomark", "end-1c") != self.history[pointer]:
|
||||||
|
pointer = prefix = None
|
||||||
|
if pointer is None or prefix is None:
|
||||||
|
prefix = self.text.get("iomark", "end-1c")
|
||||||
|
if reverse:
|
||||||
|
pointer = nhist
|
||||||
|
else:
|
||||||
|
pointer = -1
|
||||||
|
nprefix = len(prefix)
|
||||||
|
while 1:
|
||||||
|
if reverse:
|
||||||
|
pointer = pointer - 1
|
||||||
|
else:
|
||||||
|
pointer = pointer + 1
|
||||||
|
if pointer < 0 or pointer >= nhist:
|
||||||
|
self.text.bell()
|
||||||
|
if self.text.get("iomark", "end-1c") != prefix:
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.insert("iomark", prefix)
|
||||||
|
pointer = prefix = None
|
||||||
|
break
|
||||||
|
item = self.history[pointer]
|
||||||
|
if item[:nprefix] == prefix and len(item) > nprefix:
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.insert("iomark", item)
|
||||||
|
break
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
self.text.see("insert")
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.history_pointer = pointer
|
||||||
|
self.history_prefix = prefix
|
||||||
|
|
||||||
|
def history_store(self, source):
|
||||||
|
source = string.strip(source)
|
||||||
|
if len(source) > 2:
|
||||||
|
self.history.append(source)
|
||||||
|
self.history_pointer = None
|
||||||
|
self.history_prefix = None
|
||||||
|
|
||||||
|
def recall(self, s):
|
||||||
|
s = string.strip(s)
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.text.delete("iomark", "end-1c")
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
self.text.insert("insert", s)
|
||||||
|
self.text.see("insert")
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
class Outline:
|
||||||
|
|
||||||
|
def __init__(self, root=None):
|
||||||
|
if not root:
|
||||||
|
import Tkinter
|
||||||
|
root = Tkinter._default_root
|
||||||
|
if not root:
|
||||||
|
root = top = Tk()
|
||||||
|
else:
|
||||||
|
top = Toplevel(root)
|
||||||
|
top.wm_title("Outline")
|
||||||
|
self.canvas = canvas = Canvas(top, width=400, height=300,
|
||||||
|
borderwidth=2, relief="sunken",
|
||||||
|
background="#FFBBBB")
|
||||||
|
canvas.pack(expand=1, fill="both")
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
def additem(self, level, open, label):
|
||||||
|
x = 15*level + 5
|
||||||
|
y = 15*len(self.items) + 5
|
||||||
|
if open:
|
||||||
|
id1 = self.canvas.create_polygon(x+3, y+3, x+13, y+3, x+8, y+8,
|
||||||
|
outline="black",
|
||||||
|
fill="green")
|
||||||
|
else:
|
||||||
|
id1 = self.canvas.create_polygon(x+3, y+4, x+7, y+8, x+3, y+12,
|
||||||
|
outline="black",
|
||||||
|
fill="red")
|
||||||
|
w = Entry(self.canvas, borderwidth=0, background="#FFBBBB", width=0)
|
||||||
|
w.insert("end", label)
|
||||||
|
id2 = self.canvas.create_window(x+15, y, anchor="nw", window=w)
|
||||||
|
self.items.append((level, open, label, id1, w, id2))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
o = Outline()
|
||||||
|
o.additem(0, 1, "hello world")
|
||||||
|
o.additem(1, 0, "sub1")
|
||||||
|
o.additem(1, 1, "sub2")
|
||||||
|
o.additem(2, 0, "sub2.a")
|
||||||
|
o.additem(2, 0, "sub2.b")
|
||||||
|
o.additem(1, 0, "sub3")
|
||||||
|
|
||||||
|
main()
|
|
@ -0,0 +1,77 @@
|
||||||
|
from WidgetRedirector import WidgetRedirector
|
||||||
|
from Delegator import Delegator
|
||||||
|
|
||||||
|
class Percolator:
|
||||||
|
|
||||||
|
def __init__(self, text):
|
||||||
|
# XXX would be nice to inherit from Delegator
|
||||||
|
self.text = text
|
||||||
|
self.redir = WidgetRedirector(text)
|
||||||
|
self.top = self.bottom = Delegator(text)
|
||||||
|
self.bottom.insert = self.redir.register("insert", self.insert)
|
||||||
|
self.bottom.delete = self.redir.register("delete", self.delete)
|
||||||
|
self.filters = []
|
||||||
|
|
||||||
|
def insert(self, index, chars, tags=None):
|
||||||
|
# Could go away if inheriting from Delegator
|
||||||
|
self.top.insert(index, chars, tags)
|
||||||
|
|
||||||
|
def delete(self, index1, index2=None):
|
||||||
|
# Could go away if inheriting from Delegator
|
||||||
|
self.top.delete(index1, index2)
|
||||||
|
|
||||||
|
def insertfilter(self, filter):
|
||||||
|
# Perhaps rename to pushfilter()?
|
||||||
|
assert isinstance(filter, Delegator)
|
||||||
|
assert filter.delegate is None
|
||||||
|
filter.setdelegate(self.top)
|
||||||
|
self.top = filter
|
||||||
|
|
||||||
|
def removefilter(self, filter):
|
||||||
|
# XXX Perhaps should only support popfilter()?
|
||||||
|
assert isinstance(filter, Delegator)
|
||||||
|
assert filter.delegate is not None
|
||||||
|
f = self.top
|
||||||
|
if f is filter:
|
||||||
|
self.top = filter.delegate
|
||||||
|
filter.setdelegate(None)
|
||||||
|
else:
|
||||||
|
while f.delegate is not filter:
|
||||||
|
assert f is not self.bottom
|
||||||
|
f.resetcache()
|
||||||
|
f = f.delegate
|
||||||
|
f.setdelegate(filter.delegate)
|
||||||
|
filter.setdelegate(None)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
class Tracer(Delegator):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
Delegator.__init__(self, None)
|
||||||
|
def insert(self, *args):
|
||||||
|
print self.name, ": insert", args
|
||||||
|
apply(self.delegate.insert, args)
|
||||||
|
def delete(self, *args):
|
||||||
|
print self.name, ": delete", args
|
||||||
|
apply(self.delegate.delete, args)
|
||||||
|
from Tkinter import *
|
||||||
|
root = Tk()
|
||||||
|
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||||
|
text = Text()
|
||||||
|
text.pack()
|
||||||
|
text.focus_set()
|
||||||
|
p = Percolator(text)
|
||||||
|
t1 = Tracer("t1")
|
||||||
|
t2 = Tracer("t2")
|
||||||
|
p.insertfilter(t1)
|
||||||
|
p.insertfilter(t2)
|
||||||
|
root.mainloop()
|
||||||
|
p.removefilter(t2)
|
||||||
|
root.mainloop()
|
||||||
|
p.insertfilter(t2)
|
||||||
|
p.removefilter(t1)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,86 @@
|
||||||
|
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 StackViewer
|
||||||
|
sv = StackViewer(self.text._root(), self.flist)
|
||||||
|
|
||||||
|
def help(self):
|
||||||
|
from HelpWindow import HelpWindow
|
||||||
|
HelpWindow()
|
|
@ -0,0 +1,475 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
|
||||||
|
import linecache
|
||||||
|
from code import InteractiveInterpreter
|
||||||
|
|
||||||
|
from Tkinter import *
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
from EditorWindow import fixwordbreaks
|
||||||
|
from FileList import FileList, MultiEditorWindow, MultiIOBinding
|
||||||
|
from ColorDelegator import ColorDelegator
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
def recolorize_main(self):
|
||||||
|
self.tag_remove("TODO", "1.0", "iomark")
|
||||||
|
self.tag_add("SYNC", "1.0", "iomark")
|
||||||
|
ColorDelegator.recolorize_main(self)
|
||||||
|
|
||||||
|
|
||||||
|
class ModifiedInterpreter(InteractiveInterpreter):
|
||||||
|
|
||||||
|
def __init__(self, tkconsole):
|
||||||
|
self.tkconsole = tkconsole
|
||||||
|
InteractiveInterpreter.__init__(self)
|
||||||
|
|
||||||
|
gid = 0
|
||||||
|
|
||||||
|
def runsource(self, source):
|
||||||
|
# Extend base class to stuff the source in the line cache
|
||||||
|
filename = "<console#%d>" % self.gid
|
||||||
|
self.gid = self.gid + 1
|
||||||
|
lines = string.split(source, "\n")
|
||||||
|
linecache.cache[filename] = len(source)+1, 0, lines, filename
|
||||||
|
self.more = 0
|
||||||
|
return InteractiveInterpreter.runsource(self, source, filename)
|
||||||
|
|
||||||
|
def showsyntaxerror(self, filename=None):
|
||||||
|
# Extend base class to color the offending position
|
||||||
|
# (instead of printing it and pointing at it with a caret)
|
||||||
|
text = self.tkconsole.text
|
||||||
|
stuff = self.unpackerror()
|
||||||
|
if not stuff:
|
||||||
|
self.tkconsole.resetoutput()
|
||||||
|
InteractiveInterpreter.showsyntaxerror(self, filename)
|
||||||
|
return
|
||||||
|
msg, lineno, offset, line = stuff
|
||||||
|
if lineno == 1:
|
||||||
|
pos = "iomark + %d chars" % (offset-1)
|
||||||
|
else:
|
||||||
|
pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
|
||||||
|
offset-1)
|
||||||
|
text.tag_add("ERROR", pos)
|
||||||
|
text.see(pos)
|
||||||
|
char = text.get(pos)
|
||||||
|
if char in string.letters + string.digits + "_":
|
||||||
|
text.tag_add("ERROR", pos + " wordstart", pos)
|
||||||
|
self.tkconsole.resetoutput()
|
||||||
|
self.write("SyntaxError: %s\n" % str(msg))
|
||||||
|
|
||||||
|
def unpackerror(self):
|
||||||
|
type, value, tb = sys.exc_info()
|
||||||
|
ok = type == SyntaxError
|
||||||
|
if ok:
|
||||||
|
try:
|
||||||
|
msg, (dummy_filename, lineno, offset, line) = value
|
||||||
|
except:
|
||||||
|
ok = 0
|
||||||
|
if ok:
|
||||||
|
return msg, lineno, offset, line
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def showtraceback(self):
|
||||||
|
# Extend base class method to reset output properly
|
||||||
|
text = self.tkconsole.text
|
||||||
|
self.tkconsole.resetoutput()
|
||||||
|
InteractiveInterpreter.showtraceback(self)
|
||||||
|
|
||||||
|
def runcode(self, code):
|
||||||
|
# Override base class method
|
||||||
|
try:
|
||||||
|
self.tkconsole.beginexecuting()
|
||||||
|
try:
|
||||||
|
exec code in self.locals
|
||||||
|
except SystemExit:
|
||||||
|
if tkMessageBox.askyesno(
|
||||||
|
"Exit?",
|
||||||
|
"Do you want to exit altogether?",
|
||||||
|
default="yes",
|
||||||
|
master=self.tkconsole.text):
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
self.showtraceback()
|
||||||
|
except:
|
||||||
|
self.showtraceback()
|
||||||
|
finally:
|
||||||
|
self.tkconsole.endexecuting()
|
||||||
|
|
||||||
|
def write(self, s):
|
||||||
|
# Override base class write
|
||||||
|
self.tkconsole.console.write(s)
|
||||||
|
|
||||||
|
|
||||||
|
class PyShell(MultiEditorWindow):
|
||||||
|
|
||||||
|
# Override classes
|
||||||
|
ColorDelegator = ModifiedColorDelegator
|
||||||
|
IOBinding = ModifiedIOBinding
|
||||||
|
|
||||||
|
# New class
|
||||||
|
from History import History
|
||||||
|
|
||||||
|
def __init__(self, flist=None):
|
||||||
|
self.interp = ModifiedInterpreter(self)
|
||||||
|
if flist is None:
|
||||||
|
root = Tk()
|
||||||
|
fixwordbreaks(root)
|
||||||
|
root.withdraw()
|
||||||
|
flist = FileList(root)
|
||||||
|
|
||||||
|
MultiEditorWindow.__init__(self, flist, None, None)
|
||||||
|
self.config_colors()
|
||||||
|
|
||||||
|
import __builtin__
|
||||||
|
__builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
|
||||||
|
|
||||||
|
self.auto.config(prefertabs=1)
|
||||||
|
|
||||||
|
text = self.text
|
||||||
|
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)
|
||||||
|
|
||||||
|
sys.stdout = PseudoFile(self, "stdout")
|
||||||
|
##sys.stderr = PseudoFile(self, "stderr")
|
||||||
|
sys.stdin = self
|
||||||
|
self.console = PseudoFile(self, "console")
|
||||||
|
|
||||||
|
self.history = self.History(self.text)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
reading = 0
|
||||||
|
executing = 0
|
||||||
|
canceled = 0
|
||||||
|
endoffile = 0
|
||||||
|
|
||||||
|
def beginexecuting(self):
|
||||||
|
# Helper for ModifiedInterpreter
|
||||||
|
self.resetoutput()
|
||||||
|
self.executing = 1
|
||||||
|
self._cancel_check = self.cancel_check
|
||||||
|
##sys.settrace(self._cancel_check)
|
||||||
|
|
||||||
|
def endexecuting(self):
|
||||||
|
# Helper for ModifiedInterpreter
|
||||||
|
sys.settrace(None)
|
||||||
|
self.executing = 0
|
||||||
|
self.canceled = 0
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
# Extend base class method
|
||||||
|
if self.executing:
|
||||||
|
# XXX Need to ask a question here
|
||||||
|
if not tkMessageBox.askokcancel(
|
||||||
|
"Cancel?",
|
||||||
|
"The program is still running; do you want to cancel it?",
|
||||||
|
default="ok",
|
||||||
|
master=self.text):
|
||||||
|
return "cancel"
|
||||||
|
self.canceled = 1
|
||||||
|
if self.reading:
|
||||||
|
self.top.quit()
|
||||||
|
return "cancel"
|
||||||
|
reply = MultiEditorWindow.close(self)
|
||||||
|
if reply != "cancel":
|
||||||
|
# Restore std streams
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
sys.stderr = sys.__stderr__
|
||||||
|
sys.stdin = sys.__stdin__
|
||||||
|
# Break cycles
|
||||||
|
self.interp = None
|
||||||
|
self.console = None
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def ispythonsource(self, filename):
|
||||||
|
# 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 interact(self):
|
||||||
|
self.resetoutput()
|
||||||
|
self.write("Python %s on %s\n%s\n" %
|
||||||
|
(sys.version, sys.platform, sys.copyright))
|
||||||
|
try:
|
||||||
|
sys.ps1
|
||||||
|
except AttributeError:
|
||||||
|
sys.ps1 = ">>> "
|
||||||
|
self.showprompt()
|
||||||
|
import Tkinter
|
||||||
|
Tkinter._default_root = None
|
||||||
|
self.top.mainloop()
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
save = self.reading
|
||||||
|
try:
|
||||||
|
self.reading = 1
|
||||||
|
self.top.mainloop()
|
||||||
|
finally:
|
||||||
|
self.reading = save
|
||||||
|
line = self.text.get("iomark", "end-1c")
|
||||||
|
self.resetoutput()
|
||||||
|
if self.canceled:
|
||||||
|
self.canceled = 0
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
if self.endoffile:
|
||||||
|
self.endoffile = 0
|
||||||
|
return ""
|
||||||
|
return line
|
||||||
|
|
||||||
|
def cancel_callback(self, event):
|
||||||
|
try:
|
||||||
|
if self.text.compare("sel.first", "!=", "sel.last"):
|
||||||
|
return # Active selection -- always use default binding
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not (self.executing or self.reading):
|
||||||
|
self.resetoutput()
|
||||||
|
self.write("KeyboardInterrupt\n")
|
||||||
|
self.showprompt()
|
||||||
|
return "break"
|
||||||
|
self.endoffile = 0
|
||||||
|
self.canceled = 1
|
||||||
|
if self.reading:
|
||||||
|
self.top.quit()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def eof_callback(self, event):
|
||||||
|
if self.executing and not self.reading:
|
||||||
|
return # Let the default binding (delete next char) take over
|
||||||
|
if not (self.text.compare("iomark", "==", "insert") and
|
||||||
|
self.text.compare("insert", "==", "end-1c")):
|
||||||
|
return # Let the default binding (delete next char) take over
|
||||||
|
if not self.executing:
|
||||||
|
## if not tkMessageBox.askokcancel(
|
||||||
|
## "Exit?",
|
||||||
|
## "Are you sure you want to exit?",
|
||||||
|
## default="ok", master=self.text):
|
||||||
|
## return "break"
|
||||||
|
self.resetoutput()
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
self.canceled = 0
|
||||||
|
self.endoffile = 1
|
||||||
|
self.top.quit()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def home_callback(self, event):
|
||||||
|
if event.state != 0 and event.keysym == "Home":
|
||||||
|
return # <Modifier-Home>; fall back to class binding
|
||||||
|
if self.text.compare("iomark", "<=", "insert") and \
|
||||||
|
self.text.compare("insert linestart", "<=", "iomark"):
|
||||||
|
self.text.mark_set("insert", "iomark")
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.text.see("insert")
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def linefeed_callback(self, event):
|
||||||
|
# Insert a linefeed without entering anything (still autoindented)
|
||||||
|
if self.reading:
|
||||||
|
self.text.insert("insert", "\n")
|
||||||
|
self.text.see("insert")
|
||||||
|
else:
|
||||||
|
self.auto.autoindent(event)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def enter_callback(self, event):
|
||||||
|
if self.executing and not self.reading:
|
||||||
|
return # Let the default binding (insert '\n') take over
|
||||||
|
# If some text is selected, recall the selection
|
||||||
|
try:
|
||||||
|
sel = self.text.get("sel.first", "sel.last")
|
||||||
|
if sel:
|
||||||
|
self.recall(sel)
|
||||||
|
return "break"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# If we're strictly before the line containing iomark, recall
|
||||||
|
# the current line, less a leading prompt, less leading or
|
||||||
|
# trailing whitespace
|
||||||
|
if self.text.compare("insert", "<", "iomark linestart"):
|
||||||
|
# Check if there's a relevant stdin mark -- if so, use it
|
||||||
|
prev = self.text.tag_prevrange("stdin", "insert")
|
||||||
|
if prev and self.text.compare("insert", "<", prev[1]):
|
||||||
|
self.recall(self.text.get(prev[0], prev[1]))
|
||||||
|
return "break"
|
||||||
|
next = self.text.tag_nextrange("stdin", "insert")
|
||||||
|
if next and self.text.compare("insert lineend", ">=", next[0]):
|
||||||
|
self.recall(self.text.get(next[0], next[1]))
|
||||||
|
return "break"
|
||||||
|
# No stdin mark -- just get the current line
|
||||||
|
self.recall(self.text.get("insert linestart", "insert lineend"))
|
||||||
|
return "break"
|
||||||
|
# If we're anywhere in the current input (including in the
|
||||||
|
# prompt) but not at the very end, move the cursor to the end.
|
||||||
|
if self.text.compare("insert", "<", "end-1c"):
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
self.text.see("insert")
|
||||||
|
return "break"
|
||||||
|
# OK, we're already at the end -- insert a newline and run it.
|
||||||
|
if self.reading:
|
||||||
|
self.text.insert("insert", "\n")
|
||||||
|
self.text.see("insert")
|
||||||
|
else:
|
||||||
|
self.auto.autoindent(event)
|
||||||
|
self.text.tag_add("stdin", "iomark", "end-1c")
|
||||||
|
self.text.update_idletasks()
|
||||||
|
if self.reading:
|
||||||
|
self.top.quit() # Break out of recursive mainloop() in raw_input()
|
||||||
|
else:
|
||||||
|
self.runit()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def recall(self, s):
|
||||||
|
if self.history:
|
||||||
|
self.history.recall(s)
|
||||||
|
|
||||||
|
def runit(self):
|
||||||
|
line = self.text.get("iomark", "end-1c")
|
||||||
|
# Strip off last newline and surrounding whitespace.
|
||||||
|
# (To allow you to hit return twice to end a statement.)
|
||||||
|
i = len(line)
|
||||||
|
while i > 0 and line[i-1] in " \t":
|
||||||
|
i = i-1
|
||||||
|
if i > 0 and line[i-1] == "\n":
|
||||||
|
i = i-1
|
||||||
|
while i > 0 and line[i-1] in " \t":
|
||||||
|
i = i-1
|
||||||
|
line = line[:i]
|
||||||
|
more = self.interp.runsource(line)
|
||||||
|
if not more:
|
||||||
|
self.showprompt()
|
||||||
|
|
||||||
|
def cancel_check(self, frame, what, args,
|
||||||
|
dooneevent=tkinter.dooneevent,
|
||||||
|
dontwait=tkinter.DONT_WAIT):
|
||||||
|
# Hack -- use the debugger hooks to be able to handle events
|
||||||
|
# and interrupt execution at any time.
|
||||||
|
# This slows execution down quite a bit, so you may want to
|
||||||
|
# disable this (by not calling settrace() in runcode() above)
|
||||||
|
# for full-bore (uninterruptable) speed.
|
||||||
|
# XXX This should become a user option.
|
||||||
|
if self.canceled:
|
||||||
|
return
|
||||||
|
dooneevent(dontwait)
|
||||||
|
if self.canceled:
|
||||||
|
self.canceled = 0
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
return self._cancel_check
|
||||||
|
|
||||||
|
def showprompt(self):
|
||||||
|
self.resetoutput()
|
||||||
|
try:
|
||||||
|
s = str(sys.ps1)
|
||||||
|
except:
|
||||||
|
s = ""
|
||||||
|
self.console.write(s)
|
||||||
|
self.text.mark_set("insert", "end-1c")
|
||||||
|
|
||||||
|
def resetoutput(self):
|
||||||
|
source = self.text.get("iomark", "end-1c")
|
||||||
|
if self.history:
|
||||||
|
self.history.history_store(source)
|
||||||
|
if self.text.get("end-2c") != "\n":
|
||||||
|
self.text.insert("end-1c", "\n")
|
||||||
|
self.text.mark_set("iomark", "end-1c")
|
||||||
|
sys.stdout.softspace = 0
|
||||||
|
|
||||||
|
def write(self, s):
|
||||||
|
# Overrides base class write
|
||||||
|
self.console.write(s)
|
||||||
|
|
||||||
|
class PseudoFile:
|
||||||
|
|
||||||
|
def __init__(self, interp, tags):
|
||||||
|
self.interp = interp
|
||||||
|
self.text = interp.text
|
||||||
|
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
|
||||||
|
|
||||||
|
def writelines(self, l):
|
||||||
|
map(self.write, l)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global flist, root
|
||||||
|
root = Tk()
|
||||||
|
fixwordbreaks(root)
|
||||||
|
root.withdraw()
|
||||||
|
flist = FileList(root)
|
||||||
|
if sys.argv[1:]:
|
||||||
|
for filename in sys.argv[1:]:
|
||||||
|
flist.open(filename)
|
||||||
|
t = PyShell(flist)
|
||||||
|
t.interact()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,79 @@
|
||||||
|
BUGS:
|
||||||
|
|
||||||
|
- when there's a selection, typing ^X will delete the selection!
|
||||||
|
(cause: ^X is a binding for cut ;-( )
|
||||||
|
|
||||||
|
TO DO:
|
||||||
|
|
||||||
|
- restructure state sensitive code to avoid testing flags all the time
|
||||||
|
- integrated debugger
|
||||||
|
- object browser
|
||||||
|
- save some user state (e.g. window and cursor positions, bindings)
|
||||||
|
|
||||||
|
- menu bar
|
||||||
|
- 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:
|
||||||
|
- reindent, reformat text etc.
|
||||||
|
- M-[, M-] to move by paragraphs
|
||||||
|
- smart stuff with whitespace around Return
|
||||||
|
- status bar?
|
||||||
|
- better help?
|
||||||
|
|
||||||
|
Details:
|
||||||
|
|
||||||
|
- when there's a selection, left/right arrow should go to either
|
||||||
|
end of the selection
|
||||||
|
|
||||||
|
Structural problems:
|
||||||
|
|
||||||
|
- too much knowledge in FileList about EditorWindow (for example)
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Comparison to PTUI
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- PTUI's shell is worse:
|
||||||
|
no coloring;
|
||||||
|
no editing of multi-line commands;
|
||||||
|
^P seems to permanently remove some text from the buffer
|
||||||
|
|
||||||
|
- PTUI's undo is worse:
|
||||||
|
no redo;
|
||||||
|
one char at a time
|
||||||
|
|
||||||
|
- PTUI's framework is better:
|
||||||
|
status line
|
||||||
|
menu bar
|
||||||
|
buffer menu
|
||||||
|
(not sure if I like the toolbar)
|
||||||
|
|
||||||
|
- PTUI's GUI is a tad ugly:
|
||||||
|
I don't like the multiple buffers in one window model
|
||||||
|
|
||||||
|
- PTUI's help is better (HTML!)
|
||||||
|
|
||||||
|
- PTUI's search/replace is better (more features)
|
||||||
|
|
||||||
|
- 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
|
|
@ -0,0 +1,84 @@
|
||||||
|
import re
|
||||||
|
import tkSimpleDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
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"
|
||||||
|
self.text.mark_set("find", "insert")
|
||||||
|
while 1:
|
||||||
|
chars = self.text.get("find", "find lineend +1c")
|
||||||
|
##print "Searching", `chars`
|
||||||
|
if not chars:
|
||||||
|
self.text.bell()
|
||||||
|
##print "end of buffer"
|
||||||
|
break
|
||||||
|
m = self.prog.search(chars)
|
||||||
|
if m:
|
||||||
|
i, j = m.span()
|
||||||
|
self.text.mark_set("insert", "find +%dc" % j)
|
||||||
|
self.text.mark_set("find", "find +%dc" % i)
|
||||||
|
self.text.tag_remove("sel", "1.0", "end")
|
||||||
|
self.text.tag_add("sel", "find", "insert")
|
||||||
|
self.text.see("insert")
|
||||||
|
break
|
||||||
|
self.text.mark_set("find", "find lineend +1c")
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def goto_line_event(self, event):
|
||||||
|
lineno = tkSimpleDialog.askinteger("Goto",
|
||||||
|
"Go to line number:")
|
||||||
|
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")
|
|
@ -0,0 +1,288 @@
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from Tkinter import *
|
||||||
|
import linecache
|
||||||
|
from repr import Repr
|
||||||
|
|
||||||
|
|
||||||
|
class StackViewer:
|
||||||
|
|
||||||
|
def __init__(self, root=None, flist=None):
|
||||||
|
self.flist = flist
|
||||||
|
# Create root and/or toplevel window
|
||||||
|
if not root:
|
||||||
|
import Tkinter
|
||||||
|
root = Tkinter._default_root
|
||||||
|
if not root:
|
||||||
|
root = top = Tk()
|
||||||
|
else:
|
||||||
|
top = Toplevel(root)
|
||||||
|
self.root = root
|
||||||
|
self.top = top
|
||||||
|
top.wm_title("Stack viewer")
|
||||||
|
# Create top frame, with scrollbar and listbox
|
||||||
|
self.topframe = Frame(top)
|
||||||
|
self.topframe.pack(fill="both", expand=1)
|
||||||
|
self.vbar = Scrollbar(self.topframe, name="vbar")
|
||||||
|
self.vbar.pack(side="right", fill="y")
|
||||||
|
self.listbox = Listbox(self.topframe, exportselection=0,
|
||||||
|
takefocus=1, width=60)
|
||||||
|
self.listbox.pack(expand=1, fill="both")
|
||||||
|
# Tie listbox and scrollbar together
|
||||||
|
self.vbar["command"] = self.listbox.yview
|
||||||
|
self.listbox["yscrollcommand"] = self.vbar.set
|
||||||
|
# Bind events to the list box
|
||||||
|
self.listbox.bind("<ButtonRelease-1>", self.click_event)
|
||||||
|
self.listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
|
||||||
|
self.listbox.bind("<ButtonPress-3>", self.popup_event)
|
||||||
|
self.listbox.bind("<Key-Up>", self.up_event)
|
||||||
|
self.listbox.bind("<Key-Down>", self.down_event)
|
||||||
|
# Load the stack
|
||||||
|
linecache.checkcache()
|
||||||
|
stack = getstack()
|
||||||
|
self.load_stack(stack)
|
||||||
|
|
||||||
|
def load_stack(self, stack):
|
||||||
|
self.stack = stack
|
||||||
|
l = self.listbox
|
||||||
|
l.delete(0, END)
|
||||||
|
if len(stack) > 10:
|
||||||
|
l["height"] = 10
|
||||||
|
self.topframe.pack(expand=1)
|
||||||
|
else:
|
||||||
|
l["height"] = len(stack)
|
||||||
|
self.topframe.pack(expand=0)
|
||||||
|
for frame, lineno in stack:
|
||||||
|
try:
|
||||||
|
modname = frame.f_globals["__name__"]
|
||||||
|
except:
|
||||||
|
modname = "?"
|
||||||
|
code = frame.f_code
|
||||||
|
filename = code.co_filename
|
||||||
|
funcname = code.co_name
|
||||||
|
sourceline = linecache.getline(filename, lineno)
|
||||||
|
sourceline = string.strip(sourceline)
|
||||||
|
if funcname in ("?", "", None):
|
||||||
|
item = "%s, line %d: %s" % (modname, lineno, sourceline)
|
||||||
|
else:
|
||||||
|
item = "%s.%s(), line %d: %s" % (modname, funcname,
|
||||||
|
lineno, sourceline)
|
||||||
|
l.insert(END, item)
|
||||||
|
l.focus_set()
|
||||||
|
l.selection_clear(0, "end")
|
||||||
|
l.activate("end")
|
||||||
|
l.see("end")
|
||||||
|
|
||||||
|
rmenu = None
|
||||||
|
|
||||||
|
def click_event(self, event):
|
||||||
|
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||||
|
self.show_stack_frame()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def popup_event(self, event):
|
||||||
|
if not self.rmenu:
|
||||||
|
self.make_menu()
|
||||||
|
rmenu = self.rmenu
|
||||||
|
self.event = event
|
||||||
|
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||||
|
rmenu.tk_popup(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
def make_menu(self):
|
||||||
|
rmenu = Menu(self.top, tearoff=0)
|
||||||
|
rmenu.add_command(label="Go to source line",
|
||||||
|
command=self.goto_source_line)
|
||||||
|
rmenu.add_command(label="Show stack frame",
|
||||||
|
command=self.show_stack_frame)
|
||||||
|
self.rmenu = rmenu
|
||||||
|
|
||||||
|
def goto_source_line(self):
|
||||||
|
index = self.listbox.index("active")
|
||||||
|
self.show_source(index)
|
||||||
|
|
||||||
|
def show_stack_frame(self):
|
||||||
|
index = self.listbox.index("active")
|
||||||
|
self.show_frame(index)
|
||||||
|
|
||||||
|
def double_click_event(self, event):
|
||||||
|
index = self.listbox.index("active")
|
||||||
|
self.show_source(index)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def up_event(self, event):
|
||||||
|
index = self.listbox.index("active") - 1
|
||||||
|
if index < 0:
|
||||||
|
self.top.bell()
|
||||||
|
return "break"
|
||||||
|
self.show_frame(index)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def down_event(self, event):
|
||||||
|
index = self.listbox.index("active") + 1
|
||||||
|
if index >= len(self.stack):
|
||||||
|
self.top.bell()
|
||||||
|
return "break"
|
||||||
|
self.show_frame(index)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def show_source(self, index):
|
||||||
|
if not 0 <= index < len(self.stack):
|
||||||
|
self.top.bell()
|
||||||
|
return
|
||||||
|
frame, lineno = self.stack[index]
|
||||||
|
code = frame.f_code
|
||||||
|
filename = code.co_filename
|
||||||
|
if not self.flist:
|
||||||
|
self.top.bell()
|
||||||
|
return
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
self.top.bell()
|
||||||
|
return
|
||||||
|
edit = self.flist.open(filename)
|
||||||
|
edit.gotoline(lineno)
|
||||||
|
|
||||||
|
localsframe = None
|
||||||
|
localsviewer = None
|
||||||
|
localsdict = None
|
||||||
|
globalsframe = None
|
||||||
|
globalsviewer = None
|
||||||
|
globalsdict = None
|
||||||
|
curframe = None
|
||||||
|
|
||||||
|
def show_frame(self, index):
|
||||||
|
if not 0 <= index < len(self.stack):
|
||||||
|
self.top.bell()
|
||||||
|
return
|
||||||
|
self.listbox.selection_clear(0, "end")
|
||||||
|
self.listbox.selection_set(index)
|
||||||
|
self.listbox.activate(index)
|
||||||
|
self.listbox.see(index)
|
||||||
|
self.listbox.focus_set()
|
||||||
|
frame, lineno = self.stack[index]
|
||||||
|
if frame is self.curframe:
|
||||||
|
return
|
||||||
|
self.curframe = None
|
||||||
|
if frame.f_globals is not self.globalsdict:
|
||||||
|
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__"):
|
||||||
|
try:
|
||||||
|
name = str(frame.f_globals["__name__"]) + ""
|
||||||
|
except:
|
||||||
|
name = ""
|
||||||
|
if name:
|
||||||
|
title = title + " in module " + name
|
||||||
|
self.globalsdict = None
|
||||||
|
if self.globalsviewer:
|
||||||
|
self.globalsviewer.close()
|
||||||
|
self.globalsviewer = None
|
||||||
|
if not self.globalsframe:
|
||||||
|
self.globalsframe = Frame(self.top)
|
||||||
|
self.globalsdict = frame.f_globals
|
||||||
|
self.globalsviewer = NamespaceViewer(
|
||||||
|
self.globalsframe,
|
||||||
|
title,
|
||||||
|
self.globalsdict)
|
||||||
|
self.globalsframe.pack(fill="both", side="bottom")
|
||||||
|
|
||||||
|
def show_locals(self, frame):
|
||||||
|
self.localsdict = None
|
||||||
|
if self.localsviewer:
|
||||||
|
self.localsviewer.close()
|
||||||
|
self.localsviewer = None
|
||||||
|
if frame.f_locals is not frame.f_globals:
|
||||||
|
title = "Local Variables"
|
||||||
|
code = frame.f_code
|
||||||
|
funcname = code.co_name
|
||||||
|
if funcname not in ("?", "", None):
|
||||||
|
title = title + " in " + funcname
|
||||||
|
if not self.localsframe:
|
||||||
|
self.localsframe = Frame(self.top)
|
||||||
|
self.localsdict = frame.f_locals
|
||||||
|
self.localsviewer = NamespaceViewer(
|
||||||
|
self.localsframe,
|
||||||
|
title,
|
||||||
|
self.localsdict)
|
||||||
|
self.localsframe.pack(fill="both", side="top")
|
||||||
|
else:
|
||||||
|
if self.localsframe:
|
||||||
|
self.localsframe.forget()
|
||||||
|
|
||||||
|
|
||||||
|
def getstack(t=None, f=None):
|
||||||
|
if t is None:
|
||||||
|
t = sys.last_traceback
|
||||||
|
stack = []
|
||||||
|
if t and t.tb_frame is f:
|
||||||
|
t = t.tb_next
|
||||||
|
while f is not None:
|
||||||
|
stack.append((f, f.f_lineno))
|
||||||
|
if f is self.botframe:
|
||||||
|
break
|
||||||
|
f = f.f_back
|
||||||
|
stack.reverse()
|
||||||
|
while t is not None:
|
||||||
|
stack.append((t.tb_frame, t.tb_lineno))
|
||||||
|
t = t.tb_next
|
||||||
|
return stack
|
||||||
|
|
||||||
|
|
||||||
|
class NamespaceViewer:
|
||||||
|
|
||||||
|
def __init__(self, frame, title, dict):
|
||||||
|
width = 0
|
||||||
|
height = 20*len(dict) # XXX 20 == observed height of Entry widget
|
||||||
|
self.frame = frame
|
||||||
|
self.title = title
|
||||||
|
self.dict = dict
|
||||||
|
self.repr = Repr()
|
||||||
|
self.repr.maxstring = 60
|
||||||
|
self.repr.maxother = 60
|
||||||
|
self.label = Label(frame, text=title, borderwidth=2, relief="groove")
|
||||||
|
self.label.pack(fill="x")
|
||||||
|
self.vbar = vbar = Scrollbar(frame, name="vbar")
|
||||||
|
vbar.pack(side="right", fill="y")
|
||||||
|
self.canvas = canvas = Canvas(frame,
|
||||||
|
height=min(300, max(40, height)),
|
||||||
|
scrollregion=(0, 0, width, height))
|
||||||
|
canvas.pack(side="left", fill="both", expand=1)
|
||||||
|
vbar["command"] = canvas.yview
|
||||||
|
canvas["yscrollcommand"] = vbar.set
|
||||||
|
self.subframe = subframe = Frame(canvas)
|
||||||
|
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
|
||||||
|
names = dict.keys()
|
||||||
|
names.sort()
|
||||||
|
row = 0
|
||||||
|
for name in names:
|
||||||
|
value = dict[name]
|
||||||
|
svalue = self.repr.repr(value) # repr(value)
|
||||||
|
l = Label(subframe, text=name)
|
||||||
|
l.grid(row=row, column=0, sticky="nw")
|
||||||
|
## l = Label(subframe, text=svalue, justify="l", wraplength=300)
|
||||||
|
l = Entry(subframe, width=0, borderwidth=0)
|
||||||
|
l.insert(0, svalue)
|
||||||
|
## l["state"] = "disabled"
|
||||||
|
l.grid(row=row, column=1, sticky="nw")
|
||||||
|
row = row+1
|
||||||
|
frame.update_idletasks() # Alas!
|
||||||
|
width = subframe.winfo_reqwidth()
|
||||||
|
height = subframe.winfo_reqheight()
|
||||||
|
canvas["scrollregion"] = (0, 0, width, height)
|
||||||
|
if height > 300:
|
||||||
|
canvas["height"] = 300
|
||||||
|
frame.pack(expand=1)
|
||||||
|
else:
|
||||||
|
canvas["height"] = height
|
||||||
|
frame.pack(expand=0)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
for c in self.subframe, self.label, self.vbar, self.canvas:
|
||||||
|
try:
|
||||||
|
c.destroy()
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,269 @@
|
||||||
|
import sys
|
||||||
|
import string
|
||||||
|
from Tkinter import *
|
||||||
|
from Delegator import Delegator
|
||||||
|
|
||||||
|
|
||||||
|
class UndoDelegator(Delegator):
|
||||||
|
|
||||||
|
max_undo = 1000
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Delegator.__init__(self)
|
||||||
|
self.reset_undo()
|
||||||
|
|
||||||
|
def setdelegate(self, delegate):
|
||||||
|
if self.delegate is not None:
|
||||||
|
self.unbind("<<undo>>")
|
||||||
|
self.unbind("<<redo>>")
|
||||||
|
self.unbind("<<dump-undo-state>>")
|
||||||
|
Delegator.setdelegate(self, delegate)
|
||||||
|
if delegate is not None:
|
||||||
|
self.bind("<<undo>>", self.undo_event)
|
||||||
|
self.bind("<<redo>>", self.redo_event)
|
||||||
|
self.bind("<<dump-undo-state>>", self.dump_event)
|
||||||
|
|
||||||
|
def dump_event(self, event):
|
||||||
|
from pprint import pprint
|
||||||
|
pprint(self.undolist[:self.pointer])
|
||||||
|
print "pointer:", self.pointer,
|
||||||
|
print "saved:", self.saved,
|
||||||
|
print "can_merge:", self.can_merge,
|
||||||
|
print "get_saved():", self.get_saved()
|
||||||
|
pprint(self.undolist[self.pointer:])
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def reset_undo(self):
|
||||||
|
self.was_saved = -1
|
||||||
|
self.pointer = 0
|
||||||
|
self.undolist = []
|
||||||
|
self.set_saved(1)
|
||||||
|
|
||||||
|
def set_saved(self, flag):
|
||||||
|
if flag:
|
||||||
|
self.saved = self.pointer
|
||||||
|
else:
|
||||||
|
self.saved = -1
|
||||||
|
self.can_merge = 0
|
||||||
|
self.check_saved()
|
||||||
|
|
||||||
|
def get_saved(self):
|
||||||
|
return self.saved == self.pointer
|
||||||
|
|
||||||
|
saved_change_hook = None
|
||||||
|
|
||||||
|
def set_saved_change_hook(self, hook):
|
||||||
|
self.saved_change_hook = hook
|
||||||
|
|
||||||
|
was_saved = -1
|
||||||
|
|
||||||
|
def check_saved(self):
|
||||||
|
is_saved = self.get_saved()
|
||||||
|
if is_saved != self.was_saved:
|
||||||
|
self.was_saved = is_saved
|
||||||
|
if self.saved_change_hook:
|
||||||
|
self.saved_change_hook()
|
||||||
|
|
||||||
|
def insert(self, index, chars, tags=None):
|
||||||
|
self.addcmd(InsertCommand(index, chars, tags))
|
||||||
|
|
||||||
|
def delete(self, index1, index2=None):
|
||||||
|
self.addcmd(DeleteCommand(index1, index2))
|
||||||
|
|
||||||
|
def addcmd(self, cmd):
|
||||||
|
cmd.do(self.delegate)
|
||||||
|
if self.can_merge and self.pointer > 0:
|
||||||
|
lastcmd = self.undolist[self.pointer-1]
|
||||||
|
if lastcmd.merge(cmd):
|
||||||
|
return
|
||||||
|
self.undolist[self.pointer:] = [cmd]
|
||||||
|
if self.saved > self.pointer:
|
||||||
|
self.saved = -1
|
||||||
|
self.pointer = self.pointer + 1
|
||||||
|
if len(self.undolist) > self.max_undo:
|
||||||
|
##print "truncating undo list"
|
||||||
|
del self.undolist[0]
|
||||||
|
self.pointer = self.pointer - 1
|
||||||
|
if self.saved >= 0:
|
||||||
|
self.saved = self.saved - 1
|
||||||
|
self.can_merge = 1
|
||||||
|
self.check_saved()
|
||||||
|
|
||||||
|
def undo_event(self, event):
|
||||||
|
if self.pointer == 0:
|
||||||
|
self.bell()
|
||||||
|
return "break"
|
||||||
|
cmd = self.undolist[self.pointer - 1]
|
||||||
|
cmd.undo(self.delegate)
|
||||||
|
self.pointer = self.pointer - 1
|
||||||
|
self.can_merge = 0
|
||||||
|
self.check_saved()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def redo_event(self, event):
|
||||||
|
if self.pointer >= len(self.undolist):
|
||||||
|
self.bell()
|
||||||
|
return "break"
|
||||||
|
cmd = self.undolist[self.pointer]
|
||||||
|
cmd.redo(self.delegate)
|
||||||
|
self.pointer = self.pointer + 1
|
||||||
|
self.can_merge = 0
|
||||||
|
self.check_saved()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
|
||||||
|
# Base class for Undoable commands
|
||||||
|
|
||||||
|
tags = None
|
||||||
|
|
||||||
|
def __init__(self, index1, index2, chars, tags=None):
|
||||||
|
self.marks_before = {}
|
||||||
|
self.marks_after = {}
|
||||||
|
self.index1 = index1
|
||||||
|
self.index2 = index2
|
||||||
|
self.chars = chars
|
||||||
|
if tags:
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = self.__class__.__name__
|
||||||
|
t = (self.index1, self.index2, self.chars, self.tags)
|
||||||
|
if self.tags is None:
|
||||||
|
t = t[:-1]
|
||||||
|
return s + `t`
|
||||||
|
|
||||||
|
def do(self, text):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def redo(self, text):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def undo(self, text):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def merge(self, cmd):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def save_marks(self, text):
|
||||||
|
marks = {}
|
||||||
|
for name in text.mark_names():
|
||||||
|
if name != "insert" and name != "current":
|
||||||
|
marks[name] = text.index(name)
|
||||||
|
return marks
|
||||||
|
|
||||||
|
def set_marks(self, text, marks):
|
||||||
|
for name, index in marks.items():
|
||||||
|
text.mark_set(name, index)
|
||||||
|
|
||||||
|
|
||||||
|
class InsertCommand(Command):
|
||||||
|
|
||||||
|
# Undoable insert command
|
||||||
|
|
||||||
|
def __init__(self, index1, chars, tags=None):
|
||||||
|
Command.__init__(self, index1, None, chars, tags)
|
||||||
|
|
||||||
|
def do(self, text):
|
||||||
|
self.marks_before = self.save_marks(text)
|
||||||
|
self.index1 = text.index(self.index1)
|
||||||
|
if text.compare(self.index1, ">", "end-1c"):
|
||||||
|
# Insert before the final newline
|
||||||
|
self.index1 = text.index("end-1c")
|
||||||
|
text.insert(self.index1, self.chars, self.tags)
|
||||||
|
self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
|
||||||
|
self.marks_after = self.save_marks(text)
|
||||||
|
##sys.__stderr__.write("do: %s\n" % self)
|
||||||
|
|
||||||
|
def redo(self, text):
|
||||||
|
text.mark_set('insert', self.index1)
|
||||||
|
text.insert(self.index1, self.chars, self.tags)
|
||||||
|
self.set_marks(text, self.marks_after)
|
||||||
|
text.see('insert')
|
||||||
|
##sys.__stderr__.write("redo: %s\n" % self)
|
||||||
|
|
||||||
|
def undo(self, text):
|
||||||
|
text.mark_set('insert', self.index1)
|
||||||
|
text.delete(self.index1, self.index2)
|
||||||
|
self.set_marks(text, self.marks_before)
|
||||||
|
text.see('insert')
|
||||||
|
##sys.__stderr__.write("undo: %s\n" % self)
|
||||||
|
|
||||||
|
def merge(self, cmd):
|
||||||
|
if self.__class__ is not cmd.__class__:
|
||||||
|
return 0
|
||||||
|
if self.index2 != cmd.index1:
|
||||||
|
return 0
|
||||||
|
if self.tags != cmd.tags:
|
||||||
|
return 0
|
||||||
|
if len(cmd.chars) != 1:
|
||||||
|
return 0
|
||||||
|
if self.chars and \
|
||||||
|
self.classify(self.chars[-1]) != self.classify(cmd.chars):
|
||||||
|
return 0
|
||||||
|
self.index2 = cmd.index2
|
||||||
|
self.chars = self.chars + cmd.chars
|
||||||
|
return 1
|
||||||
|
|
||||||
|
alphanumeric = string.letters + string.digits + "_"
|
||||||
|
|
||||||
|
def classify(self, c):
|
||||||
|
if c in self.alphanumeric:
|
||||||
|
return "alphanumeric"
|
||||||
|
if c == "\n":
|
||||||
|
return "newline"
|
||||||
|
return "punctuation"
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteCommand(Command):
|
||||||
|
|
||||||
|
# Undoable delete command
|
||||||
|
|
||||||
|
def __init__(self, index1, index2=None):
|
||||||
|
Command.__init__(self, index1, index2, None, None)
|
||||||
|
|
||||||
|
def do(self, text):
|
||||||
|
self.marks_before = self.save_marks(text)
|
||||||
|
self.index1 = text.index(self.index1)
|
||||||
|
if self.index2:
|
||||||
|
self.index2 = text.index(self.index2)
|
||||||
|
else:
|
||||||
|
self.index2 = text.index(self.index1 + " +1c")
|
||||||
|
if text.compare(self.index2, ">", "end-1c"):
|
||||||
|
# Don't delete the final newline
|
||||||
|
self.index2 = text.index("end-1c")
|
||||||
|
self.chars = text.get(self.index1, self.index2)
|
||||||
|
text.delete(self.index1, self.index2)
|
||||||
|
self.marks_after = self.save_marks(text)
|
||||||
|
##sys.__stderr__.write("do: %s\n" % self)
|
||||||
|
|
||||||
|
def redo(self, text):
|
||||||
|
text.mark_set('insert', self.index1)
|
||||||
|
text.delete(self.index1, self.index2)
|
||||||
|
self.set_marks(text, self.marks_after)
|
||||||
|
text.see('insert')
|
||||||
|
##sys.__stderr__.write("redo: %s\n" % self)
|
||||||
|
|
||||||
|
def undo(self, text):
|
||||||
|
text.mark_set('insert', self.index1)
|
||||||
|
text.insert(self.index1, self.chars)
|
||||||
|
self.set_marks(text, self.marks_before)
|
||||||
|
text.see('insert')
|
||||||
|
##sys.__stderr__.write("undo: %s\n" % self)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
from Percolator import Percolator
|
||||||
|
root = Tk()
|
||||||
|
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||||
|
text = Text()
|
||||||
|
text.pack()
|
||||||
|
text.focus_set()
|
||||||
|
p = Percolator(text)
|
||||||
|
d = UndoDelegator()
|
||||||
|
p.insertfilter(d)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,84 @@
|
||||||
|
from Tkinter import *
|
||||||
|
|
||||||
|
|
||||||
|
class WidgetRedirector:
|
||||||
|
|
||||||
|
"""Support for redirecting arbitrary widget subcommands."""
|
||||||
|
|
||||||
|
def __init__(self, widget):
|
||||||
|
self.dict = {}
|
||||||
|
self.widget = widget
|
||||||
|
self.tk = tk = widget.tk
|
||||||
|
w = widget._w
|
||||||
|
self.orig = w + "_orig"
|
||||||
|
tk.call("rename", w, self.orig)
|
||||||
|
tk.createcommand(w, self.dispatch)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__,
|
||||||
|
self.widget._w)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.dict = {}
|
||||||
|
widget = self.widget; del self.widget
|
||||||
|
orig = self.orig; del self.orig
|
||||||
|
tk = widget.tk
|
||||||
|
w = widget._w
|
||||||
|
tk.deletecommand(w)
|
||||||
|
tk.call("rename", w, orig)
|
||||||
|
|
||||||
|
def register(self, name, function):
|
||||||
|
if self.dict.has_key(name):
|
||||||
|
previous = function
|
||||||
|
else:
|
||||||
|
previous = OriginalCommand(self, name)
|
||||||
|
self.dict[name] = function
|
||||||
|
setattr(self.widget, name, function)
|
||||||
|
return previous
|
||||||
|
|
||||||
|
def dispatch(self, cmd, *args):
|
||||||
|
m = self.dict.get(cmd)
|
||||||
|
try:
|
||||||
|
if m:
|
||||||
|
return apply(m, args)
|
||||||
|
else:
|
||||||
|
return self.tk.call((self.orig, cmd) + args)
|
||||||
|
except TclError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class OriginalCommand:
|
||||||
|
|
||||||
|
def __init__(self, redir, name):
|
||||||
|
self.redir = redir
|
||||||
|
self.name = name
|
||||||
|
self.tk = redir.tk
|
||||||
|
self.orig = redir.orig
|
||||||
|
self.tk_call = self.tk.call
|
||||||
|
self.orig_and_name = (self.orig, self.name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "OriginalCommand(%s, %s)" % (`self.redir`, `self.name`)
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.tk_call(self.orig_and_name + args)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
root = Tk()
|
||||||
|
text = Text()
|
||||||
|
text.pack()
|
||||||
|
text.focus_set()
|
||||||
|
redir = WidgetRedirector(text)
|
||||||
|
global orig_insert
|
||||||
|
def my_insert(*args):
|
||||||
|
print "insert", args
|
||||||
|
apply(orig_insert, args)
|
||||||
|
orig_insert = redir.register("insert", my_insert)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,60 @@
|
||||||
|
Windows and files:
|
||||||
|
|
||||||
|
^X ^N creates new empty text editor window
|
||||||
|
^X ^C closes all windows
|
||||||
|
Alt-F4 or ^X ^0 (that's control-x-control-zero) closes current window
|
||||||
|
^X ^D opens a file from dialog box
|
||||||
|
^X ^S saves to current file
|
||||||
|
^X ^W saves to file from dialog box
|
||||||
|
^X w save a copy to file from dialog box
|
||||||
|
|
||||||
|
Navigation:
|
||||||
|
|
||||||
|
Arrow keys and Page Up/Down to move around
|
||||||
|
Home/End go to begin/end of line
|
||||||
|
Control-Home/End go to begin/end of file
|
||||||
|
Some Emacs bindings may also work, e.g. ^A/^E
|
||||||
|
|
||||||
|
Searching: all searches are forward from the cursor without
|
||||||
|
wrap-around, case sensitive, Perl-style regular expression matches
|
||||||
|
|
||||||
|
^S without a selection opens search dialog box
|
||||||
|
^S with a selection searches for selected text
|
||||||
|
^U ^S repeats last search
|
||||||
|
Alt-G opens dialog box to go to a specific line
|
||||||
|
|
||||||
|
Editing:
|
||||||
|
|
||||||
|
Backspace deletes left of cursor, Delete right of cursor
|
||||||
|
Cut and paste use platform's conventions
|
||||||
|
^[ or Alt-[ left-shifts (dedents) the current line or selection
|
||||||
|
^] or Alt-] right-shifts (indents) the current line or selection
|
||||||
|
Alt-/ expands last word you type (like Emacs dabbrev)
|
||||||
|
|
||||||
|
Undo:
|
||||||
|
|
||||||
|
^Z undoes last change; repeat to undo more
|
||||||
|
Alt-Z redoes last undone change; repeat to redo more
|
||||||
|
|
||||||
|
Console window:
|
||||||
|
|
||||||
|
^C interrupts executing command
|
||||||
|
^D sends end-of-file; closes console if typed at >>> prompt
|
||||||
|
|
||||||
|
If you get a traceback, right-click on any line listing a
|
||||||
|
filename and line number and select "Go to line from
|
||||||
|
traceback" to open that file and go to the indicated line
|
||||||
|
|
||||||
|
Python syntax colors: the coloring is applied in a background thread
|
||||||
|
|
||||||
|
Keywords orange
|
||||||
|
Strings green
|
||||||
|
Comments red
|
||||||
|
Definitions blue
|
||||||
|
|
||||||
|
Console colors:
|
||||||
|
|
||||||
|
Console output red
|
||||||
|
stdout blue
|
||||||
|
stderr dark green
|
||||||
|
stdin purple
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
import PyShell
|
||||||
|
PyShell.main()
|
Loading…
Reference in New Issue