ยปCore Development>Code coverage>Lib/idlelib/UndoDelegator.py

Python code coverage for Lib/idlelib/UndoDelegator.py

#countcontent
1n/aimport string
2n/afrom tkinter import *
3n/a
4n/afrom idlelib.Delegator import Delegator
5n/a
6n/a#$ event <<redo>>
7n/a#$ win <Control-y>
8n/a#$ unix <Alt-z>
9n/a
10n/a#$ event <<undo>>
11n/a#$ win <Control-z>
12n/a#$ unix <Control-z>
13n/a
14n/a#$ event <<dump-undo-state>>
15n/a#$ win <Control-backslash>
16n/a#$ unix <Control-backslash>
17n/a
18n/a
19n/aclass UndoDelegator(Delegator):
20n/a
21n/a max_undo = 1000
22n/a
23n/a def __init__(self):
24n/a Delegator.__init__(self)
25n/a self.reset_undo()
26n/a
27n/a def setdelegate(self, delegate):
28n/a if self.delegate is not None:
29n/a self.unbind("<<undo>>")
30n/a self.unbind("<<redo>>")
31n/a self.unbind("<<dump-undo-state>>")
32n/a Delegator.setdelegate(self, delegate)
33n/a if delegate is not None:
34n/a self.bind("<<undo>>", self.undo_event)
35n/a self.bind("<<redo>>", self.redo_event)
36n/a self.bind("<<dump-undo-state>>", self.dump_event)
37n/a
38n/a def dump_event(self, event):
39n/a from pprint import pprint
40n/a pprint(self.undolist[:self.pointer])
41n/a print("pointer:", self.pointer, end=' ')
42n/a print("saved:", self.saved, end=' ')
43n/a print("can_merge:", self.can_merge, end=' ')
44n/a print("get_saved():", self.get_saved())
45n/a pprint(self.undolist[self.pointer:])
46n/a return "break"
47n/a
48n/a def reset_undo(self):
49n/a self.was_saved = -1
50n/a self.pointer = 0
51n/a self.undolist = []
52n/a self.undoblock = 0 # or a CommandSequence instance
53n/a self.set_saved(1)
54n/a
55n/a def set_saved(self, flag):
56n/a if flag:
57n/a self.saved = self.pointer
58n/a else:
59n/a self.saved = -1
60n/a self.can_merge = False
61n/a self.check_saved()
62n/a
63n/a def get_saved(self):
64n/a return self.saved == self.pointer
65n/a
66n/a saved_change_hook = None
67n/a
68n/a def set_saved_change_hook(self, hook):
69n/a self.saved_change_hook = hook
70n/a
71n/a was_saved = -1
72n/a
73n/a def check_saved(self):
74n/a is_saved = self.get_saved()
75n/a if is_saved != self.was_saved:
76n/a self.was_saved = is_saved
77n/a if self.saved_change_hook:
78n/a self.saved_change_hook()
79n/a
80n/a def insert(self, index, chars, tags=None):
81n/a self.addcmd(InsertCommand(index, chars, tags))
82n/a
83n/a def delete(self, index1, index2=None):
84n/a self.addcmd(DeleteCommand(index1, index2))
85n/a
86n/a # Clients should call undo_block_start() and undo_block_stop()
87n/a # around a sequence of editing cmds to be treated as a unit by
88n/a # undo & redo. Nested matching calls are OK, and the inner calls
89n/a # then act like nops. OK too if no editing cmds, or only one
90n/a # editing cmd, is issued in between: if no cmds, the whole
91n/a # sequence has no effect; and if only one cmd, that cmd is entered
92n/a # directly into the undo list, as if undo_block_xxx hadn't been
93n/a # called. The intent of all that is to make this scheme easy
94n/a # to use: all the client has to worry about is making sure each
95n/a # _start() call is matched by a _stop() call.
96n/a
97n/a def undo_block_start(self):
98n/a if self.undoblock == 0:
99n/a self.undoblock = CommandSequence()
100n/a self.undoblock.bump_depth()
101n/a
102n/a def undo_block_stop(self):
103n/a if self.undoblock.bump_depth(-1) == 0:
104n/a cmd = self.undoblock
105n/a self.undoblock = 0
106n/a if len(cmd) > 0:
107n/a if len(cmd) == 1:
108n/a # no need to wrap a single cmd
109n/a cmd = cmd.getcmd(0)
110n/a # this blk of cmds, or single cmd, has already
111n/a # been done, so don't execute it again
112n/a self.addcmd(cmd, 0)
113n/a
114n/a def addcmd(self, cmd, execute=True):
115n/a if execute:
116n/a cmd.do(self.delegate)
117n/a if self.undoblock != 0:
118n/a self.undoblock.append(cmd)
119n/a return
120n/a if self.can_merge and self.pointer > 0:
121n/a lastcmd = self.undolist[self.pointer-1]
122n/a if lastcmd.merge(cmd):
123n/a return
124n/a self.undolist[self.pointer:] = [cmd]
125n/a if self.saved > self.pointer:
126n/a self.saved = -1
127n/a self.pointer = self.pointer + 1
128n/a if len(self.undolist) > self.max_undo:
129n/a ##print "truncating undo list"
130n/a del self.undolist[0]
131n/a self.pointer = self.pointer - 1
132n/a if self.saved >= 0:
133n/a self.saved = self.saved - 1
134n/a self.can_merge = True
135n/a self.check_saved()
136n/a
137n/a def undo_event(self, event):
138n/a if self.pointer == 0:
139n/a self.bell()
140n/a return "break"
141n/a cmd = self.undolist[self.pointer - 1]
142n/a cmd.undo(self.delegate)
143n/a self.pointer = self.pointer - 1
144n/a self.can_merge = False
145n/a self.check_saved()
146n/a return "break"
147n/a
148n/a def redo_event(self, event):
149n/a if self.pointer >= len(self.undolist):
150n/a self.bell()
151n/a return "break"
152n/a cmd = self.undolist[self.pointer]
153n/a cmd.redo(self.delegate)
154n/a self.pointer = self.pointer + 1
155n/a self.can_merge = False
156n/a self.check_saved()
157n/a return "break"
158n/a
159n/a
160n/aclass Command:
161n/a
162n/a # Base class for Undoable commands
163n/a
164n/a tags = None
165n/a
166n/a def __init__(self, index1, index2, chars, tags=None):
167n/a self.marks_before = {}
168n/a self.marks_after = {}
169n/a self.index1 = index1
170n/a self.index2 = index2
171n/a self.chars = chars
172n/a if tags:
173n/a self.tags = tags
174n/a
175n/a def __repr__(self):
176n/a s = self.__class__.__name__
177n/a t = (self.index1, self.index2, self.chars, self.tags)
178n/a if self.tags is None:
179n/a t = t[:-1]
180n/a return s + repr(t)
181n/a
182n/a def do(self, text):
183n/a pass
184n/a
185n/a def redo(self, text):
186n/a pass
187n/a
188n/a def undo(self, text):
189n/a pass
190n/a
191n/a def merge(self, cmd):
192n/a return 0
193n/a
194n/a def save_marks(self, text):
195n/a marks = {}
196n/a for name in text.mark_names():
197n/a if name != "insert" and name != "current":
198n/a marks[name] = text.index(name)
199n/a return marks
200n/a
201n/a def set_marks(self, text, marks):
202n/a for name, index in marks.items():
203n/a text.mark_set(name, index)
204n/a
205n/a
206n/aclass InsertCommand(Command):
207n/a
208n/a # Undoable insert command
209n/a
210n/a def __init__(self, index1, chars, tags=None):
211n/a Command.__init__(self, index1, None, chars, tags)
212n/a
213n/a def do(self, text):
214n/a self.marks_before = self.save_marks(text)
215n/a self.index1 = text.index(self.index1)
216n/a if text.compare(self.index1, ">", "end-1c"):
217n/a # Insert before the final newline
218n/a self.index1 = text.index("end-1c")
219n/a text.insert(self.index1, self.chars, self.tags)
220n/a self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
221n/a self.marks_after = self.save_marks(text)
222n/a ##sys.__stderr__.write("do: %s\n" % self)
223n/a
224n/a def redo(self, text):
225n/a text.mark_set('insert', self.index1)
226n/a text.insert(self.index1, self.chars, self.tags)
227n/a self.set_marks(text, self.marks_after)
228n/a text.see('insert')
229n/a ##sys.__stderr__.write("redo: %s\n" % self)
230n/a
231n/a def undo(self, text):
232n/a text.mark_set('insert', self.index1)
233n/a text.delete(self.index1, self.index2)
234n/a self.set_marks(text, self.marks_before)
235n/a text.see('insert')
236n/a ##sys.__stderr__.write("undo: %s\n" % self)
237n/a
238n/a def merge(self, cmd):
239n/a if self.__class__ is not cmd.__class__:
240n/a return False
241n/a if self.index2 != cmd.index1:
242n/a return False
243n/a if self.tags != cmd.tags:
244n/a return False
245n/a if len(cmd.chars) != 1:
246n/a return False
247n/a if self.chars and \
248n/a self.classify(self.chars[-1]) != self.classify(cmd.chars):
249n/a return False
250n/a self.index2 = cmd.index2
251n/a self.chars = self.chars + cmd.chars
252n/a return True
253n/a
254n/a alphanumeric = string.ascii_letters + string.digits + "_"
255n/a
256n/a def classify(self, c):
257n/a if c in self.alphanumeric:
258n/a return "alphanumeric"
259n/a if c == "\n":
260n/a return "newline"
261n/a return "punctuation"
262n/a
263n/a
264n/aclass DeleteCommand(Command):
265n/a
266n/a # Undoable delete command
267n/a
268n/a def __init__(self, index1, index2=None):
269n/a Command.__init__(self, index1, index2, None, None)
270n/a
271n/a def do(self, text):
272n/a self.marks_before = self.save_marks(text)
273n/a self.index1 = text.index(self.index1)
274n/a if self.index2:
275n/a self.index2 = text.index(self.index2)
276n/a else:
277n/a self.index2 = text.index(self.index1 + " +1c")
278n/a if text.compare(self.index2, ">", "end-1c"):
279n/a # Don't delete the final newline
280n/a self.index2 = text.index("end-1c")
281n/a self.chars = text.get(self.index1, self.index2)
282n/a text.delete(self.index1, self.index2)
283n/a self.marks_after = self.save_marks(text)
284n/a ##sys.__stderr__.write("do: %s\n" % self)
285n/a
286n/a def redo(self, text):
287n/a text.mark_set('insert', self.index1)
288n/a text.delete(self.index1, self.index2)
289n/a self.set_marks(text, self.marks_after)
290n/a text.see('insert')
291n/a ##sys.__stderr__.write("redo: %s\n" % self)
292n/a
293n/a def undo(self, text):
294n/a text.mark_set('insert', self.index1)
295n/a text.insert(self.index1, self.chars)
296n/a self.set_marks(text, self.marks_before)
297n/a text.see('insert')
298n/a ##sys.__stderr__.write("undo: %s\n" % self)
299n/a
300n/aclass CommandSequence(Command):
301n/a
302n/a # Wrapper for a sequence of undoable cmds to be undone/redone
303n/a # as a unit
304n/a
305n/a def __init__(self):
306n/a self.cmds = []
307n/a self.depth = 0
308n/a
309n/a def __repr__(self):
310n/a s = self.__class__.__name__
311n/a strs = []
312n/a for cmd in self.cmds:
313n/a strs.append(" %r" % (cmd,))
314n/a return s + "(\n" + ",\n".join(strs) + "\n)"
315n/a
316n/a def __len__(self):
317n/a return len(self.cmds)
318n/a
319n/a def append(self, cmd):
320n/a self.cmds.append(cmd)
321n/a
322n/a def getcmd(self, i):
323n/a return self.cmds[i]
324n/a
325n/a def redo(self, text):
326n/a for cmd in self.cmds:
327n/a cmd.redo(text)
328n/a
329n/a def undo(self, text):
330n/a cmds = self.cmds[:]
331n/a cmds.reverse()
332n/a for cmd in cmds:
333n/a cmd.undo(text)
334n/a
335n/a def bump_depth(self, incr=1):
336n/a self.depth = self.depth + incr
337n/a return self.depth
338n/a
339n/adef main():
340n/a from idlelib.Percolator import Percolator
341n/a root = Tk()
342n/a root.wm_protocol("WM_DELETE_WINDOW", root.quit)
343n/a text = Text()
344n/a text.pack()
345n/a text.focus_set()
346n/a p = Percolator(text)
347n/a d = UndoDelegator()
348n/a p.insertfilter(d)
349n/a root.mainloop()
350n/a
351n/aif __name__ == "__main__":
352n/a main()