blob: 45481ed53f540871d0c9874001eeea28981248f0 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
33 path = module.__path__
34 return file, filename, descr
35
David Scherer7aced172000-08-15 01:13:23 +000036class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000037 from Percolator import Percolator
38 from ColorDelegator import ColorDelegator
39 from UndoDelegator import UndoDelegator
40 from IOBinding import IOBinding
41 import Bindings
42 from Tkinter import Toplevel
43 from MultiStatusBar import MultiStatusBar
44
David Scherer7aced172000-08-15 01:13:23 +000045 vars = {}
46
47 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000048 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000049 self.flist = flist
50 root = root or flist.root
51 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000052 self.menubar = Menu(root)
53 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000054 if flist:
55 self.vars = flist.vars
56 #self.top.instanceDict makes flist.inversedict avalable to
57 #configDialog.py so it can access all EditorWindow instaces
58 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000059 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
60 'recent-files.lst')
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +000061 self.break_set = False
David Scherer7aced172000-08-15 01:13:23 +000062 self.vbar = vbar = Scrollbar(top, name='vbar')
63 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +000064 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
65 foreground=idleConf.GetHighlight(currentTheme,
66 'normal',fgBg='fg'),
67 background=idleConf.GetHighlight(currentTheme,
68 'normal',fgBg='bg'),
69 highlightcolor=idleConf.GetHighlight(currentTheme,
70 'hilite',fgBg='fg'),
71 highlightbackground=idleConf.GetHighlight(currentTheme,
72 'hilite',fgBg='bg'),
73 insertbackground=idleConf.GetHighlight(currentTheme,
74 'cursor',fgBg='fg'),
75 width=idleConf.GetOption('main','EditorWindow','width'),
76 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000077
78 self.createmenubar()
79 self.apply_bindings()
80
81 self.top.protocol("WM_DELETE_WINDOW", self.close)
82 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +000083 text.bind("<<cut>>", self.cut)
84 text.bind("<<copy>>", self.copy)
85 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +000086 text.bind("<<center-insert>>", self.center_insert_event)
87 text.bind("<<help>>", self.help_dialog)
88 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +000089 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +000090 text.bind("<<python-docs>>", self.python_docs)
91 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +000092 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +000093 text.bind("<<open-module>>", self.open_module)
94 text.bind("<<do-nothing>>", lambda event: "break")
95 text.bind("<<select-all>>", self.select_all)
96 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +000097 text.bind("<<find>>", self.find_event)
98 text.bind("<<find-again>>", self.find_again_event)
99 text.bind("<<find-in-files>>", self.find_in_files_event)
100 text.bind("<<find-selection>>", self.find_selection_event)
101 text.bind("<<replace>>", self.replace_event)
102 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000103 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000104 text.bind("<<smart-backspace>>",self.smart_backspace_event)
105 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
106 text.bind("<<smart-indent>>",self.smart_indent_event)
107 text.bind("<<indent-region>>",self.indent_region_event)
108 text.bind("<<dedent-region>>",self.dedent_region_event)
109 text.bind("<<comment-region>>",self.comment_region_event)
110 text.bind("<<uncomment-region>>",self.uncomment_region_event)
111 text.bind("<<tabify-region>>",self.tabify_region_event)
112 text.bind("<<untabify-region>>",self.untabify_region_event)
113 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
114 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
115
David Scherer7aced172000-08-15 01:13:23 +0000116 if flist:
117 flist.inversedict[self] = key
118 if key:
119 flist.dict[key] = self
120 text.bind("<<open-new-window>>", self.flist.new_callback)
121 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
122 text.bind("<<open-class-browser>>", self.open_class_browser)
123 text.bind("<<open-path-browser>>", self.open_path_browser)
124
Steven M. Gava898a3652001-10-07 11:10:44 +0000125 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000126 vbar['command'] = text.yview
127 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000128 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000129 fontWeight='normal'
130 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
131 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000132 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000133 idleConf.GetOption('main','EditorWindow','font-size'),
134 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000135 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
136 text.pack(side=TOP, fill=BOTH, expand=1)
137 text.focus_set()
138
139 self.per = per = self.Percolator(text)
140 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000141 self.color = color = self.ColorDelegator()
142 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000143 ##print "Initial colorizer"
144 else:
145 ##print "No initial colorizer"
146 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000147
148 self.undo = undo = self.UndoDelegator()
149 per.insertfilter(undo)
150 text.undo_block_start = undo.undo_block_start
151 text.undo_block_stop = undo.undo_block_stop
152 undo.set_saved_change_hook(self.saved_change_hook)
153
154 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000155 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000156 io.set_filename_change_hook(self.filename_change_hook)
157
Steven M. Gava1d46e402002-03-27 08:40:46 +0000158 #create the Recent Files submenu
159 self.menuRecentFiles=Menu(self.menubar)
160 self.menudict['file'].insert_cascade(3,label='Recent Files',
161 underline=0,menu=self.menuRecentFiles)
162 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000163
David Scherer7aced172000-08-15 01:13:23 +0000164 if filename:
165 if os.path.exists(filename):
166 io.loadfile(filename)
167 else:
168 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000169 self.saved_change_hook()
170
171 self.load_extensions()
172
173 menu = self.menudict.get('windows')
174 if menu:
175 end = menu.index("end")
176 if end is None:
177 end = -1
178 if end >= 0:
179 menu.add_separator()
180 end = end + 1
181 self.wmenu_end = end
182 WindowList.register_callback(self.postwindowsmenu)
183
184 # Some abstractions so IDLE extensions are cross-IDE
185 self.askyesno = tkMessageBox.askyesno
186 self.askinteger = tkSimpleDialog.askinteger
187 self.showerror = tkMessageBox.showerror
188
189 if self.extensions.has_key('AutoIndent'):
190 self.extensions['AutoIndent'].set_indentation_params(
191 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000192
David Scherer7aced172000-08-15 01:13:23 +0000193 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000194 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000195 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
196 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
197 self.status_bar.pack(side=BOTTOM, fill=X)
198 self.text.bind('<KeyRelease>', self.set_line_and_column)
199 self.text.bind('<ButtonRelease>', self.set_line_and_column)
200 self.text.after_idle(self.set_line_and_column)
201
202 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000203 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000204 self.status_bar.set_label('column', 'Col: %s' % column)
205 self.status_bar.set_label('line', 'Ln: %s' % line)
206
207 def wakeup(self):
208 if self.top.wm_state() == "iconic":
209 self.top.wm_deiconify()
210 else:
211 self.top.tkraise()
212 self.text.focus_set()
213
214 menu_specs = [
215 ("file", "_File"),
216 ("edit", "_Edit"),
217 ("format", "F_ormat"),
218 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000219 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000220 ("windows", "_Windows"),
221 ("help", "_Help"),
222 ]
223
224 def createmenubar(self):
225 mbar = self.menubar
226 self.menudict = menudict = {}
227 for name, label in self.menu_specs:
228 underline, label = prepstr(label)
229 menudict[name] = menu = Menu(mbar, name=name)
230 mbar.add_cascade(label=label, menu=menu, underline=underline)
231 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000232 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000233 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000234
235 def postwindowsmenu(self):
236 # Only called when Windows menu exists
237 # XXX Actually, this Just-In-Time updating interferes badly
238 # XXX with the tear-off feature. It would be better to update
239 # XXX all Windows menus whenever the list of windows changes.
240 menu = self.menudict['windows']
241 end = menu.index("end")
242 if end is None:
243 end = -1
244 if end > self.wmenu_end:
245 menu.delete(self.wmenu_end+1, end)
246 WindowList.add_windows_to_menu(menu)
247
248 rmenu = None
249
250 def right_menu_event(self, event):
251 self.text.tag_remove("sel", "1.0", "end")
252 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
253 if not self.rmenu:
254 self.make_rmenu()
255 rmenu = self.rmenu
256 self.event = event
257 iswin = sys.platform[:3] == 'win'
258 if iswin:
259 self.text.config(cursor="arrow")
260 rmenu.tk_popup(event.x_root, event.y_root)
261 if iswin:
262 self.text.config(cursor="ibeam")
263
264 rmenu_specs = [
265 # ("Label", "<<virtual-event>>"), ...
266 ("Close", "<<close-window>>"), # Example
267 ]
268
269 def make_rmenu(self):
270 rmenu = Menu(self.text, tearoff=0)
271 for label, eventname in self.rmenu_specs:
272 def command(text=self.text, eventname=eventname):
273 text.event_generate(eventname)
274 rmenu.add_command(label=label, command=command)
275 self.rmenu = rmenu
276
277 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000278 aboutDialog.AboutDialog(self.top,'About IDLEfork')
279
Steven M. Gava3b55a892001-11-21 05:56:26 +0000280 def config_dialog(self, event=None):
281 configDialog.ConfigDialog(self.top,'Settings')
282
David Scherer7aced172000-08-15 01:13:23 +0000283 def good_advice(self, event=None):
284 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
285
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000286 def view_readme(self, event=None):
287 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
288 textView.TextViewer(self.top,'IDLEfork - README',fn)
289
David Scherer7aced172000-08-15 01:13:23 +0000290 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000291 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
292 textView.TextViewer(self.top,'Help',fn)
293
David Scherer7aced172000-08-15 01:13:23 +0000294 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000295 if sys.platform[:3] == "win":
296 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000297 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000298 fn = os.path.normpath(fn)
299 if os.path.isfile(fn):
300 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000301 else:
302 fn = os.path.dirname(__file__)
303 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
304 fn = os.path.normpath(fn)
305 if os.path.isfile(fn):
306 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000307 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000308 def python_docs(self, event=None):
309 os.startfile(self.help_url)
310 else:
311 def python_docs(self, event=None):
312 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000313
314 def display_docs(self, url):
315 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000316
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000317 def cut(self,event):
318 self.text.event_generate("<<Cut>>")
319 return "break"
320
321 def copy(self,event):
322 self.text.event_generate("<<Copy>>")
323 return "break"
324
325 def paste(self,event):
326 self.text.event_generate("<<Paste>>")
327 return "break"
328
David Scherer7aced172000-08-15 01:13:23 +0000329 def select_all(self, event=None):
330 self.text.tag_add("sel", "1.0", "end-1c")
331 self.text.mark_set("insert", "1.0")
332 self.text.see("insert")
333 return "break"
334
335 def remove_selection(self, event=None):
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.see("insert")
338
Steven M. Gavac5976402002-01-04 03:06:08 +0000339 def find_event(self, event):
340 SearchDialog.find(self.text)
341 return "break"
342
343 def find_again_event(self, event):
344 SearchDialog.find_again(self.text)
345 return "break"
346
347 def find_selection_event(self, event):
348 SearchDialog.find_selection(self.text)
349 return "break"
350
351 def find_in_files_event(self, event):
352 GrepDialog.grep(self.text, self.io, self.flist)
353 return "break"
354
355 def replace_event(self, event):
356 ReplaceDialog.replace(self.text)
357 return "break"
358
359 def goto_line_event(self, event):
360 text = self.text
361 lineno = tkSimpleDialog.askinteger("Goto",
362 "Go to line number:",parent=text)
363 if lineno is None:
364 return "break"
365 if lineno <= 0:
366 text.bell()
367 return "break"
368 text.mark_set("insert", "%d.0" % lineno)
369 text.see("insert")
370
David Scherer7aced172000-08-15 01:13:23 +0000371 def open_module(self, event=None):
372 # XXX Shouldn't this be in IOBinding or in FileList?
373 try:
374 name = self.text.get("sel.first", "sel.last")
375 except TclError:
376 name = ""
377 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000378 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000379 if not name:
380 name = tkSimpleDialog.askstring("Module",
381 "Enter the name of a Python module\n"
382 "to search on sys.path and open:",
383 parent=self.text)
384 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000385 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000386 if not name:
387 return
David Scherer7aced172000-08-15 01:13:23 +0000388 # XXX Ought to insert current file's directory in front of path
389 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000390 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000391 except (NameError, ImportError), msg:
392 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
393 return
394 if type != imp.PY_SOURCE:
395 tkMessageBox.showerror("Unsupported type",
396 "%s is not a source module" % name, parent=self.text)
397 return
398 if f:
399 f.close()
400 if self.flist:
401 self.flist.open(file)
402 else:
403 self.io.loadfile(file)
404
405 def open_class_browser(self, event=None):
406 filename = self.io.filename
407 if not filename:
408 tkMessageBox.showerror(
409 "No filename",
410 "This buffer has no associated filename",
411 master=self.text)
412 self.text.focus_set()
413 return None
414 head, tail = os.path.split(filename)
415 base, ext = os.path.splitext(tail)
416 import ClassBrowser
417 ClassBrowser.ClassBrowser(self.flist, base, [head])
418
419 def open_path_browser(self, event=None):
420 import PathBrowser
421 PathBrowser.PathBrowser(self.flist)
422
423 def gotoline(self, lineno):
424 if lineno is not None and lineno > 0:
425 self.text.mark_set("insert", "%d.0" % lineno)
426 self.text.tag_remove("sel", "1.0", "end")
427 self.text.tag_add("sel", "insert", "insert +1l")
428 self.center()
429
430 def ispythonsource(self, filename):
431 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000432 return True
David Scherer7aced172000-08-15 01:13:23 +0000433 base, ext = os.path.splitext(os.path.basename(filename))
434 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000435 return True
David Scherer7aced172000-08-15 01:13:23 +0000436 try:
437 f = open(filename)
438 line = f.readline()
439 f.close()
440 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000441 return False
442 return line.startswith('#!') and 'python' in line
David Scherer7aced172000-08-15 01:13:23 +0000443
444 def close_hook(self):
445 if self.flist:
446 self.flist.close_edit(self)
447
448 def set_close_hook(self, close_hook):
449 self.close_hook = close_hook
450
451 def filename_change_hook(self):
452 if self.flist:
453 self.flist.filename_changed_edit(self)
454 self.saved_change_hook()
455 if self.ispythonsource(self.io.filename):
456 self.addcolorizer()
457 else:
458 self.rmcolorizer()
459
460 def addcolorizer(self):
461 if self.color:
462 return
463 ##print "Add colorizer"
464 self.per.removefilter(self.undo)
465 self.color = self.ColorDelegator()
466 self.per.insertfilter(self.color)
467 self.per.insertfilter(self.undo)
468
469 def rmcolorizer(self):
470 if not self.color:
471 return
472 ##print "Remove colorizer"
473 self.per.removefilter(self.undo)
474 self.per.removefilter(self.color)
475 self.color = None
476 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000477
478 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000479 "Update the colour theme if it is changed"
480 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000481 if self.color:
482 self.color = self.ColorDelegator()
483 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000484
Steven M. Gavab1585412002-03-12 00:21:56 +0000485 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000486 "Update the text widgets' font if it is changed"
487 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000488 fontWeight='normal'
489 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
490 fontWeight='bold'
491 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
492 idleConf.GetOption('main','EditorWindow','font-size'),
493 fontWeight))
494
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000495 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000496 "Update the keybindings if they are changed"
497 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000498 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
499 keydefs = self.Bindings.default_keydefs
500 for event, keylist in keydefs.items():
501 self.text.event_delete(event)
502 self.apply_bindings()
503 #update menu accelerators
504 menuEventDict={}
505 for menu in self.Bindings.menudefs:
506 menuEventDict[menu[0]]={}
507 for item in menu[1]:
508 if item:
509 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
510 for menubarItem in self.menudict.keys():
511 menu=self.menudict[menubarItem]
512 end=menu.index(END)+1
513 for index in range(0,end):
514 if menu.type(index)=='command':
515 accel=menu.entrycget(index,'accelerator')
516 if accel:
517 itemName=menu.entrycget(index,'label')
518 event=''
519 if menuEventDict.has_key(menubarItem):
520 if menuEventDict[menubarItem].has_key(itemName):
521 event=menuEventDict[menubarItem][itemName]
522 if event:
523 #print 'accel was:',accel
524 accel=get_accelerator(keydefs, event)
525 menu.entryconfig(index,accelerator=accel)
526 #print 'accel now:',accel,'\n'
527
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000528 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000529 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000530 menuList=idleConf.GetAllExtraHelpSourcesList()
531 helpMenu=self.menudict['help']
532 cascadeIndex=helpMenu.index(END)-1
533 if menuList:
534 if not hasattr(self,'menuExtraHelp'):
535 self.menuExtraHelp=Menu(self.menubar)
536 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
537 underline=1,menu=self.menuExtraHelp)
538 self.menuExtraHelp.delete(1,END)
539 for menuItem in menuList:
540 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000541 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000542 else: #no extra help items
543 if hasattr(self,'menuExtraHelp'):
544 helpMenu.delete(cascadeIndex-1)
545 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000546
547 def __DisplayExtraHelpCallback(self,helpFile):
548 def DisplayExtraHelp(helpFile=helpFile):
549 self.display_docs(helpFile)
550 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000551
Steven M. Gava1d46e402002-03-27 08:40:46 +0000552 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000553 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000554 rfList=[]
555 if os.path.exists(self.recentFilesPath):
556 RFfile=open(self.recentFilesPath,'r')
557 try:
558 rfList=RFfile.readlines()
559 finally:
560 RFfile.close()
561 if newFile:
562 newFile=os.path.abspath(newFile)+'\n'
563 if newFile in rfList:
564 rfList.remove(newFile)
565 rfList.insert(0,newFile)
566 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000567 #print self.flist.inversedict
568 #print self.top.instanceDict
569 #print self
Steven M. Gava1d46e402002-03-27 08:40:46 +0000570 if rfList:
571 for instance in self.top.instanceDict.keys():
572 instance.menuRecentFiles.delete(1,END)
573 for file in rfList:
574 fileName=file[0:-1]
575 instance.menuRecentFiles.add_command(label=fileName,
576 command=instance.__RecentFileCallback(fileName))
577
578 def __CleanRecentFiles(self,rfList):
579 origRfList=rfList[:]
580 count=0
581 nonFiles=[]
582 for path in rfList:
583 if not os.path.exists(path[0:-1]):
584 nonFiles.append(count)
585 count=count+1
586 if nonFiles:
587 nonFiles.reverse()
588 for index in nonFiles:
589 del(rfList[index])
590 if len(rfList)>19:
591 rfList=rfList[0:19]
592 #if rfList != origRfList:
593 RFfile=open(self.recentFilesPath,'w')
594 try:
595 RFfile.writelines(rfList)
596 finally:
597 RFfile.close()
598 return rfList
599
600 def __RecentFileCallback(self,fileName):
601 def OpenRecentFile(fileName=fileName):
602 self.io.open(editFile=fileName)
603 return OpenRecentFile
604
David Scherer7aced172000-08-15 01:13:23 +0000605 def saved_change_hook(self):
606 short = self.short_title()
607 long = self.long_title()
608 if short and long:
609 title = short + " - " + long
610 elif short:
611 title = short
612 elif long:
613 title = long
614 else:
615 title = "Untitled"
616 icon = short or long or title
617 if not self.get_saved():
618 title = "*%s*" % title
619 icon = "*%s" % icon
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000620 if self.break_set:
621 shell = self.flist.pyshell
622 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000623 self.top.wm_title(title)
624 self.top.wm_iconname(icon)
625
626 def get_saved(self):
627 return self.undo.get_saved()
628
629 def set_saved(self, flag):
630 self.undo.set_saved(flag)
631
632 def reset_undo(self):
633 self.undo.reset_undo()
634
635 def short_title(self):
636 filename = self.io.filename
637 if filename:
638 filename = os.path.basename(filename)
639 return filename
640
641 def long_title(self):
642 return self.io.filename or ""
643
644 def center_insert_event(self, event):
645 self.center()
646
647 def center(self, mark="insert"):
648 text = self.text
649 top, bot = self.getwindowlines()
650 lineno = self.getlineno(mark)
651 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000652 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000653 text.yview(float(newtop))
654
655 def getwindowlines(self):
656 text = self.text
657 top = self.getlineno("@0,0")
658 bot = self.getlineno("@0,65535")
659 if top == bot and text.winfo_height() == 1:
660 # Geometry manager hasn't run yet
661 height = int(text['height'])
662 bot = top + height - 1
663 return top, bot
664
665 def getlineno(self, mark="insert"):
666 text = self.text
667 return int(float(text.index(mark)))
668
669 def close_event(self, event):
670 self.close()
671
672 def maybesave(self):
673 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000674 if not self.get_saved():
675 if self.top.state()!='normal':
676 self.top.deiconify()
677 self.top.lower()
678 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000679 return self.io.maybesave()
680
681 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000682 reply = self.maybesave()
683 if reply != "cancel":
684 self._close()
685 return reply
686
687 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000688 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000689 if self.io.filename:
690 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000691 if self.break_set:
692 shell = self.flist.pyshell
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000693 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000694 WindowList.unregister_callback(self.postwindowsmenu)
695 if self.close_hook:
696 self.close_hook()
697 self.flist = None
698 colorizing = 0
699 self.unload_extensions()
700 self.io.close(); self.io = None
701 self.undo = None # XXX
702 if self.color:
703 colorizing = self.color.colorizing
704 doh = colorizing and self.top
705 self.color.close(doh) # Cancel colorization
706 self.text = None
707 self.vars = None
708 self.per.close(); self.per = None
709 if not colorizing:
710 self.top.destroy()
711
712 def load_extensions(self):
713 self.extensions = {}
714 self.load_standard_extensions()
715
716 def unload_extensions(self):
717 for ins in self.extensions.values():
718 if hasattr(ins, "close"):
719 ins.close()
720 self.extensions = {}
721
722 def load_standard_extensions(self):
723 for name in self.get_standard_extension_names():
724 try:
725 self.load_extension(name)
726 except:
727 print "Failed to load extension", `name`
728 import traceback
729 traceback.print_exc()
730
731 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000732 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000733
734 def load_extension(self, name):
735 mod = __import__(name, globals(), locals(), [])
736 cls = getattr(mod, name)
737 ins = cls(self)
738 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000739 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000740 if keydefs:
741 self.apply_bindings(keydefs)
742 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000743 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000744 while methodname[:1] == '<':
745 methodname = methodname[1:]
746 while methodname[-1:] == '>':
747 methodname = methodname[:-1]
748 methodname = methodname + "_event"
749 if hasattr(ins, methodname):
750 self.text.bind(vevent, getattr(ins, methodname))
751 if hasattr(ins, "menudefs"):
752 self.fill_menus(ins.menudefs, keydefs)
753 return ins
754
755 def apply_bindings(self, keydefs=None):
756 if keydefs is None:
757 keydefs = self.Bindings.default_keydefs
758 text = self.text
759 text.keydefs = keydefs
760 for event, keylist in keydefs.items():
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000761 ##print>>sys.__stderr__, "event, list: ", event, keylist
David Scherer7aced172000-08-15 01:13:23 +0000762 if keylist:
763 apply(text.event_add, (event,) + tuple(keylist))
764
765 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000766 """Add appropriate entries to the menus and submenus
767
768 Menus that are absent or None in self.menudict are ignored.
769 """
David Scherer7aced172000-08-15 01:13:23 +0000770 if defs is None:
771 defs = self.Bindings.menudefs
772 if keydefs is None:
773 keydefs = self.Bindings.default_keydefs
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000774 ##print>>sys.__stderr__, "*keydefs: " , keydefs
David Scherer7aced172000-08-15 01:13:23 +0000775 menudict = self.menudict
776 text = self.text
777 for mname, itemlist in defs:
778 menu = menudict.get(mname)
779 if not menu:
780 continue
781 for item in itemlist:
782 if not item:
783 menu.add_separator()
784 else:
785 label, event = item
786 checkbutton = (label[:1] == '!')
787 if checkbutton:
788 label = label[1:]
789 underline, label = prepstr(label)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000790 ##print>>sys.__stderr__, "*Event: " , event
David Scherer7aced172000-08-15 01:13:23 +0000791 accelerator = get_accelerator(keydefs, event)
792 def command(text=text, event=event):
793 text.event_generate(event)
794 if checkbutton:
795 var = self.getrawvar(event, BooleanVar)
796 menu.add_checkbutton(label=label, underline=underline,
797 command=command, accelerator=accelerator,
798 variable=var)
799 else:
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000800 ##print>>sys.__stderr__, "label, ul, cmd, accel: ",
801 ## label, underline, command,
802 ## accelerator
David Scherer7aced172000-08-15 01:13:23 +0000803 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000804 command=command,
805 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000806
807 def getvar(self, name):
808 var = self.getrawvar(name)
809 if var:
810 return var.get()
811
812 def setvar(self, name, value, vartype=None):
813 var = self.getrawvar(name, vartype)
814 if var:
815 var.set(value)
816
817 def getrawvar(self, name, vartype=None):
818 var = self.vars.get(name)
819 if not var and vartype:
820 self.vars[name] = var = vartype(self.text)
821 return var
822
823 # Tk implementations of "virtual text methods" -- each platform
824 # reusing IDLE's support code needs to define these for its GUI's
825 # flavor of widget.
826
827 # Is character at text_index in a Python string? Return 0 for
828 # "guaranteed no", true for anything else. This info is expensive
829 # to compute ab initio, but is probably already known by the
830 # platform's colorizer.
831
832 def is_char_in_string(self, text_index):
833 if self.color:
834 # Return true iff colorizer hasn't (re)gotten this far
835 # yet, or the character is tagged as being in a string
836 return self.text.tag_prevrange("TODO", text_index) or \
837 "STRING" in self.text.tag_names(text_index)
838 else:
839 # The colorizer is missing: assume the worst
840 return 1
841
842 # If a selection is defined in the text widget, return (start,
843 # end) as Tkinter text indices, otherwise return (None, None)
844 def get_selection_indices(self):
845 try:
846 first = self.text.index("sel.first")
847 last = self.text.index("sel.last")
848 return first, last
849 except TclError:
850 return None, None
851
852 # Return the text widget's current view of what a tab stop means
853 # (equivalent width in spaces).
854
855 def get_tabwidth(self):
856 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
857 return int(current)
858
859 # Set the text widget's current view of what a tab stop means.
860
861 def set_tabwidth(self, newtabwidth):
862 text = self.text
863 if self.get_tabwidth() != newtabwidth:
864 pixels = text.tk.call("font", "measure", text["font"],
865 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000866 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000867 text.configure(tabs=pixels)
868
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000869### begin autoindent code ###
870
871 # usetabs true -> literal tab characters are used by indent and
872 # dedent cmds, possibly mixed with spaces if
873 # indentwidth is not a multiple of tabwidth
874 # false -> tab characters are converted to spaces by indent
875 # and dedent cmds, and ditto TAB keystrokes
876 # indentwidth is the number of characters per logical indent level.
877 # tabwidth is the display width of a literal tab character.
878 # CAUTION: telling Tk to use anything other than its default
879 # tab setting causes it to use an entirely different tabbing algorithm,
880 # treating tab stops as fixed distances from the left margin.
881 # Nobody expects this, so for now tabwidth should never be changed.
882 usetabs = 0
883 indentwidth = 4
884 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
885
886 # If context_use_ps1 is true, parsing searches back for a ps1 line;
887 # else searches for a popular (if, def, ...) Python stmt.
888 context_use_ps1 = 0
889
890 # When searching backwards for a reliable place to begin parsing,
891 # first start num_context_lines[0] lines back, then
892 # num_context_lines[1] lines back if that didn't work, and so on.
893 # The last value should be huge (larger than the # of lines in a
894 # conceivable file).
895 # Making the initial values larger slows things down more often.
896 num_context_lines = 50, 500, 5000000
897
898 def config(self, **options):
899 for key, value in options.items():
900 if key == 'usetabs':
901 self.usetabs = value
902 elif key == 'indentwidth':
903 self.indentwidth = value
904 elif key == 'tabwidth':
905 self.tabwidth = value
906 elif key == 'context_use_ps1':
907 self.context_use_ps1 = value
908 else:
909 raise KeyError, "bad option name: %s" % `key`
910
911 # If ispythonsource and guess are true, guess a good value for
912 # indentwidth based on file content (if possible), and if
913 # indentwidth != tabwidth set usetabs false.
914 # In any case, adjust the Text widget's view of what a tab
915 # character means.
916
917 def set_indentation_params(self, ispythonsource, guess=1):
918 if guess and ispythonsource:
919 i = self.guess_indent()
920 if 2 <= i <= 8:
921 self.indentwidth = i
922 if self.indentwidth != self.tabwidth:
923 self.usetabs = 0
924
925 self.set_tabwidth(self.tabwidth)
926
927 def smart_backspace_event(self, event):
928 text = self.text
929 first, last = self.get_selection_indices()
930 if first and last:
931 text.delete(first, last)
932 text.mark_set("insert", first)
933 return "break"
934 # Delete whitespace left, until hitting a real char or closest
935 # preceding virtual tab stop.
936 chars = text.get("insert linestart", "insert")
937 if chars == '':
938 if text.compare("insert", ">", "1.0"):
939 # easy: delete preceding newline
940 text.delete("insert-1c")
941 else:
942 text.bell() # at start of buffer
943 return "break"
944 if chars[-1] not in " \t":
945 # easy: delete preceding real char
946 text.delete("insert-1c")
947 return "break"
948 # Ick. It may require *inserting* spaces if we back up over a
949 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000950 tabwidth = self.tabwidth
951 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000952 assert have > 0
953 want = ((have - 1) // self.indentwidth) * self.indentwidth
954 ncharsdeleted = 0
955 while 1:
956 chars = chars[:-1]
957 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000958 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000959 if have <= want or chars[-1] not in " \t":
960 break
961 text.undo_block_start()
962 text.delete("insert-%dc" % ncharsdeleted, "insert")
963 if have < want:
964 text.insert("insert", ' ' * (want - have))
965 text.undo_block_stop()
966 return "break"
967
968 def smart_indent_event(self, event):
969 # if intraline selection:
970 # delete it
971 # elif multiline selection:
972 # do indent-region & return
973 # indent one level
974 text = self.text
975 first, last = self.get_selection_indices()
976 text.undo_block_start()
977 try:
978 if first and last:
979 if index2line(first) != index2line(last):
980 return self.indent_region_event(event)
981 text.delete(first, last)
982 text.mark_set("insert", first)
983 prefix = text.get("insert linestart", "insert")
984 raw, effective = classifyws(prefix, self.tabwidth)
985 if raw == len(prefix):
986 # only whitespace to the left
987 self.reindent_to(effective + self.indentwidth)
988 else:
989 if self.usetabs:
990 pad = '\t'
991 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000992 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000993 n = self.indentwidth
994 pad = ' ' * (n - effective % n)
995 text.insert("insert", pad)
996 text.see("insert")
997 return "break"
998 finally:
999 text.undo_block_stop()
1000
1001 def newline_and_indent_event(self, event):
1002 text = self.text
1003 first, last = self.get_selection_indices()
1004 text.undo_block_start()
1005 try:
1006 if first and last:
1007 text.delete(first, last)
1008 text.mark_set("insert", first)
1009 line = text.get("insert linestart", "insert")
1010 i, n = 0, len(line)
1011 while i < n and line[i] in " \t":
1012 i = i+1
1013 if i == n:
1014 # the cursor is in or at leading indentation; just inject
1015 # an empty line at the start
1016 text.insert("insert linestart", '\n')
1017 return "break"
1018 indent = line[:i]
1019 # strip whitespace before insert point
1020 i = 0
1021 while line and line[-1] in " \t":
1022 line = line[:-1]
1023 i = i+1
1024 if i:
1025 text.delete("insert - %d chars" % i, "insert")
1026 # strip whitespace after insert point
1027 while text.get("insert") in " \t":
1028 text.delete("insert")
1029 # start new line
1030 text.insert("insert", '\n')
1031
1032 # adjust indentation for continuations and block
1033 # open/close first need to find the last stmt
1034 lno = index2line(text.index('insert'))
1035 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1036 for context in self.num_context_lines:
1037 startat = max(lno - context, 1)
1038 startatindex = `startat` + ".0"
1039 rawtext = text.get(startatindex, "insert")
1040 y.set_str(rawtext)
1041 bod = y.find_good_parse_start(
1042 self.context_use_ps1,
1043 self._build_char_in_string_func(startatindex))
1044 if bod is not None or startat == 1:
1045 break
1046 y.set_lo(bod or 0)
1047 c = y.get_continuation_type()
1048 if c != PyParse.C_NONE:
1049 # The current stmt hasn't ended yet.
1050 if c == PyParse.C_STRING:
1051 # inside a string; just mimic the current indent
1052 text.insert("insert", indent)
1053 elif c == PyParse.C_BRACKET:
1054 # line up with the first (if any) element of the
1055 # last open bracket structure; else indent one
1056 # level beyond the indent of the line with the
1057 # last open bracket
1058 self.reindent_to(y.compute_bracket_indent())
1059 elif c == PyParse.C_BACKSLASH:
1060 # if more than one line in this stmt already, just
1061 # mimic the current indent; else if initial line
1062 # has a start on an assignment stmt, indent to
1063 # beyond leftmost =; else to beyond first chunk of
1064 # non-whitespace on initial line
1065 if y.get_num_lines_in_stmt() > 1:
1066 text.insert("insert", indent)
1067 else:
1068 self.reindent_to(y.compute_backslash_indent())
1069 else:
1070 assert 0, "bogus continuation type " + `c`
1071 return "break"
1072
1073 # This line starts a brand new stmt; indent relative to
1074 # indentation of initial line of closest preceding
1075 # interesting stmt.
1076 indent = y.get_base_indent_string()
1077 text.insert("insert", indent)
1078 if y.is_block_opener():
1079 self.smart_indent_event(event)
1080 elif indent and y.is_block_closer():
1081 self.smart_backspace_event(event)
1082 return "break"
1083 finally:
1084 text.see("insert")
1085 text.undo_block_stop()
1086
1087 auto_indent = newline_and_indent_event
1088
1089 # Our editwin provides a is_char_in_string function that works
1090 # with a Tk text index, but PyParse only knows about offsets into
1091 # a string. This builds a function for PyParse that accepts an
1092 # offset.
1093
1094 def _build_char_in_string_func(self, startindex):
1095 def inner(offset, _startindex=startindex,
1096 _icis=self.is_char_in_string):
1097 return _icis(_startindex + "+%dc" % offset)
1098 return inner
1099
1100 def indent_region_event(self, event):
1101 head, tail, chars, lines = self.get_region()
1102 for pos in range(len(lines)):
1103 line = lines[pos]
1104 if line:
1105 raw, effective = classifyws(line, self.tabwidth)
1106 effective = effective + self.indentwidth
1107 lines[pos] = self._make_blanks(effective) + line[raw:]
1108 self.set_region(head, tail, chars, lines)
1109 return "break"
1110
1111 def dedent_region_event(self, event):
1112 head, tail, chars, lines = self.get_region()
1113 for pos in range(len(lines)):
1114 line = lines[pos]
1115 if line:
1116 raw, effective = classifyws(line, self.tabwidth)
1117 effective = max(effective - self.indentwidth, 0)
1118 lines[pos] = self._make_blanks(effective) + line[raw:]
1119 self.set_region(head, tail, chars, lines)
1120 return "break"
1121
1122 def comment_region_event(self, event):
1123 head, tail, chars, lines = self.get_region()
1124 for pos in range(len(lines) - 1):
1125 line = lines[pos]
1126 lines[pos] = '##' + line
1127 self.set_region(head, tail, chars, lines)
1128
1129 def uncomment_region_event(self, event):
1130 head, tail, chars, lines = self.get_region()
1131 for pos in range(len(lines)):
1132 line = lines[pos]
1133 if not line:
1134 continue
1135 if line[:2] == '##':
1136 line = line[2:]
1137 elif line[:1] == '#':
1138 line = line[1:]
1139 lines[pos] = line
1140 self.set_region(head, tail, chars, lines)
1141
1142 def tabify_region_event(self, event):
1143 head, tail, chars, lines = self.get_region()
1144 tabwidth = self._asktabwidth()
1145 for pos in range(len(lines)):
1146 line = lines[pos]
1147 if line:
1148 raw, effective = classifyws(line, tabwidth)
1149 ntabs, nspaces = divmod(effective, tabwidth)
1150 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1151 self.set_region(head, tail, chars, lines)
1152
1153 def untabify_region_event(self, event):
1154 head, tail, chars, lines = self.get_region()
1155 tabwidth = self._asktabwidth()
1156 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001157 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001158 self.set_region(head, tail, chars, lines)
1159
1160 def toggle_tabs_event(self, event):
1161 if self.askyesno(
1162 "Toggle tabs",
1163 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1164 parent=self.text):
1165 self.usetabs = not self.usetabs
1166 return "break"
1167
1168 # XXX this isn't bound to anything -- see class tabwidth comments
1169 def change_tabwidth_event(self, event):
1170 new = self._asktabwidth()
1171 if new != self.tabwidth:
1172 self.tabwidth = new
1173 self.set_indentation_params(0, guess=0)
1174 return "break"
1175
1176 def change_indentwidth_event(self, event):
1177 new = self.askinteger(
1178 "Indent width",
1179 "New indent width (2-16)",
1180 parent=self.text,
1181 initialvalue=self.indentwidth,
1182 minvalue=2,
1183 maxvalue=16)
1184 if new and new != self.indentwidth:
1185 self.indentwidth = new
1186 return "break"
1187
1188 def get_region(self):
1189 text = self.text
1190 first, last = self.get_selection_indices()
1191 if first and last:
1192 head = text.index(first + " linestart")
1193 tail = text.index(last + "-1c lineend +1c")
1194 else:
1195 head = text.index("insert linestart")
1196 tail = text.index("insert lineend +1c")
1197 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001198 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001199 return head, tail, chars, lines
1200
1201 def set_region(self, head, tail, chars, lines):
1202 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001203 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001204 if newchars == chars:
1205 text.bell()
1206 return
1207 text.tag_remove("sel", "1.0", "end")
1208 text.mark_set("insert", head)
1209 text.undo_block_start()
1210 text.delete(head, tail)
1211 text.insert(head, newchars)
1212 text.undo_block_stop()
1213 text.tag_add("sel", head, "insert")
1214
1215 # Make string that displays as n leading blanks.
1216
1217 def _make_blanks(self, n):
1218 if self.usetabs:
1219 ntabs, nspaces = divmod(n, self.tabwidth)
1220 return '\t' * ntabs + ' ' * nspaces
1221 else:
1222 return ' ' * n
1223
1224 # Delete from beginning of line to insert point, then reinsert
1225 # column logical (meaning use tabs if appropriate) spaces.
1226
1227 def reindent_to(self, column):
1228 text = self.text
1229 text.undo_block_start()
1230 if text.compare("insert linestart", "!=", "insert"):
1231 text.delete("insert linestart", "insert")
1232 if column:
1233 text.insert("insert", self._make_blanks(column))
1234 text.undo_block_stop()
1235
1236 def _asktabwidth(self):
1237 return self.askinteger(
1238 "Tab width",
1239 "Spaces per tab? (2-16)",
1240 parent=self.text,
1241 initialvalue=self.indentwidth,
1242 minvalue=2,
1243 maxvalue=16) or self.tabwidth
1244
1245 # Guess indentwidth from text content.
1246 # Return guessed indentwidth. This should not be believed unless
1247 # it's in a reasonable range (e.g., it will be 0 if no indented
1248 # blocks are found).
1249
1250 def guess_indent(self):
1251 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1252 if opener and indented:
1253 raw, indentsmall = classifyws(opener, self.tabwidth)
1254 raw, indentlarge = classifyws(indented, self.tabwidth)
1255 else:
1256 indentsmall = indentlarge = 0
1257 return indentlarge - indentsmall
1258
1259# "line.col" -> line, as an int
1260def index2line(index):
1261 return int(float(index))
1262
1263# Look at the leading whitespace in s.
1264# Return pair (# of leading ws characters,
1265# effective # of leading blanks after expanding
1266# tabs to width tabwidth)
1267
1268def classifyws(s, tabwidth):
1269 raw = effective = 0
1270 for ch in s:
1271 if ch == ' ':
1272 raw = raw + 1
1273 effective = effective + 1
1274 elif ch == '\t':
1275 raw = raw + 1
1276 effective = (effective // tabwidth + 1) * tabwidth
1277 else:
1278 break
1279 return raw, effective
1280
1281import tokenize
1282_tokenize = tokenize
1283del tokenize
1284
1285class IndentSearcher:
1286
1287 # .run() chews over the Text widget, looking for a block opener
1288 # and the stmt following it. Returns a pair,
1289 # (line containing block opener, line containing stmt)
1290 # Either or both may be None.
1291
1292 def __init__(self, text, tabwidth):
1293 self.text = text
1294 self.tabwidth = tabwidth
1295 self.i = self.finished = 0
1296 self.blkopenline = self.indentedline = None
1297
1298 def readline(self):
1299 if self.finished:
1300 return ""
1301 i = self.i = self.i + 1
1302 mark = `i` + ".0"
1303 if self.text.compare(mark, ">=", "end"):
1304 return ""
1305 return self.text.get(mark, mark + " lineend+1c")
1306
1307 def tokeneater(self, type, token, start, end, line,
1308 INDENT=_tokenize.INDENT,
1309 NAME=_tokenize.NAME,
1310 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1311 if self.finished:
1312 pass
1313 elif type == NAME and token in OPENERS:
1314 self.blkopenline = line
1315 elif type == INDENT and self.blkopenline:
1316 self.indentedline = line
1317 self.finished = 1
1318
1319 def run(self):
1320 save_tabsize = _tokenize.tabsize
1321 _tokenize.tabsize = self.tabwidth
1322 try:
1323 try:
1324 _tokenize.tokenize(self.readline, self.tokeneater)
1325 except _tokenize.TokenError:
1326 # since we cut off the tokenizer early, we can trigger
1327 # spurious errors
1328 pass
1329 finally:
1330 _tokenize.tabsize = save_tabsize
1331 return self.blkopenline, self.indentedline
1332
1333### end autoindent code ###
1334
David Scherer7aced172000-08-15 01:13:23 +00001335def prepstr(s):
1336 # Helper to extract the underscore from a string, e.g.
1337 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001338 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001339 if i >= 0:
1340 s = s[:i] + s[i+1:]
1341 return i, s
1342
1343
1344keynames = {
1345 'bracketleft': '[',
1346 'bracketright': ']',
1347 'slash': '/',
1348}
1349
1350def get_accelerator(keydefs, event):
1351 keylist = keydefs.get(event)
1352 if not keylist:
1353 return ""
1354 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001355 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001356 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1357 s = re.sub("Key-", "", s)
1358 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1359 s = re.sub("Control-", "Ctrl-", s)
1360 s = re.sub("-", "+", s)
1361 s = re.sub("><", " ", s)
1362 s = re.sub("<", "", s)
1363 s = re.sub(">", "", s)
1364 return s
1365
1366
1367def fixwordbreaks(root):
1368 # Make sure that Tk's double-click and next/previous word
1369 # operations use our definition of a word (i.e. an identifier)
1370 tk = root.tk
1371 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1372 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1373 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1374
1375
1376def test():
1377 root = Tk()
1378 fixwordbreaks(root)
1379 root.withdraw()
1380 if sys.argv[1:]:
1381 filename = sys.argv[1]
1382 else:
1383 filename = None
1384 edit = EditorWindow(root=root, filename=filename)
1385 edit.set_close_hook(root.quit)
1386 root.mainloop()
1387 root.destroy()
1388
1389if __name__ == '__main__':
1390 test()