blob: 47256dc6280cea982f2236edeabb3d14c1968d59 [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. Kaiser1061e722003-01-04 01:43:53 +000063 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000064 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000065 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'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000075 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +000076 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)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +000083 text.bind("<<cut>>", self.cut)
84 text.bind("<<copy>>", self.copy)
85 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +000086 text.bind("<<center-insert>>", self.center_insert_event)
87 text.bind("<<help>>", self.help_dialog)
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"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000216 ("options", "_Options"),
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
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000280 def view_readme(self, event=None):
281 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000282 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000283
David Scherer7aced172000-08-15 01:13:23 +0000284 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000285 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000286 textView.TextViewer(self.top,'Help',fn)
287
David Scherer7aced172000-08-15 01:13:23 +0000288 help_url = "http://www.python.org/doc/current/"
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000289 if sys.platform[:3] == "win":
290 fn = os.path.dirname(__file__)
Steven M. Gava931625d2002-04-22 00:38:26 +0000291 fn = os.path.join(fn, os.pardir, os.pardir, "pythlp.chm")
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000292 fn = os.path.normpath(fn)
293 if os.path.isfile(fn):
294 help_url = fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000295 else:
296 fn = os.path.dirname(__file__)
297 fn = os.path.join(fn, os.pardir, os.pardir, "Doc", "index.html")
298 fn = os.path.normpath(fn)
299 if os.path.isfile(fn):
300 help_url = fn
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000301 del fn
Steven M. Gava931625d2002-04-22 00:38:26 +0000302 def python_docs(self, event=None):
303 os.startfile(self.help_url)
304 else:
305 def python_docs(self, event=None):
306 self.display_docs(self.help_url)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000307
308 def display_docs(self, url):
309 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000310
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000311 def cut(self,event):
312 self.text.event_generate("<<Cut>>")
313 return "break"
314
315 def copy(self,event):
316 self.text.event_generate("<<Copy>>")
317 return "break"
318
319 def paste(self,event):
320 self.text.event_generate("<<Paste>>")
321 return "break"
322
David Scherer7aced172000-08-15 01:13:23 +0000323 def select_all(self, event=None):
324 self.text.tag_add("sel", "1.0", "end-1c")
325 self.text.mark_set("insert", "1.0")
326 self.text.see("insert")
327 return "break"
328
329 def remove_selection(self, event=None):
330 self.text.tag_remove("sel", "1.0", "end")
331 self.text.see("insert")
332
Steven M. Gavac5976402002-01-04 03:06:08 +0000333 def find_event(self, event):
334 SearchDialog.find(self.text)
335 return "break"
336
337 def find_again_event(self, event):
338 SearchDialog.find_again(self.text)
339 return "break"
340
341 def find_selection_event(self, event):
342 SearchDialog.find_selection(self.text)
343 return "break"
344
345 def find_in_files_event(self, event):
346 GrepDialog.grep(self.text, self.io, self.flist)
347 return "break"
348
349 def replace_event(self, event):
350 ReplaceDialog.replace(self.text)
351 return "break"
352
353 def goto_line_event(self, event):
354 text = self.text
355 lineno = tkSimpleDialog.askinteger("Goto",
356 "Go to line number:",parent=text)
357 if lineno is None:
358 return "break"
359 if lineno <= 0:
360 text.bell()
361 return "break"
362 text.mark_set("insert", "%d.0" % lineno)
363 text.see("insert")
364
David Scherer7aced172000-08-15 01:13:23 +0000365 def open_module(self, event=None):
366 # XXX Shouldn't this be in IOBinding or in FileList?
367 try:
368 name = self.text.get("sel.first", "sel.last")
369 except TclError:
370 name = ""
371 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000372 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000373 if not name:
374 name = tkSimpleDialog.askstring("Module",
375 "Enter the name of a Python module\n"
376 "to search on sys.path and open:",
377 parent=self.text)
378 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000379 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000380 if not name:
381 return
David Scherer7aced172000-08-15 01:13:23 +0000382 # XXX Ought to insert current file's directory in front of path
383 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000384 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000385 except (NameError, ImportError), msg:
386 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
387 return
388 if type != imp.PY_SOURCE:
389 tkMessageBox.showerror("Unsupported type",
390 "%s is not a source module" % name, parent=self.text)
391 return
392 if f:
393 f.close()
394 if self.flist:
395 self.flist.open(file)
396 else:
397 self.io.loadfile(file)
398
399 def open_class_browser(self, event=None):
400 filename = self.io.filename
401 if not filename:
402 tkMessageBox.showerror(
403 "No filename",
404 "This buffer has no associated filename",
405 master=self.text)
406 self.text.focus_set()
407 return None
408 head, tail = os.path.split(filename)
409 base, ext = os.path.splitext(tail)
410 import ClassBrowser
411 ClassBrowser.ClassBrowser(self.flist, base, [head])
412
413 def open_path_browser(self, event=None):
414 import PathBrowser
415 PathBrowser.PathBrowser(self.flist)
416
417 def gotoline(self, lineno):
418 if lineno is not None and lineno > 0:
419 self.text.mark_set("insert", "%d.0" % lineno)
420 self.text.tag_remove("sel", "1.0", "end")
421 self.text.tag_add("sel", "insert", "insert +1l")
422 self.center()
423
424 def ispythonsource(self, filename):
425 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000426 return True
David Scherer7aced172000-08-15 01:13:23 +0000427 base, ext = os.path.splitext(os.path.basename(filename))
428 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000429 return True
David Scherer7aced172000-08-15 01:13:23 +0000430 try:
431 f = open(filename)
432 line = f.readline()
433 f.close()
434 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000435 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000436 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000437
438 def close_hook(self):
439 if self.flist:
440 self.flist.close_edit(self)
441
442 def set_close_hook(self, close_hook):
443 self.close_hook = close_hook
444
445 def filename_change_hook(self):
446 if self.flist:
447 self.flist.filename_changed_edit(self)
448 self.saved_change_hook()
449 if self.ispythonsource(self.io.filename):
450 self.addcolorizer()
451 else:
452 self.rmcolorizer()
453
454 def addcolorizer(self):
455 if self.color:
456 return
David Scherer7aced172000-08-15 01:13:23 +0000457 self.per.removefilter(self.undo)
458 self.color = self.ColorDelegator()
459 self.per.insertfilter(self.color)
460 self.per.insertfilter(self.undo)
461
462 def rmcolorizer(self):
463 if not self.color:
464 return
David Scherer7aced172000-08-15 01:13:23 +0000465 self.per.removefilter(self.undo)
466 self.per.removefilter(self.color)
467 self.color = None
468 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000469
Steven M. Gavab77d3432002-03-02 07:16:21 +0000470 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000471 "Update the colour theme if it is changed"
472 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000473 if self.color:
474 self.color = self.ColorDelegator()
475 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000476
Steven M. Gavab1585412002-03-12 00:21:56 +0000477 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000478 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000479 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000480 fontWeight='normal'
481 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
482 fontWeight='bold'
483 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
484 idleConf.GetOption('main','EditorWindow','font-size'),
485 fontWeight))
486
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000487 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000488 "Update the keybindings if they are changed"
489 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000490 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
491 keydefs = self.Bindings.default_keydefs
492 for event, keylist in keydefs.items():
493 self.text.event_delete(event)
494 self.apply_bindings()
495 #update menu accelerators
496 menuEventDict={}
497 for menu in self.Bindings.menudefs:
498 menuEventDict[menu[0]]={}
499 for item in menu[1]:
500 if item:
501 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
502 for menubarItem in self.menudict.keys():
503 menu=self.menudict[menubarItem]
504 end=menu.index(END)+1
505 for index in range(0,end):
506 if menu.type(index)=='command':
507 accel=menu.entrycget(index,'accelerator')
508 if accel:
509 itemName=menu.entrycget(index,'label')
510 event=''
511 if menuEventDict.has_key(menubarItem):
512 if menuEventDict[menubarItem].has_key(itemName):
513 event=menuEventDict[menubarItem][itemName]
514 if event:
515 #print 'accel was:',accel
516 accel=get_accelerator(keydefs, event)
517 menu.entryconfig(index,accelerator=accel)
518 #print 'accel now:',accel,'\n'
519
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000520 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000521 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000522 menuList=idleConf.GetAllExtraHelpSourcesList()
523 helpMenu=self.menudict['help']
524 cascadeIndex=helpMenu.index(END)-1
525 if menuList:
526 if not hasattr(self,'menuExtraHelp'):
527 self.menuExtraHelp=Menu(self.menubar)
528 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
529 underline=1,menu=self.menuExtraHelp)
530 self.menuExtraHelp.delete(1,END)
531 for menuItem in menuList:
532 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000533 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000534 else: #no extra help items
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000535 if hasattr(self,'menuExtraHelp'):
536 helpMenu.delete(cascadeIndex-1)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000537 del(self.menuExtraHelp)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000538
Steven M. Gava1d46e402002-03-27 08:40:46 +0000539 def __DisplayExtraHelpCallback(self,helpFile):
540 def DisplayExtraHelp(helpFile=helpFile):
541 self.display_docs(helpFile)
542 return DisplayExtraHelp
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000543
Steven M. Gava1d46e402002-03-27 08:40:46 +0000544 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000545 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000546 rfList=[]
547 if os.path.exists(self.recentFilesPath):
548 RFfile=open(self.recentFilesPath,'r')
549 try:
550 rfList=RFfile.readlines()
551 finally:
552 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000553 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000554 newFile=os.path.abspath(newFile)+'\n'
555 if newFile in rfList:
556 rfList.remove(newFile)
557 rfList.insert(0,newFile)
558 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000559 #print self.flist.inversedict
560 #print self.top.instanceDict
561 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000562 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000563 if rfList:
564 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000565 menu = instance.menuRecentFiles
566 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000567 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000568 for file in rfList:
569 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000570 callback = instance.__RecentFileCallback(fileName)
571 if i > ullen: # don't underline menuitems
572 ul=None
573 menu.add_command(label=ullist[i] + " " + fileName,
574 command=callback,
575 underline=ul)
576 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577
Steven M. Gava1d46e402002-03-27 08:40:46 +0000578 def __CleanRecentFiles(self,rfList):
579 origRfList=rfList[:]
580 count=0
581 nonFiles=[]
582 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000583 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000584 nonFiles.append(count)
585 count=count+1
586 if nonFiles:
587 nonFiles.reverse()
588 for index in nonFiles:
589 del(rfList[index])
590 if len(rfList)>19:
591 rfList=rfList[0:19]
592 #if rfList != origRfList:
593 RFfile=open(self.recentFilesPath,'w')
594 try:
595 RFfile.writelines(rfList)
596 finally:
597 RFfile.close()
598 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000599
Steven M. Gava1d46e402002-03-27 08:40:46 +0000600 def __RecentFileCallback(self,fileName):
601 def OpenRecentFile(fileName=fileName):
602 self.io.open(editFile=fileName)
603 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000604
David Scherer7aced172000-08-15 01:13:23 +0000605 def saved_change_hook(self):
606 short = self.short_title()
607 long = self.long_title()
608 if short and long:
609 title = short + " - " + long
610 elif short:
611 title = short
612 elif long:
613 title = long
614 else:
615 title = "Untitled"
616 icon = short or long or title
617 if not self.get_saved():
618 title = "*%s*" % title
619 icon = "*%s" % icon
620 self.top.wm_title(title)
621 self.top.wm_iconname(icon)
622
623 def get_saved(self):
624 return self.undo.get_saved()
625
626 def set_saved(self, flag):
627 self.undo.set_saved(flag)
628
629 def reset_undo(self):
630 self.undo.reset_undo()
631
632 def short_title(self):
633 filename = self.io.filename
634 if filename:
635 filename = os.path.basename(filename)
636 return filename
637
638 def long_title(self):
639 return self.io.filename or ""
640
641 def center_insert_event(self, event):
642 self.center()
643
644 def center(self, mark="insert"):
645 text = self.text
646 top, bot = self.getwindowlines()
647 lineno = self.getlineno(mark)
648 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000649 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000650 text.yview(float(newtop))
651
652 def getwindowlines(self):
653 text = self.text
654 top = self.getlineno("@0,0")
655 bot = self.getlineno("@0,65535")
656 if top == bot and text.winfo_height() == 1:
657 # Geometry manager hasn't run yet
658 height = int(text['height'])
659 bot = top + height - 1
660 return top, bot
661
662 def getlineno(self, mark="insert"):
663 text = self.text
664 return int(float(text.index(mark)))
665
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000666 def get_geometry(self):
667 "Return (width, height, x, y)"
668 geom = self.top.wm_geometry()
669 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
670 tuple = (map(int, m.groups()))
671 return tuple
672
David Scherer7aced172000-08-15 01:13:23 +0000673 def close_event(self, event):
674 self.close()
675
676 def maybesave(self):
677 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000678 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000679 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000680 self.top.deiconify()
681 self.top.lower()
682 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000683 return self.io.maybesave()
684
685 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000686 reply = self.maybesave()
687 if reply != "cancel":
688 self._close()
689 return reply
690
691 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000692 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000693 if self.io.filename:
694 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000695 WindowList.unregister_callback(self.postwindowsmenu)
696 if self.close_hook:
697 self.close_hook()
698 self.flist = None
699 colorizing = 0
700 self.unload_extensions()
701 self.io.close(); self.io = None
702 self.undo = None # XXX
703 if self.color:
704 colorizing = self.color.colorizing
705 doh = colorizing and self.top
706 self.color.close(doh) # Cancel colorization
707 self.text = None
708 self.vars = None
709 self.per.close(); self.per = None
710 if not colorizing:
711 self.top.destroy()
712
713 def load_extensions(self):
714 self.extensions = {}
715 self.load_standard_extensions()
716
717 def unload_extensions(self):
718 for ins in self.extensions.values():
719 if hasattr(ins, "close"):
720 ins.close()
721 self.extensions = {}
722
723 def load_standard_extensions(self):
724 for name in self.get_standard_extension_names():
725 try:
726 self.load_extension(name)
727 except:
728 print "Failed to load extension", `name`
729 import traceback
730 traceback.print_exc()
731
732 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000733 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000734
735 def load_extension(self, name):
736 mod = __import__(name, globals(), locals(), [])
737 cls = getattr(mod, name)
738 ins = cls(self)
739 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000740 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000741 if keydefs:
742 self.apply_bindings(keydefs)
743 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000744 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000745 while methodname[:1] == '<':
746 methodname = methodname[1:]
747 while methodname[-1:] == '>':
748 methodname = methodname[:-1]
749 methodname = methodname + "_event"
750 if hasattr(ins, methodname):
751 self.text.bind(vevent, getattr(ins, methodname))
752 if hasattr(ins, "menudefs"):
753 self.fill_menus(ins.menudefs, keydefs)
754 return ins
755
756 def apply_bindings(self, keydefs=None):
757 if keydefs is None:
758 keydefs = self.Bindings.default_keydefs
759 text = self.text
760 text.keydefs = keydefs
761 for event, keylist in keydefs.items():
762 if keylist:
763 apply(text.event_add, (event,) + tuple(keylist))
764
765 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000766 """Add appropriate entries to the menus and submenus
767
768 Menus that are absent or None in self.menudict are ignored.
769 """
David Scherer7aced172000-08-15 01:13:23 +0000770 if defs is None:
771 defs = self.Bindings.menudefs
772 if keydefs is None:
773 keydefs = self.Bindings.default_keydefs
774 menudict = self.menudict
775 text = self.text
776 for mname, itemlist in defs:
777 menu = menudict.get(mname)
778 if not menu:
779 continue
780 for item in itemlist:
781 if not item:
782 menu.add_separator()
783 else:
784 label, event = item
785 checkbutton = (label[:1] == '!')
786 if checkbutton:
787 label = label[1:]
788 underline, label = prepstr(label)
789 accelerator = get_accelerator(keydefs, event)
790 def command(text=text, event=event):
791 text.event_generate(event)
792 if checkbutton:
793 var = self.getrawvar(event, BooleanVar)
794 menu.add_checkbutton(label=label, underline=underline,
795 command=command, accelerator=accelerator,
796 variable=var)
797 else:
798 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000799 command=command,
800 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000801
802 def getvar(self, name):
803 var = self.getrawvar(name)
804 if var:
805 return var.get()
806
807 def setvar(self, name, value, vartype=None):
808 var = self.getrawvar(name, vartype)
809 if var:
810 var.set(value)
811
812 def getrawvar(self, name, vartype=None):
813 var = self.vars.get(name)
814 if not var and vartype:
815 self.vars[name] = var = vartype(self.text)
816 return var
817
818 # Tk implementations of "virtual text methods" -- each platform
819 # reusing IDLE's support code needs to define these for its GUI's
820 # flavor of widget.
821
822 # Is character at text_index in a Python string? Return 0 for
823 # "guaranteed no", true for anything else. This info is expensive
824 # to compute ab initio, but is probably already known by the
825 # platform's colorizer.
826
827 def is_char_in_string(self, text_index):
828 if self.color:
829 # Return true iff colorizer hasn't (re)gotten this far
830 # yet, or the character is tagged as being in a string
831 return self.text.tag_prevrange("TODO", text_index) or \
832 "STRING" in self.text.tag_names(text_index)
833 else:
834 # The colorizer is missing: assume the worst
835 return 1
836
837 # If a selection is defined in the text widget, return (start,
838 # end) as Tkinter text indices, otherwise return (None, None)
839 def get_selection_indices(self):
840 try:
841 first = self.text.index("sel.first")
842 last = self.text.index("sel.last")
843 return first, last
844 except TclError:
845 return None, None
846
847 # Return the text widget's current view of what a tab stop means
848 # (equivalent width in spaces).
849
850 def get_tabwidth(self):
851 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
852 return int(current)
853
854 # Set the text widget's current view of what a tab stop means.
855
856 def set_tabwidth(self, newtabwidth):
857 text = self.text
858 if self.get_tabwidth() != newtabwidth:
859 pixels = text.tk.call("font", "measure", text["font"],
860 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000861 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000862 text.configure(tabs=pixels)
863
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000864### begin autoindent code ###
865
866 # usetabs true -> literal tab characters are used by indent and
867 # dedent cmds, possibly mixed with spaces if
868 # indentwidth is not a multiple of tabwidth
869 # false -> tab characters are converted to spaces by indent
870 # and dedent cmds, and ditto TAB keystrokes
871 # indentwidth is the number of characters per logical indent level.
872 # tabwidth is the display width of a literal tab character.
873 # CAUTION: telling Tk to use anything other than its default
874 # tab setting causes it to use an entirely different tabbing algorithm,
875 # treating tab stops as fixed distances from the left margin.
876 # Nobody expects this, so for now tabwidth should never be changed.
877 usetabs = 0
878 indentwidth = 4
879 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
880
881 # If context_use_ps1 is true, parsing searches back for a ps1 line;
882 # else searches for a popular (if, def, ...) Python stmt.
883 context_use_ps1 = 0
884
885 # When searching backwards for a reliable place to begin parsing,
886 # first start num_context_lines[0] lines back, then
887 # num_context_lines[1] lines back if that didn't work, and so on.
888 # The last value should be huge (larger than the # of lines in a
889 # conceivable file).
890 # Making the initial values larger slows things down more often.
891 num_context_lines = 50, 500, 5000000
892
893 def config(self, **options):
894 for key, value in options.items():
895 if key == 'usetabs':
896 self.usetabs = value
897 elif key == 'indentwidth':
898 self.indentwidth = value
899 elif key == 'tabwidth':
900 self.tabwidth = value
901 elif key == 'context_use_ps1':
902 self.context_use_ps1 = value
903 else:
904 raise KeyError, "bad option name: %s" % `key`
905
906 # If ispythonsource and guess are true, guess a good value for
907 # indentwidth based on file content (if possible), and if
908 # indentwidth != tabwidth set usetabs false.
909 # In any case, adjust the Text widget's view of what a tab
910 # character means.
911
912 def set_indentation_params(self, ispythonsource, guess=1):
913 if guess and ispythonsource:
914 i = self.guess_indent()
915 if 2 <= i <= 8:
916 self.indentwidth = i
917 if self.indentwidth != self.tabwidth:
918 self.usetabs = 0
919
920 self.set_tabwidth(self.tabwidth)
921
922 def smart_backspace_event(self, event):
923 text = self.text
924 first, last = self.get_selection_indices()
925 if first and last:
926 text.delete(first, last)
927 text.mark_set("insert", first)
928 return "break"
929 # Delete whitespace left, until hitting a real char or closest
930 # preceding virtual tab stop.
931 chars = text.get("insert linestart", "insert")
932 if chars == '':
933 if text.compare("insert", ">", "1.0"):
934 # easy: delete preceding newline
935 text.delete("insert-1c")
936 else:
937 text.bell() # at start of buffer
938 return "break"
939 if chars[-1] not in " \t":
940 # easy: delete preceding real char
941 text.delete("insert-1c")
942 return "break"
943 # Ick. It may require *inserting* spaces if we back up over a
944 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000945 tabwidth = self.tabwidth
946 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000947 assert have > 0
948 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000949 # Debug prompt is multilined....
950 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000951 ncharsdeleted = 0
952 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000953 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000954 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000955 chars = chars[:-1]
956 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000957 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000958 if have <= want or chars[-1] not in " \t":
959 break
960 text.undo_block_start()
961 text.delete("insert-%dc" % ncharsdeleted, "insert")
962 if have < want:
963 text.insert("insert", ' ' * (want - have))
964 text.undo_block_stop()
965 return "break"
966
967 def smart_indent_event(self, event):
968 # if intraline selection:
969 # delete it
970 # elif multiline selection:
971 # do indent-region & return
972 # indent one level
973 text = self.text
974 first, last = self.get_selection_indices()
975 text.undo_block_start()
976 try:
977 if first and last:
978 if index2line(first) != index2line(last):
979 return self.indent_region_event(event)
980 text.delete(first, last)
981 text.mark_set("insert", first)
982 prefix = text.get("insert linestart", "insert")
983 raw, effective = classifyws(prefix, self.tabwidth)
984 if raw == len(prefix):
985 # only whitespace to the left
986 self.reindent_to(effective + self.indentwidth)
987 else:
988 if self.usetabs:
989 pad = '\t'
990 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000991 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000992 n = self.indentwidth
993 pad = ' ' * (n - effective % n)
994 text.insert("insert", pad)
995 text.see("insert")
996 return "break"
997 finally:
998 text.undo_block_stop()
999
1000 def newline_and_indent_event(self, event):
1001 text = self.text
1002 first, last = self.get_selection_indices()
1003 text.undo_block_start()
1004 try:
1005 if first and last:
1006 text.delete(first, last)
1007 text.mark_set("insert", first)
1008 line = text.get("insert linestart", "insert")
1009 i, n = 0, len(line)
1010 while i < n and line[i] in " \t":
1011 i = i+1
1012 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001013 # the cursor is in or at leading indentation in a continuation
1014 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001015 text.insert("insert linestart", '\n')
1016 return "break"
1017 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001018 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001019 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001020 last_line_of_prompt = sys.ps1.split('\n')[-1]
1021 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001022 line = line[:-1]
1023 i = i+1
1024 if i:
1025 text.delete("insert - %d chars" % i, "insert")
1026 # strip whitespace after insert point
1027 while text.get("insert") in " \t":
1028 text.delete("insert")
1029 # start new line
1030 text.insert("insert", '\n')
1031
1032 # adjust indentation for continuations and block
1033 # open/close first need to find the last stmt
1034 lno = index2line(text.index('insert'))
1035 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1036 for context in self.num_context_lines:
1037 startat = max(lno - context, 1)
1038 startatindex = `startat` + ".0"
1039 rawtext = text.get(startatindex, "insert")
1040 y.set_str(rawtext)
1041 bod = y.find_good_parse_start(
1042 self.context_use_ps1,
1043 self._build_char_in_string_func(startatindex))
1044 if bod is not None or startat == 1:
1045 break
1046 y.set_lo(bod or 0)
1047 c = y.get_continuation_type()
1048 if c != PyParse.C_NONE:
1049 # The current stmt hasn't ended yet.
1050 if c == PyParse.C_STRING:
1051 # inside a string; just mimic the current indent
1052 text.insert("insert", indent)
1053 elif c == PyParse.C_BRACKET:
1054 # line up with the first (if any) element of the
1055 # last open bracket structure; else indent one
1056 # level beyond the indent of the line with the
1057 # last open bracket
1058 self.reindent_to(y.compute_bracket_indent())
1059 elif c == PyParse.C_BACKSLASH:
1060 # if more than one line in this stmt already, just
1061 # mimic the current indent; else if initial line
1062 # has a start on an assignment stmt, indent to
1063 # beyond leftmost =; else to beyond first chunk of
1064 # non-whitespace on initial line
1065 if y.get_num_lines_in_stmt() > 1:
1066 text.insert("insert", indent)
1067 else:
1068 self.reindent_to(y.compute_backslash_indent())
1069 else:
1070 assert 0, "bogus continuation type " + `c`
1071 return "break"
1072
1073 # This line starts a brand new stmt; indent relative to
1074 # indentation of initial line of closest preceding
1075 # interesting stmt.
1076 indent = y.get_base_indent_string()
1077 text.insert("insert", indent)
1078 if y.is_block_opener():
1079 self.smart_indent_event(event)
1080 elif indent and y.is_block_closer():
1081 self.smart_backspace_event(event)
1082 return "break"
1083 finally:
1084 text.see("insert")
1085 text.undo_block_stop()
1086
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001087 # Our editwin provides a is_char_in_string function that works
1088 # with a Tk text index, but PyParse only knows about offsets into
1089 # a string. This builds a function for PyParse that accepts an
1090 # offset.
1091
1092 def _build_char_in_string_func(self, startindex):
1093 def inner(offset, _startindex=startindex,
1094 _icis=self.is_char_in_string):
1095 return _icis(_startindex + "+%dc" % offset)
1096 return inner
1097
1098 def indent_region_event(self, event):
1099 head, tail, chars, lines = self.get_region()
1100 for pos in range(len(lines)):
1101 line = lines[pos]
1102 if line:
1103 raw, effective = classifyws(line, self.tabwidth)
1104 effective = effective + self.indentwidth
1105 lines[pos] = self._make_blanks(effective) + line[raw:]
1106 self.set_region(head, tail, chars, lines)
1107 return "break"
1108
1109 def dedent_region_event(self, event):
1110 head, tail, chars, lines = self.get_region()
1111 for pos in range(len(lines)):
1112 line = lines[pos]
1113 if line:
1114 raw, effective = classifyws(line, self.tabwidth)
1115 effective = max(effective - self.indentwidth, 0)
1116 lines[pos] = self._make_blanks(effective) + line[raw:]
1117 self.set_region(head, tail, chars, lines)
1118 return "break"
1119
1120 def comment_region_event(self, event):
1121 head, tail, chars, lines = self.get_region()
1122 for pos in range(len(lines) - 1):
1123 line = lines[pos]
1124 lines[pos] = '##' + line
1125 self.set_region(head, tail, chars, lines)
1126
1127 def uncomment_region_event(self, event):
1128 head, tail, chars, lines = self.get_region()
1129 for pos in range(len(lines)):
1130 line = lines[pos]
1131 if not line:
1132 continue
1133 if line[:2] == '##':
1134 line = line[2:]
1135 elif line[:1] == '#':
1136 line = line[1:]
1137 lines[pos] = line
1138 self.set_region(head, tail, chars, lines)
1139
1140 def tabify_region_event(self, event):
1141 head, tail, chars, lines = self.get_region()
1142 tabwidth = self._asktabwidth()
1143 for pos in range(len(lines)):
1144 line = lines[pos]
1145 if line:
1146 raw, effective = classifyws(line, tabwidth)
1147 ntabs, nspaces = divmod(effective, tabwidth)
1148 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1149 self.set_region(head, tail, chars, lines)
1150
1151 def untabify_region_event(self, event):
1152 head, tail, chars, lines = self.get_region()
1153 tabwidth = self._asktabwidth()
1154 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001155 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001156 self.set_region(head, tail, chars, lines)
1157
1158 def toggle_tabs_event(self, event):
1159 if self.askyesno(
1160 "Toggle tabs",
1161 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1162 parent=self.text):
1163 self.usetabs = not self.usetabs
1164 return "break"
1165
1166 # XXX this isn't bound to anything -- see class tabwidth comments
1167 def change_tabwidth_event(self, event):
1168 new = self._asktabwidth()
1169 if new != self.tabwidth:
1170 self.tabwidth = new
1171 self.set_indentation_params(0, guess=0)
1172 return "break"
1173
1174 def change_indentwidth_event(self, event):
1175 new = self.askinteger(
1176 "Indent width",
1177 "New indent width (2-16)",
1178 parent=self.text,
1179 initialvalue=self.indentwidth,
1180 minvalue=2,
1181 maxvalue=16)
1182 if new and new != self.indentwidth:
1183 self.indentwidth = new
1184 return "break"
1185
1186 def get_region(self):
1187 text = self.text
1188 first, last = self.get_selection_indices()
1189 if first and last:
1190 head = text.index(first + " linestart")
1191 tail = text.index(last + "-1c lineend +1c")
1192 else:
1193 head = text.index("insert linestart")
1194 tail = text.index("insert lineend +1c")
1195 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001196 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001197 return head, tail, chars, lines
1198
1199 def set_region(self, head, tail, chars, lines):
1200 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001201 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001202 if newchars == chars:
1203 text.bell()
1204 return
1205 text.tag_remove("sel", "1.0", "end")
1206 text.mark_set("insert", head)
1207 text.undo_block_start()
1208 text.delete(head, tail)
1209 text.insert(head, newchars)
1210 text.undo_block_stop()
1211 text.tag_add("sel", head, "insert")
1212
1213 # Make string that displays as n leading blanks.
1214
1215 def _make_blanks(self, n):
1216 if self.usetabs:
1217 ntabs, nspaces = divmod(n, self.tabwidth)
1218 return '\t' * ntabs + ' ' * nspaces
1219 else:
1220 return ' ' * n
1221
1222 # Delete from beginning of line to insert point, then reinsert
1223 # column logical (meaning use tabs if appropriate) spaces.
1224
1225 def reindent_to(self, column):
1226 text = self.text
1227 text.undo_block_start()
1228 if text.compare("insert linestart", "!=", "insert"):
1229 text.delete("insert linestart", "insert")
1230 if column:
1231 text.insert("insert", self._make_blanks(column))
1232 text.undo_block_stop()
1233
1234 def _asktabwidth(self):
1235 return self.askinteger(
1236 "Tab width",
1237 "Spaces per tab? (2-16)",
1238 parent=self.text,
1239 initialvalue=self.indentwidth,
1240 minvalue=2,
1241 maxvalue=16) or self.tabwidth
1242
1243 # Guess indentwidth from text content.
1244 # Return guessed indentwidth. This should not be believed unless
1245 # it's in a reasonable range (e.g., it will be 0 if no indented
1246 # blocks are found).
1247
1248 def guess_indent(self):
1249 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1250 if opener and indented:
1251 raw, indentsmall = classifyws(opener, self.tabwidth)
1252 raw, indentlarge = classifyws(indented, self.tabwidth)
1253 else:
1254 indentsmall = indentlarge = 0
1255 return indentlarge - indentsmall
1256
1257# "line.col" -> line, as an int
1258def index2line(index):
1259 return int(float(index))
1260
1261# Look at the leading whitespace in s.
1262# Return pair (# of leading ws characters,
1263# effective # of leading blanks after expanding
1264# tabs to width tabwidth)
1265
1266def classifyws(s, tabwidth):
1267 raw = effective = 0
1268 for ch in s:
1269 if ch == ' ':
1270 raw = raw + 1
1271 effective = effective + 1
1272 elif ch == '\t':
1273 raw = raw + 1
1274 effective = (effective // tabwidth + 1) * tabwidth
1275 else:
1276 break
1277 return raw, effective
1278
1279import tokenize
1280_tokenize = tokenize
1281del tokenize
1282
1283class IndentSearcher:
1284
1285 # .run() chews over the Text widget, looking for a block opener
1286 # and the stmt following it. Returns a pair,
1287 # (line containing block opener, line containing stmt)
1288 # Either or both may be None.
1289
1290 def __init__(self, text, tabwidth):
1291 self.text = text
1292 self.tabwidth = tabwidth
1293 self.i = self.finished = 0
1294 self.blkopenline = self.indentedline = None
1295
1296 def readline(self):
1297 if self.finished:
1298 return ""
1299 i = self.i = self.i + 1
1300 mark = `i` + ".0"
1301 if self.text.compare(mark, ">=", "end"):
1302 return ""
1303 return self.text.get(mark, mark + " lineend+1c")
1304
1305 def tokeneater(self, type, token, start, end, line,
1306 INDENT=_tokenize.INDENT,
1307 NAME=_tokenize.NAME,
1308 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1309 if self.finished:
1310 pass
1311 elif type == NAME and token in OPENERS:
1312 self.blkopenline = line
1313 elif type == INDENT and self.blkopenline:
1314 self.indentedline = line
1315 self.finished = 1
1316
1317 def run(self):
1318 save_tabsize = _tokenize.tabsize
1319 _tokenize.tabsize = self.tabwidth
1320 try:
1321 try:
1322 _tokenize.tokenize(self.readline, self.tokeneater)
1323 except _tokenize.TokenError:
1324 # since we cut off the tokenizer early, we can trigger
1325 # spurious errors
1326 pass
1327 finally:
1328 _tokenize.tabsize = save_tabsize
1329 return self.blkopenline, self.indentedline
1330
1331### end autoindent code ###
1332
David Scherer7aced172000-08-15 01:13:23 +00001333def prepstr(s):
1334 # Helper to extract the underscore from a string, e.g.
1335 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001336 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001337 if i >= 0:
1338 s = s[:i] + s[i+1:]
1339 return i, s
1340
1341
1342keynames = {
1343 'bracketleft': '[',
1344 'bracketright': ']',
1345 'slash': '/',
1346}
1347
1348def get_accelerator(keydefs, event):
1349 keylist = keydefs.get(event)
1350 if not keylist:
1351 return ""
1352 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001353 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001354 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1355 s = re.sub("Key-", "", s)
1356 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1357 s = re.sub("Control-", "Ctrl-", s)
1358 s = re.sub("-", "+", s)
1359 s = re.sub("><", " ", s)
1360 s = re.sub("<", "", s)
1361 s = re.sub(">", "", s)
1362 return s
1363
1364
1365def fixwordbreaks(root):
1366 # Make sure that Tk's double-click and next/previous word
1367 # operations use our definition of a word (i.e. an identifier)
1368 tk = root.tk
1369 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1370 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1371 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1372
1373
1374def test():
1375 root = Tk()
1376 fixwordbreaks(root)
1377 root.withdraw()
1378 if sys.argv[1:]:
1379 filename = sys.argv[1]
1380 else:
1381 filename = None
1382 edit = EditorWindow(root=root, filename=filename)
1383 edit.set_close_hook(root.quit)
1384 root.mainloop()
1385 root.destroy()
1386
1387if __name__ == '__main__':
1388 test()