blob: b9e44e109b4cb5cf07b46dba5ebc5513872b2b39 [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
David Scherer7aced172000-08-15 01:13:23 +000022class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000023 from Percolator import Percolator
24 from ColorDelegator import ColorDelegator
25 from UndoDelegator import UndoDelegator
26 from IOBinding import IOBinding
27 import Bindings
28 from Tkinter import Toplevel
29 from MultiStatusBar import MultiStatusBar
30
David Scherer7aced172000-08-15 01:13:23 +000031 vars = {}
32
33 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000034 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000035 self.flist = flist
36 root = root or flist.root
37 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000038 self.menubar = Menu(root)
39 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000040 if flist:
41 self.vars = flist.vars
42 #self.top.instanceDict makes flist.inversedict avalable to
43 #configDialog.py so it can access all EditorWindow instaces
44 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000045 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
46 'recent-files.lst')
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +000047 self.break_set = False
David Scherer7aced172000-08-15 01:13:23 +000048 self.vbar = vbar = Scrollbar(top, name='vbar')
49 self.text_frame = text_frame = Frame(top)
Steven M. Gavadc72f482002-01-03 11:51:07 +000050 self.text = text = Text(text_frame, name='text', padx=5, wrap=None,
51 foreground=idleConf.GetHighlight(currentTheme,
52 'normal',fgBg='fg'),
53 background=idleConf.GetHighlight(currentTheme,
54 'normal',fgBg='bg'),
55 highlightcolor=idleConf.GetHighlight(currentTheme,
56 'hilite',fgBg='fg'),
57 highlightbackground=idleConf.GetHighlight(currentTheme,
58 'hilite',fgBg='bg'),
59 insertbackground=idleConf.GetHighlight(currentTheme,
60 'cursor',fgBg='fg'),
61 width=idleConf.GetOption('main','EditorWindow','width'),
62 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000063
64 self.createmenubar()
65 self.apply_bindings()
66
67 self.top.protocol("WM_DELETE_WINDOW", self.close)
68 self.top.bind("<<close-window>>", self.close_event)
69 text.bind("<<center-insert>>", self.center_insert_event)
70 text.bind("<<help>>", self.help_dialog)
71 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +000072 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +000073 text.bind("<<python-docs>>", self.python_docs)
74 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +000075 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +000076 text.bind("<<open-module>>", self.open_module)
77 text.bind("<<do-nothing>>", lambda event: "break")
78 text.bind("<<select-all>>", self.select_all)
79 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +000080 text.bind("<<find>>", self.find_event)
81 text.bind("<<find-again>>", self.find_again_event)
82 text.bind("<<find-in-files>>", self.find_in_files_event)
83 text.bind("<<find-selection>>", self.find_selection_event)
84 text.bind("<<replace>>", self.replace_event)
85 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +000086 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000087 text.bind("<<smart-backspace>>",self.smart_backspace_event)
88 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
89 text.bind("<<smart-indent>>",self.smart_indent_event)
90 text.bind("<<indent-region>>",self.indent_region_event)
91 text.bind("<<dedent-region>>",self.dedent_region_event)
92 text.bind("<<comment-region>>",self.comment_region_event)
93 text.bind("<<uncomment-region>>",self.uncomment_region_event)
94 text.bind("<<tabify-region>>",self.tabify_region_event)
95 text.bind("<<untabify-region>>",self.untabify_region_event)
96 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
97 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
98
David Scherer7aced172000-08-15 01:13:23 +000099 if flist:
100 flist.inversedict[self] = key
101 if key:
102 flist.dict[key] = self
103 text.bind("<<open-new-window>>", self.flist.new_callback)
104 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
105 text.bind("<<open-class-browser>>", self.open_class_browser)
106 text.bind("<<open-path-browser>>", self.open_path_browser)
107
Steven M. Gava898a3652001-10-07 11:10:44 +0000108 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000109 vbar['command'] = text.yview
110 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000111 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000112 fontWeight='normal'
113 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
114 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000115 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000116 idleConf.GetOption('main','EditorWindow','font-size'),
117 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000118 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
119 text.pack(side=TOP, fill=BOTH, expand=1)
120 text.focus_set()
121
122 self.per = per = self.Percolator(text)
123 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000124 self.color = color = self.ColorDelegator()
125 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000126 ##print "Initial colorizer"
127 else:
128 ##print "No initial colorizer"
129 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000130
131 self.undo = undo = self.UndoDelegator()
132 per.insertfilter(undo)
133 text.undo_block_start = undo.undo_block_start
134 text.undo_block_stop = undo.undo_block_stop
135 undo.set_saved_change_hook(self.saved_change_hook)
136
137 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000138 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000139 io.set_filename_change_hook(self.filename_change_hook)
140
Steven M. Gava1d46e402002-03-27 08:40:46 +0000141 #create the Recent Files submenu
142 self.menuRecentFiles=Menu(self.menubar)
143 self.menudict['file'].insert_cascade(3,label='Recent Files',
144 underline=0,menu=self.menuRecentFiles)
145 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000146
David Scherer7aced172000-08-15 01:13:23 +0000147 if filename:
148 if os.path.exists(filename):
149 io.loadfile(filename)
150 else:
151 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000152 self.saved_change_hook()
153
154 self.load_extensions()
155
156 menu = self.menudict.get('windows')
157 if menu:
158 end = menu.index("end")
159 if end is None:
160 end = -1
161 if end >= 0:
162 menu.add_separator()
163 end = end + 1
164 self.wmenu_end = end
165 WindowList.register_callback(self.postwindowsmenu)
166
167 # Some abstractions so IDLE extensions are cross-IDE
168 self.askyesno = tkMessageBox.askyesno
169 self.askinteger = tkSimpleDialog.askinteger
170 self.showerror = tkMessageBox.showerror
171
172 if self.extensions.has_key('AutoIndent'):
173 self.extensions['AutoIndent'].set_indentation_params(
174 self.ispythonsource(filename))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000175
David Scherer7aced172000-08-15 01:13:23 +0000176 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000177 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000178 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
179 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
180 self.status_bar.pack(side=BOTTOM, fill=X)
181 self.text.bind('<KeyRelease>', self.set_line_and_column)
182 self.text.bind('<ButtonRelease>', self.set_line_and_column)
183 self.text.after_idle(self.set_line_and_column)
184
185 def set_line_and_column(self, event=None):
186 line, column = string.split(self.text.index(INSERT), '.')
187 self.status_bar.set_label('column', 'Col: %s' % column)
188 self.status_bar.set_label('line', 'Ln: %s' % line)
189
190 def wakeup(self):
191 if self.top.wm_state() == "iconic":
192 self.top.wm_deiconify()
193 else:
194 self.top.tkraise()
195 self.text.focus_set()
196
197 menu_specs = [
198 ("file", "_File"),
199 ("edit", "_Edit"),
200 ("format", "F_ormat"),
201 ("run", "_Run"),
Steven M. Gava82c66822002-02-18 01:45:43 +0000202 ("settings", "_Settings"),
David Scherer7aced172000-08-15 01:13:23 +0000203 ("windows", "_Windows"),
204 ("help", "_Help"),
205 ]
206
207 def createmenubar(self):
208 mbar = self.menubar
209 self.menudict = menudict = {}
210 for name, label in self.menu_specs:
211 underline, label = prepstr(label)
212 menudict[name] = menu = Menu(mbar, name=name)
213 mbar.add_cascade(label=label, menu=menu, underline=underline)
214 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000215 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000216 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000217
218 def postwindowsmenu(self):
219 # Only called when Windows menu exists
220 # XXX Actually, this Just-In-Time updating interferes badly
221 # XXX with the tear-off feature. It would be better to update
222 # XXX all Windows menus whenever the list of windows changes.
223 menu = self.menudict['windows']
224 end = menu.index("end")
225 if end is None:
226 end = -1
227 if end > self.wmenu_end:
228 menu.delete(self.wmenu_end+1, end)
229 WindowList.add_windows_to_menu(menu)
230
231 rmenu = None
232
233 def right_menu_event(self, event):
234 self.text.tag_remove("sel", "1.0", "end")
235 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
236 if not self.rmenu:
237 self.make_rmenu()
238 rmenu = self.rmenu
239 self.event = event
240 iswin = sys.platform[:3] == 'win'
241 if iswin:
242 self.text.config(cursor="arrow")
243 rmenu.tk_popup(event.x_root, event.y_root)
244 if iswin:
245 self.text.config(cursor="ibeam")
246
247 rmenu_specs = [
248 # ("Label", "<<virtual-event>>"), ...
249 ("Close", "<<close-window>>"), # Example
250 ]
251
252 def make_rmenu(self):
253 rmenu = Menu(self.text, tearoff=0)
254 for label, eventname in self.rmenu_specs:
255 def command(text=self.text, eventname=eventname):
256 text.event_generate(eventname)
257 rmenu.add_command(label=label, command=command)
258 self.rmenu = rmenu
259
260 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000261 aboutDialog.AboutDialog(self.top,'About IDLEfork')
262
Steven M. Gava3b55a892001-11-21 05:56:26 +0000263 def config_dialog(self, event=None):
264 configDialog.ConfigDialog(self.top,'Settings')
265
David Scherer7aced172000-08-15 01:13:23 +0000266 def good_advice(self, event=None):
267 tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
268
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000269 def view_readme(self, event=None):
270 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
271 textView.TextViewer(self.top,'IDLEfork - README',fn)
272
David Scherer7aced172000-08-15 01:13:23 +0000273 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000274 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
275 textView.TextViewer(self.top,'Help',fn)
276
David Scherer7aced172000-08-15 01:13:23 +0000277 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000278 if sys.platform[:3] == "win":
279 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000280 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000281 fn = os.path.normpath(fn)
282 if os.path.isfile(fn):
283 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000284 else:
285 fn = os.path.dirname(__file__)
286 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
287 fn = os.path.normpath(fn)
288 if os.path.isfile(fn):
289 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000290 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000291 def python_docs(self, event=None):
292 os.startfile(self.help_url)
293 else:
294 def python_docs(self, event=None):
295 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000296
297 def display_docs(self, url):
298 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000299
300 def select_all(self, event=None):
301 self.text.tag_add("sel", "1.0", "end-1c")
302 self.text.mark_set("insert", "1.0")
303 self.text.see("insert")
304 return "break"
305
306 def remove_selection(self, event=None):
307 self.text.tag_remove("sel", "1.0", "end")
308 self.text.see("insert")
309
Steven M. Gavac5976402002-01-04 03:06:08 +0000310 def find_event(self, event):
311 SearchDialog.find(self.text)
312 return "break"
313
314 def find_again_event(self, event):
315 SearchDialog.find_again(self.text)
316 return "break"
317
318 def find_selection_event(self, event):
319 SearchDialog.find_selection(self.text)
320 return "break"
321
322 def find_in_files_event(self, event):
323 GrepDialog.grep(self.text, self.io, self.flist)
324 return "break"
325
326 def replace_event(self, event):
327 ReplaceDialog.replace(self.text)
328 return "break"
329
330 def goto_line_event(self, event):
331 text = self.text
332 lineno = tkSimpleDialog.askinteger("Goto",
333 "Go to line number:",parent=text)
334 if lineno is None:
335 return "break"
336 if lineno <= 0:
337 text.bell()
338 return "break"
339 text.mark_set("insert", "%d.0" % lineno)
340 text.see("insert")
341
David Scherer7aced172000-08-15 01:13:23 +0000342 def open_module(self, event=None):
343 # XXX Shouldn't this be in IOBinding or in FileList?
344 try:
345 name = self.text.get("sel.first", "sel.last")
346 except TclError:
347 name = ""
348 else:
349 name = string.strip(name)
350 if not name:
351 name = tkSimpleDialog.askstring("Module",
352 "Enter the name of a Python module\n"
353 "to search on sys.path and open:",
354 parent=self.text)
355 if name:
356 name = string.strip(name)
357 if not name:
358 return
359 # XXX Ought to support package syntax
360 # XXX Ought to insert current file's directory in front of path
361 try:
362 (f, file, (suffix, mode, type)) = imp.find_module(name)
363 except (NameError, ImportError), msg:
364 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
365 return
366 if type != imp.PY_SOURCE:
367 tkMessageBox.showerror("Unsupported type",
368 "%s is not a source module" % name, parent=self.text)
369 return
370 if f:
371 f.close()
372 if self.flist:
373 self.flist.open(file)
374 else:
375 self.io.loadfile(file)
376
377 def open_class_browser(self, event=None):
378 filename = self.io.filename
379 if not filename:
380 tkMessageBox.showerror(
381 "No filename",
382 "This buffer has no associated filename",
383 master=self.text)
384 self.text.focus_set()
385 return None
386 head, tail = os.path.split(filename)
387 base, ext = os.path.splitext(tail)
388 import ClassBrowser
389 ClassBrowser.ClassBrowser(self.flist, base, [head])
390
391 def open_path_browser(self, event=None):
392 import PathBrowser
393 PathBrowser.PathBrowser(self.flist)
394
395 def gotoline(self, lineno):
396 if lineno is not None and lineno > 0:
397 self.text.mark_set("insert", "%d.0" % lineno)
398 self.text.tag_remove("sel", "1.0", "end")
399 self.text.tag_add("sel", "insert", "insert +1l")
400 self.center()
401
402 def ispythonsource(self, filename):
403 if not filename:
404 return 1
405 base, ext = os.path.splitext(os.path.basename(filename))
406 if os.path.normcase(ext) in (".py", ".pyw"):
407 return 1
408 try:
409 f = open(filename)
410 line = f.readline()
411 f.close()
412 except IOError:
413 return 0
414 return line[:2] == '#!' and string.find(line, 'python') >= 0
415
416 def close_hook(self):
417 if self.flist:
418 self.flist.close_edit(self)
419
420 def set_close_hook(self, close_hook):
421 self.close_hook = close_hook
422
423 def filename_change_hook(self):
424 if self.flist:
425 self.flist.filename_changed_edit(self)
426 self.saved_change_hook()
427 if self.ispythonsource(self.io.filename):
428 self.addcolorizer()
429 else:
430 self.rmcolorizer()
431
432 def addcolorizer(self):
433 if self.color:
434 return
435 ##print "Add colorizer"
436 self.per.removefilter(self.undo)
437 self.color = self.ColorDelegator()
438 self.per.insertfilter(self.color)
439 self.per.insertfilter(self.undo)
440
441 def rmcolorizer(self):
442 if not self.color:
443 return
444 ##print "Remove colorizer"
445 self.per.removefilter(self.undo)
446 self.per.removefilter(self.color)
447 self.color = None
448 self.per.insertfilter(self.undo)
Steven M. Gavab77d3432002-03-02 07:16:21 +0000449
450 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000451 "Update the colour theme if it is changed"
452 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000453 if self.color:
454 self.color = self.ColorDelegator()
455 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000456
Steven M. Gavab1585412002-03-12 00:21:56 +0000457 def ResetFont(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000458 "Update the text widgets' font if it is changed"
459 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000460 fontWeight='normal'
461 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
462 fontWeight='bold'
463 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
464 idleConf.GetOption('main','EditorWindow','font-size'),
465 fontWeight))
466
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000467 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000468 "Update the keybindings if they are changed"
469 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000470 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
471 keydefs = self.Bindings.default_keydefs
472 for event, keylist in keydefs.items():
473 self.text.event_delete(event)
474 self.apply_bindings()
475 #update menu accelerators
476 menuEventDict={}
477 for menu in self.Bindings.menudefs:
478 menuEventDict[menu[0]]={}
479 for item in menu[1]:
480 if item:
481 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
482 for menubarItem in self.menudict.keys():
483 menu=self.menudict[menubarItem]
484 end=menu.index(END)+1
485 for index in range(0,end):
486 if menu.type(index)=='command':
487 accel=menu.entrycget(index,'accelerator')
488 if accel:
489 itemName=menu.entrycget(index,'label')
490 event=''
491 if menuEventDict.has_key(menubarItem):
492 if menuEventDict[menubarItem].has_key(itemName):
493 event=menuEventDict[menubarItem][itemName]
494 if event:
495 #print 'accel was:',accel
496 accel=get_accelerator(keydefs, event)
497 menu.entryconfig(index,accelerator=accel)
498 #print 'accel now:',accel,'\n'
499
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000500 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000501 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000502 menuList=idleConf.GetAllExtraHelpSourcesList()
503 helpMenu=self.menudict['help']
504 cascadeIndex=helpMenu.index(END)-1
505 if menuList:
506 if not hasattr(self,'menuExtraHelp'):
507 self.menuExtraHelp=Menu(self.menubar)
508 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
509 underline=1,menu=self.menuExtraHelp)
510 self.menuExtraHelp.delete(1,END)
511 for menuItem in menuList:
512 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000513 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000514 else: #no extra help items
515 if hasattr(self,'menuExtraHelp'):
516 helpMenu.delete(cascadeIndex-1)
517 del(self.menuExtraHelp)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000518
519 def __DisplayExtraHelpCallback(self,helpFile):
520 def DisplayExtraHelp(helpFile=helpFile):
521 self.display_docs(helpFile)
522 return DisplayExtraHelp
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000523
Steven M. Gava1d46e402002-03-27 08:40:46 +0000524 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000525 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000526 rfList=[]
527 if os.path.exists(self.recentFilesPath):
528 RFfile=open(self.recentFilesPath,'r')
529 try:
530 rfList=RFfile.readlines()
531 finally:
532 RFfile.close()
533 if newFile:
534 newFile=os.path.abspath(newFile)+'\n'
535 if newFile in rfList:
536 rfList.remove(newFile)
537 rfList.insert(0,newFile)
538 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000539 #print self.flist.inversedict
540 #print self.top.instanceDict
541 #print self
Steven M. Gava1d46e402002-03-27 08:40:46 +0000542 if rfList:
543 for instance in self.top.instanceDict.keys():
544 instance.menuRecentFiles.delete(1,END)
545 for file in rfList:
546 fileName=file[0:-1]
547 instance.menuRecentFiles.add_command(label=fileName,
548 command=instance.__RecentFileCallback(fileName))
549
550 def __CleanRecentFiles(self,rfList):
551 origRfList=rfList[:]
552 count=0
553 nonFiles=[]
554 for path in rfList:
555 if not os.path.exists(path[0:-1]):
556 nonFiles.append(count)
557 count=count+1
558 if nonFiles:
559 nonFiles.reverse()
560 for index in nonFiles:
561 del(rfList[index])
562 if len(rfList)>19:
563 rfList=rfList[0:19]
564 #if rfList != origRfList:
565 RFfile=open(self.recentFilesPath,'w')
566 try:
567 RFfile.writelines(rfList)
568 finally:
569 RFfile.close()
570 return rfList
571
572 def __RecentFileCallback(self,fileName):
573 def OpenRecentFile(fileName=fileName):
574 self.io.open(editFile=fileName)
575 return OpenRecentFile
576
David Scherer7aced172000-08-15 01:13:23 +0000577 def saved_change_hook(self):
578 short = self.short_title()
579 long = self.long_title()
580 if short and long:
581 title = short + " - " + long
582 elif short:
583 title = short
584 elif long:
585 title = long
586 else:
587 title = "Untitled"
588 icon = short or long or title
589 if not self.get_saved():
590 title = "*%s*" % title
591 icon = "*%s" % icon
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000592 if self.break_set:
593 shell = self.flist.pyshell
594 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000595 self.top.wm_title(title)
596 self.top.wm_iconname(icon)
597
598 def get_saved(self):
599 return self.undo.get_saved()
600
601 def set_saved(self, flag):
602 self.undo.set_saved(flag)
603
604 def reset_undo(self):
605 self.undo.reset_undo()
606
607 def short_title(self):
608 filename = self.io.filename
609 if filename:
610 filename = os.path.basename(filename)
611 return filename
612
613 def long_title(self):
614 return self.io.filename or ""
615
616 def center_insert_event(self, event):
617 self.center()
618
619 def center(self, mark="insert"):
620 text = self.text
621 top, bot = self.getwindowlines()
622 lineno = self.getlineno(mark)
623 height = bot - top
624 newtop = max(1, lineno - height/2)
625 text.yview(float(newtop))
626
627 def getwindowlines(self):
628 text = self.text
629 top = self.getlineno("@0,0")
630 bot = self.getlineno("@0,65535")
631 if top == bot and text.winfo_height() == 1:
632 # Geometry manager hasn't run yet
633 height = int(text['height'])
634 bot = top + height - 1
635 return top, bot
636
637 def getlineno(self, mark="insert"):
638 text = self.text
639 return int(float(text.index(mark)))
640
641 def close_event(self, event):
642 self.close()
643
644 def maybesave(self):
645 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000646 if not self.get_saved():
647 if self.top.state()!='normal':
648 self.top.deiconify()
649 self.top.lower()
650 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000651 return self.io.maybesave()
652
653 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000654 reply = self.maybesave()
655 if reply != "cancel":
656 self._close()
657 return reply
658
659 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000660 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000661 if self.io.filename:
662 self.UpdateRecentFilesList(newFile=self.io.filename)
Kurt B. Kaiser889f8bf2002-07-06 04:22:25 +0000663 if self.break_set:
664 shell = self.flist.pyshell
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000665 shell.interp.debugger.clear_file_breaks(self)
David Scherer7aced172000-08-15 01:13:23 +0000666 WindowList.unregister_callback(self.postwindowsmenu)
667 if self.close_hook:
668 self.close_hook()
669 self.flist = None
670 colorizing = 0
671 self.unload_extensions()
672 self.io.close(); self.io = None
673 self.undo = None # XXX
674 if self.color:
675 colorizing = self.color.colorizing
676 doh = colorizing and self.top
677 self.color.close(doh) # Cancel colorization
678 self.text = None
679 self.vars = None
680 self.per.close(); self.per = None
681 if not colorizing:
682 self.top.destroy()
683
684 def load_extensions(self):
685 self.extensions = {}
686 self.load_standard_extensions()
687
688 def unload_extensions(self):
689 for ins in self.extensions.values():
690 if hasattr(ins, "close"):
691 ins.close()
692 self.extensions = {}
693
694 def load_standard_extensions(self):
695 for name in self.get_standard_extension_names():
696 try:
697 self.load_extension(name)
698 except:
699 print "Failed to load extension", `name`
700 import traceback
701 traceback.print_exc()
702
703 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000704 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000705
706 def load_extension(self, name):
707 mod = __import__(name, globals(), locals(), [])
708 cls = getattr(mod, name)
709 ins = cls(self)
710 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000711 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000712 if keydefs:
713 self.apply_bindings(keydefs)
714 for vevent in keydefs.keys():
715 methodname = string.replace(vevent, "-", "_")
716 while methodname[:1] == '<':
717 methodname = methodname[1:]
718 while methodname[-1:] == '>':
719 methodname = methodname[:-1]
720 methodname = methodname + "_event"
721 if hasattr(ins, methodname):
722 self.text.bind(vevent, getattr(ins, methodname))
723 if hasattr(ins, "menudefs"):
724 self.fill_menus(ins.menudefs, keydefs)
725 return ins
726
727 def apply_bindings(self, keydefs=None):
728 if keydefs is None:
729 keydefs = self.Bindings.default_keydefs
730 text = self.text
731 text.keydefs = keydefs
732 for event, keylist in keydefs.items():
733 if keylist:
734 apply(text.event_add, (event,) + tuple(keylist))
735
736 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000737 """Add appropriate entries to the menus and submenus
738
739 Menus that are absent or None in self.menudict are ignored.
740 """
David Scherer7aced172000-08-15 01:13:23 +0000741 if defs is None:
742 defs = self.Bindings.menudefs
743 if keydefs is None:
744 keydefs = self.Bindings.default_keydefs
745 menudict = self.menudict
746 text = self.text
747 for mname, itemlist in defs:
748 menu = menudict.get(mname)
749 if not menu:
750 continue
751 for item in itemlist:
752 if not item:
753 menu.add_separator()
754 else:
755 label, event = item
756 checkbutton = (label[:1] == '!')
757 if checkbutton:
758 label = label[1:]
759 underline, label = prepstr(label)
760 accelerator = get_accelerator(keydefs, event)
761 def command(text=text, event=event):
762 text.event_generate(event)
763 if checkbutton:
764 var = self.getrawvar(event, BooleanVar)
765 menu.add_checkbutton(label=label, underline=underline,
766 command=command, accelerator=accelerator,
767 variable=var)
768 else:
769 menu.add_command(label=label, underline=underline,
770 command=command, accelerator=accelerator)
771
772 def getvar(self, name):
773 var = self.getrawvar(name)
774 if var:
775 return var.get()
776
777 def setvar(self, name, value, vartype=None):
778 var = self.getrawvar(name, vartype)
779 if var:
780 var.set(value)
781
782 def getrawvar(self, name, vartype=None):
783 var = self.vars.get(name)
784 if not var and vartype:
785 self.vars[name] = var = vartype(self.text)
786 return var
787
788 # Tk implementations of "virtual text methods" -- each platform
789 # reusing IDLE's support code needs to define these for its GUI's
790 # flavor of widget.
791
792 # Is character at text_index in a Python string? Return 0 for
793 # "guaranteed no", true for anything else. This info is expensive
794 # to compute ab initio, but is probably already known by the
795 # platform's colorizer.
796
797 def is_char_in_string(self, text_index):
798 if self.color:
799 # Return true iff colorizer hasn't (re)gotten this far
800 # yet, or the character is tagged as being in a string
801 return self.text.tag_prevrange("TODO", text_index) or \
802 "STRING" in self.text.tag_names(text_index)
803 else:
804 # The colorizer is missing: assume the worst
805 return 1
806
807 # If a selection is defined in the text widget, return (start,
808 # end) as Tkinter text indices, otherwise return (None, None)
809 def get_selection_indices(self):
810 try:
811 first = self.text.index("sel.first")
812 last = self.text.index("sel.last")
813 return first, last
814 except TclError:
815 return None, None
816
817 # Return the text widget's current view of what a tab stop means
818 # (equivalent width in spaces).
819
820 def get_tabwidth(self):
821 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
822 return int(current)
823
824 # Set the text widget's current view of what a tab stop means.
825
826 def set_tabwidth(self, newtabwidth):
827 text = self.text
828 if self.get_tabwidth() != newtabwidth:
829 pixels = text.tk.call("font", "measure", text["font"],
830 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000831 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000832 text.configure(tabs=pixels)
833
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000834### begin autoindent code ###
835
836 # usetabs true -> literal tab characters are used by indent and
837 # dedent cmds, possibly mixed with spaces if
838 # indentwidth is not a multiple of tabwidth
839 # false -> tab characters are converted to spaces by indent
840 # and dedent cmds, and ditto TAB keystrokes
841 # indentwidth is the number of characters per logical indent level.
842 # tabwidth is the display width of a literal tab character.
843 # CAUTION: telling Tk to use anything other than its default
844 # tab setting causes it to use an entirely different tabbing algorithm,
845 # treating tab stops as fixed distances from the left margin.
846 # Nobody expects this, so for now tabwidth should never be changed.
847 usetabs = 0
848 indentwidth = 4
849 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
850
851 # If context_use_ps1 is true, parsing searches back for a ps1 line;
852 # else searches for a popular (if, def, ...) Python stmt.
853 context_use_ps1 = 0
854
855 # When searching backwards for a reliable place to begin parsing,
856 # first start num_context_lines[0] lines back, then
857 # num_context_lines[1] lines back if that didn't work, and so on.
858 # The last value should be huge (larger than the # of lines in a
859 # conceivable file).
860 # Making the initial values larger slows things down more often.
861 num_context_lines = 50, 500, 5000000
862
863 def config(self, **options):
864 for key, value in options.items():
865 if key == 'usetabs':
866 self.usetabs = value
867 elif key == 'indentwidth':
868 self.indentwidth = value
869 elif key == 'tabwidth':
870 self.tabwidth = value
871 elif key == 'context_use_ps1':
872 self.context_use_ps1 = value
873 else:
874 raise KeyError, "bad option name: %s" % `key`
875
876 # If ispythonsource and guess are true, guess a good value for
877 # indentwidth based on file content (if possible), and if
878 # indentwidth != tabwidth set usetabs false.
879 # In any case, adjust the Text widget's view of what a tab
880 # character means.
881
882 def set_indentation_params(self, ispythonsource, guess=1):
883 if guess and ispythonsource:
884 i = self.guess_indent()
885 if 2 <= i <= 8:
886 self.indentwidth = i
887 if self.indentwidth != self.tabwidth:
888 self.usetabs = 0
889
890 self.set_tabwidth(self.tabwidth)
891
892 def smart_backspace_event(self, event):
893 text = self.text
894 first, last = self.get_selection_indices()
895 if first and last:
896 text.delete(first, last)
897 text.mark_set("insert", first)
898 return "break"
899 # Delete whitespace left, until hitting a real char or closest
900 # preceding virtual tab stop.
901 chars = text.get("insert linestart", "insert")
902 if chars == '':
903 if text.compare("insert", ">", "1.0"):
904 # easy: delete preceding newline
905 text.delete("insert-1c")
906 else:
907 text.bell() # at start of buffer
908 return "break"
909 if chars[-1] not in " \t":
910 # easy: delete preceding real char
911 text.delete("insert-1c")
912 return "break"
913 # Ick. It may require *inserting* spaces if we back up over a
914 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000915 tabwidth = self.tabwidth
916 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000917 assert have > 0
918 want = ((have - 1) // self.indentwidth) * self.indentwidth
919 ncharsdeleted = 0
920 while 1:
921 chars = chars[:-1]
922 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000923 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000924 if have <= want or chars[-1] not in " \t":
925 break
926 text.undo_block_start()
927 text.delete("insert-%dc" % ncharsdeleted, "insert")
928 if have < want:
929 text.insert("insert", ' ' * (want - have))
930 text.undo_block_stop()
931 return "break"
932
933 def smart_indent_event(self, event):
934 # if intraline selection:
935 # delete it
936 # elif multiline selection:
937 # do indent-region & return
938 # indent one level
939 text = self.text
940 first, last = self.get_selection_indices()
941 text.undo_block_start()
942 try:
943 if first and last:
944 if index2line(first) != index2line(last):
945 return self.indent_region_event(event)
946 text.delete(first, last)
947 text.mark_set("insert", first)
948 prefix = text.get("insert linestart", "insert")
949 raw, effective = classifyws(prefix, self.tabwidth)
950 if raw == len(prefix):
951 # only whitespace to the left
952 self.reindent_to(effective + self.indentwidth)
953 else:
954 if self.usetabs:
955 pad = '\t'
956 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000957 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000958 n = self.indentwidth
959 pad = ' ' * (n - effective % n)
960 text.insert("insert", pad)
961 text.see("insert")
962 return "break"
963 finally:
964 text.undo_block_stop()
965
966 def newline_and_indent_event(self, event):
967 text = self.text
968 first, last = self.get_selection_indices()
969 text.undo_block_start()
970 try:
971 if first and last:
972 text.delete(first, last)
973 text.mark_set("insert", first)
974 line = text.get("insert linestart", "insert")
975 i, n = 0, len(line)
976 while i < n and line[i] in " \t":
977 i = i+1
978 if i == n:
979 # the cursor is in or at leading indentation; just inject
980 # an empty line at the start
981 text.insert("insert linestart", '\n')
982 return "break"
983 indent = line[:i]
984 # strip whitespace before insert point
985 i = 0
986 while line and line[-1] in " \t":
987 line = line[:-1]
988 i = i+1
989 if i:
990 text.delete("insert - %d chars" % i, "insert")
991 # strip whitespace after insert point
992 while text.get("insert") in " \t":
993 text.delete("insert")
994 # start new line
995 text.insert("insert", '\n')
996
997 # adjust indentation for continuations and block
998 # open/close first need to find the last stmt
999 lno = index2line(text.index('insert'))
1000 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1001 for context in self.num_context_lines:
1002 startat = max(lno - context, 1)
1003 startatindex = `startat` + ".0"
1004 rawtext = text.get(startatindex, "insert")
1005 y.set_str(rawtext)
1006 bod = y.find_good_parse_start(
1007 self.context_use_ps1,
1008 self._build_char_in_string_func(startatindex))
1009 if bod is not None or startat == 1:
1010 break
1011 y.set_lo(bod or 0)
1012 c = y.get_continuation_type()
1013 if c != PyParse.C_NONE:
1014 # The current stmt hasn't ended yet.
1015 if c == PyParse.C_STRING:
1016 # inside a string; just mimic the current indent
1017 text.insert("insert", indent)
1018 elif c == PyParse.C_BRACKET:
1019 # line up with the first (if any) element of the
1020 # last open bracket structure; else indent one
1021 # level beyond the indent of the line with the
1022 # last open bracket
1023 self.reindent_to(y.compute_bracket_indent())
1024 elif c == PyParse.C_BACKSLASH:
1025 # if more than one line in this stmt already, just
1026 # mimic the current indent; else if initial line
1027 # has a start on an assignment stmt, indent to
1028 # beyond leftmost =; else to beyond first chunk of
1029 # non-whitespace on initial line
1030 if y.get_num_lines_in_stmt() > 1:
1031 text.insert("insert", indent)
1032 else:
1033 self.reindent_to(y.compute_backslash_indent())
1034 else:
1035 assert 0, "bogus continuation type " + `c`
1036 return "break"
1037
1038 # This line starts a brand new stmt; indent relative to
1039 # indentation of initial line of closest preceding
1040 # interesting stmt.
1041 indent = y.get_base_indent_string()
1042 text.insert("insert", indent)
1043 if y.is_block_opener():
1044 self.smart_indent_event(event)
1045 elif indent and y.is_block_closer():
1046 self.smart_backspace_event(event)
1047 return "break"
1048 finally:
1049 text.see("insert")
1050 text.undo_block_stop()
1051
1052 auto_indent = newline_and_indent_event
1053
1054 # Our editwin provides a is_char_in_string function that works
1055 # with a Tk text index, but PyParse only knows about offsets into
1056 # a string. This builds a function for PyParse that accepts an
1057 # offset.
1058
1059 def _build_char_in_string_func(self, startindex):
1060 def inner(offset, _startindex=startindex,
1061 _icis=self.is_char_in_string):
1062 return _icis(_startindex + "+%dc" % offset)
1063 return inner
1064
1065 def indent_region_event(self, event):
1066 head, tail, chars, lines = self.get_region()
1067 for pos in range(len(lines)):
1068 line = lines[pos]
1069 if line:
1070 raw, effective = classifyws(line, self.tabwidth)
1071 effective = effective + self.indentwidth
1072 lines[pos] = self._make_blanks(effective) + line[raw:]
1073 self.set_region(head, tail, chars, lines)
1074 return "break"
1075
1076 def dedent_region_event(self, event):
1077 head, tail, chars, lines = self.get_region()
1078 for pos in range(len(lines)):
1079 line = lines[pos]
1080 if line:
1081 raw, effective = classifyws(line, self.tabwidth)
1082 effective = max(effective - self.indentwidth, 0)
1083 lines[pos] = self._make_blanks(effective) + line[raw:]
1084 self.set_region(head, tail, chars, lines)
1085 return "break"
1086
1087 def comment_region_event(self, event):
1088 head, tail, chars, lines = self.get_region()
1089 for pos in range(len(lines) - 1):
1090 line = lines[pos]
1091 lines[pos] = '##' + line
1092 self.set_region(head, tail, chars, lines)
1093
1094 def uncomment_region_event(self, event):
1095 head, tail, chars, lines = self.get_region()
1096 for pos in range(len(lines)):
1097 line = lines[pos]
1098 if not line:
1099 continue
1100 if line[:2] == '##':
1101 line = line[2:]
1102 elif line[:1] == '#':
1103 line = line[1:]
1104 lines[pos] = line
1105 self.set_region(head, tail, chars, lines)
1106
1107 def tabify_region_event(self, event):
1108 head, tail, chars, lines = self.get_region()
1109 tabwidth = self._asktabwidth()
1110 for pos in range(len(lines)):
1111 line = lines[pos]
1112 if line:
1113 raw, effective = classifyws(line, tabwidth)
1114 ntabs, nspaces = divmod(effective, tabwidth)
1115 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1116 self.set_region(head, tail, chars, lines)
1117
1118 def untabify_region_event(self, event):
1119 head, tail, chars, lines = self.get_region()
1120 tabwidth = self._asktabwidth()
1121 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001122 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 self.set_region(head, tail, chars, lines)
1124
1125 def toggle_tabs_event(self, event):
1126 if self.askyesno(
1127 "Toggle tabs",
1128 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1129 parent=self.text):
1130 self.usetabs = not self.usetabs
1131 return "break"
1132
1133 # XXX this isn't bound to anything -- see class tabwidth comments
1134 def change_tabwidth_event(self, event):
1135 new = self._asktabwidth()
1136 if new != self.tabwidth:
1137 self.tabwidth = new
1138 self.set_indentation_params(0, guess=0)
1139 return "break"
1140
1141 def change_indentwidth_event(self, event):
1142 new = self.askinteger(
1143 "Indent width",
1144 "New indent width (2-16)",
1145 parent=self.text,
1146 initialvalue=self.indentwidth,
1147 minvalue=2,
1148 maxvalue=16)
1149 if new and new != self.indentwidth:
1150 self.indentwidth = new
1151 return "break"
1152
1153 def get_region(self):
1154 text = self.text
1155 first, last = self.get_selection_indices()
1156 if first and last:
1157 head = text.index(first + " linestart")
1158 tail = text.index(last + "-1c lineend +1c")
1159 else:
1160 head = text.index("insert linestart")
1161 tail = text.index("insert lineend +1c")
1162 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001163 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001164 return head, tail, chars, lines
1165
1166 def set_region(self, head, tail, chars, lines):
1167 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001168 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001169 if newchars == chars:
1170 text.bell()
1171 return
1172 text.tag_remove("sel", "1.0", "end")
1173 text.mark_set("insert", head)
1174 text.undo_block_start()
1175 text.delete(head, tail)
1176 text.insert(head, newchars)
1177 text.undo_block_stop()
1178 text.tag_add("sel", head, "insert")
1179
1180 # Make string that displays as n leading blanks.
1181
1182 def _make_blanks(self, n):
1183 if self.usetabs:
1184 ntabs, nspaces = divmod(n, self.tabwidth)
1185 return '\t' * ntabs + ' ' * nspaces
1186 else:
1187 return ' ' * n
1188
1189 # Delete from beginning of line to insert point, then reinsert
1190 # column logical (meaning use tabs if appropriate) spaces.
1191
1192 def reindent_to(self, column):
1193 text = self.text
1194 text.undo_block_start()
1195 if text.compare("insert linestart", "!=", "insert"):
1196 text.delete("insert linestart", "insert")
1197 if column:
1198 text.insert("insert", self._make_blanks(column))
1199 text.undo_block_stop()
1200
1201 def _asktabwidth(self):
1202 return self.askinteger(
1203 "Tab width",
1204 "Spaces per tab? (2-16)",
1205 parent=self.text,
1206 initialvalue=self.indentwidth,
1207 minvalue=2,
1208 maxvalue=16) or self.tabwidth
1209
1210 # Guess indentwidth from text content.
1211 # Return guessed indentwidth. This should not be believed unless
1212 # it's in a reasonable range (e.g., it will be 0 if no indented
1213 # blocks are found).
1214
1215 def guess_indent(self):
1216 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1217 if opener and indented:
1218 raw, indentsmall = classifyws(opener, self.tabwidth)
1219 raw, indentlarge = classifyws(indented, self.tabwidth)
1220 else:
1221 indentsmall = indentlarge = 0
1222 return indentlarge - indentsmall
1223
1224# "line.col" -> line, as an int
1225def index2line(index):
1226 return int(float(index))
1227
1228# Look at the leading whitespace in s.
1229# Return pair (# of leading ws characters,
1230# effective # of leading blanks after expanding
1231# tabs to width tabwidth)
1232
1233def classifyws(s, tabwidth):
1234 raw = effective = 0
1235 for ch in s:
1236 if ch == ' ':
1237 raw = raw + 1
1238 effective = effective + 1
1239 elif ch == '\t':
1240 raw = raw + 1
1241 effective = (effective // tabwidth + 1) * tabwidth
1242 else:
1243 break
1244 return raw, effective
1245
1246import tokenize
1247_tokenize = tokenize
1248del tokenize
1249
1250class IndentSearcher:
1251
1252 # .run() chews over the Text widget, looking for a block opener
1253 # and the stmt following it. Returns a pair,
1254 # (line containing block opener, line containing stmt)
1255 # Either or both may be None.
1256
1257 def __init__(self, text, tabwidth):
1258 self.text = text
1259 self.tabwidth = tabwidth
1260 self.i = self.finished = 0
1261 self.blkopenline = self.indentedline = None
1262
1263 def readline(self):
1264 if self.finished:
1265 return ""
1266 i = self.i = self.i + 1
1267 mark = `i` + ".0"
1268 if self.text.compare(mark, ">=", "end"):
1269 return ""
1270 return self.text.get(mark, mark + " lineend+1c")
1271
1272 def tokeneater(self, type, token, start, end, line,
1273 INDENT=_tokenize.INDENT,
1274 NAME=_tokenize.NAME,
1275 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1276 if self.finished:
1277 pass
1278 elif type == NAME and token in OPENERS:
1279 self.blkopenline = line
1280 elif type == INDENT and self.blkopenline:
1281 self.indentedline = line
1282 self.finished = 1
1283
1284 def run(self):
1285 save_tabsize = _tokenize.tabsize
1286 _tokenize.tabsize = self.tabwidth
1287 try:
1288 try:
1289 _tokenize.tokenize(self.readline, self.tokeneater)
1290 except _tokenize.TokenError:
1291 # since we cut off the tokenizer early, we can trigger
1292 # spurious errors
1293 pass
1294 finally:
1295 _tokenize.tabsize = save_tabsize
1296 return self.blkopenline, self.indentedline
1297
1298### end autoindent code ###
1299
David Scherer7aced172000-08-15 01:13:23 +00001300def prepstr(s):
1301 # Helper to extract the underscore from a string, e.g.
1302 # prepstr("Co_py") returns (2, "Copy").
1303 i = string.find(s, '_')
1304 if i >= 0:
1305 s = s[:i] + s[i+1:]
1306 return i, s
1307
1308
1309keynames = {
1310 'bracketleft': '[',
1311 'bracketright': ']',
1312 'slash': '/',
1313}
1314
1315def get_accelerator(keydefs, event):
1316 keylist = keydefs.get(event)
1317 if not keylist:
1318 return ""
1319 s = keylist[0]
1320 s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
1321 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1322 s = re.sub("Key-", "", s)
1323 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1324 s = re.sub("Control-", "Ctrl-", s)
1325 s = re.sub("-", "+", s)
1326 s = re.sub("><", " ", s)
1327 s = re.sub("<", "", s)
1328 s = re.sub(">", "", s)
1329 return s
1330
1331
1332def fixwordbreaks(root):
1333 # Make sure that Tk's double-click and next/previous word
1334 # operations use our definition of a word (i.e. an identifier)
1335 tk = root.tk
1336 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1337 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1338 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1339
1340
1341def test():
1342 root = Tk()
1343 fixwordbreaks(root)
1344 root.withdraw()
1345 if sys.argv[1:]:
1346 filename = sys.argv[1]
1347 else:
1348 filename = None
1349 edit = EditorWindow(root=root, filename=filename)
1350 edit.set_close_hook(root.quit)
1351 root.mainloop()
1352 root.destroy()
1353
1354if __name__ == '__main__':
1355 test()