blob: 96a894a4b55a334e25f2afd570e24f7f2ad73850 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
33 path = module.__path__
34 return file, filename, descr
35
David Scherer7aced172000-08-15 01:13:23 +000036class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000037 from Percolator import Percolator
38 from ColorDelegator import ColorDelegator
39 from UndoDelegator import UndoDelegator
40 from IOBinding import IOBinding
41 import Bindings
42 from Tkinter import Toplevel
43 from MultiStatusBar import MultiStatusBar
44
David Scherer7aced172000-08-15 01:13:23 +000045 vars = {}
46
47 def __init__(self, flist=None, filename=None, key=None, root=None):
Steven M. Gavadc72f482002-01-03 11:51:07 +000048 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000049 self.flist = flist
50 root = root or flist.root
51 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000052 self.menubar = Menu(root)
53 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000054 if flist:
55 self.vars = flist.vars
56 #self.top.instanceDict makes flist.inversedict avalable to
57 #configDialog.py so it can access all EditorWindow instaces
58 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000059 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
60 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000061 self.vbar = vbar = Scrollbar(top, name='vbar')
62 self.text_frame = text_frame = Frame(top)
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000063 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000064 foreground=idleConf.GetHighlight(currentTheme,
65 'normal',fgBg='fg'),
66 background=idleConf.GetHighlight(currentTheme,
67 'normal',fgBg='bg'),
68 highlightcolor=idleConf.GetHighlight(currentTheme,
69 'hilite',fgBg='fg'),
70 highlightbackground=idleConf.GetHighlight(currentTheme,
71 'hilite',fgBg='bg'),
72 insertbackground=idleConf.GetHighlight(currentTheme,
73 'cursor',fgBg='fg'),
74 width=idleConf.GetOption('main','EditorWindow','width'),
75 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000076
77 self.createmenubar()
78 self.apply_bindings()
79
80 self.top.protocol("WM_DELETE_WINDOW", self.close)
81 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +000082 text.bind("<<cut>>", self.cut)
83 text.bind("<<copy>>", self.copy)
84 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +000085 text.bind("<<center-insert>>", self.center_insert_event)
86 text.bind("<<help>>", self.help_dialog)
87 text.bind("<<good-advice>>", self.good_advice)
Steven M. Gavaabdfc412001-08-11 07:46:26 +000088 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +000089 text.bind("<<python-docs>>", self.python_docs)
90 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +000091 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +000092 text.bind("<<open-module>>", self.open_module)
93 text.bind("<<do-nothing>>", lambda event: "break")
94 text.bind("<<select-all>>", self.select_all)
95 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +000096 text.bind("<<find>>", self.find_event)
97 text.bind("<<find-again>>", self.find_again_event)
98 text.bind("<<find-in-files>>", self.find_in_files_event)
99 text.bind("<<find-selection>>", self.find_selection_event)
100 text.bind("<<replace>>", self.replace_event)
101 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000102 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000103 text.bind("<<smart-backspace>>",self.smart_backspace_event)
104 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
105 text.bind("<<smart-indent>>",self.smart_indent_event)
106 text.bind("<<indent-region>>",self.indent_region_event)
107 text.bind("<<dedent-region>>",self.dedent_region_event)
108 text.bind("<<comment-region>>",self.comment_region_event)
109 text.bind("<<uncomment-region>>",self.uncomment_region_event)
110 text.bind("<<tabify-region>>",self.tabify_region_event)
111 text.bind("<<untabify-region>>",self.untabify_region_event)
112 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
113 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000114
David Scherer7aced172000-08-15 01:13:23 +0000115 if flist:
116 flist.inversedict[self] = key
117 if key:
118 flist.dict[key] = self
119 text.bind("<<open-new-window>>", self.flist.new_callback)
120 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
121 text.bind("<<open-class-browser>>", self.open_class_browser)
122 text.bind("<<open-path-browser>>", self.open_path_browser)
123
Steven M. Gava898a3652001-10-07 11:10:44 +0000124 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000125 vbar['command'] = text.yview
126 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000127 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000128 fontWeight='normal'
129 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
130 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000131 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000132 idleConf.GetOption('main','EditorWindow','font-size'),
133 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000134 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
135 text.pack(side=TOP, fill=BOTH, expand=1)
136 text.focus_set()
137
138 self.per = per = self.Percolator(text)
139 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000140 self.color = color = self.ColorDelegator()
141 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000142 else:
David Scherer7aced172000-08-15 01:13:23 +0000143 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))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +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')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000276
Steven M. Gava3b55a892001-11-21 05:56:26 +0000277 def config_dialog(self, event=None):
278 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000279
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')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000285 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000286
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')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000289 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
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000314 def cut(self,event):
315 self.text.event_generate("<<Cut>>")
316 return "break"
317
318 def copy(self,event):
319 self.text.event_generate("<<Copy>>")
320 return "break"
321
322 def paste(self,event):
323 self.text.event_generate("<<Paste>>")
324 return "break"
325
David Scherer7aced172000-08-15 01:13:23 +0000326 def select_all(self, event=None):
327 self.text.tag_add("sel", "1.0", "end-1c")
328 self.text.mark_set("insert", "1.0")
329 self.text.see("insert")
330 return "break"
331
332 def remove_selection(self, event=None):
333 self.text.tag_remove("sel", "1.0", "end")
334 self.text.see("insert")
335
Steven M. Gavac5976402002-01-04 03:06:08 +0000336 def find_event(self, event):
337 SearchDialog.find(self.text)
338 return "break"
339
340 def find_again_event(self, event):
341 SearchDialog.find_again(self.text)
342 return "break"
343
344 def find_selection_event(self, event):
345 SearchDialog.find_selection(self.text)
346 return "break"
347
348 def find_in_files_event(self, event):
349 GrepDialog.grep(self.text, self.io, self.flist)
350 return "break"
351
352 def replace_event(self, event):
353 ReplaceDialog.replace(self.text)
354 return "break"
355
356 def goto_line_event(self, event):
357 text = self.text
358 lineno = tkSimpleDialog.askinteger("Goto",
359 "Go to line number:",parent=text)
360 if lineno is None:
361 return "break"
362 if lineno <= 0:
363 text.bell()
364 return "break"
365 text.mark_set("insert", "%d.0" % lineno)
366 text.see("insert")
367
David Scherer7aced172000-08-15 01:13:23 +0000368 def open_module(self, event=None):
369 # XXX Shouldn't this be in IOBinding or in FileList?
370 try:
371 name = self.text.get("sel.first", "sel.last")
372 except TclError:
373 name = ""
374 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000375 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000376 if not name:
377 name = tkSimpleDialog.askstring("Module",
378 "Enter the name of a Python module\n"
379 "to search on sys.path and open:",
380 parent=self.text)
381 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000382 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000383 if not name:
384 return
David Scherer7aced172000-08-15 01:13:23 +0000385 # XXX Ought to insert current file's directory in front of path
386 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000387 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000388 except (NameError, ImportError), msg:
389 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
390 return
391 if type != imp.PY_SOURCE:
392 tkMessageBox.showerror("Unsupported type",
393 "%s is not a source module" % name, parent=self.text)
394 return
395 if f:
396 f.close()
397 if self.flist:
398 self.flist.open(file)
399 else:
400 self.io.loadfile(file)
401
402 def open_class_browser(self, event=None):
403 filename = self.io.filename
404 if not filename:
405 tkMessageBox.showerror(
406 "No filename",
407 "This buffer has no associated filename",
408 master=self.text)
409 self.text.focus_set()
410 return None
411 head, tail = os.path.split(filename)
412 base, ext = os.path.splitext(tail)
413 import ClassBrowser
414 ClassBrowser.ClassBrowser(self.flist, base, [head])
415
416 def open_path_browser(self, event=None):
417 import PathBrowser
418 PathBrowser.PathBrowser(self.flist)
419
420 def gotoline(self, lineno):
421 if lineno is not None and lineno > 0:
422 self.text.mark_set("insert", "%d.0" % lineno)
423 self.text.tag_remove("sel", "1.0", "end")
424 self.text.tag_add("sel", "insert", "insert +1l")
425 self.center()
426
427 def ispythonsource(self, filename):
428 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000429 return True
David Scherer7aced172000-08-15 01:13:23 +0000430 base, ext = os.path.splitext(os.path.basename(filename))
431 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000432 return True
David Scherer7aced172000-08-15 01:13:23 +0000433 try:
434 f = open(filename)
435 line = f.readline()
436 f.close()
437 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000438 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000439 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000440
441 def close_hook(self):
442 if self.flist:
443 self.flist.close_edit(self)
444
445 def set_close_hook(self, close_hook):
446 self.close_hook = close_hook
447
448 def filename_change_hook(self):
449 if self.flist:
450 self.flist.filename_changed_edit(self)
451 self.saved_change_hook()
452 if self.ispythonsource(self.io.filename):
453 self.addcolorizer()
454 else:
455 self.rmcolorizer()
456
457 def addcolorizer(self):
458 if self.color:
459 return
David Scherer7aced172000-08-15 01:13:23 +0000460 self.per.removefilter(self.undo)
461 self.color = self.ColorDelegator()
462 self.per.insertfilter(self.color)
463 self.per.insertfilter(self.undo)
464
465 def rmcolorizer(self):
466 if not self.color:
467 return
David Scherer7aced172000-08-15 01:13:23 +0000468 self.per.removefilter(self.undo)
469 self.per.removefilter(self.color)
470 self.color = None
471 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000472
Steven M. Gavab77d3432002-03-02 07:16:21 +0000473 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000474 "Update the colour theme if it is changed"
475 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000476 if self.color:
477 self.color = self.ColorDelegator()
478 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000479
Steven M. Gavab1585412002-03-12 00:21:56 +0000480 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000481 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000482 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000483 fontWeight='normal'
484 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
485 fontWeight='bold'
486 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
487 idleConf.GetOption('main','EditorWindow','font-size'),
488 fontWeight))
489
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000490 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000491 "Update the keybindings if they are changed"
492 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000493 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
494 keydefs = self.Bindings.default_keydefs
495 for event, keylist in keydefs.items():
496 self.text.event_delete(event)
497 self.apply_bindings()
498 #update menu accelerators
499 menuEventDict={}
500 for menu in self.Bindings.menudefs:
501 menuEventDict[menu[0]]={}
502 for item in menu[1]:
503 if item:
504 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
505 for menubarItem in self.menudict.keys():
506 menu=self.menudict[menubarItem]
507 end=menu.index(END)+1
508 for index in range(0,end):
509 if menu.type(index)=='command':
510 accel=menu.entrycget(index,'accelerator')
511 if accel:
512 itemName=menu.entrycget(index,'label')
513 event=''
514 if menuEventDict.has_key(menubarItem):
515 if menuEventDict[menubarItem].has_key(itemName):
516 event=menuEventDict[menubarItem][itemName]
517 if event:
518 #print 'accel was:',accel
519 accel=get_accelerator(keydefs, event)
520 menu.entryconfig(index,accelerator=accel)
521 #print 'accel now:',accel,'\n'
522
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000523 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000524 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000525 menuList=idleConf.GetAllExtraHelpSourcesList()
526 helpMenu=self.menudict['help']
527 cascadeIndex=helpMenu.index(END)-1
528 if menuList:
529 if not hasattr(self,'menuExtraHelp'):
530 self.menuExtraHelp=Menu(self.menubar)
531 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
532 underline=1,menu=self.menuExtraHelp)
533 self.menuExtraHelp.delete(1,END)
534 for menuItem in menuList:
535 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000536 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000537 else: #no extra help items
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538 if hasattr(self,'menuExtraHelp'):
539 helpMenu.delete(cascadeIndex-1)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000540 del(self.menuExtraHelp)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000541
Steven M. Gava1d46e402002-03-27 08:40:46 +0000542 def __DisplayExtraHelpCallback(self,helpFile):
543 def DisplayExtraHelp(helpFile=helpFile):
544 self.display_docs(helpFile)
545 return DisplayExtraHelp
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000546
Steven M. Gava1d46e402002-03-27 08:40:46 +0000547 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000548 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000549 rfList=[]
550 if os.path.exists(self.recentFilesPath):
551 RFfile=open(self.recentFilesPath,'r')
552 try:
553 rfList=RFfile.readlines()
554 finally:
555 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000556 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000557 newFile=os.path.abspath(newFile)+'\n'
558 if newFile in rfList:
559 rfList.remove(newFile)
560 rfList.insert(0,newFile)
561 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000562 #print self.flist.inversedict
563 #print self.top.instanceDict
564 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000565 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000566 if rfList:
567 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000568 menu = instance.menuRecentFiles
569 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000570 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000571 for file in rfList:
572 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000573 callback = instance.__RecentFileCallback(fileName)
574 if i > ullen: # don't underline menuitems
575 ul=None
576 menu.add_command(label=ullist[i] + " " + fileName,
577 command=callback,
578 underline=ul)
579 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000580
Steven M. Gava1d46e402002-03-27 08:40:46 +0000581 def __CleanRecentFiles(self,rfList):
582 origRfList=rfList[:]
583 count=0
584 nonFiles=[]
585 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000586 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000587 nonFiles.append(count)
588 count=count+1
589 if nonFiles:
590 nonFiles.reverse()
591 for index in nonFiles:
592 del(rfList[index])
593 if len(rfList)>19:
594 rfList=rfList[0:19]
595 #if rfList != origRfList:
596 RFfile=open(self.recentFilesPath,'w')
597 try:
598 RFfile.writelines(rfList)
599 finally:
600 RFfile.close()
601 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000602
Steven M. Gava1d46e402002-03-27 08:40:46 +0000603 def __RecentFileCallback(self,fileName):
604 def OpenRecentFile(fileName=fileName):
605 self.io.open(editFile=fileName)
606 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000607
David Scherer7aced172000-08-15 01:13:23 +0000608 def saved_change_hook(self):
609 short = self.short_title()
610 long = self.long_title()
611 if short and long:
612 title = short + " - " + long
613 elif short:
614 title = short
615 elif long:
616 title = long
617 else:
618 title = "Untitled"
619 icon = short or long or title
620 if not self.get_saved():
621 title = "*%s*" % title
622 icon = "*%s" % icon
623 self.top.wm_title(title)
624 self.top.wm_iconname(icon)
625
626 def get_saved(self):
627 return self.undo.get_saved()
628
629 def set_saved(self, flag):
630 self.undo.set_saved(flag)
631
632 def reset_undo(self):
633 self.undo.reset_undo()
634
635 def short_title(self):
636 filename = self.io.filename
637 if filename:
638 filename = os.path.basename(filename)
639 return filename
640
641 def long_title(self):
642 return self.io.filename or ""
643
644 def center_insert_event(self, event):
645 self.center()
646
647 def center(self, mark="insert"):
648 text = self.text
649 top, bot = self.getwindowlines()
650 lineno = self.getlineno(mark)
651 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000652 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000653 text.yview(float(newtop))
654
655 def getwindowlines(self):
656 text = self.text
657 top = self.getlineno("@0,0")
658 bot = self.getlineno("@0,65535")
659 if top == bot and text.winfo_height() == 1:
660 # Geometry manager hasn't run yet
661 height = int(text['height'])
662 bot = top + height - 1
663 return top, bot
664
665 def getlineno(self, mark="insert"):
666 text = self.text
667 return int(float(text.index(mark)))
668
669 def close_event(self, event):
670 self.close()
671
672 def maybesave(self):
673 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000674 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000675 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000676 self.top.deiconify()
677 self.top.lower()
678 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000679 return self.io.maybesave()
680
681 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000682 reply = self.maybesave()
683 if reply != "cancel":
684 self._close()
685 return reply
686
687 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000688 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000689 if self.io.filename:
690 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000691 WindowList.unregister_callback(self.postwindowsmenu)
692 if self.close_hook:
693 self.close_hook()
694 self.flist = None
695 colorizing = 0
696 self.unload_extensions()
697 self.io.close(); self.io = None
698 self.undo = None # XXX
699 if self.color:
700 colorizing = self.color.colorizing
701 doh = colorizing and self.top
702 self.color.close(doh) # Cancel colorization
703 self.text = None
704 self.vars = None
705 self.per.close(); self.per = None
706 if not colorizing:
707 self.top.destroy()
708
709 def load_extensions(self):
710 self.extensions = {}
711 self.load_standard_extensions()
712
713 def unload_extensions(self):
714 for ins in self.extensions.values():
715 if hasattr(ins, "close"):
716 ins.close()
717 self.extensions = {}
718
719 def load_standard_extensions(self):
720 for name in self.get_standard_extension_names():
721 try:
722 self.load_extension(name)
723 except:
724 print "Failed to load extension", `name`
725 import traceback
726 traceback.print_exc()
727
728 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000729 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000730
731 def load_extension(self, name):
732 mod = __import__(name, globals(), locals(), [])
733 cls = getattr(mod, name)
734 ins = cls(self)
735 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000736 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000737 if keydefs:
738 self.apply_bindings(keydefs)
739 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000740 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000741 while methodname[:1] == '<':
742 methodname = methodname[1:]
743 while methodname[-1:] == '>':
744 methodname = methodname[:-1]
745 methodname = methodname + "_event"
746 if hasattr(ins, methodname):
747 self.text.bind(vevent, getattr(ins, methodname))
748 if hasattr(ins, "menudefs"):
749 self.fill_menus(ins.menudefs, keydefs)
750 return ins
751
752 def apply_bindings(self, keydefs=None):
753 if keydefs is None:
754 keydefs = self.Bindings.default_keydefs
755 text = self.text
756 text.keydefs = keydefs
757 for event, keylist in keydefs.items():
758 if keylist:
759 apply(text.event_add, (event,) + tuple(keylist))
760
761 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000762 """Add appropriate entries to the menus and submenus
763
764 Menus that are absent or None in self.menudict are ignored.
765 """
David Scherer7aced172000-08-15 01:13:23 +0000766 if defs is None:
767 defs = self.Bindings.menudefs
768 if keydefs is None:
769 keydefs = self.Bindings.default_keydefs
770 menudict = self.menudict
771 text = self.text
772 for mname, itemlist in defs:
773 menu = menudict.get(mname)
774 if not menu:
775 continue
776 for item in itemlist:
777 if not item:
778 menu.add_separator()
779 else:
780 label, event = item
781 checkbutton = (label[:1] == '!')
782 if checkbutton:
783 label = label[1:]
784 underline, label = prepstr(label)
785 accelerator = get_accelerator(keydefs, event)
786 def command(text=text, event=event):
787 text.event_generate(event)
788 if checkbutton:
789 var = self.getrawvar(event, BooleanVar)
790 menu.add_checkbutton(label=label, underline=underline,
791 command=command, accelerator=accelerator,
792 variable=var)
793 else:
794 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000795 command=command,
796 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000797
798 def getvar(self, name):
799 var = self.getrawvar(name)
800 if var:
801 return var.get()
802
803 def setvar(self, name, value, vartype=None):
804 var = self.getrawvar(name, vartype)
805 if var:
806 var.set(value)
807
808 def getrawvar(self, name, vartype=None):
809 var = self.vars.get(name)
810 if not var and vartype:
811 self.vars[name] = var = vartype(self.text)
812 return var
813
814 # Tk implementations of "virtual text methods" -- each platform
815 # reusing IDLE's support code needs to define these for its GUI's
816 # flavor of widget.
817
818 # Is character at text_index in a Python string? Return 0 for
819 # "guaranteed no", true for anything else. This info is expensive
820 # to compute ab initio, but is probably already known by the
821 # platform's colorizer.
822
823 def is_char_in_string(self, text_index):
824 if self.color:
825 # Return true iff colorizer hasn't (re)gotten this far
826 # yet, or the character is tagged as being in a string
827 return self.text.tag_prevrange("TODO", text_index) or \
828 "STRING" in self.text.tag_names(text_index)
829 else:
830 # The colorizer is missing: assume the worst
831 return 1
832
833 # If a selection is defined in the text widget, return (start,
834 # end) as Tkinter text indices, otherwise return (None, None)
835 def get_selection_indices(self):
836 try:
837 first = self.text.index("sel.first")
838 last = self.text.index("sel.last")
839 return first, last
840 except TclError:
841 return None, None
842
843 # Return the text widget's current view of what a tab stop means
844 # (equivalent width in spaces).
845
846 def get_tabwidth(self):
847 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
848 return int(current)
849
850 # Set the text widget's current view of what a tab stop means.
851
852 def set_tabwidth(self, newtabwidth):
853 text = self.text
854 if self.get_tabwidth() != newtabwidth:
855 pixels = text.tk.call("font", "measure", text["font"],
856 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000857 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000858 text.configure(tabs=pixels)
859
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000860### begin autoindent code ###
861
862 # usetabs true -> literal tab characters are used by indent and
863 # dedent cmds, possibly mixed with spaces if
864 # indentwidth is not a multiple of tabwidth
865 # false -> tab characters are converted to spaces by indent
866 # and dedent cmds, and ditto TAB keystrokes
867 # indentwidth is the number of characters per logical indent level.
868 # tabwidth is the display width of a literal tab character.
869 # CAUTION: telling Tk to use anything other than its default
870 # tab setting causes it to use an entirely different tabbing algorithm,
871 # treating tab stops as fixed distances from the left margin.
872 # Nobody expects this, so for now tabwidth should never be changed.
873 usetabs = 0
874 indentwidth = 4
875 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
876
877 # If context_use_ps1 is true, parsing searches back for a ps1 line;
878 # else searches for a popular (if, def, ...) Python stmt.
879 context_use_ps1 = 0
880
881 # When searching backwards for a reliable place to begin parsing,
882 # first start num_context_lines[0] lines back, then
883 # num_context_lines[1] lines back if that didn't work, and so on.
884 # The last value should be huge (larger than the # of lines in a
885 # conceivable file).
886 # Making the initial values larger slows things down more often.
887 num_context_lines = 50, 500, 5000000
888
889 def config(self, **options):
890 for key, value in options.items():
891 if key == 'usetabs':
892 self.usetabs = value
893 elif key == 'indentwidth':
894 self.indentwidth = value
895 elif key == 'tabwidth':
896 self.tabwidth = value
897 elif key == 'context_use_ps1':
898 self.context_use_ps1 = value
899 else:
900 raise KeyError, "bad option name: %s" % `key`
901
902 # If ispythonsource and guess are true, guess a good value for
903 # indentwidth based on file content (if possible), and if
904 # indentwidth != tabwidth set usetabs false.
905 # In any case, adjust the Text widget's view of what a tab
906 # character means.
907
908 def set_indentation_params(self, ispythonsource, guess=1):
909 if guess and ispythonsource:
910 i = self.guess_indent()
911 if 2 <= i <= 8:
912 self.indentwidth = i
913 if self.indentwidth != self.tabwidth:
914 self.usetabs = 0
915
916 self.set_tabwidth(self.tabwidth)
917
918 def smart_backspace_event(self, event):
919 text = self.text
920 first, last = self.get_selection_indices()
921 if first and last:
922 text.delete(first, last)
923 text.mark_set("insert", first)
924 return "break"
925 # Delete whitespace left, until hitting a real char or closest
926 # preceding virtual tab stop.
927 chars = text.get("insert linestart", "insert")
928 if chars == '':
929 if text.compare("insert", ">", "1.0"):
930 # easy: delete preceding newline
931 text.delete("insert-1c")
932 else:
933 text.bell() # at start of buffer
934 return "break"
935 if chars[-1] not in " \t":
936 # easy: delete preceding real char
937 text.delete("insert-1c")
938 return "break"
939 # Ick. It may require *inserting* spaces if we back up over a
940 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000941 tabwidth = self.tabwidth
942 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000943 assert have > 0
944 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000945 # Debug prompt is multilined....
946 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000947 ncharsdeleted = 0
948 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000949 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000950 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000951 chars = chars[:-1]
952 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000953 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000954 if have <= want or chars[-1] not in " \t":
955 break
956 text.undo_block_start()
957 text.delete("insert-%dc" % ncharsdeleted, "insert")
958 if have < want:
959 text.insert("insert", ' ' * (want - have))
960 text.undo_block_stop()
961 return "break"
962
963 def smart_indent_event(self, event):
964 # if intraline selection:
965 # delete it
966 # elif multiline selection:
967 # do indent-region & return
968 # indent one level
969 text = self.text
970 first, last = self.get_selection_indices()
971 text.undo_block_start()
972 try:
973 if first and last:
974 if index2line(first) != index2line(last):
975 return self.indent_region_event(event)
976 text.delete(first, last)
977 text.mark_set("insert", first)
978 prefix = text.get("insert linestart", "insert")
979 raw, effective = classifyws(prefix, self.tabwidth)
980 if raw == len(prefix):
981 # only whitespace to the left
982 self.reindent_to(effective + self.indentwidth)
983 else:
984 if self.usetabs:
985 pad = '\t'
986 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000987 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000988 n = self.indentwidth
989 pad = ' ' * (n - effective % n)
990 text.insert("insert", pad)
991 text.see("insert")
992 return "break"
993 finally:
994 text.undo_block_stop()
995
996 def newline_and_indent_event(self, event):
997 text = self.text
998 first, last = self.get_selection_indices()
999 text.undo_block_start()
1000 try:
1001 if first and last:
1002 text.delete(first, last)
1003 text.mark_set("insert", first)
1004 line = text.get("insert linestart", "insert")
1005 i, n = 0, len(line)
1006 while i < n and line[i] in " \t":
1007 i = i+1
1008 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001009 # the cursor is in or at leading indentation in a continuation
1010 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001011 text.insert("insert linestart", '\n')
1012 return "break"
1013 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001014 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001015 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001016 last_line_of_prompt = sys.ps1.split('\n')[-1]
1017 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001018 line = line[:-1]
1019 i = i+1
1020 if i:
1021 text.delete("insert - %d chars" % i, "insert")
1022 # strip whitespace after insert point
1023 while text.get("insert") in " \t":
1024 text.delete("insert")
1025 # start new line
1026 text.insert("insert", '\n')
1027
1028 # adjust indentation for continuations and block
1029 # open/close first need to find the last stmt
1030 lno = index2line(text.index('insert'))
1031 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1032 for context in self.num_context_lines:
1033 startat = max(lno - context, 1)
1034 startatindex = `startat` + ".0"
1035 rawtext = text.get(startatindex, "insert")
1036 y.set_str(rawtext)
1037 bod = y.find_good_parse_start(
1038 self.context_use_ps1,
1039 self._build_char_in_string_func(startatindex))
1040 if bod is not None or startat == 1:
1041 break
1042 y.set_lo(bod or 0)
1043 c = y.get_continuation_type()
1044 if c != PyParse.C_NONE:
1045 # The current stmt hasn't ended yet.
1046 if c == PyParse.C_STRING:
1047 # inside a string; just mimic the current indent
1048 text.insert("insert", indent)
1049 elif c == PyParse.C_BRACKET:
1050 # line up with the first (if any) element of the
1051 # last open bracket structure; else indent one
1052 # level beyond the indent of the line with the
1053 # last open bracket
1054 self.reindent_to(y.compute_bracket_indent())
1055 elif c == PyParse.C_BACKSLASH:
1056 # if more than one line in this stmt already, just
1057 # mimic the current indent; else if initial line
1058 # has a start on an assignment stmt, indent to
1059 # beyond leftmost =; else to beyond first chunk of
1060 # non-whitespace on initial line
1061 if y.get_num_lines_in_stmt() > 1:
1062 text.insert("insert", indent)
1063 else:
1064 self.reindent_to(y.compute_backslash_indent())
1065 else:
1066 assert 0, "bogus continuation type " + `c`
1067 return "break"
1068
1069 # This line starts a brand new stmt; indent relative to
1070 # indentation of initial line of closest preceding
1071 # interesting stmt.
1072 indent = y.get_base_indent_string()
1073 text.insert("insert", indent)
1074 if y.is_block_opener():
1075 self.smart_indent_event(event)
1076 elif indent and y.is_block_closer():
1077 self.smart_backspace_event(event)
1078 return "break"
1079 finally:
1080 text.see("insert")
1081 text.undo_block_stop()
1082
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001083 # Our editwin provides a is_char_in_string function that works
1084 # with a Tk text index, but PyParse only knows about offsets into
1085 # a string. This builds a function for PyParse that accepts an
1086 # offset.
1087
1088 def _build_char_in_string_func(self, startindex):
1089 def inner(offset, _startindex=startindex,
1090 _icis=self.is_char_in_string):
1091 return _icis(_startindex + "+%dc" % offset)
1092 return inner
1093
1094 def indent_region_event(self, event):
1095 head, tail, chars, lines = self.get_region()
1096 for pos in range(len(lines)):
1097 line = lines[pos]
1098 if line:
1099 raw, effective = classifyws(line, self.tabwidth)
1100 effective = effective + self.indentwidth
1101 lines[pos] = self._make_blanks(effective) + line[raw:]
1102 self.set_region(head, tail, chars, lines)
1103 return "break"
1104
1105 def dedent_region_event(self, event):
1106 head, tail, chars, lines = self.get_region()
1107 for pos in range(len(lines)):
1108 line = lines[pos]
1109 if line:
1110 raw, effective = classifyws(line, self.tabwidth)
1111 effective = max(effective - self.indentwidth, 0)
1112 lines[pos] = self._make_blanks(effective) + line[raw:]
1113 self.set_region(head, tail, chars, lines)
1114 return "break"
1115
1116 def comment_region_event(self, event):
1117 head, tail, chars, lines = self.get_region()
1118 for pos in range(len(lines) - 1):
1119 line = lines[pos]
1120 lines[pos] = '##' + line
1121 self.set_region(head, tail, chars, lines)
1122
1123 def uncomment_region_event(self, event):
1124 head, tail, chars, lines = self.get_region()
1125 for pos in range(len(lines)):
1126 line = lines[pos]
1127 if not line:
1128 continue
1129 if line[:2] == '##':
1130 line = line[2:]
1131 elif line[:1] == '#':
1132 line = line[1:]
1133 lines[pos] = line
1134 self.set_region(head, tail, chars, lines)
1135
1136 def tabify_region_event(self, event):
1137 head, tail, chars, lines = self.get_region()
1138 tabwidth = self._asktabwidth()
1139 for pos in range(len(lines)):
1140 line = lines[pos]
1141 if line:
1142 raw, effective = classifyws(line, tabwidth)
1143 ntabs, nspaces = divmod(effective, tabwidth)
1144 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1145 self.set_region(head, tail, chars, lines)
1146
1147 def untabify_region_event(self, event):
1148 head, tail, chars, lines = self.get_region()
1149 tabwidth = self._asktabwidth()
1150 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001151 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001152 self.set_region(head, tail, chars, lines)
1153
1154 def toggle_tabs_event(self, event):
1155 if self.askyesno(
1156 "Toggle tabs",
1157 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1158 parent=self.text):
1159 self.usetabs = not self.usetabs
1160 return "break"
1161
1162 # XXX this isn't bound to anything -- see class tabwidth comments
1163 def change_tabwidth_event(self, event):
1164 new = self._asktabwidth()
1165 if new != self.tabwidth:
1166 self.tabwidth = new
1167 self.set_indentation_params(0, guess=0)
1168 return "break"
1169
1170 def change_indentwidth_event(self, event):
1171 new = self.askinteger(
1172 "Indent width",
1173 "New indent width (2-16)",
1174 parent=self.text,
1175 initialvalue=self.indentwidth,
1176 minvalue=2,
1177 maxvalue=16)
1178 if new and new != self.indentwidth:
1179 self.indentwidth = new
1180 return "break"
1181
1182 def get_region(self):
1183 text = self.text
1184 first, last = self.get_selection_indices()
1185 if first and last:
1186 head = text.index(first + " linestart")
1187 tail = text.index(last + "-1c lineend +1c")
1188 else:
1189 head = text.index("insert linestart")
1190 tail = text.index("insert lineend +1c")
1191 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001192 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001193 return head, tail, chars, lines
1194
1195 def set_region(self, head, tail, chars, lines):
1196 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001197 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001198 if newchars == chars:
1199 text.bell()
1200 return
1201 text.tag_remove("sel", "1.0", "end")
1202 text.mark_set("insert", head)
1203 text.undo_block_start()
1204 text.delete(head, tail)
1205 text.insert(head, newchars)
1206 text.undo_block_stop()
1207 text.tag_add("sel", head, "insert")
1208
1209 # Make string that displays as n leading blanks.
1210
1211 def _make_blanks(self, n):
1212 if self.usetabs:
1213 ntabs, nspaces = divmod(n, self.tabwidth)
1214 return '\t' * ntabs + ' ' * nspaces
1215 else:
1216 return ' ' * n
1217
1218 # Delete from beginning of line to insert point, then reinsert
1219 # column logical (meaning use tabs if appropriate) spaces.
1220
1221 def reindent_to(self, column):
1222 text = self.text
1223 text.undo_block_start()
1224 if text.compare("insert linestart", "!=", "insert"):
1225 text.delete("insert linestart", "insert")
1226 if column:
1227 text.insert("insert", self._make_blanks(column))
1228 text.undo_block_stop()
1229
1230 def _asktabwidth(self):
1231 return self.askinteger(
1232 "Tab width",
1233 "Spaces per tab? (2-16)",
1234 parent=self.text,
1235 initialvalue=self.indentwidth,
1236 minvalue=2,
1237 maxvalue=16) or self.tabwidth
1238
1239 # Guess indentwidth from text content.
1240 # Return guessed indentwidth. This should not be believed unless
1241 # it's in a reasonable range (e.g., it will be 0 if no indented
1242 # blocks are found).
1243
1244 def guess_indent(self):
1245 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1246 if opener and indented:
1247 raw, indentsmall = classifyws(opener, self.tabwidth)
1248 raw, indentlarge = classifyws(indented, self.tabwidth)
1249 else:
1250 indentsmall = indentlarge = 0
1251 return indentlarge - indentsmall
1252
1253# "line.col" -> line, as an int
1254def index2line(index):
1255 return int(float(index))
1256
1257# Look at the leading whitespace in s.
1258# Return pair (# of leading ws characters,
1259# effective # of leading blanks after expanding
1260# tabs to width tabwidth)
1261
1262def classifyws(s, tabwidth):
1263 raw = effective = 0
1264 for ch in s:
1265 if ch == ' ':
1266 raw = raw + 1
1267 effective = effective + 1
1268 elif ch == '\t':
1269 raw = raw + 1
1270 effective = (effective // tabwidth + 1) * tabwidth
1271 else:
1272 break
1273 return raw, effective
1274
1275import tokenize
1276_tokenize = tokenize
1277del tokenize
1278
1279class IndentSearcher:
1280
1281 # .run() chews over the Text widget, looking for a block opener
1282 # and the stmt following it. Returns a pair,
1283 # (line containing block opener, line containing stmt)
1284 # Either or both may be None.
1285
1286 def __init__(self, text, tabwidth):
1287 self.text = text
1288 self.tabwidth = tabwidth
1289 self.i = self.finished = 0
1290 self.blkopenline = self.indentedline = None
1291
1292 def readline(self):
1293 if self.finished:
1294 return ""
1295 i = self.i = self.i + 1
1296 mark = `i` + ".0"
1297 if self.text.compare(mark, ">=", "end"):
1298 return ""
1299 return self.text.get(mark, mark + " lineend+1c")
1300
1301 def tokeneater(self, type, token, start, end, line,
1302 INDENT=_tokenize.INDENT,
1303 NAME=_tokenize.NAME,
1304 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1305 if self.finished:
1306 pass
1307 elif type == NAME and token in OPENERS:
1308 self.blkopenline = line
1309 elif type == INDENT and self.blkopenline:
1310 self.indentedline = line
1311 self.finished = 1
1312
1313 def run(self):
1314 save_tabsize = _tokenize.tabsize
1315 _tokenize.tabsize = self.tabwidth
1316 try:
1317 try:
1318 _tokenize.tokenize(self.readline, self.tokeneater)
1319 except _tokenize.TokenError:
1320 # since we cut off the tokenizer early, we can trigger
1321 # spurious errors
1322 pass
1323 finally:
1324 _tokenize.tabsize = save_tabsize
1325 return self.blkopenline, self.indentedline
1326
1327### end autoindent code ###
1328
David Scherer7aced172000-08-15 01:13:23 +00001329def prepstr(s):
1330 # Helper to extract the underscore from a string, e.g.
1331 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001332 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001333 if i >= 0:
1334 s = s[:i] + s[i+1:]
1335 return i, s
1336
1337
1338keynames = {
1339 'bracketleft': '[',
1340 'bracketright': ']',
1341 'slash': '/',
1342}
1343
1344def get_accelerator(keydefs, event):
1345 keylist = keydefs.get(event)
1346 if not keylist:
1347 return ""
1348 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001349 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001350 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1351 s = re.sub("Key-", "", s)
1352 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1353 s = re.sub("Control-", "Ctrl-", s)
1354 s = re.sub("-", "+", s)
1355 s = re.sub("><", " ", s)
1356 s = re.sub("<", "", s)
1357 s = re.sub(">", "", s)
1358 return s
1359
1360
1361def fixwordbreaks(root):
1362 # Make sure that Tk's double-click and next/previous word
1363 # operations use our definition of a word (i.e. an identifier)
1364 tk = root.tk
1365 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1366 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1367 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1368
1369
1370def test():
1371 root = Tk()
1372 fixwordbreaks(root)
1373 root.withdraw()
1374 if sys.argv[1:]:
1375 filename = sys.argv[1]
1376 else:
1377 filename = None
1378 edit = EditorWindow(root=root, filename=filename)
1379 edit.set_close_hook(root.quit)
1380 root.mainloop()
1381 root.destroy()
1382
1383if __name__ == '__main__':
1384 test()