blob: 4de7ad09d8f8bf87b04303de6e7537c0dc0d0fcc [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
145 if first and last:
146 if index2line(first) != index2line(last):
147 return self.indent_region_event(event)
148 text.delete(first, last)
149 text.mark_set("insert", first)
150 if self.prefertabs:
151 pad = '\t'
152 else:
153 n = len(self.spaceindent)
154 prefix = text.get("insert linestart", "insert")
155 pad = ' ' * (n - len(prefix) % n)
156 text.insert("insert", pad)
157 text.see("insert")
158 return "break"
159
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000160 def newline_and_indent_event(self, event):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000161 text = self.text
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000162 try:
163 first = text.index("sel.first")
164 last = text.index("sel.last")
165 except TclError:
166 first = last = None
167 if first and last:
168 text.delete(first, last)
169 text.mark_set("insert", first)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000170 line = text.get("insert linestart", "insert")
171 i, n = 0, len(line)
172 while i < n and line[i] in " \t":
173 i = i+1
174 indent = line[:i]
Guido van Rossum17c516e1999-04-19 16:23:15 +0000175 # strip trailing whitespace
176 i = 0
177 while line and line[-1] in " \t":
178 line = line[:-1]
179 i = i + 1
180 if i:
181 text.delete("insert - %d chars" % i, "insert")
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000182 text.insert("insert", "\n" + indent)
Guido van Rossum17c516e1999-04-19 16:23:15 +0000183 if _is_block_opener(line):
184 self.smart_indent_event(event)
185 elif indent and _is_block_closer(line) and line[-1:] != "\\":
186 self.smart_backspace_event(event)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000187 text.see("insert")
188 return "break"
189
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000190 auto_indent = newline_and_indent_event
191
192 def indent_region_event(self, event):
193 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000194 for pos in range(len(lines)):
195 line = lines[pos]
196 if line:
197 i, n = 0, len(line)
198 while i < n and line[i] in " \t":
199 i = i+1
200 line = line[:i] + " " + line[i:]
201 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000202 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000203 return "break"
204
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000205 def dedent_region_event(self, event):
206 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000207 for pos in range(len(lines)):
208 line = lines[pos]
209 if line:
210 i, n = 0, len(line)
211 while i < n and line[i] in " \t":
212 i = i+1
213 indent, line = line[:i], line[i:]
214 if indent:
215 if indent == "\t" or indent[-2:] == "\t\t":
216 indent = indent[:-1] + " "
217 elif indent[-4:] == " ":
218 indent = indent[:-4]
219 else:
220 indent = string.expandtabs(indent, 8)
221 indent = indent[:-4]
222 line = indent + line
223 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000224 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000225 return "break"
226
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000227 def comment_region_event(self, event):
228 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000229 for pos in range(len(lines)):
230 line = lines[pos]
231 if not line:
232 continue
233 lines[pos] = '##' + line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000234 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000235
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000236 def uncomment_region_event(self, event):
237 head, tail, chars, lines = self.get_region()
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000238 for pos in range(len(lines)):
239 line = lines[pos]
240 if not line:
241 continue
242 if line[:2] == '##':
243 line = line[2:]
244 elif line[:1] == '#':
245 line = line[1:]
246 lines[pos] = line
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000247 self.set_region(head, tail, chars, lines)
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000248
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000249 def tabify_region_event(self, event):
250 head, tail, chars, lines = self.get_region()
251 lines = map(tabify, lines)
252 self.set_region(head, tail, chars, lines)
253
254 def untabify_region_event(self, event):
255 head, tail, chars, lines = self.get_region()
256 lines = map(string.expandtabs, lines)
257 self.set_region(head, tail, chars, lines)
258
259 def get_region(self):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000260 text = self.text
261 head = text.index("sel.first linestart")
262 tail = text.index("sel.last -1c lineend +1c")
263 if not (head and tail):
264 head = text.index("insert linestart")
265 tail = text.index("insert lineend +1c")
266 chars = text.get(head, tail)
267 lines = string.split(chars, "\n")
268 return head, tail, chars, lines
269
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000270 def set_region(self, head, tail, chars, lines):
Guido van Rossum3b4ca0d1998-10-10 18:48:31 +0000271 text = self.text
272 newchars = string.join(lines, "\n")
273 if newchars == chars:
274 text.bell()
275 return
276 text.tag_remove("sel", "1.0", "end")
277 text.mark_set("insert", head)
278 text.delete(head, tail)
279 text.insert(head, newchars)
280 text.tag_add("sel", head, "insert")
Guido van Rossum504b0bf1999-01-02 21:28:54 +0000281
282def tabify(line, tabsize=8):
283 spaces = tabsize * ' '
284 for i in range(0, len(line), tabsize):
285 if line[i:i+tabsize] != spaces:
286 break
287 else:
288 i = len(line)
289 return '\t' * (i/tabsize) + line[i:]
Guido van Rossum17c516e1999-04-19 16:23:15 +0000290
291# "line.col" -> line, as an int
292def index2line(index):
293 return int(float(index))