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

Python code coverage for Lib/idlelib/undo.py

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