blob: 386490d5138f9e685e359118daafa37a16994efb [file] [log] [blame]
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +00001import string
Guido van Rossum504b0bf1999-01-02 21:28:54 +00002from Tkinter import TclError
3
4###$ event <<newline-and-indent>>
5###$ win <Key-Return>
6###$ win <KP_Enter>
7###$ unix <Key-Return>
8###$ unix <KP_Enter>
9
10###$ event <<indent-region>>
11###$ win <Control-bracketright>
12###$ unix <Alt-bracketright>
13###$ unix <Control-bracketright>
14
15###$ event <<dedent-region>>
16###$ win <Control-bracketleft>
17###$ unix <Alt-bracketleft>
18###$ unix <Control-bracketleft>
19
20###$ event <<comment-region>>
21###$ win <Alt-Key-3>
22###$ unix <Alt-Key-3>
23
24###$ event <<uncomment-region>>
25###$ win <Alt-Key-4>
26###$ unix <Alt-Key-4>
27
28###$ event <<tabify-region>>
29###$ win <Alt-Key-5>
30###$ unix <Alt-Key-5>
31
32###$ event <<untabify-region>>
33###$ win <Alt-Key-6>
34###$ unix <Alt-Key-6>
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000035
Guido van Rossum17c516e1999-04-19 16:23:15 +000036import re
37_is_block_opener = re.compile(r":\s*(#.*)?$").search
38_is_block_closer = re.compile(r"""
39 \s*
40 ( return
41 | break
42 | continue
43 | raise
44 | pass
45 )
46 \b
47""", re.VERBOSE).match
48del re
49
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000050class AutoIndent:
51
Guido van Rossum504b0bf1999-01-02 21:28:54 +000052 menudefs = [
53 ('edit', [
54 None,
55 ('_Indent region', '<<indent-region>>'),
56 ('_Dedent region', '<<dedent-region>>'),
57 ('Comment _out region', '<<comment-region>>'),
58 ('U_ncomment region', '<<uncomment-region>>'),
59 ('Tabify region', '<<tabify-region>>'),
60 ('Untabify region', '<<untabify-region>>'),
61 ]),
62 ]
63
Guido van Rossum33f2b7b1999-01-03 00:47:35 +000064 keydefs = {
65 '<<smart-backspace>>': ['<Key-BackSpace>'],
Guido van Rossum504b0bf1999-01-02 21:28:54 +000066 '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
Guido van Rossum17c516e1999-04-19 16:23:15 +000067 '<<smart-indent>>': ['<Key-Tab>']
Guido van Rossum33f2b7b1999-01-03 00:47:35 +000068 }
69
70 windows_keydefs = {
Guido van Rossum504b0bf1999-01-02 21:28:54 +000071 '<<indent-region>>': ['<Control-bracketright>'],
72 '<<dedent-region>>': ['<Control-bracketleft>'],
73 '<<comment-region>>': ['<Alt-Key-3>'],
74 '<<uncomment-region>>': ['<Alt-Key-4>'],
75 '<<tabify-region>>': ['<Alt-Key-5>'],
76 '<<untabify-region>>': ['<Alt-Key-6>'],
77 }
78
79 unix_keydefs = {
Guido van Rossum504b0bf1999-01-02 21:28:54 +000080 '<<indent-region>>': ['<Alt-bracketright>',
81 '<Meta-bracketright>',
82 '<Control-bracketright>'],
83 '<<dedent-region>>': ['<Alt-bracketleft>',
84 '<Meta-bracketleft>',
85 '<Control-bracketleft>'],
86 '<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
87 '<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
88 '<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
89 '<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
90 }
91
92 prefertabs = 0
93 spaceindent = 4*" "
94
95 def __init__(self, editwin):
96 self.text = editwin.text
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +000097
98 def config(self, **options):
99 for key, value in options.items():
100 if key == 'prefertabs':
101 self.prefertabs = value
102 elif key == 'spaceindent':
103 self.spaceindent = value
104 else:
105 raise KeyError, "bad option name: %s" % `key`
106
Guido van Rossum33f2b7b1999-01-03 00:47:35 +0000107 def smart_backspace_event(self, event):
108 text = self.text
109 try:
110 first = text.index("sel.first")
111 last = text.index("sel.last")
112 except TclError:
113 first = last = None
114 if first and last:
115 text.delete(first, last)
116 text.mark_set("insert", first)
117 return "break"
118 # After Tim Peters
119 ndelete = 1
120 chars = text.get("insert linestart", "insert")
121 i = 0
122 n = len(chars)
123 while i < n and chars[i] in " \t":
124 i = i+1
125 if i == n and chars[-4:] == " ":
126 ndelete = 4
127 text.delete("insert - %d chars" % ndelete, "insert")
128 return "break"
129
Guido van Rossum17c516e1999-04-19 16:23:15 +0000130 def smart_indent_event(self, event):
131 # if intraline selection:
132 # delete it
133 # elif multiline selection:
134 # do indent-region & return
135 # if tabs preferred:
136 # insert a tab
137 # else:
138 # insert spaces up to next higher multiple of indent level
139 text = self.text
140 try:
141 first = text.index("sel.first")
142 last = text.index("sel.last")
143 except TclError:
144 first = last = None
Guido van Rossum318a70d1999-05-03 15:49:52 +0000145 text.undo_block_start()
146 try:
147 if first and last:
148 if index2line(first) != index2line(last):
149 return self.indent_region_event(event)
150 text.delete(first, last)
151 text.mark_set("insert", first)
152 if self.prefertabs:
153 pad = '\t'
154 else:
155 n = len(self.spaceindent)
156 prefix = text.get("insert linestart", "insert")
157 pad = ' ' * (n - len(prefix) % n)
158 text.insert("insert", pad)
159 text.see("insert")
160 return "break"
161 finally:
162 text.undo_block_stop()
Guido van Rossum17c516e1999-04-19 16:23:15 +0000163
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000164 def newline_and_indent_event(self, event):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000165 text = self.text
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000166 try:
167 first = text.index("sel.first")
168 last = text.index("sel.last")
169 except TclError:
170 first = last = None
Guido van Rossum318a70d1999-05-03 15:49:52 +0000171 text.undo_block_start()
172 try:
173 if first and last:
174 text.delete(first, last)
175 text.mark_set("insert", first)
176 line = text.get("insert linestart", "insert")
177 i, n = 0, len(line)
178 while i < n and line[i] in " \t":
179 i = i+1
180 indent = line[:i]
181 # strip trailing whitespace
182 i = 0
183 while line and line[-1] in " \t":
184 line = line[:-1]
185 i = i + 1
186 if i:
187 text.delete("insert - %d chars" % i, "insert")
188 text.insert("insert", "\n" + indent)
189 if _is_block_opener(line):
190 self.smart_indent_event(event)
191 elif indent and _is_block_closer(line) and line[-1:] != "\\":
192 self.smart_backspace_event(event)
193 text.see("insert")
194 return "break"
195 finally:
196 text.undo_block_stop()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000197
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000198 auto_indent = newline_and_indent_event
199
200 def indent_region_event(self, event):
201 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000202 for pos in range(len(lines)):
203 line = lines[pos]
204 if line:
205 i, n = 0, len(line)
206 while i < n and line[i] in " \t":
207 i = i+1
208 line = line[:i] + " " + line[i:]
209 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000210 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000211 return "break"
212
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000213 def dedent_region_event(self, event):
214 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000215 for pos in range(len(lines)):
216 line = lines[pos]
217 if line:
218 i, n = 0, len(line)
219 while i < n and line[i] in " \t":
220 i = i+1
221 indent, line = line[:i], line[i:]
222 if indent:
223 if indent == "\t" or indent[-2:] == "\t\t":
224 indent = indent[:-1] + " "
225 elif indent[-4:] == " ":
226 indent = indent[:-4]
227 else:
228 indent = string.expandtabs(indent, 8)
229 indent = indent[:-4]
230 line = indent + line
231 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000232 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000233 return "break"
234
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000235 def comment_region_event(self, event):
236 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000237 for pos in range(len(lines)):
238 line = lines[pos]
239 if not line:
240 continue
241 lines[pos] = '##' + line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000242 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000243
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000244 def uncomment_region_event(self, event):
245 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000246 for pos in range(len(lines)):
247 line = lines[pos]
248 if not line:
249 continue
250 if line[:2] == '##':
251 line = line[2:]
252 elif line[:1] == '#':
253 line = line[1:]
254 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000255 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000256
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000257 def tabify_region_event(self, event):
258 head, tail, chars, lines = self.get_region()
259 lines = map(tabify, lines)
260 self.set_region(head, tail, chars, lines)
261
262 def untabify_region_event(self, event):
263 head, tail, chars, lines = self.get_region()
264 lines = map(string.expandtabs, lines)
265 self.set_region(head, tail, chars, lines)
266
267 def get_region(self):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000268 text = self.text
269 head = text.index("sel.first linestart")
270 tail = text.index("sel.last -1c lineend +1c")
271 if not (head and tail):
272 head = text.index("insert linestart")
273 tail = text.index("insert lineend +1c")
274 chars = text.get(head, tail)
275 lines = string.split(chars, "\n")
276 return head, tail, chars, lines
277
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000278 def set_region(self, head, tail, chars, lines):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000279 text = self.text
280 newchars = string.join(lines, "\n")
281 if newchars == chars:
282 text.bell()
283 return
284 text.tag_remove("sel", "1.0", "end")
285 text.mark_set("insert", head)
Guido van Rossum318a70d1999-05-03 15:49:52 +0000286 text.undo_block_start()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000287 text.delete(head, tail)
288 text.insert(head, newchars)
Guido van Rossum318a70d1999-05-03 15:49:52 +0000289 text.undo_block_stop()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000290 text.tag_add("sel", head, "insert")
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000291
292def tabify(line, tabsize=8):
293 spaces = tabsize * ' '
294 for i in range(0, len(line), tabsize):
295 if line[i:i+tabsize] != spaces:
296 break
297 else:
298 i = len(line)
299 return '\t' * (i/tabsize) + line[i:]
Guido van Rossum17c516e1999-04-19 16:23:15 +0000300
301# "line.col" -> line, as an int
302def index2line(index):
303 return int(float(index))