blob: f039503dd96212f620119c61515a9e6726a0f78c [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
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000441 return line.startswith('#!') and line.find('python') >= 0
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
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000955 # Debug prompt is multilined....
956 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000957 ncharsdeleted = 0
958 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000959 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000960 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000961 chars = chars[:-1]
962 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000963 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000964 if have <= want or chars[-1] not in " \t":
965 break
966 text.undo_block_start()
967 text.delete("insert-%dc" % ncharsdeleted, "insert")
968 if have < want:
969 text.insert("insert", ' ' * (want - have))
970 text.undo_block_stop()
971 return "break"
972
973 def smart_indent_event(self, event):
974 # if intraline selection:
975 # delete it
976 # elif multiline selection:
977 # do indent-region & return
978 # indent one level
979 text = self.text
980 first, last = self.get_selection_indices()
981 text.undo_block_start()
982 try:
983 if first and last:
984 if index2line(first) != index2line(last):
985 return self.indent_region_event(event)
986 text.delete(first, last)
987 text.mark_set("insert", first)
988 prefix = text.get("insert linestart", "insert")
989 raw, effective = classifyws(prefix, self.tabwidth)
990 if raw == len(prefix):
991 # only whitespace to the left
992 self.reindent_to(effective + self.indentwidth)
993 else:
994 if self.usetabs:
995 pad = '\t'
996 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000997 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000998 n = self.indentwidth
999 pad = ' ' * (n - effective % n)
1000 text.insert("insert", pad)
1001 text.see("insert")
1002 return "break"
1003 finally:
1004 text.undo_block_stop()
1005
1006 def newline_and_indent_event(self, event):
1007 text = self.text
1008 first, last = self.get_selection_indices()
1009 text.undo_block_start()
1010 try:
1011 if first and last:
1012 text.delete(first, last)
1013 text.mark_set("insert", first)
1014 line = text.get("insert linestart", "insert")
1015 i, n = 0, len(line)
1016 while i < n and line[i] in " \t":
1017 i = i+1
1018 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001019 # the cursor is in or at leading indentation in a continuation
1020 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001021 text.insert("insert linestart", '\n')
1022 return "break"
1023 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001024 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001025 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001026 last_line_of_prompt = sys.ps1.split('\n')[-1]
1027 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001028 line = line[:-1]
1029 i = i+1
1030 if i:
1031 text.delete("insert - %d chars" % i, "insert")
1032 # strip whitespace after insert point
1033 while text.get("insert") in " \t":
1034 text.delete("insert")
1035 # start new line
1036 text.insert("insert", '\n')
1037
1038 # adjust indentation for continuations and block
1039 # open/close first need to find the last stmt
1040 lno = index2line(text.index('insert'))
1041 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1042 for context in self.num_context_lines:
1043 startat = max(lno - context, 1)
1044 startatindex = `startat` + ".0"
1045 rawtext = text.get(startatindex, "insert")
1046 y.set_str(rawtext)
1047 bod = y.find_good_parse_start(
1048 self.context_use_ps1,
1049 self._build_char_in_string_func(startatindex))
1050 if bod is not None or startat == 1:
1051 break
1052 y.set_lo(bod or 0)
1053 c = y.get_continuation_type()
1054 if c != PyParse.C_NONE:
1055 # The current stmt hasn't ended yet.
1056 if c == PyParse.C_STRING:
1057 # inside a string; just mimic the current indent
1058 text.insert("insert", indent)
1059 elif c == PyParse.C_BRACKET:
1060 # line up with the first (if any) element of the
1061 # last open bracket structure; else indent one
1062 # level beyond the indent of the line with the
1063 # last open bracket
1064 self.reindent_to(y.compute_bracket_indent())
1065 elif c == PyParse.C_BACKSLASH:
1066 # if more than one line in this stmt already, just
1067 # mimic the current indent; else if initial line
1068 # has a start on an assignment stmt, indent to
1069 # beyond leftmost =; else to beyond first chunk of
1070 # non-whitespace on initial line
1071 if y.get_num_lines_in_stmt() > 1:
1072 text.insert("insert", indent)
1073 else:
1074 self.reindent_to(y.compute_backslash_indent())
1075 else:
1076 assert 0, "bogus continuation type " + `c`
1077 return "break"
1078
1079 # This line starts a brand new stmt; indent relative to
1080 # indentation of initial line of closest preceding
1081 # interesting stmt.
1082 indent = y.get_base_indent_string()
1083 text.insert("insert", indent)
1084 if y.is_block_opener():
1085 self.smart_indent_event(event)
1086 elif indent and y.is_block_closer():
1087 self.smart_backspace_event(event)
1088 return "break"
1089 finally:
1090 text.see("insert")
1091 text.undo_block_stop()
1092
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001093 # Our editwin provides a is_char_in_string function that works
1094 # with a Tk text index, but PyParse only knows about offsets into
1095 # a string. This builds a function for PyParse that accepts an
1096 # offset.
1097
1098 def _build_char_in_string_func(self, startindex):
1099 def inner(offset, _startindex=startindex,
1100 _icis=self.is_char_in_string):
1101 return _icis(_startindex + "+%dc" % offset)
1102 return inner
1103
1104 def indent_region_event(self, event):
1105 head, tail, chars, lines = self.get_region()
1106 for pos in range(len(lines)):
1107 line = lines[pos]
1108 if line:
1109 raw, effective = classifyws(line, self.tabwidth)
1110 effective = effective + self.indentwidth
1111 lines[pos] = self._make_blanks(effective) + line[raw:]
1112 self.set_region(head, tail, chars, lines)
1113 return "break"
1114
1115 def dedent_region_event(self, event):
1116 head, tail, chars, lines = self.get_region()
1117 for pos in range(len(lines)):
1118 line = lines[pos]
1119 if line:
1120 raw, effective = classifyws(line, self.tabwidth)
1121 effective = max(effective - self.indentwidth, 0)
1122 lines[pos] = self._make_blanks(effective) + line[raw:]
1123 self.set_region(head, tail, chars, lines)
1124 return "break"
1125
1126 def comment_region_event(self, event):
1127 head, tail, chars, lines = self.get_region()
1128 for pos in range(len(lines) - 1):
1129 line = lines[pos]
1130 lines[pos] = '##' + line
1131 self.set_region(head, tail, chars, lines)
1132
1133 def uncomment_region_event(self, event):
1134 head, tail, chars, lines = self.get_region()
1135 for pos in range(len(lines)):
1136 line = lines[pos]
1137 if not line:
1138 continue
1139 if line[:2] == '##':
1140 line = line[2:]
1141 elif line[:1] == '#':
1142 line = line[1:]
1143 lines[pos] = line
1144 self.set_region(head, tail, chars, lines)
1145
1146 def tabify_region_event(self, event):
1147 head, tail, chars, lines = self.get_region()
1148 tabwidth = self._asktabwidth()
1149 for pos in range(len(lines)):
1150 line = lines[pos]
1151 if line:
1152 raw, effective = classifyws(line, tabwidth)
1153 ntabs, nspaces = divmod(effective, tabwidth)
1154 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1155 self.set_region(head, tail, chars, lines)
1156
1157 def untabify_region_event(self, event):
1158 head, tail, chars, lines = self.get_region()
1159 tabwidth = self._asktabwidth()
1160 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001161 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001162 self.set_region(head, tail, chars, lines)
1163
1164 def toggle_tabs_event(self, event):
1165 if self.askyesno(
1166 "Toggle tabs",
1167 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1168 parent=self.text):
1169 self.usetabs = not self.usetabs
1170 return "break"
1171
1172 # XXX this isn't bound to anything -- see class tabwidth comments
1173 def change_tabwidth_event(self, event):
1174 new = self._asktabwidth()
1175 if new != self.tabwidth:
1176 self.tabwidth = new
1177 self.set_indentation_params(0, guess=0)
1178 return "break"
1179
1180 def change_indentwidth_event(self, event):
1181 new = self.askinteger(
1182 "Indent width",
1183 "New indent width (2-16)",
1184 parent=self.text,
1185 initialvalue=self.indentwidth,
1186 minvalue=2,
1187 maxvalue=16)
1188 if new and new != self.indentwidth:
1189 self.indentwidth = new
1190 return "break"
1191
1192 def get_region(self):
1193 text = self.text
1194 first, last = self.get_selection_indices()
1195 if first and last:
1196 head = text.index(first + " linestart")
1197 tail = text.index(last + "-1c lineend +1c")
1198 else:
1199 head = text.index("insert linestart")
1200 tail = text.index("insert lineend +1c")
1201 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001202 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001203 return head, tail, chars, lines
1204
1205 def set_region(self, head, tail, chars, lines):
1206 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001207 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001208 if newchars == chars:
1209 text.bell()
1210 return
1211 text.tag_remove("sel", "1.0", "end")
1212 text.mark_set("insert", head)
1213 text.undo_block_start()
1214 text.delete(head, tail)
1215 text.insert(head, newchars)
1216 text.undo_block_stop()
1217 text.tag_add("sel", head, "insert")
1218
1219 # Make string that displays as n leading blanks.
1220
1221 def _make_blanks(self, n):
1222 if self.usetabs:
1223 ntabs, nspaces = divmod(n, self.tabwidth)
1224 return '\t' * ntabs + ' ' * nspaces
1225 else:
1226 return ' ' * n
1227
1228 # Delete from beginning of line to insert point, then reinsert
1229 # column logical (meaning use tabs if appropriate) spaces.
1230
1231 def reindent_to(self, column):
1232 text = self.text
1233 text.undo_block_start()
1234 if text.compare("insert linestart", "!=", "insert"):
1235 text.delete("insert linestart", "insert")
1236 if column:
1237 text.insert("insert", self._make_blanks(column))
1238 text.undo_block_stop()
1239
1240 def _asktabwidth(self):
1241 return self.askinteger(
1242 "Tab width",
1243 "Spaces per tab? (2-16)",
1244 parent=self.text,
1245 initialvalue=self.indentwidth,
1246 minvalue=2,
1247 maxvalue=16) or self.tabwidth
1248
1249 # Guess indentwidth from text content.
1250 # Return guessed indentwidth. This should not be believed unless
1251 # it's in a reasonable range (e.g., it will be 0 if no indented
1252 # blocks are found).
1253
1254 def guess_indent(self):
1255 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1256 if opener and indented:
1257 raw, indentsmall = classifyws(opener, self.tabwidth)
1258 raw, indentlarge = classifyws(indented, self.tabwidth)
1259 else:
1260 indentsmall = indentlarge = 0
1261 return indentlarge - indentsmall
1262
1263# "line.col" -> line, as an int
1264def index2line(index):
1265 return int(float(index))
1266
1267# Look at the leading whitespace in s.
1268# Return pair (# of leading ws characters,
1269# effective # of leading blanks after expanding
1270# tabs to width tabwidth)
1271
1272def classifyws(s, tabwidth):
1273 raw = effective = 0
1274 for ch in s:
1275 if ch == ' ':
1276 raw = raw + 1
1277 effective = effective + 1
1278 elif ch == '\t':
1279 raw = raw + 1
1280 effective = (effective // tabwidth + 1) * tabwidth
1281 else:
1282 break
1283 return raw, effective
1284
1285import tokenize
1286_tokenize = tokenize
1287del tokenize
1288
1289class IndentSearcher:
1290
1291 # .run() chews over the Text widget, looking for a block opener
1292 # and the stmt following it. Returns a pair,
1293 # (line containing block opener, line containing stmt)
1294 # Either or both may be None.
1295
1296 def __init__(self, text, tabwidth):
1297 self.text = text
1298 self.tabwidth = tabwidth
1299 self.i = self.finished = 0
1300 self.blkopenline = self.indentedline = None
1301
1302 def readline(self):
1303 if self.finished:
1304 return ""
1305 i = self.i = self.i + 1
1306 mark = `i` + ".0"
1307 if self.text.compare(mark, ">=", "end"):
1308 return ""
1309 return self.text.get(mark, mark + " lineend+1c")
1310
1311 def tokeneater(self, type, token, start, end, line,
1312 INDENT=_tokenize.INDENT,
1313 NAME=_tokenize.NAME,
1314 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1315 if self.finished:
1316 pass
1317 elif type == NAME and token in OPENERS:
1318 self.blkopenline = line
1319 elif type == INDENT and self.blkopenline:
1320 self.indentedline = line
1321 self.finished = 1
1322
1323 def run(self):
1324 save_tabsize = _tokenize.tabsize
1325 _tokenize.tabsize = self.tabwidth
1326 try:
1327 try:
1328 _tokenize.tokenize(self.readline, self.tokeneater)
1329 except _tokenize.TokenError:
1330 # since we cut off the tokenizer early, we can trigger
1331 # spurious errors
1332 pass
1333 finally:
1334 _tokenize.tabsize = save_tabsize
1335 return self.blkopenline, self.indentedline
1336
1337### end autoindent code ###
1338
David Scherer7aced172000-08-15 01:13:23 +00001339def prepstr(s):
1340 # Helper to extract the underscore from a string, e.g.
1341 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001342 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001343 if i >= 0:
1344 s = s[:i] + s[i+1:]
1345 return i, s
1346
1347
1348keynames = {
1349 'bracketleft': '[',
1350 'bracketright': ']',
1351 'slash': '/',
1352}
1353
1354def get_accelerator(keydefs, event):
1355 keylist = keydefs.get(event)
1356 if not keylist:
1357 return ""
1358 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001359 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001360 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1361 s = re.sub("Key-", "", s)
1362 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1363 s = re.sub("Control-", "Ctrl-", s)
1364 s = re.sub("-", "+", s)
1365 s = re.sub("><", " ", s)
1366 s = re.sub("<", "", s)
1367 s = re.sub(">", "", s)
1368 return s
1369
1370
1371def fixwordbreaks(root):
1372 # Make sure that Tk's double-click and next/previous word
1373 # operations use our definition of a word (i.e. an identifier)
1374 tk = root.tk
1375 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1376 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1377 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1378
1379
1380def test():
1381 root = Tk()
1382 fixwordbreaks(root)
1383 root.withdraw()
1384 if sys.argv[1:]:
1385 filename = sys.argv[1]
1386 else:
1387 filename = None
1388 edit = EditorWindow(root=root, filename=filename)
1389 edit.set_close_hook(root.quit)
1390 root.mainloop()
1391 root.destroy()
1392
1393if __name__ == '__main__':
1394 test()