blob: b9522fc3e1928c91f933d3848de3092e02b89857 [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)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000033 try:
34 path = module.__path__
35 except AttributeError:
36 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000037 return file, filename, descr
38
David Scherer7aced172000-08-15 01:13:23 +000039class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000040 from Percolator import Percolator
41 from ColorDelegator import ColorDelegator
42 from UndoDelegator import UndoDelegator
43 from IOBinding import IOBinding
44 import Bindings
45 from Tkinter import Toplevel
46 from MultiStatusBar import MultiStatusBar
47
David Scherer7aced172000-08-15 01:13:23 +000048 vars = {}
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000049 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000050
51 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000052 if EditorWindow.help_url is None:
53 if sys.platform.count('linux'):
54 # look for html docs in a couple of standard places
55 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
56 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
57 dochome = '/var/www/html/python/index.html'
58 else:
59 basepath = '/usr/share/doc/' # standard location
60 dochome = os.path.join(basepath, pyver,
61 'Doc', 'index.html')
62 else:
63 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
64 dochome = os.path.normpath(dochome)
65 if os.path.isfile(dochome):
66 EditorWindow.help_url = dochome
67 else:
68 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000069 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000070 self.flist = flist
71 root = root or flist.root
72 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000073 self.menubar = Menu(root)
74 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000075 if flist:
76 self.vars = flist.vars
77 #self.top.instanceDict makes flist.inversedict avalable to
78 #configDialog.py so it can access all EditorWindow instaces
79 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000080 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
81 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000082 self.vbar = vbar = Scrollbar(top, name='vbar')
83 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000084 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000085 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000086 foreground=idleConf.GetHighlight(currentTheme,
87 'normal',fgBg='fg'),
88 background=idleConf.GetHighlight(currentTheme,
89 'normal',fgBg='bg'),
90 highlightcolor=idleConf.GetHighlight(currentTheme,
91 'hilite',fgBg='fg'),
92 highlightbackground=idleConf.GetHighlight(currentTheme,
93 'hilite',fgBg='bg'),
94 insertbackground=idleConf.GetHighlight(currentTheme,
95 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000096 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +000097 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000098
99 self.createmenubar()
100 self.apply_bindings()
101
102 self.top.protocol("WM_DELETE_WINDOW", self.close)
103 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000104 text.bind("<<cut>>", self.cut)
105 text.bind("<<copy>>", self.copy)
106 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000107 text.bind("<<center-insert>>", self.center_insert_event)
108 text.bind("<<help>>", self.help_dialog)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000109 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000110 text.bind("<<python-docs>>", self.python_docs)
111 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000112 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000113 text.bind("<<open-module>>", self.open_module)
114 text.bind("<<do-nothing>>", lambda event: "break")
115 text.bind("<<select-all>>", self.select_all)
116 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000117 text.bind("<<find>>", self.find_event)
118 text.bind("<<find-again>>", self.find_again_event)
119 text.bind("<<find-in-files>>", self.find_in_files_event)
120 text.bind("<<find-selection>>", self.find_selection_event)
121 text.bind("<<replace>>", self.replace_event)
122 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000123 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000124 text.bind("<<smart-backspace>>",self.smart_backspace_event)
125 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
126 text.bind("<<smart-indent>>",self.smart_indent_event)
127 text.bind("<<indent-region>>",self.indent_region_event)
128 text.bind("<<dedent-region>>",self.dedent_region_event)
129 text.bind("<<comment-region>>",self.comment_region_event)
130 text.bind("<<uncomment-region>>",self.uncomment_region_event)
131 text.bind("<<tabify-region>>",self.tabify_region_event)
132 text.bind("<<untabify-region>>",self.untabify_region_event)
133 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
134 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000135
David Scherer7aced172000-08-15 01:13:23 +0000136 if flist:
137 flist.inversedict[self] = key
138 if key:
139 flist.dict[key] = self
140 text.bind("<<open-new-window>>", self.flist.new_callback)
141 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
142 text.bind("<<open-class-browser>>", self.open_class_browser)
143 text.bind("<<open-path-browser>>", self.open_path_browser)
144
Steven M. Gava898a3652001-10-07 11:10:44 +0000145 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000146 vbar['command'] = text.yview
147 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000148 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000149 fontWeight='normal'
150 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
151 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000152 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000153 idleConf.GetOption('main','EditorWindow','font-size'),
154 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000155 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
156 text.pack(side=TOP, fill=BOTH, expand=1)
157 text.focus_set()
158
159 self.per = per = self.Percolator(text)
160 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000161 self.color = color = self.ColorDelegator()
162 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000163 else:
David Scherer7aced172000-08-15 01:13:23 +0000164 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000165
166 self.undo = undo = self.UndoDelegator()
167 per.insertfilter(undo)
168 text.undo_block_start = undo.undo_block_start
169 text.undo_block_stop = undo.undo_block_stop
170 undo.set_saved_change_hook(self.saved_change_hook)
171
172 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000173 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000174 io.set_filename_change_hook(self.filename_change_hook)
175
Steven M. Gava1d46e402002-03-27 08:40:46 +0000176 #create the Recent Files submenu
177 self.menuRecentFiles=Menu(self.menubar)
178 self.menudict['file'].insert_cascade(3,label='Recent Files',
179 underline=0,menu=self.menuRecentFiles)
180 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000181
David Scherer7aced172000-08-15 01:13:23 +0000182 if filename:
183 if os.path.exists(filename):
184 io.loadfile(filename)
185 else:
186 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000187 self.saved_change_hook()
188
189 self.load_extensions()
190
191 menu = self.menudict.get('windows')
192 if menu:
193 end = menu.index("end")
194 if end is None:
195 end = -1
196 if end >= 0:
197 menu.add_separator()
198 end = end + 1
199 self.wmenu_end = end
200 WindowList.register_callback(self.postwindowsmenu)
201
202 # Some abstractions so IDLE extensions are cross-IDE
203 self.askyesno = tkMessageBox.askyesno
204 self.askinteger = tkSimpleDialog.askinteger
205 self.showerror = tkMessageBox.showerror
206
207 if self.extensions.has_key('AutoIndent'):
208 self.extensions['AutoIndent'].set_indentation_params(
209 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000210
David Scherer7aced172000-08-15 01:13:23 +0000211 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000212 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000213 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
214 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
215 self.status_bar.pack(side=BOTTOM, fill=X)
216 self.text.bind('<KeyRelease>', self.set_line_and_column)
217 self.text.bind('<ButtonRelease>', self.set_line_and_column)
218 self.text.after_idle(self.set_line_and_column)
219
220 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000221 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000222 self.status_bar.set_label('column', 'Col: %s' % column)
223 self.status_bar.set_label('line', 'Ln: %s' % line)
224
225 def wakeup(self):
226 if self.top.wm_state() == "iconic":
227 self.top.wm_deiconify()
228 else:
229 self.top.tkraise()
230 self.text.focus_set()
231
232 menu_specs = [
233 ("file", "_File"),
234 ("edit", "_Edit"),
235 ("format", "F_ormat"),
236 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000237 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000238 ("windows", "_Windows"),
239 ("help", "_Help"),
240 ]
241
242 def createmenubar(self):
243 mbar = self.menubar
244 self.menudict = menudict = {}
245 for name, label in self.menu_specs:
246 underline, label = prepstr(label)
247 menudict[name] = menu = Menu(mbar, name=name)
248 mbar.add_cascade(label=label, menu=menu, underline=underline)
249 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000250 self.base_helpmenu_length = self.menudict['help'].index(END)
251 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000252
253 def postwindowsmenu(self):
254 # Only called when Windows menu exists
255 # XXX Actually, this Just-In-Time updating interferes badly
256 # XXX with the tear-off feature. It would be better to update
257 # XXX all Windows menus whenever the list of windows changes.
258 menu = self.menudict['windows']
259 end = menu.index("end")
260 if end is None:
261 end = -1
262 if end > self.wmenu_end:
263 menu.delete(self.wmenu_end+1, end)
264 WindowList.add_windows_to_menu(menu)
265
266 rmenu = None
267
268 def right_menu_event(self, event):
269 self.text.tag_remove("sel", "1.0", "end")
270 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
271 if not self.rmenu:
272 self.make_rmenu()
273 rmenu = self.rmenu
274 self.event = event
275 iswin = sys.platform[:3] == 'win'
276 if iswin:
277 self.text.config(cursor="arrow")
278 rmenu.tk_popup(event.x_root, event.y_root)
279 if iswin:
280 self.text.config(cursor="ibeam")
281
282 rmenu_specs = [
283 # ("Label", "<<virtual-event>>"), ...
284 ("Close", "<<close-window>>"), # Example
285 ]
286
287 def make_rmenu(self):
288 rmenu = Menu(self.text, tearoff=0)
289 for label, eventname in self.rmenu_specs:
290 def command(text=self.text, eventname=eventname):
291 text.event_generate(eventname)
292 rmenu.add_command(label=label, command=command)
293 self.rmenu = rmenu
294
295 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000296 aboutDialog.AboutDialog(self.top,'About IDLEfork')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000297
Steven M. Gava3b55a892001-11-21 05:56:26 +0000298 def config_dialog(self, event=None):
299 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000300
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000301 def view_readme(self, event=None):
302 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000303 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000304
David Scherer7aced172000-08-15 01:13:23 +0000305 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000306 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307 textView.TextViewer(self.top,'Help',fn)
308
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000309 def python_docs(self, event=None):
310 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000311 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000312 return "break"
313 else:
314 webbrowser.open(self.help_url)
315 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000316
317 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000318 if not (url.startswith('www') or url.startswith('http')):
319 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000320 if sys.platform.count('win') or sys.platform.count('nt'):
321 os.startfile(url)
322 else:
323 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000324
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000325 def cut(self,event):
326 self.text.event_generate("<<Cut>>")
327 return "break"
328
329 def copy(self,event):
330 self.text.event_generate("<<Copy>>")
331 return "break"
332
333 def paste(self,event):
334 self.text.event_generate("<<Paste>>")
335 return "break"
336
David Scherer7aced172000-08-15 01:13:23 +0000337 def select_all(self, event=None):
338 self.text.tag_add("sel", "1.0", "end-1c")
339 self.text.mark_set("insert", "1.0")
340 self.text.see("insert")
341 return "break"
342
343 def remove_selection(self, event=None):
344 self.text.tag_remove("sel", "1.0", "end")
345 self.text.see("insert")
346
Steven M. Gavac5976402002-01-04 03:06:08 +0000347 def find_event(self, event):
348 SearchDialog.find(self.text)
349 return "break"
350
351 def find_again_event(self, event):
352 SearchDialog.find_again(self.text)
353 return "break"
354
355 def find_selection_event(self, event):
356 SearchDialog.find_selection(self.text)
357 return "break"
358
359 def find_in_files_event(self, event):
360 GrepDialog.grep(self.text, self.io, self.flist)
361 return "break"
362
363 def replace_event(self, event):
364 ReplaceDialog.replace(self.text)
365 return "break"
366
367 def goto_line_event(self, event):
368 text = self.text
369 lineno = tkSimpleDialog.askinteger("Goto",
370 "Go to line number:",parent=text)
371 if lineno is None:
372 return "break"
373 if lineno <= 0:
374 text.bell()
375 return "break"
376 text.mark_set("insert", "%d.0" % lineno)
377 text.see("insert")
378
David Scherer7aced172000-08-15 01:13:23 +0000379 def open_module(self, event=None):
380 # XXX Shouldn't this be in IOBinding or in FileList?
381 try:
382 name = self.text.get("sel.first", "sel.last")
383 except TclError:
384 name = ""
385 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000386 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000387 if not name:
388 name = tkSimpleDialog.askstring("Module",
389 "Enter the name of a Python module\n"
390 "to search on sys.path and open:",
391 parent=self.text)
392 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000393 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000394 if not name:
395 return
David Scherer7aced172000-08-15 01:13:23 +0000396 # XXX Ought to insert current file's directory in front of path
397 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000398 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000399 except (NameError, ImportError), msg:
400 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
401 return
402 if type != imp.PY_SOURCE:
403 tkMessageBox.showerror("Unsupported type",
404 "%s is not a source module" % name, parent=self.text)
405 return
406 if f:
407 f.close()
408 if self.flist:
409 self.flist.open(file)
410 else:
411 self.io.loadfile(file)
412
413 def open_class_browser(self, event=None):
414 filename = self.io.filename
415 if not filename:
416 tkMessageBox.showerror(
417 "No filename",
418 "This buffer has no associated filename",
419 master=self.text)
420 self.text.focus_set()
421 return None
422 head, tail = os.path.split(filename)
423 base, ext = os.path.splitext(tail)
424 import ClassBrowser
425 ClassBrowser.ClassBrowser(self.flist, base, [head])
426
427 def open_path_browser(self, event=None):
428 import PathBrowser
429 PathBrowser.PathBrowser(self.flist)
430
431 def gotoline(self, lineno):
432 if lineno is not None and lineno > 0:
433 self.text.mark_set("insert", "%d.0" % lineno)
434 self.text.tag_remove("sel", "1.0", "end")
435 self.text.tag_add("sel", "insert", "insert +1l")
436 self.center()
437
438 def ispythonsource(self, filename):
439 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000440 return True
David Scherer7aced172000-08-15 01:13:23 +0000441 base, ext = os.path.splitext(os.path.basename(filename))
442 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000443 return True
David Scherer7aced172000-08-15 01:13:23 +0000444 try:
445 f = open(filename)
446 line = f.readline()
447 f.close()
448 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000449 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000450 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000451
452 def close_hook(self):
453 if self.flist:
454 self.flist.close_edit(self)
455
456 def set_close_hook(self, close_hook):
457 self.close_hook = close_hook
458
459 def filename_change_hook(self):
460 if self.flist:
461 self.flist.filename_changed_edit(self)
462 self.saved_change_hook()
463 if self.ispythonsource(self.io.filename):
464 self.addcolorizer()
465 else:
466 self.rmcolorizer()
467
468 def addcolorizer(self):
469 if self.color:
470 return
David Scherer7aced172000-08-15 01:13:23 +0000471 self.per.removefilter(self.undo)
472 self.color = self.ColorDelegator()
473 self.per.insertfilter(self.color)
474 self.per.insertfilter(self.undo)
475
476 def rmcolorizer(self):
477 if not self.color:
478 return
David Scherer7aced172000-08-15 01:13:23 +0000479 self.per.removefilter(self.undo)
480 self.per.removefilter(self.color)
481 self.color = None
482 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000483
Steven M. Gavab77d3432002-03-02 07:16:21 +0000484 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000485 "Update the colour theme if it is changed"
486 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000487 if self.color:
488 self.color = self.ColorDelegator()
489 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000490
Steven M. Gavab1585412002-03-12 00:21:56 +0000491 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000492 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000493 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000494 fontWeight='normal'
495 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
496 fontWeight='bold'
497 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
498 idleConf.GetOption('main','EditorWindow','font-size'),
499 fontWeight))
500
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000501 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000502 "Update the keybindings if they are changed"
503 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000504 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
505 keydefs = self.Bindings.default_keydefs
506 for event, keylist in keydefs.items():
507 self.text.event_delete(event)
508 self.apply_bindings()
509 #update menu accelerators
510 menuEventDict={}
511 for menu in self.Bindings.menudefs:
512 menuEventDict[menu[0]]={}
513 for item in menu[1]:
514 if item:
515 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
516 for menubarItem in self.menudict.keys():
517 menu=self.menudict[menubarItem]
518 end=menu.index(END)+1
519 for index in range(0,end):
520 if menu.type(index)=='command':
521 accel=menu.entrycget(index,'accelerator')
522 if accel:
523 itemName=menu.entrycget(index,'label')
524 event=''
525 if menuEventDict.has_key(menubarItem):
526 if menuEventDict[menubarItem].has_key(itemName):
527 event=menuEventDict[menubarItem][itemName]
528 if event:
529 #print 'accel was:',accel
530 accel=get_accelerator(keydefs, event)
531 menu.entryconfig(index,accelerator=accel)
532 #print 'accel now:',accel,'\n'
533
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000534 def reset_help_menu_entries(self):
535 "Update the additional help entries on the Help menu"
536 help_list = idleConf.GetAllExtraHelpSourcesList()
537 helpmenu = self.menudict['help']
538 # first delete the extra help entries, if any
539 helpmenu_length = helpmenu.index(END)
540 if helpmenu_length > self.base_helpmenu_length:
541 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
542 # then rebuild them
543 if help_list:
544 helpmenu.add_separator()
545 for entry in help_list:
546 cmd = self.__extra_help_callback(entry[1])
547 helpmenu.add_command(label=entry[0], command=cmd)
548 # and update the menu dictionary
549 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000550
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000551 def __extra_help_callback(self, helpfile):
552 "Create a callback with the helpfile value frozen at definition time"
553 def display_extra_help(helpfile=helpfile):
554 self.display_docs(helpfile)
555 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000556
Steven M. Gava1d46e402002-03-27 08:40:46 +0000557 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000558 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000559 rfList=[]
560 if os.path.exists(self.recentFilesPath):
561 RFfile=open(self.recentFilesPath,'r')
562 try:
563 rfList=RFfile.readlines()
564 finally:
565 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000566 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000567 newFile=os.path.abspath(newFile)+'\n'
568 if newFile in rfList:
569 rfList.remove(newFile)
570 rfList.insert(0,newFile)
571 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000572 #print self.flist.inversedict
573 #print self.top.instanceDict
574 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000575 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000576 if rfList:
577 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000578 menu = instance.menuRecentFiles
579 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000580 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000581 for file in rfList:
582 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000583 callback = instance.__RecentFileCallback(fileName)
584 if i > ullen: # don't underline menuitems
585 ul=None
586 menu.add_command(label=ullist[i] + " " + fileName,
587 command=callback,
588 underline=ul)
589 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000590
Steven M. Gava1d46e402002-03-27 08:40:46 +0000591 def __CleanRecentFiles(self,rfList):
592 origRfList=rfList[:]
593 count=0
594 nonFiles=[]
595 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000596 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000597 nonFiles.append(count)
598 count=count+1
599 if nonFiles:
600 nonFiles.reverse()
601 for index in nonFiles:
602 del(rfList[index])
603 if len(rfList)>19:
604 rfList=rfList[0:19]
605 #if rfList != origRfList:
606 RFfile=open(self.recentFilesPath,'w')
607 try:
608 RFfile.writelines(rfList)
609 finally:
610 RFfile.close()
611 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000612
Steven M. Gava1d46e402002-03-27 08:40:46 +0000613 def __RecentFileCallback(self,fileName):
614 def OpenRecentFile(fileName=fileName):
615 self.io.open(editFile=fileName)
616 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000617
David Scherer7aced172000-08-15 01:13:23 +0000618 def saved_change_hook(self):
619 short = self.short_title()
620 long = self.long_title()
621 if short and long:
622 title = short + " - " + long
623 elif short:
624 title = short
625 elif long:
626 title = long
627 else:
628 title = "Untitled"
629 icon = short or long or title
630 if not self.get_saved():
631 title = "*%s*" % title
632 icon = "*%s" % icon
633 self.top.wm_title(title)
634 self.top.wm_iconname(icon)
635
636 def get_saved(self):
637 return self.undo.get_saved()
638
639 def set_saved(self, flag):
640 self.undo.set_saved(flag)
641
642 def reset_undo(self):
643 self.undo.reset_undo()
644
645 def short_title(self):
646 filename = self.io.filename
647 if filename:
648 filename = os.path.basename(filename)
649 return filename
650
651 def long_title(self):
652 return self.io.filename or ""
653
654 def center_insert_event(self, event):
655 self.center()
656
657 def center(self, mark="insert"):
658 text = self.text
659 top, bot = self.getwindowlines()
660 lineno = self.getlineno(mark)
661 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000662 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000663 text.yview(float(newtop))
664
665 def getwindowlines(self):
666 text = self.text
667 top = self.getlineno("@0,0")
668 bot = self.getlineno("@0,65535")
669 if top == bot and text.winfo_height() == 1:
670 # Geometry manager hasn't run yet
671 height = int(text['height'])
672 bot = top + height - 1
673 return top, bot
674
675 def getlineno(self, mark="insert"):
676 text = self.text
677 return int(float(text.index(mark)))
678
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000679 def get_geometry(self):
680 "Return (width, height, x, y)"
681 geom = self.top.wm_geometry()
682 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
683 tuple = (map(int, m.groups()))
684 return tuple
685
David Scherer7aced172000-08-15 01:13:23 +0000686 def close_event(self, event):
687 self.close()
688
689 def maybesave(self):
690 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000691 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000692 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000693 self.top.deiconify()
694 self.top.lower()
695 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000696 return self.io.maybesave()
697
698 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000699 reply = self.maybesave()
700 if reply != "cancel":
701 self._close()
702 return reply
703
704 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000705 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000706 if self.io.filename:
707 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000708 WindowList.unregister_callback(self.postwindowsmenu)
709 if self.close_hook:
710 self.close_hook()
711 self.flist = None
712 colorizing = 0
713 self.unload_extensions()
714 self.io.close(); self.io = None
715 self.undo = None # XXX
716 if self.color:
717 colorizing = self.color.colorizing
718 doh = colorizing and self.top
719 self.color.close(doh) # Cancel colorization
720 self.text = None
721 self.vars = None
722 self.per.close(); self.per = None
723 if not colorizing:
724 self.top.destroy()
725
726 def load_extensions(self):
727 self.extensions = {}
728 self.load_standard_extensions()
729
730 def unload_extensions(self):
731 for ins in self.extensions.values():
732 if hasattr(ins, "close"):
733 ins.close()
734 self.extensions = {}
735
736 def load_standard_extensions(self):
737 for name in self.get_standard_extension_names():
738 try:
739 self.load_extension(name)
740 except:
741 print "Failed to load extension", `name`
742 import traceback
743 traceback.print_exc()
744
745 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000746 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000747
748 def load_extension(self, name):
749 mod = __import__(name, globals(), locals(), [])
750 cls = getattr(mod, name)
751 ins = cls(self)
752 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000753 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000754 if keydefs:
755 self.apply_bindings(keydefs)
756 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000757 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000758 while methodname[:1] == '<':
759 methodname = methodname[1:]
760 while methodname[-1:] == '>':
761 methodname = methodname[:-1]
762 methodname = methodname + "_event"
763 if hasattr(ins, methodname):
764 self.text.bind(vevent, getattr(ins, methodname))
765 if hasattr(ins, "menudefs"):
766 self.fill_menus(ins.menudefs, keydefs)
767 return ins
768
769 def apply_bindings(self, keydefs=None):
770 if keydefs is None:
771 keydefs = self.Bindings.default_keydefs
772 text = self.text
773 text.keydefs = keydefs
774 for event, keylist in keydefs.items():
775 if keylist:
776 apply(text.event_add, (event,) + tuple(keylist))
777
778 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000779 """Add appropriate entries to the menus and submenus
780
781 Menus that are absent or None in self.menudict are ignored.
782 """
David Scherer7aced172000-08-15 01:13:23 +0000783 if defs is None:
784 defs = self.Bindings.menudefs
785 if keydefs is None:
786 keydefs = self.Bindings.default_keydefs
787 menudict = self.menudict
788 text = self.text
789 for mname, itemlist in defs:
790 menu = menudict.get(mname)
791 if not menu:
792 continue
793 for item in itemlist:
794 if not item:
795 menu.add_separator()
796 else:
797 label, event = item
798 checkbutton = (label[:1] == '!')
799 if checkbutton:
800 label = label[1:]
801 underline, label = prepstr(label)
802 accelerator = get_accelerator(keydefs, event)
803 def command(text=text, event=event):
804 text.event_generate(event)
805 if checkbutton:
806 var = self.getrawvar(event, BooleanVar)
807 menu.add_checkbutton(label=label, underline=underline,
808 command=command, accelerator=accelerator,
809 variable=var)
810 else:
811 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000812 command=command,
813 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000814
815 def getvar(self, name):
816 var = self.getrawvar(name)
817 if var:
818 return var.get()
819
820 def setvar(self, name, value, vartype=None):
821 var = self.getrawvar(name, vartype)
822 if var:
823 var.set(value)
824
825 def getrawvar(self, name, vartype=None):
826 var = self.vars.get(name)
827 if not var and vartype:
828 self.vars[name] = var = vartype(self.text)
829 return var
830
831 # Tk implementations of "virtual text methods" -- each platform
832 # reusing IDLE's support code needs to define these for its GUI's
833 # flavor of widget.
834
835 # Is character at text_index in a Python string? Return 0 for
836 # "guaranteed no", true for anything else. This info is expensive
837 # to compute ab initio, but is probably already known by the
838 # platform's colorizer.
839
840 def is_char_in_string(self, text_index):
841 if self.color:
842 # Return true iff colorizer hasn't (re)gotten this far
843 # yet, or the character is tagged as being in a string
844 return self.text.tag_prevrange("TODO", text_index) or \
845 "STRING" in self.text.tag_names(text_index)
846 else:
847 # The colorizer is missing: assume the worst
848 return 1
849
850 # If a selection is defined in the text widget, return (start,
851 # end) as Tkinter text indices, otherwise return (None, None)
852 def get_selection_indices(self):
853 try:
854 first = self.text.index("sel.first")
855 last = self.text.index("sel.last")
856 return first, last
857 except TclError:
858 return None, None
859
860 # Return the text widget's current view of what a tab stop means
861 # (equivalent width in spaces).
862
863 def get_tabwidth(self):
864 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
865 return int(current)
866
867 # Set the text widget's current view of what a tab stop means.
868
869 def set_tabwidth(self, newtabwidth):
870 text = self.text
871 if self.get_tabwidth() != newtabwidth:
872 pixels = text.tk.call("font", "measure", text["font"],
873 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000874 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000875 text.configure(tabs=pixels)
876
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000877### begin autoindent code ###
878
879 # usetabs true -> literal tab characters are used by indent and
880 # dedent cmds, possibly mixed with spaces if
881 # indentwidth is not a multiple of tabwidth
882 # false -> tab characters are converted to spaces by indent
883 # and dedent cmds, and ditto TAB keystrokes
884 # indentwidth is the number of characters per logical indent level.
885 # tabwidth is the display width of a literal tab character.
886 # CAUTION: telling Tk to use anything other than its default
887 # tab setting causes it to use an entirely different tabbing algorithm,
888 # treating tab stops as fixed distances from the left margin.
889 # Nobody expects this, so for now tabwidth should never be changed.
890 usetabs = 0
891 indentwidth = 4
892 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
893
894 # If context_use_ps1 is true, parsing searches back for a ps1 line;
895 # else searches for a popular (if, def, ...) Python stmt.
896 context_use_ps1 = 0
897
898 # When searching backwards for a reliable place to begin parsing,
899 # first start num_context_lines[0] lines back, then
900 # num_context_lines[1] lines back if that didn't work, and so on.
901 # The last value should be huge (larger than the # of lines in a
902 # conceivable file).
903 # Making the initial values larger slows things down more often.
904 num_context_lines = 50, 500, 5000000
905
906 def config(self, **options):
907 for key, value in options.items():
908 if key == 'usetabs':
909 self.usetabs = value
910 elif key == 'indentwidth':
911 self.indentwidth = value
912 elif key == 'tabwidth':
913 self.tabwidth = value
914 elif key == 'context_use_ps1':
915 self.context_use_ps1 = value
916 else:
917 raise KeyError, "bad option name: %s" % `key`
918
919 # If ispythonsource and guess are true, guess a good value for
920 # indentwidth based on file content (if possible), and if
921 # indentwidth != tabwidth set usetabs false.
922 # In any case, adjust the Text widget's view of what a tab
923 # character means.
924
925 def set_indentation_params(self, ispythonsource, guess=1):
926 if guess and ispythonsource:
927 i = self.guess_indent()
928 if 2 <= i <= 8:
929 self.indentwidth = i
930 if self.indentwidth != self.tabwidth:
931 self.usetabs = 0
932
933 self.set_tabwidth(self.tabwidth)
934
935 def smart_backspace_event(self, event):
936 text = self.text
937 first, last = self.get_selection_indices()
938 if first and last:
939 text.delete(first, last)
940 text.mark_set("insert", first)
941 return "break"
942 # Delete whitespace left, until hitting a real char or closest
943 # preceding virtual tab stop.
944 chars = text.get("insert linestart", "insert")
945 if chars == '':
946 if text.compare("insert", ">", "1.0"):
947 # easy: delete preceding newline
948 text.delete("insert-1c")
949 else:
950 text.bell() # at start of buffer
951 return "break"
952 if chars[-1] not in " \t":
953 # easy: delete preceding real char
954 text.delete("insert-1c")
955 return "break"
956 # Ick. It may require *inserting* spaces if we back up over a
957 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000958 tabwidth = self.tabwidth
959 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000960 assert have > 0
961 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000962 # Debug prompt is multilined....
963 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000964 ncharsdeleted = 0
965 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000966 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000967 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000968 chars = chars[:-1]
969 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000970 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000971 if have <= want or chars[-1] not in " \t":
972 break
973 text.undo_block_start()
974 text.delete("insert-%dc" % ncharsdeleted, "insert")
975 if have < want:
976 text.insert("insert", ' ' * (want - have))
977 text.undo_block_stop()
978 return "break"
979
980 def smart_indent_event(self, event):
981 # if intraline selection:
982 # delete it
983 # elif multiline selection:
984 # do indent-region & return
985 # indent one level
986 text = self.text
987 first, last = self.get_selection_indices()
988 text.undo_block_start()
989 try:
990 if first and last:
991 if index2line(first) != index2line(last):
992 return self.indent_region_event(event)
993 text.delete(first, last)
994 text.mark_set("insert", first)
995 prefix = text.get("insert linestart", "insert")
996 raw, effective = classifyws(prefix, self.tabwidth)
997 if raw == len(prefix):
998 # only whitespace to the left
999 self.reindent_to(effective + self.indentwidth)
1000 else:
1001 if self.usetabs:
1002 pad = '\t'
1003 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001004 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001005 n = self.indentwidth
1006 pad = ' ' * (n - effective % n)
1007 text.insert("insert", pad)
1008 text.see("insert")
1009 return "break"
1010 finally:
1011 text.undo_block_stop()
1012
1013 def newline_and_indent_event(self, event):
1014 text = self.text
1015 first, last = self.get_selection_indices()
1016 text.undo_block_start()
1017 try:
1018 if first and last:
1019 text.delete(first, last)
1020 text.mark_set("insert", first)
1021 line = text.get("insert linestart", "insert")
1022 i, n = 0, len(line)
1023 while i < n and line[i] in " \t":
1024 i = i+1
1025 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001026 # the cursor is in or at leading indentation in a continuation
1027 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001028 text.insert("insert linestart", '\n')
1029 return "break"
1030 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001031 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001032 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001033 last_line_of_prompt = sys.ps1.split('\n')[-1]
1034 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001035 line = line[:-1]
1036 i = i+1
1037 if i:
1038 text.delete("insert - %d chars" % i, "insert")
1039 # strip whitespace after insert point
1040 while text.get("insert") in " \t":
1041 text.delete("insert")
1042 # start new line
1043 text.insert("insert", '\n')
1044
1045 # adjust indentation for continuations and block
1046 # open/close first need to find the last stmt
1047 lno = index2line(text.index('insert'))
1048 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1049 for context in self.num_context_lines:
1050 startat = max(lno - context, 1)
1051 startatindex = `startat` + ".0"
1052 rawtext = text.get(startatindex, "insert")
1053 y.set_str(rawtext)
1054 bod = y.find_good_parse_start(
1055 self.context_use_ps1,
1056 self._build_char_in_string_func(startatindex))
1057 if bod is not None or startat == 1:
1058 break
1059 y.set_lo(bod or 0)
1060 c = y.get_continuation_type()
1061 if c != PyParse.C_NONE:
1062 # The current stmt hasn't ended yet.
1063 if c == PyParse.C_STRING:
1064 # inside a string; just mimic the current indent
1065 text.insert("insert", indent)
1066 elif c == PyParse.C_BRACKET:
1067 # line up with the first (if any) element of the
1068 # last open bracket structure; else indent one
1069 # level beyond the indent of the line with the
1070 # last open bracket
1071 self.reindent_to(y.compute_bracket_indent())
1072 elif c == PyParse.C_BACKSLASH:
1073 # if more than one line in this stmt already, just
1074 # mimic the current indent; else if initial line
1075 # has a start on an assignment stmt, indent to
1076 # beyond leftmost =; else to beyond first chunk of
1077 # non-whitespace on initial line
1078 if y.get_num_lines_in_stmt() > 1:
1079 text.insert("insert", indent)
1080 else:
1081 self.reindent_to(y.compute_backslash_indent())
1082 else:
1083 assert 0, "bogus continuation type " + `c`
1084 return "break"
1085
1086 # This line starts a brand new stmt; indent relative to
1087 # indentation of initial line of closest preceding
1088 # interesting stmt.
1089 indent = y.get_base_indent_string()
1090 text.insert("insert", indent)
1091 if y.is_block_opener():
1092 self.smart_indent_event(event)
1093 elif indent and y.is_block_closer():
1094 self.smart_backspace_event(event)
1095 return "break"
1096 finally:
1097 text.see("insert")
1098 text.undo_block_stop()
1099
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001100 # Our editwin provides a is_char_in_string function that works
1101 # with a Tk text index, but PyParse only knows about offsets into
1102 # a string. This builds a function for PyParse that accepts an
1103 # offset.
1104
1105 def _build_char_in_string_func(self, startindex):
1106 def inner(offset, _startindex=startindex,
1107 _icis=self.is_char_in_string):
1108 return _icis(_startindex + "+%dc" % offset)
1109 return inner
1110
1111 def indent_region_event(self, event):
1112 head, tail, chars, lines = self.get_region()
1113 for pos in range(len(lines)):
1114 line = lines[pos]
1115 if line:
1116 raw, effective = classifyws(line, self.tabwidth)
1117 effective = effective + self.indentwidth
1118 lines[pos] = self._make_blanks(effective) + line[raw:]
1119 self.set_region(head, tail, chars, lines)
1120 return "break"
1121
1122 def dedent_region_event(self, event):
1123 head, tail, chars, lines = self.get_region()
1124 for pos in range(len(lines)):
1125 line = lines[pos]
1126 if line:
1127 raw, effective = classifyws(line, self.tabwidth)
1128 effective = max(effective - self.indentwidth, 0)
1129 lines[pos] = self._make_blanks(effective) + line[raw:]
1130 self.set_region(head, tail, chars, lines)
1131 return "break"
1132
1133 def comment_region_event(self, event):
1134 head, tail, chars, lines = self.get_region()
1135 for pos in range(len(lines) - 1):
1136 line = lines[pos]
1137 lines[pos] = '##' + line
1138 self.set_region(head, tail, chars, lines)
1139
1140 def uncomment_region_event(self, event):
1141 head, tail, chars, lines = self.get_region()
1142 for pos in range(len(lines)):
1143 line = lines[pos]
1144 if not line:
1145 continue
1146 if line[:2] == '##':
1147 line = line[2:]
1148 elif line[:1] == '#':
1149 line = line[1:]
1150 lines[pos] = line
1151 self.set_region(head, tail, chars, lines)
1152
1153 def tabify_region_event(self, event):
1154 head, tail, chars, lines = self.get_region()
1155 tabwidth = self._asktabwidth()
1156 for pos in range(len(lines)):
1157 line = lines[pos]
1158 if line:
1159 raw, effective = classifyws(line, tabwidth)
1160 ntabs, nspaces = divmod(effective, tabwidth)
1161 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1162 self.set_region(head, tail, chars, lines)
1163
1164 def untabify_region_event(self, event):
1165 head, tail, chars, lines = self.get_region()
1166 tabwidth = self._asktabwidth()
1167 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001168 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001169 self.set_region(head, tail, chars, lines)
1170
1171 def toggle_tabs_event(self, event):
1172 if self.askyesno(
1173 "Toggle tabs",
1174 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1175 parent=self.text):
1176 self.usetabs = not self.usetabs
1177 return "break"
1178
1179 # XXX this isn't bound to anything -- see class tabwidth comments
1180 def change_tabwidth_event(self, event):
1181 new = self._asktabwidth()
1182 if new != self.tabwidth:
1183 self.tabwidth = new
1184 self.set_indentation_params(0, guess=0)
1185 return "break"
1186
1187 def change_indentwidth_event(self, event):
1188 new = self.askinteger(
1189 "Indent width",
1190 "New indent width (2-16)",
1191 parent=self.text,
1192 initialvalue=self.indentwidth,
1193 minvalue=2,
1194 maxvalue=16)
1195 if new and new != self.indentwidth:
1196 self.indentwidth = new
1197 return "break"
1198
1199 def get_region(self):
1200 text = self.text
1201 first, last = self.get_selection_indices()
1202 if first and last:
1203 head = text.index(first + " linestart")
1204 tail = text.index(last + "-1c lineend +1c")
1205 else:
1206 head = text.index("insert linestart")
1207 tail = text.index("insert lineend +1c")
1208 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001209 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001210 return head, tail, chars, lines
1211
1212 def set_region(self, head, tail, chars, lines):
1213 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001214 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 if newchars == chars:
1216 text.bell()
1217 return
1218 text.tag_remove("sel", "1.0", "end")
1219 text.mark_set("insert", head)
1220 text.undo_block_start()
1221 text.delete(head, tail)
1222 text.insert(head, newchars)
1223 text.undo_block_stop()
1224 text.tag_add("sel", head, "insert")
1225
1226 # Make string that displays as n leading blanks.
1227
1228 def _make_blanks(self, n):
1229 if self.usetabs:
1230 ntabs, nspaces = divmod(n, self.tabwidth)
1231 return '\t' * ntabs + ' ' * nspaces
1232 else:
1233 return ' ' * n
1234
1235 # Delete from beginning of line to insert point, then reinsert
1236 # column logical (meaning use tabs if appropriate) spaces.
1237
1238 def reindent_to(self, column):
1239 text = self.text
1240 text.undo_block_start()
1241 if text.compare("insert linestart", "!=", "insert"):
1242 text.delete("insert linestart", "insert")
1243 if column:
1244 text.insert("insert", self._make_blanks(column))
1245 text.undo_block_stop()
1246
1247 def _asktabwidth(self):
1248 return self.askinteger(
1249 "Tab width",
1250 "Spaces per tab? (2-16)",
1251 parent=self.text,
1252 initialvalue=self.indentwidth,
1253 minvalue=2,
1254 maxvalue=16) or self.tabwidth
1255
1256 # Guess indentwidth from text content.
1257 # Return guessed indentwidth. This should not be believed unless
1258 # it's in a reasonable range (e.g., it will be 0 if no indented
1259 # blocks are found).
1260
1261 def guess_indent(self):
1262 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1263 if opener and indented:
1264 raw, indentsmall = classifyws(opener, self.tabwidth)
1265 raw, indentlarge = classifyws(indented, self.tabwidth)
1266 else:
1267 indentsmall = indentlarge = 0
1268 return indentlarge - indentsmall
1269
1270# "line.col" -> line, as an int
1271def index2line(index):
1272 return int(float(index))
1273
1274# Look at the leading whitespace in s.
1275# Return pair (# of leading ws characters,
1276# effective # of leading blanks after expanding
1277# tabs to width tabwidth)
1278
1279def classifyws(s, tabwidth):
1280 raw = effective = 0
1281 for ch in s:
1282 if ch == ' ':
1283 raw = raw + 1
1284 effective = effective + 1
1285 elif ch == '\t':
1286 raw = raw + 1
1287 effective = (effective // tabwidth + 1) * tabwidth
1288 else:
1289 break
1290 return raw, effective
1291
1292import tokenize
1293_tokenize = tokenize
1294del tokenize
1295
1296class IndentSearcher:
1297
1298 # .run() chews over the Text widget, looking for a block opener
1299 # and the stmt following it. Returns a pair,
1300 # (line containing block opener, line containing stmt)
1301 # Either or both may be None.
1302
1303 def __init__(self, text, tabwidth):
1304 self.text = text
1305 self.tabwidth = tabwidth
1306 self.i = self.finished = 0
1307 self.blkopenline = self.indentedline = None
1308
1309 def readline(self):
1310 if self.finished:
1311 return ""
1312 i = self.i = self.i + 1
1313 mark = `i` + ".0"
1314 if self.text.compare(mark, ">=", "end"):
1315 return ""
1316 return self.text.get(mark, mark + " lineend+1c")
1317
1318 def tokeneater(self, type, token, start, end, line,
1319 INDENT=_tokenize.INDENT,
1320 NAME=_tokenize.NAME,
1321 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1322 if self.finished:
1323 pass
1324 elif type == NAME and token in OPENERS:
1325 self.blkopenline = line
1326 elif type == INDENT and self.blkopenline:
1327 self.indentedline = line
1328 self.finished = 1
1329
1330 def run(self):
1331 save_tabsize = _tokenize.tabsize
1332 _tokenize.tabsize = self.tabwidth
1333 try:
1334 try:
1335 _tokenize.tokenize(self.readline, self.tokeneater)
1336 except _tokenize.TokenError:
1337 # since we cut off the tokenizer early, we can trigger
1338 # spurious errors
1339 pass
1340 finally:
1341 _tokenize.tabsize = save_tabsize
1342 return self.blkopenline, self.indentedline
1343
1344### end autoindent code ###
1345
David Scherer7aced172000-08-15 01:13:23 +00001346def prepstr(s):
1347 # Helper to extract the underscore from a string, e.g.
1348 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001349 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001350 if i >= 0:
1351 s = s[:i] + s[i+1:]
1352 return i, s
1353
1354
1355keynames = {
1356 'bracketleft': '[',
1357 'bracketright': ']',
1358 'slash': '/',
1359}
1360
1361def get_accelerator(keydefs, event):
1362 keylist = keydefs.get(event)
1363 if not keylist:
1364 return ""
1365 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001366 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001367 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1368 s = re.sub("Key-", "", s)
1369 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1370 s = re.sub("Control-", "Ctrl-", s)
1371 s = re.sub("-", "+", s)
1372 s = re.sub("><", " ", s)
1373 s = re.sub("<", "", s)
1374 s = re.sub(">", "", s)
1375 return s
1376
1377
1378def fixwordbreaks(root):
1379 # Make sure that Tk's double-click and next/previous word
1380 # operations use our definition of a word (i.e. an identifier)
1381 tk = root.tk
1382 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1383 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1384 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1385
1386
1387def test():
1388 root = Tk()
1389 fixwordbreaks(root)
1390 root.withdraw()
1391 if sys.argv[1:]:
1392 filename = sys.argv[1]
1393 else:
1394 filename = None
1395 edit = EditorWindow(root=root, filename=filename)
1396 edit.set_close_hook(root.quit)
1397 root.mainloop()
1398 root.destroy()
1399
1400if __name__ == '__main__':
1401 test()