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