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