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