mirror of
https://github.com/python/cpython.git
synced 2024-12-01 11:15:56 +01:00
3b4ca0ddad
Features: text editor with syntax coloring and undo; subclassed into interactive Python shell which adds history.
270 lines
7.5 KiB
Python
270 lines
7.5 KiB
Python
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()
|