blob: 5368f27ec6b36ddfc3a98f19c18bddc5abae1255 [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):
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000286 print>>sys.__stderr__, "** __file__: ", __file__
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000287 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
288 textView.TextViewer(self.top,'IDLEfork - README',fn)
289
David Scherer7aced172000-08-15 01:13:23 +0000290 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000291 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
292 textView.TextViewer(self.top,'Help',fn)
293
David Scherer7aced172000-08-15 01:13:23 +0000294 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000295 if sys.platform[:3] == "win":
296 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000297 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000298 fn = os.path.normpath(fn)
299 if os.path.isfile(fn):
300 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000301 else:
302 fn = os.path.dirname(__file__)
303 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
304 fn = os.path.normpath(fn)
305 if os.path.isfile(fn):
306 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000307 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000308 def python_docs(self, event=None):
309 os.startfile(self.help_url)
310 else:
311 def python_docs(self, event=None):
312 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000313
314 def display_docs(self, url):
315 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000316
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000317 def cut(self,event):
318 self.text.event_generate("<<Cut>>")
319 return "break"
320
321 def copy(self,event):
322 self.text.event_generate("<<Copy>>")
323 return "break"
324
325 def paste(self,event):
326 self.text.event_generate("<<Paste>>")
327 return "break"
328
David Scherer7aced172000-08-15 01:13:23 +0000329 def select_all(self, event=None):
330 self.text.tag_add("sel", "1.0", "end-1c")
331 self.text.mark_set("insert", "1.0")
332 self.text.see("insert")
333 return "break"
334
335 def remove_selection(self, event=None):
336 self.text.tag_remove("sel", "1.0", "end")
337 self.text.see("insert")
338
Steven M. Gavac5976402002-01-04 03:06:08 +0000339 def find_event(self, event):
340 SearchDialog.find(self.text)
341 return "break"
342
343 def find_again_event(self, event):
344 SearchDialog.find_again(self.text)
345 return "break"
346
347 def find_selection_event(self, event):
348 SearchDialog.find_selection(self.text)
349 return "break"
350
351 def find_in_files_event(self, event):
352 GrepDialog.grep(self.text, self.io, self.flist)
353 return "break"
354
355 def replace_event(self, event):
356 ReplaceDialog.replace(self.text)
357 return "break"
358
359 def goto_line_event(self, event):
360 text = self.text
361 lineno = tkSimpleDialog.askinteger("Goto",
362 "Go to line number:",parent=text)
363 if lineno is None:
364 return "break"
365 if lineno <= 0:
366 text.bell()
367 return "break"
368 text.mark_set("insert", "%d.0" % lineno)
369 text.see("insert")
370
David Scherer7aced172000-08-15 01:13:23 +0000371 def open_module(self, event=None):
372 # XXX Shouldn't this be in IOBinding or in FileList?
373 try:
374 name = self.text.get("sel.first", "sel.last")
375 except TclError:
376 name = ""
377 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000378 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000379 if not name:
380 name = tkSimpleDialog.askstring("Module",
381 "Enter the name of a Python module\n"
382 "to search on sys.path and open:",
383 parent=self.text)
384 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000385 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000386 if not name:
387 return
David Scherer7aced172000-08-15 01:13:23 +0000388 # XXX Ought to insert current file's directory in front of path
389 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000390 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000391 except (NameError, ImportError), msg:
392 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
393 return
394 if type != imp.PY_SOURCE:
395 tkMessageBox.showerror("Unsupported type",
396 "%s is not a source module" % name, parent=self.text)
397 return
398 if f:
399 f.close()
400 if self.flist:
401 self.flist.open(file)
402 else:
403 self.io.loadfile(file)
404
405 def open_class_browser(self, event=None):
406 filename = self.io.filename
407 if not filename:
408 tkMessageBox.showerror(
409 "No filename",
410 "This buffer has no associated filename",
411 master=self.text)
412 self.text.focus_set()
413 return None
414 head, tail = os.path.split(filename)
415 base, ext = os.path.splitext(tail)
416 import ClassBrowser
417 ClassBrowser.ClassBrowser(self.flist, base, [head])
418
419 def open_path_browser(self, event=None):
420 import PathBrowser
421 PathBrowser.PathBrowser(self.flist)
422
423 def gotoline(self, lineno):
424 if lineno is not None and lineno > 0:
425 self.text.mark_set("insert", "%d.0" % lineno)
426 self.text.tag_remove("sel", "1.0", "end")
427 self.text.tag_add("sel", "insert", "insert +1l")
428 self.center()
429
430 def ispythonsource(self, filename):
431 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000432 return True
David Scherer7aced172000-08-15 01:13:23 +0000433 base, ext = os.path.splitext(os.path.basename(filename))
434 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000435 return True
David Scherer7aced172000-08-15 01:13:23 +0000436 try:
437 f = open(filename)
438 line = f.readline()
439 f.close()
440 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000441 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000442 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000443
444 def close_hook(self):
445 if self.flist:
446 self.flist.close_edit(self)
447
448 def set_close_hook(self, close_hook):
449 self.close_hook = close_hook
450
451 def filename_change_hook(self):
452 if self.flist:
453 self.flist.filename_changed_edit(self)
454 self.saved_change_hook()
455 if self.ispythonsource(self.io.filename):
456 self.addcolorizer()
457 else:
458 self.rmcolorizer()
459
460 def addcolorizer(self):
461 if self.color:
462 return
463 ##print "Add colorizer"
464 self.per.removefilter(self.undo)
465 self.color = self.ColorDelegator()
466 self.per.insertfilter(self.color)
467 self.per.insertfilter(self.undo)
468
469 def rmcolorizer(self):
470 if not self.color:
471 return
472 ##print "Remove colorizer"
473 self.per.removefilter(self.undo)
474 self.per.removefilter(self.color)
475 self.color = None
476 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000477
478 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000479 "Update the colour theme if it is changed"
480 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000481 if self.color:
482 self.color = self.ColorDelegator()
483 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000484
Steven M. Gavab1585412002-03-12 00:21:56 +0000485 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000486 "Update the text widgets' font if it is changed"
487 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000488 fontWeight='normal'
489 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
490 fontWeight='bold'
491 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
492 idleConf.GetOption('main','EditorWindow','font-size'),
493 fontWeight))
494
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000495 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000496 "Update the keybindings if they are changed"
497 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000498 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
499 keydefs = self.Bindings.default_keydefs
500 for event, keylist in keydefs.items():
501 self.text.event_delete(event)
502 self.apply_bindings()
503 #update menu accelerators
504 menuEventDict={}
505 for menu in self.Bindings.menudefs:
506 menuEventDict[menu[0]]={}
507 for item in menu[1]:
508 if item:
509 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
510 for menubarItem in self.menudict.keys():
511 menu=self.menudict[menubarItem]
512 end=menu.index(END)+1
513 for index in range(0,end):
514 if menu.type(index)=='command':
515 accel=menu.entrycget(index,'accelerator')
516 if accel:
517 itemName=menu.entrycget(index,'label')
518 event=''
519 if menuEventDict.has_key(menubarItem):
520 if menuEventDict[menubarItem].has_key(itemName):
521 event=menuEventDict[menubarItem][itemName]
522 if event:
523 #print 'accel was:',accel
524 accel=get_accelerator(keydefs, event)
525 menu.entryconfig(index,accelerator=accel)
526 #print 'accel now:',accel,'\n'
527
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000528 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000529 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000530 menuList=idleConf.GetAllExtraHelpSourcesList()
531 helpMenu=self.menudict['help']
532 cascadeIndex=helpMenu.index(END)-1
533 if menuList:
534 if not hasattr(self,'menuExtraHelp'):
535 self.menuExtraHelp=Menu(self.menubar)
536 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
537 underline=1,menu=self.menuExtraHelp)
538 self.menuExtraHelp.delete(1,END)
539 for menuItem in menuList:
540 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000541 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000542 else: #no extra help items
543 if hasattr(self,'menuExtraHelp'):
544 helpMenu.delete(cascadeIndex-1)
545 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000546
547 def __DisplayExtraHelpCallback(self,helpFile):
548 def DisplayExtraHelp(helpFile=helpFile):
549 self.display_docs(helpFile)
550 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000551
Steven M. Gava1d46e402002-03-27 08:40:46 +0000552 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000553 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000554 rfList=[]
555 if os.path.exists(self.recentFilesPath):
556 RFfile=open(self.recentFilesPath,'r')
557 try:
558 rfList=RFfile.readlines()
559 finally:
560 RFfile.close()
561 if newFile:
562 newFile=os.path.abspath(newFile)+'\n'
563 if newFile in rfList:
564 rfList.remove(newFile)
565 rfList.insert(0,newFile)
566 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000567 #print self.flist.inversedict
568 #print self.top.instanceDict
569 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000570 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000571 if rfList:
572 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000573 menu = instance.menuRecentFiles
574 menu.delete(1,END)
575 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000576 for file in rfList:
577 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000578 callback = instance.__RecentFileCallback(fileName)
579 if i > ullen: # don't underline menuitems
580 ul=None
581 menu.add_command(label=ullist[i] + " " + fileName,
582 command=callback,
583 underline=ul)
584 i += 1
Steven M. Gava1d46e402002-03-27 08:40:46 +0000585
586 def __CleanRecentFiles(self,rfList):
587 origRfList=rfList[:]
588 count=0
589 nonFiles=[]
590 for path in rfList:
591 if not os.path.exists(path[0:-1]):
592 nonFiles.append(count)
593 count=count+1
594 if nonFiles:
595 nonFiles.reverse()
596 for index in nonFiles:
597 del(rfList[index])
598 if len(rfList)>19:
599 rfList=rfList[0:19]
600 #if rfList != origRfList:
601 RFfile=open(self.recentFilesPath,'w')
602 try:
603 RFfile.writelines(rfList)
604 finally:
605 RFfile.close()
606 return rfList
607
608 def __RecentFileCallback(self,fileName):
609 def OpenRecentFile(fileName=fileName):
610 self.io.open(editFile=fileName)
611 return OpenRecentFile
612
David Scherer7aced172000-08-15 01:13:23 +0000613 def saved_change_hook(self):
614 short = self.short_title()
615 long = self.long_title()
616 if short and long:
617 title = short + " - " + long
618 elif short:
619 title = short
620 elif long:
621 title = long
622 else:
623 title = "Untitled"
624 icon = short or long or title
625 if not self.get_saved():
626 title = "*%s*" % title
627 icon = "*%s" % icon
628 self.top.wm_title(title)
629 self.top.wm_iconname(icon)
630
631 def get_saved(self):
632 return self.undo.get_saved()
633
634 def set_saved(self, flag):
635 self.undo.set_saved(flag)
636
637 def reset_undo(self):
638 self.undo.reset_undo()
639
640 def short_title(self):
641 filename = self.io.filename
642 if filename:
643 filename = os.path.basename(filename)
644 return filename
645
646 def long_title(self):
647 return self.io.filename or ""
648
649 def center_insert_event(self, event):
650 self.center()
651
652 def center(self, mark="insert"):
653 text = self.text
654 top, bot = self.getwindowlines()
655 lineno = self.getlineno(mark)
656 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000657 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000658 text.yview(float(newtop))
659
660 def getwindowlines(self):
661 text = self.text
662 top = self.getlineno("@0,0")
663 bot = self.getlineno("@0,65535")
664 if top == bot and text.winfo_height() == 1:
665 # Geometry manager hasn't run yet
666 height = int(text['height'])
667 bot = top + height - 1
668 return top, bot
669
670 def getlineno(self, mark="insert"):
671 text = self.text
672 return int(float(text.index(mark)))
673
674 def close_event(self, event):
675 self.close()
676
677 def maybesave(self):
678 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000679 if not self.get_saved():
680 if self.top.state()!='normal':
681 self.top.deiconify()
682 self.top.lower()
683 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000684 return self.io.maybesave()
685
686 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000687 reply = self.maybesave()
688 if reply != "cancel":
689 self._close()
690 return reply
691
692 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000693 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000694 if self.io.filename:
695 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000696 WindowList.unregister_callback(self.postwindowsmenu)
697 if self.close_hook:
698 self.close_hook()
699 self.flist = None
700 colorizing = 0
701 self.unload_extensions()
702 self.io.close(); self.io = None
703 self.undo = None # XXX
704 if self.color:
705 colorizing = self.color.colorizing
706 doh = colorizing and self.top
707 self.color.close(doh) # Cancel colorization
708 self.text = None
709 self.vars = None
710 self.per.close(); self.per = None
711 if not colorizing:
712 self.top.destroy()
713
714 def load_extensions(self):
715 self.extensions = {}
716 self.load_standard_extensions()
717
718 def unload_extensions(self):
719 for ins in self.extensions.values():
720 if hasattr(ins, "close"):
721 ins.close()
722 self.extensions = {}
723
724 def load_standard_extensions(self):
725 for name in self.get_standard_extension_names():
726 try:
727 self.load_extension(name)
728 except:
729 print "Failed to load extension", `name`
730 import traceback
731 traceback.print_exc()
732
733 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000734 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000735
736 def load_extension(self, name):
737 mod = __import__(name, globals(), locals(), [])
738 cls = getattr(mod, name)
739 ins = cls(self)
740 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000741 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000742 if keydefs:
743 self.apply_bindings(keydefs)
744 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000745 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000746 while methodname[:1] == '<':
747 methodname = methodname[1:]
748 while methodname[-1:] == '>':
749 methodname = methodname[:-1]
750 methodname = methodname + "_event"
751 if hasattr(ins, methodname):
752 self.text.bind(vevent, getattr(ins, methodname))
753 if hasattr(ins, "menudefs"):
754 self.fill_menus(ins.menudefs, keydefs)
755 return ins
756
757 def apply_bindings(self, keydefs=None):
758 if keydefs is None:
759 keydefs = self.Bindings.default_keydefs
760 text = self.text
761 text.keydefs = keydefs
762 for event, keylist in keydefs.items():
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000763 ##print>>sys.__stderr__, "event, list: ", event, keylist
David Scherer7aced172000-08-15 01:13:23 +0000764 if keylist:
765 apply(text.event_add, (event,) + tuple(keylist))
766
767 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000768 """Add appropriate entries to the menus and submenus
769
770 Menus that are absent or None in self.menudict are ignored.
771 """
David Scherer7aced172000-08-15 01:13:23 +0000772 if defs is None:
773 defs = self.Bindings.menudefs
774 if keydefs is None:
775 keydefs = self.Bindings.default_keydefs
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000776 ##print>>sys.__stderr__, "*keydefs: " , keydefs
David Scherer7aced172000-08-15 01:13:23 +0000777 menudict = self.menudict
778 text = self.text
779 for mname, itemlist in defs:
780 menu = menudict.get(mname)
781 if not menu:
782 continue
783 for item in itemlist:
784 if not item:
785 menu.add_separator()
786 else:
787 label, event = item
788 checkbutton = (label[:1] == '!')
789 if checkbutton:
790 label = label[1:]
791 underline, label = prepstr(label)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000792 ##print>>sys.__stderr__, "*Event: " , event
David Scherer7aced172000-08-15 01:13:23 +0000793 accelerator = get_accelerator(keydefs, event)
794 def command(text=text, event=event):
795 text.event_generate(event)
796 if checkbutton:
797 var = self.getrawvar(event, BooleanVar)
798 menu.add_checkbutton(label=label, underline=underline,
799 command=command, accelerator=accelerator,
800 variable=var)
801 else:
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000802 ##print>>sys.__stderr__, "label, ul, cmd, accel: ",
803 ## label, underline, command,
804 ## accelerator
David Scherer7aced172000-08-15 01:13:23 +0000805 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000806 command=command,
807 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000808
809 def getvar(self, name):
810 var = self.getrawvar(name)
811 if var:
812 return var.get()
813
814 def setvar(self, name, value, vartype=None):
815 var = self.getrawvar(name, vartype)
816 if var:
817 var.set(value)
818
819 def getrawvar(self, name, vartype=None):
820 var = self.vars.get(name)
821 if not var and vartype:
822 self.vars[name] = var = vartype(self.text)
823 return var
824
825 # Tk implementations of "virtual text methods" -- each platform
826 # reusing IDLE's support code needs to define these for its GUI's
827 # flavor of widget.
828
829 # Is character at text_index in a Python string? Return 0 for
830 # "guaranteed no", true for anything else. This info is expensive
831 # to compute ab initio, but is probably already known by the
832 # platform's colorizer.
833
834 def is_char_in_string(self, text_index):
835 if self.color:
836 # Return true iff colorizer hasn't (re)gotten this far
837 # yet, or the character is tagged as being in a string
838 return self.text.tag_prevrange("TODO", text_index) or \
839 "STRING" in self.text.tag_names(text_index)
840 else:
841 # The colorizer is missing: assume the worst
842 return 1
843
844 # If a selection is defined in the text widget, return (start,
845 # end) as Tkinter text indices, otherwise return (None, None)
846 def get_selection_indices(self):
847 try:
848 first = self.text.index("sel.first")
849 last = self.text.index("sel.last")
850 return first, last
851 except TclError:
852 return None, None
853
854 # Return the text widget's current view of what a tab stop means
855 # (equivalent width in spaces).
856
857 def get_tabwidth(self):
858 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
859 return int(current)
860
861 # Set the text widget's current view of what a tab stop means.
862
863 def set_tabwidth(self, newtabwidth):
864 text = self.text
865 if self.get_tabwidth() != newtabwidth:
866 pixels = text.tk.call("font", "measure", text["font"],
867 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000868 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000869 text.configure(tabs=pixels)
870
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000871### begin autoindent code ###
872
873 # usetabs true -> literal tab characters are used by indent and
874 # dedent cmds, possibly mixed with spaces if
875 # indentwidth is not a multiple of tabwidth
876 # false -> tab characters are converted to spaces by indent
877 # and dedent cmds, and ditto TAB keystrokes
878 # indentwidth is the number of characters per logical indent level.
879 # tabwidth is the display width of a literal tab character.
880 # CAUTION: telling Tk to use anything other than its default
881 # tab setting causes it to use an entirely different tabbing algorithm,
882 # treating tab stops as fixed distances from the left margin.
883 # Nobody expects this, so for now tabwidth should never be changed.
884 usetabs = 0
885 indentwidth = 4
886 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
887
888 # If context_use_ps1 is true, parsing searches back for a ps1 line;
889 # else searches for a popular (if, def, ...) Python stmt.
890 context_use_ps1 = 0
891
892 # When searching backwards for a reliable place to begin parsing,
893 # first start num_context_lines[0] lines back, then
894 # num_context_lines[1] lines back if that didn't work, and so on.
895 # The last value should be huge (larger than the # of lines in a
896 # conceivable file).
897 # Making the initial values larger slows things down more often.
898 num_context_lines = 50, 500, 5000000
899
900 def config(self, **options):
901 for key, value in options.items():
902 if key == 'usetabs':
903 self.usetabs = value
904 elif key == 'indentwidth':
905 self.indentwidth = value
906 elif key == 'tabwidth':
907 self.tabwidth = value
908 elif key == 'context_use_ps1':
909 self.context_use_ps1 = value
910 else:
911 raise KeyError, "bad option name: %s" % `key`
912
913 # If ispythonsource and guess are true, guess a good value for
914 # indentwidth based on file content (if possible), and if
915 # indentwidth != tabwidth set usetabs false.
916 # In any case, adjust the Text widget's view of what a tab
917 # character means.
918
919 def set_indentation_params(self, ispythonsource, guess=1):
920 if guess and ispythonsource:
921 i = self.guess_indent()
922 if 2 <= i <= 8:
923 self.indentwidth = i
924 if self.indentwidth != self.tabwidth:
925 self.usetabs = 0
926
927 self.set_tabwidth(self.tabwidth)
928
929 def smart_backspace_event(self, event):
930 text = self.text
931 first, last = self.get_selection_indices()
932 if first and last:
933 text.delete(first, last)
934 text.mark_set("insert", first)
935 return "break"
936 # Delete whitespace left, until hitting a real char or closest
937 # preceding virtual tab stop.
938 chars = text.get("insert linestart", "insert")
939 if chars == '':
940 if text.compare("insert", ">", "1.0"):
941 # easy: delete preceding newline
942 text.delete("insert-1c")
943 else:
944 text.bell() # at start of buffer
945 return "break"
946 if chars[-1] not in " \t":
947 # easy: delete preceding real char
948 text.delete("insert-1c")
949 return "break"
950 # Ick. It may require *inserting* spaces if we back up over a
951 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000952 tabwidth = self.tabwidth
953 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000954 assert have > 0
955 want = ((have - 1) // self.indentwidth) * self.indentwidth
956 ncharsdeleted = 0
957 while 1:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000958 if chars == sys.ps1:
959 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000960 chars = chars[:-1]
961 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000962 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000963 if have <= want or chars[-1] not in " \t":
964 break
965 text.undo_block_start()
966 text.delete("insert-%dc" % ncharsdeleted, "insert")
967 if have < want:
968 text.insert("insert", ' ' * (want - have))
969 text.undo_block_stop()
970 return "break"
971
972 def smart_indent_event(self, event):
973 # if intraline selection:
974 # delete it
975 # elif multiline selection:
976 # do indent-region & return
977 # indent one level
978 text = self.text
979 first, last = self.get_selection_indices()
980 text.undo_block_start()
981 try:
982 if first and last:
983 if index2line(first) != index2line(last):
984 return self.indent_region_event(event)
985 text.delete(first, last)
986 text.mark_set("insert", first)
987 prefix = text.get("insert linestart", "insert")
988 raw, effective = classifyws(prefix, self.tabwidth)
989 if raw == len(prefix):
990 # only whitespace to the left
991 self.reindent_to(effective + self.indentwidth)
992 else:
993 if self.usetabs:
994 pad = '\t'
995 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000996 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000997 n = self.indentwidth
998 pad = ' ' * (n - effective % n)
999 text.insert("insert", pad)
1000 text.see("insert")
1001 return "break"
1002 finally:
1003 text.undo_block_stop()
1004
1005 def newline_and_indent_event(self, event):
1006 text = self.text
1007 first, last = self.get_selection_indices()
1008 text.undo_block_start()
1009 try:
1010 if first and last:
1011 text.delete(first, last)
1012 text.mark_set("insert", first)
1013 line = text.get("insert linestart", "insert")
1014 i, n = 0, len(line)
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001015 if line == sys.ps1:
1016 return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001017 while i < n and line[i] in " \t":
1018 i = i+1
1019 if i == n:
1020 # the cursor is in or at leading indentation; just inject
1021 # an empty line at the start
1022 text.insert("insert linestart", '\n')
1023 return "break"
1024 indent = line[:i]
1025 # strip whitespace before insert point
1026 i = 0
1027 while line and line[-1] in " \t":
1028 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()