Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 1 | import string |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 2 | from 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 Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 35 | |
Guido van Rossum | 17c516e | 1999-04-19 16:23:15 +0000 | [diff] [blame] | 36 | import 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 |
| 48 | del re |
| 49 | |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 50 | class AutoIndent: |
| 51 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 52 | 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 Rossum | 33f2b7b | 1999-01-03 00:47:35 +0000 | [diff] [blame] | 64 | keydefs = { |
| 65 | '<<smart-backspace>>': ['<Key-BackSpace>'], |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 66 | '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], |
Guido van Rossum | 17c516e | 1999-04-19 16:23:15 +0000 | [diff] [blame] | 67 | '<<smart-indent>>': ['<Key-Tab>'] |
Guido van Rossum | 33f2b7b | 1999-01-03 00:47:35 +0000 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | windows_keydefs = { |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 71 | '<<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 Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 80 | '<<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 Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 97 | |
| 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 Rossum | 33f2b7b | 1999-01-03 00:47:35 +0000 | [diff] [blame] | 107 | 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 Rossum | 17c516e | 1999-04-19 16:23:15 +0000 | [diff] [blame] | 130 | 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 Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 145 | 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 Rossum | 17c516e | 1999-04-19 16:23:15 +0000 | [diff] [blame] | 163 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 164 | def newline_and_indent_event(self, event): |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 165 | text = self.text |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 166 | try: |
| 167 | first = text.index("sel.first") |
| 168 | last = text.index("sel.last") |
| 169 | except TclError: |
| 170 | first = last = None |
Guido van Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 171 | 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 Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 197 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 198 | auto_indent = newline_and_indent_event |
| 199 | |
| 200 | def indent_region_event(self, event): |
| 201 | head, tail, chars, lines = self.get_region() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 202 | 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 Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 210 | self.set_region(head, tail, chars, lines) |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 211 | return "break" |
| 212 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 213 | def dedent_region_event(self, event): |
| 214 | head, tail, chars, lines = self.get_region() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 215 | 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 Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 232 | self.set_region(head, tail, chars, lines) |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 233 | return "break" |
| 234 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 235 | def comment_region_event(self, event): |
| 236 | head, tail, chars, lines = self.get_region() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 237 | for pos in range(len(lines)): |
| 238 | line = lines[pos] |
| 239 | if not line: |
| 240 | continue |
| 241 | lines[pos] = '##' + line |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 242 | self.set_region(head, tail, chars, lines) |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 243 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 244 | def uncomment_region_event(self, event): |
| 245 | head, tail, chars, lines = self.get_region() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 246 | 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 Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 255 | self.set_region(head, tail, chars, lines) |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 256 | |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 257 | 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 Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 268 | 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 Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 278 | def set_region(self, head, tail, chars, lines): |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 279 | 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 Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 286 | text.undo_block_start() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 287 | text.delete(head, tail) |
| 288 | text.insert(head, newchars) |
Guido van Rossum | 318a70d | 1999-05-03 15:49:52 +0000 | [diff] [blame] | 289 | text.undo_block_stop() |
Guido van Rossum | 3b4ca0d | 1998-10-10 18:48:31 +0000 | [diff] [blame] | 290 | text.tag_add("sel", head, "insert") |
Guido van Rossum | 504b0bf | 1999-01-02 21:28:54 +0000 | [diff] [blame] | 291 | |
| 292 | def 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 Rossum | 17c516e | 1999-04-19 16:23:15 +0000 | [diff] [blame] | 300 | |
| 301 | # "line.col" -> line, as an int |
| 302 | def index2line(index): |
| 303 | return int(float(index)) |