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