-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtext.py
More file actions
164 lines (150 loc) · 5.48 KB
/
Copy pathtext.py
File metadata and controls
164 lines (150 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import tkinter as tk
import pyperclip
from typing import List, Tuple, Union
from custom_stack import Stack
class UndoText(tk.Text):
"""
A text widget with undo and redo functionality.
"""
def __init__(self, *args: List[Union[str, int]], **kwargs: dict) -> None:
"""
Initialize the UndoText widget.
Args:
*args: Positional arguments to pass to the parent class.
**kwargs: Keyword arguments to pass to the parent class.
"""
super().__init__(*args, **kwargs)
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
self.bind("<<Paste>>", self.paste)
self._undo_stack: Stack = Stack()
self._redo_stack: Stack = Stack()
def _proxy(self, *args: List[Union[str, int]]) -> str:
"""
Intercept and handle Text widget commands.
Args:
*args: Additional arguments for the command.
Returns:
The result of the original Text widget command.
"""
try:
if args[0] in ["insert", "delete"]:
self._handle_edit_command(args)
elif args[0] == "tag":
self._handle_tag_command(args)
result = self.tk.call((self._orig,) + args)
return result
except tk.TclError:
pass
def _handle_edit_command(self, args: List[Union[str, int]]) -> None:
"""
Handle insert and delete commands for undo and redo.
Args:
args: Arguments for the command.
"""
print(args)
if args[1] == "end":
index = self.index("end-1c")
else:
index = self.index(args[1])
if args[0] == "insert":
undo_args = ("delete", index, "{}+{}c".format(index, len(args[2])))
else:
if len(args) == 2:
undo_args = ("insert", index, self.get(self.index(args[1])))
else:
undo_args = (
"insert",
index,
self.get(self.index(args[1]), self.index(args[2])),
)
print(f"{undo_args=}")
self._clear_redo_stack()
self._undo_stack.append((undo_args, args))
def _handle_tag_command(self, args: List[Union[str, int]]) -> None:
"""
Handle tag add and remove commands for undo and redo.
Args:
args: Arguments for the command.
"""
if args[1] in ["add", "remove"] and args[2] != "sel":
indexes = tuple(self.index(ind) for ind in args[3:])
undo_args = (
"tag",
"remove" if args[1] == "add" else "add",
args[2],
) + indexes
self._clear_redo_stack()
self._undo_stack.append((undo_args, args))
def _clear_redo_stack(self) -> None:
"""
Clear the redo stack to prevent redoing after making new changes.
"""
while not self._redo_stack.is_empty:
self._redo_stack.pop()
def paste(self, event: tk.Event) -> None:
"""
Handle paste event with special functionality.
Args:
event: The paste event.
"""
tag_ranges = self.tag_ranges("sel")
if tag_ranges:
selection_start = self.index(tk.SEL_FIRST)
selection_end = self.index(tk.SEL_LAST)
self.delete(selection_start, selection_end)
self.mark_set(tk.INSERT, selection_start)
self.insert(tk.INSERT, pyperclip.paste())
self.see(tk.INSERT)
return "break"
def undo(self) -> None:
"""
Undo the last text edit operation.
"""
if not self._undo_stack:
return
undo_args, redo_args = self._undo_stack.pop()
self._redo_stack.append((undo_args, redo_args))
self.tk.call((self._orig,) + undo_args)
def redo(self) -> None:
"""
Redo the last undone text edit operation.
"""
if not self._redo_stack:
return
undo_args, redo_args = self._redo_stack.pop()
if redo_args[0] == "tag":
if redo_args[1] == "add":
self.tag_add(redo_args[2], undo_args[3], undo_args[4])
else:
self.tag_remove(redo_args[2], redo_args[3], redo_args[4])
self._undo_stack.append((undo_args, redo_args))
else:
self._undo_stack.append((undo_args, redo_args))
try:
self.tk.call((self._orig,) + redo_args)
except tk.TclError:
pass
def dump_tags(self) -> List[Tuple[str, str, str]]:
"""
Serialize the tags and their associated text ranges.
Returns:
A list of tag information (tag name, start index, end index).
"""
tag_info: List[Tuple[str, str, str]] = []
for tag in self.tag_names():
if tag != "sel":
indices = self.tag_ranges(tag)
for i in range(0, len(indices), 2):
tag_info.append((tag, indices[i], indices[i + 1]))
return tag_info
def load_tags(self, tags: List[Tuple[str, str, str]]) -> None:
"""
Load tags from serialized tag information.
Args:
tags: A list of tag information (tag name, start index, end index).
"""
self.tag_remove("sel", "1.0", "end")
for tag, start, end in tags:
self.tag_add(tag, start, end)