Commit 3b4ca0dd authored by Guido van Rossum's avatar Guido van Rossum

Initial checking of Tk-based Python IDE.

Features: text editor with syntax coloring and undo;
subclassed into interactive Python shell which adds history.
parent dc1adabc
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:]
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")
# 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)
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()
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
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()
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()
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
import os
import sys
from Tkinter import *
class HelpWindow:
helpfile = "help.txt"
helptitle = "Help Window"
def __init__(self, root=None):
if not root:
import Tkinter
root = Tkinter._default_root
if root:
self.top = top = Toplevel(root)
else:
self.top = top = root = Tk()
helpfile = self.helpfile
if not os.path.exists(helpfile):
base = os.path.basename(self.helpfile)
for dir in sys.path:
fullname = os.path.join(dir, base)
if os.path.exists(fullname):
helpfile = fullname
break
try:
f = open(helpfile)
data = f.read()
f.close()
except IOError, msg:
data = "Can't open the help file (%s)" % `helpfile`
top.protocol("WM_DELETE_WINDOW", self.close_command)
top.wm_title(self.helptitle)
self.close_button = Button(top, text="close",
command=self.close_command)
self.close_button.pack(side="bottom")
self.vbar = vbar = Scrollbar(top, name="vbar")
self.text = text = Text(top)
vbar["command"] = text.yview
text["yscrollcommand"] = vbar.set
vbar.pack(side="right", fill="y")
text.pack(side="left", fill="both", expand=1)
text.insert("1.0", data)
text.config(state="disabled")
text.see("1.0")
def close_command(self):
self.top.destroy()
def main():
h = HelpWindow()
h.top.mainloop()
if __name__ == "__main__":
main()
import string
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")
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()
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")
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()
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()
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()
This diff is collapsed.
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
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")
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
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()
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()
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
#! /usr/bin/env python
import PyShell
PyShell.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment