blob: d574b05fa1c5caa4da13032feebe937122a9ca6b [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)
David Scherer7aced172000-08-15 01:13:23 +0000109 text.bind("<<python-docs>>", self.python_docs)
110 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000111 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000112 text.bind("<<open-module>>", self.open_module)
113 text.bind("<<do-nothing>>", lambda event: "break")
114 text.bind("<<select-all>>", self.select_all)
115 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000116 text.bind("<<find>>", self.find_event)
117 text.bind("<<find-again>>", self.find_again_event)
118 text.bind("<<find-in-files>>", self.find_in_files_event)
119 text.bind("<<find-selection>>", self.find_selection_event)
120 text.bind("<<replace>>", self.replace_event)
121 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000122 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000123 text.bind("<<smart-backspace>>",self.smart_backspace_event)
124 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
125 text.bind("<<smart-indent>>",self.smart_indent_event)
126 text.bind("<<indent-region>>",self.indent_region_event)
127 text.bind("<<dedent-region>>",self.dedent_region_event)
128 text.bind("<<comment-region>>",self.comment_region_event)
129 text.bind("<<uncomment-region>>",self.uncomment_region_event)
130 text.bind("<<tabify-region>>",self.tabify_region_event)
131 text.bind("<<untabify-region>>",self.untabify_region_event)
132 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
133 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000134 text.bind("<Left>", self.move_at_edge_if_selection(0))
135 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000136
David Scherer7aced172000-08-15 01:13:23 +0000137 if flist:
138 flist.inversedict[self] = key
139 if key:
140 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000141 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000142 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
143 text.bind("<<open-class-browser>>", self.open_class_browser)
144 text.bind("<<open-path-browser>>", self.open_path_browser)
145
Steven M. Gava898a3652001-10-07 11:10:44 +0000146 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000147 vbar['command'] = text.yview
148 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000149 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000150 fontWeight='normal'
151 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
152 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000153 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000154 idleConf.GetOption('main','EditorWindow','font-size'),
155 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000156 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
157 text.pack(side=TOP, fill=BOTH, expand=1)
158 text.focus_set()
159
160 self.per = per = self.Percolator(text)
161 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000162 self.color = color = self.ColorDelegator()
163 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000164 else:
David Scherer7aced172000-08-15 01:13:23 +0000165 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000166
167 self.undo = undo = self.UndoDelegator()
168 per.insertfilter(undo)
169 text.undo_block_start = undo.undo_block_start
170 text.undo_block_stop = undo.undo_block_stop
171 undo.set_saved_change_hook(self.saved_change_hook)
172
173 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000174 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000175 io.set_filename_change_hook(self.filename_change_hook)
176
Steven M. Gava1d46e402002-03-27 08:40:46 +0000177 #create the Recent Files submenu
178 self.menuRecentFiles=Menu(self.menubar)
179 self.menudict['file'].insert_cascade(3,label='Recent Files',
180 underline=0,menu=self.menuRecentFiles)
181 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000182
David Scherer7aced172000-08-15 01:13:23 +0000183 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000184 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000185 io.loadfile(filename)
186 else:
187 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000188 self.saved_change_hook()
189
190 self.load_extensions()
191
192 menu = self.menudict.get('windows')
193 if menu:
194 end = menu.index("end")
195 if end is None:
196 end = -1
197 if end >= 0:
198 menu.add_separator()
199 end = end + 1
200 self.wmenu_end = end
201 WindowList.register_callback(self.postwindowsmenu)
202
203 # Some abstractions so IDLE extensions are cross-IDE
204 self.askyesno = tkMessageBox.askyesno
205 self.askinteger = tkSimpleDialog.askinteger
206 self.showerror = tkMessageBox.showerror
207
208 if self.extensions.has_key('AutoIndent'):
209 self.extensions['AutoIndent'].set_indentation_params(
210 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000211
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000212 def new_callback(self, event):
213 dirname, basename = self.io.defaultfilename()
214 self.flist.new(dirname)
215 return "break"
216
David Scherer7aced172000-08-15 01:13:23 +0000217 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000218 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000219 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
220 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
221 self.status_bar.pack(side=BOTTOM, fill=X)
222 self.text.bind('<KeyRelease>', self.set_line_and_column)
223 self.text.bind('<ButtonRelease>', self.set_line_and_column)
224 self.text.after_idle(self.set_line_and_column)
225
226 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000227 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000228 self.status_bar.set_label('column', 'Col: %s' % column)
229 self.status_bar.set_label('line', 'Ln: %s' % line)
230
231 def wakeup(self):
232 if self.top.wm_state() == "iconic":
233 self.top.wm_deiconify()
234 else:
235 self.top.tkraise()
236 self.text.focus_set()
237
238 menu_specs = [
239 ("file", "_File"),
240 ("edit", "_Edit"),
241 ("format", "F_ormat"),
242 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000243 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000244 ("windows", "_Windows"),
245 ("help", "_Help"),
246 ]
247
248 def createmenubar(self):
249 mbar = self.menubar
250 self.menudict = menudict = {}
251 for name, label in self.menu_specs:
252 underline, label = prepstr(label)
253 menudict[name] = menu = Menu(mbar, name=name)
254 mbar.add_cascade(label=label, menu=menu, underline=underline)
255 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000256 self.base_helpmenu_length = self.menudict['help'].index(END)
257 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000258
259 def postwindowsmenu(self):
260 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000261 menu = self.menudict['windows']
262 end = menu.index("end")
263 if end is None:
264 end = -1
265 if end > self.wmenu_end:
266 menu.delete(self.wmenu_end+1, end)
267 WindowList.add_windows_to_menu(menu)
268
269 rmenu = None
270
271 def right_menu_event(self, event):
272 self.text.tag_remove("sel", "1.0", "end")
273 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
274 if not self.rmenu:
275 self.make_rmenu()
276 rmenu = self.rmenu
277 self.event = event
278 iswin = sys.platform[:3] == 'win'
279 if iswin:
280 self.text.config(cursor="arrow")
281 rmenu.tk_popup(event.x_root, event.y_root)
282 if iswin:
283 self.text.config(cursor="ibeam")
284
285 rmenu_specs = [
286 # ("Label", "<<virtual-event>>"), ...
287 ("Close", "<<close-window>>"), # Example
288 ]
289
290 def make_rmenu(self):
291 rmenu = Menu(self.text, tearoff=0)
292 for label, eventname in self.rmenu_specs:
293 def command(text=self.text, eventname=eventname):
294 text.event_generate(eventname)
295 rmenu.add_command(label=label, command=command)
296 self.rmenu = rmenu
297
298 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000299 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000300
Steven M. Gava3b55a892001-11-21 05:56:26 +0000301 def config_dialog(self, event=None):
302 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000303
David Scherer7aced172000-08-15 01:13:23 +0000304 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000305 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000306 textView.TextViewer(self.top,'Help',fn)
307
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000308 def python_docs(self, event=None):
309 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000310 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000311 return "break"
312 else:
313 webbrowser.open(self.help_url)
314 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000315
316 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000317 if not (url.startswith('www') or url.startswith('http')):
318 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000319 if sys.platform.count('win') or sys.platform.count('nt'):
320 os.startfile(url)
321 else:
322 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000323
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000324 def cut(self,event):
325 self.text.event_generate("<<Cut>>")
326 return "break"
327
328 def copy(self,event):
329 self.text.event_generate("<<Copy>>")
330 return "break"
331
332 def paste(self,event):
333 self.text.event_generate("<<Paste>>")
334 return "break"
335
David Scherer7aced172000-08-15 01:13:23 +0000336 def select_all(self, event=None):
337 self.text.tag_add("sel", "1.0", "end-1c")
338 self.text.mark_set("insert", "1.0")
339 self.text.see("insert")
340 return "break"
341
342 def remove_selection(self, event=None):
343 self.text.tag_remove("sel", "1.0", "end")
344 self.text.see("insert")
345
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000346 def move_at_edge_if_selection(self, edge_index):
347 """Cursor move begins at start or end of selection
348
349 When a left/right cursor key is pressed create and return to Tkinter a
350 function which causes a cursor move from the associated edge of the
351 selection.
352
353 """
354 self_text_index = self.text.index
355 self_text_mark_set = self.text.mark_set
356 edges_table = ("sel.first+1c", "sel.last-1c")
357 def move_at_edge(event):
358 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
359 try:
360 self_text_index("sel.first")
361 self_text_mark_set("insert", edges_table[edge_index])
362 except TclError:
363 pass
364 return move_at_edge
365
Steven M. Gavac5976402002-01-04 03:06:08 +0000366 def find_event(self, event):
367 SearchDialog.find(self.text)
368 return "break"
369
370 def find_again_event(self, event):
371 SearchDialog.find_again(self.text)
372 return "break"
373
374 def find_selection_event(self, event):
375 SearchDialog.find_selection(self.text)
376 return "break"
377
378 def find_in_files_event(self, event):
379 GrepDialog.grep(self.text, self.io, self.flist)
380 return "break"
381
382 def replace_event(self, event):
383 ReplaceDialog.replace(self.text)
384 return "break"
385
386 def goto_line_event(self, event):
387 text = self.text
388 lineno = tkSimpleDialog.askinteger("Goto",
389 "Go to line number:",parent=text)
390 if lineno is None:
391 return "break"
392 if lineno <= 0:
393 text.bell()
394 return "break"
395 text.mark_set("insert", "%d.0" % lineno)
396 text.see("insert")
397
David Scherer7aced172000-08-15 01:13:23 +0000398 def open_module(self, event=None):
399 # XXX Shouldn't this be in IOBinding or in FileList?
400 try:
401 name = self.text.get("sel.first", "sel.last")
402 except TclError:
403 name = ""
404 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000405 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000406 name = tkSimpleDialog.askstring("Module",
407 "Enter the name of a Python module\n"
408 "to search on sys.path and open:",
409 parent=self.text, initialvalue=name)
410 if name:
411 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000412 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000413 return
David Scherer7aced172000-08-15 01:13:23 +0000414 # XXX Ought to insert current file's directory in front of path
415 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000416 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000417 except (NameError, ImportError), msg:
418 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
419 return
420 if type != imp.PY_SOURCE:
421 tkMessageBox.showerror("Unsupported type",
422 "%s is not a source module" % name, parent=self.text)
423 return
424 if f:
425 f.close()
426 if self.flist:
427 self.flist.open(file)
428 else:
429 self.io.loadfile(file)
430
431 def open_class_browser(self, event=None):
432 filename = self.io.filename
433 if not filename:
434 tkMessageBox.showerror(
435 "No filename",
436 "This buffer has no associated filename",
437 master=self.text)
438 self.text.focus_set()
439 return None
440 head, tail = os.path.split(filename)
441 base, ext = os.path.splitext(tail)
442 import ClassBrowser
443 ClassBrowser.ClassBrowser(self.flist, base, [head])
444
445 def open_path_browser(self, event=None):
446 import PathBrowser
447 PathBrowser.PathBrowser(self.flist)
448
449 def gotoline(self, lineno):
450 if lineno is not None and lineno > 0:
451 self.text.mark_set("insert", "%d.0" % lineno)
452 self.text.tag_remove("sel", "1.0", "end")
453 self.text.tag_add("sel", "insert", "insert +1l")
454 self.center()
455
456 def ispythonsource(self, filename):
457 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000458 return True
David Scherer7aced172000-08-15 01:13:23 +0000459 base, ext = os.path.splitext(os.path.basename(filename))
460 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000461 return True
David Scherer7aced172000-08-15 01:13:23 +0000462 try:
463 f = open(filename)
464 line = f.readline()
465 f.close()
466 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000467 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000468 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000469
470 def close_hook(self):
471 if self.flist:
472 self.flist.close_edit(self)
473
474 def set_close_hook(self, close_hook):
475 self.close_hook = close_hook
476
477 def filename_change_hook(self):
478 if self.flist:
479 self.flist.filename_changed_edit(self)
480 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000481 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000482 if self.ispythonsource(self.io.filename):
483 self.addcolorizer()
484 else:
485 self.rmcolorizer()
486
487 def addcolorizer(self):
488 if self.color:
489 return
David Scherer7aced172000-08-15 01:13:23 +0000490 self.per.removefilter(self.undo)
491 self.color = self.ColorDelegator()
492 self.per.insertfilter(self.color)
493 self.per.insertfilter(self.undo)
494
495 def rmcolorizer(self):
496 if not self.color:
497 return
David Scherer7aced172000-08-15 01:13:23 +0000498 self.per.removefilter(self.undo)
499 self.per.removefilter(self.color)
500 self.color = None
501 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000502
Steven M. Gavab77d3432002-03-02 07:16:21 +0000503 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000504 "Update the colour theme if it is changed"
505 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000506 if self.color:
507 self.color = self.ColorDelegator()
508 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000509
Steven M. Gavab1585412002-03-12 00:21:56 +0000510 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000511 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000512 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000513 fontWeight='normal'
514 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
515 fontWeight='bold'
516 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
517 idleConf.GetOption('main','EditorWindow','font-size'),
518 fontWeight))
519
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000520 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000521 "Update the keybindings if they are changed"
522 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000523 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
524 keydefs = self.Bindings.default_keydefs
525 for event, keylist in keydefs.items():
526 self.text.event_delete(event)
527 self.apply_bindings()
528 #update menu accelerators
529 menuEventDict={}
530 for menu in self.Bindings.menudefs:
531 menuEventDict[menu[0]]={}
532 for item in menu[1]:
533 if item:
534 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
535 for menubarItem in self.menudict.keys():
536 menu=self.menudict[menubarItem]
537 end=menu.index(END)+1
538 for index in range(0,end):
539 if menu.type(index)=='command':
540 accel=menu.entrycget(index,'accelerator')
541 if accel:
542 itemName=menu.entrycget(index,'label')
543 event=''
544 if menuEventDict.has_key(menubarItem):
545 if menuEventDict[menubarItem].has_key(itemName):
546 event=menuEventDict[menubarItem][itemName]
547 if event:
548 #print 'accel was:',accel
549 accel=get_accelerator(keydefs, event)
550 menu.entryconfig(index,accelerator=accel)
551 #print 'accel now:',accel,'\n'
552
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000553 def reset_help_menu_entries(self):
554 "Update the additional help entries on the Help menu"
555 help_list = idleConf.GetAllExtraHelpSourcesList()
556 helpmenu = self.menudict['help']
557 # first delete the extra help entries, if any
558 helpmenu_length = helpmenu.index(END)
559 if helpmenu_length > self.base_helpmenu_length:
560 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
561 # then rebuild them
562 if help_list:
563 helpmenu.add_separator()
564 for entry in help_list:
565 cmd = self.__extra_help_callback(entry[1])
566 helpmenu.add_command(label=entry[0], command=cmd)
567 # and update the menu dictionary
568 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000569
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000570 def __extra_help_callback(self, helpfile):
571 "Create a callback with the helpfile value frozen at definition time"
572 def display_extra_help(helpfile=helpfile):
573 self.display_docs(helpfile)
574 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000575
Steven M. Gava1d46e402002-03-27 08:40:46 +0000576 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000577 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000578 rfList=[]
579 if os.path.exists(self.recentFilesPath):
580 RFfile=open(self.recentFilesPath,'r')
581 try:
582 rfList=RFfile.readlines()
583 finally:
584 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000585 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000586 newFile=os.path.abspath(newFile)+'\n'
587 if newFile in rfList:
588 rfList.remove(newFile)
589 rfList.insert(0,newFile)
590 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000591 #print self.flist.inversedict
592 #print self.top.instanceDict
593 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000594 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000595 if rfList:
596 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000597 menu = instance.menuRecentFiles
598 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000599 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000600 for file in rfList:
601 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000602 callback = instance.__RecentFileCallback(fileName)
603 if i > ullen: # don't underline menuitems
604 ul=None
605 menu.add_command(label=ullist[i] + " " + fileName,
606 command=callback,
607 underline=ul)
608 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000609
Steven M. Gava1d46e402002-03-27 08:40:46 +0000610 def __CleanRecentFiles(self,rfList):
611 origRfList=rfList[:]
612 count=0
613 nonFiles=[]
614 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000615 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000616 nonFiles.append(count)
617 count=count+1
618 if nonFiles:
619 nonFiles.reverse()
620 for index in nonFiles:
621 del(rfList[index])
622 if len(rfList)>19:
623 rfList=rfList[0:19]
624 #if rfList != origRfList:
625 RFfile=open(self.recentFilesPath,'w')
626 try:
627 RFfile.writelines(rfList)
628 finally:
629 RFfile.close()
630 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000631
Steven M. Gava1d46e402002-03-27 08:40:46 +0000632 def __RecentFileCallback(self,fileName):
633 def OpenRecentFile(fileName=fileName):
634 self.io.open(editFile=fileName)
635 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636
David Scherer7aced172000-08-15 01:13:23 +0000637 def saved_change_hook(self):
638 short = self.short_title()
639 long = self.long_title()
640 if short and long:
641 title = short + " - " + long
642 elif short:
643 title = short
644 elif long:
645 title = long
646 else:
647 title = "Untitled"
648 icon = short or long or title
649 if not self.get_saved():
650 title = "*%s*" % title
651 icon = "*%s" % icon
652 self.top.wm_title(title)
653 self.top.wm_iconname(icon)
654
655 def get_saved(self):
656 return self.undo.get_saved()
657
658 def set_saved(self, flag):
659 self.undo.set_saved(flag)
660
661 def reset_undo(self):
662 self.undo.reset_undo()
663
664 def short_title(self):
665 filename = self.io.filename
666 if filename:
667 filename = os.path.basename(filename)
668 return filename
669
670 def long_title(self):
671 return self.io.filename or ""
672
673 def center_insert_event(self, event):
674 self.center()
675
676 def center(self, mark="insert"):
677 text = self.text
678 top, bot = self.getwindowlines()
679 lineno = self.getlineno(mark)
680 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000681 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000682 text.yview(float(newtop))
683
684 def getwindowlines(self):
685 text = self.text
686 top = self.getlineno("@0,0")
687 bot = self.getlineno("@0,65535")
688 if top == bot and text.winfo_height() == 1:
689 # Geometry manager hasn't run yet
690 height = int(text['height'])
691 bot = top + height - 1
692 return top, bot
693
694 def getlineno(self, mark="insert"):
695 text = self.text
696 return int(float(text.index(mark)))
697
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000698 def get_geometry(self):
699 "Return (width, height, x, y)"
700 geom = self.top.wm_geometry()
701 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
702 tuple = (map(int, m.groups()))
703 return tuple
704
David Scherer7aced172000-08-15 01:13:23 +0000705 def close_event(self, event):
706 self.close()
707
708 def maybesave(self):
709 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000710 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000711 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000712 self.top.deiconify()
713 self.top.lower()
714 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000715 return self.io.maybesave()
716
717 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000718 reply = self.maybesave()
719 if reply != "cancel":
720 self._close()
721 return reply
722
723 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000724 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000725 if self.io.filename:
726 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000727 WindowList.unregister_callback(self.postwindowsmenu)
728 if self.close_hook:
729 self.close_hook()
730 self.flist = None
731 colorizing = 0
732 self.unload_extensions()
733 self.io.close(); self.io = None
734 self.undo = None # XXX
735 if self.color:
736 colorizing = self.color.colorizing
737 doh = colorizing and self.top
738 self.color.close(doh) # Cancel colorization
739 self.text = None
740 self.vars = None
741 self.per.close(); self.per = None
742 if not colorizing:
743 self.top.destroy()
744
745 def load_extensions(self):
746 self.extensions = {}
747 self.load_standard_extensions()
748
749 def unload_extensions(self):
750 for ins in self.extensions.values():
751 if hasattr(ins, "close"):
752 ins.close()
753 self.extensions = {}
754
755 def load_standard_extensions(self):
756 for name in self.get_standard_extension_names():
757 try:
758 self.load_extension(name)
759 except:
760 print "Failed to load extension", `name`
761 import traceback
762 traceback.print_exc()
763
764 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000765 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000766
767 def load_extension(self, name):
768 mod = __import__(name, globals(), locals(), [])
769 cls = getattr(mod, name)
770 ins = cls(self)
771 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000772 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000773 if keydefs:
774 self.apply_bindings(keydefs)
775 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000776 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000777 while methodname[:1] == '<':
778 methodname = methodname[1:]
779 while methodname[-1:] == '>':
780 methodname = methodname[:-1]
781 methodname = methodname + "_event"
782 if hasattr(ins, methodname):
783 self.text.bind(vevent, getattr(ins, methodname))
784 if hasattr(ins, "menudefs"):
785 self.fill_menus(ins.menudefs, keydefs)
786 return ins
787
788 def apply_bindings(self, keydefs=None):
789 if keydefs is None:
790 keydefs = self.Bindings.default_keydefs
791 text = self.text
792 text.keydefs = keydefs
793 for event, keylist in keydefs.items():
794 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000795 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000796
797 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000798 """Add appropriate entries to the menus and submenus
799
800 Menus that are absent or None in self.menudict are ignored.
801 """
David Scherer7aced172000-08-15 01:13:23 +0000802 if defs is None:
803 defs = self.Bindings.menudefs
804 if keydefs is None:
805 keydefs = self.Bindings.default_keydefs
806 menudict = self.menudict
807 text = self.text
808 for mname, itemlist in defs:
809 menu = menudict.get(mname)
810 if not menu:
811 continue
812 for item in itemlist:
813 if not item:
814 menu.add_separator()
815 else:
816 label, event = item
817 checkbutton = (label[:1] == '!')
818 if checkbutton:
819 label = label[1:]
820 underline, label = prepstr(label)
821 accelerator = get_accelerator(keydefs, event)
822 def command(text=text, event=event):
823 text.event_generate(event)
824 if checkbutton:
825 var = self.getrawvar(event, BooleanVar)
826 menu.add_checkbutton(label=label, underline=underline,
827 command=command, accelerator=accelerator,
828 variable=var)
829 else:
830 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000831 command=command,
832 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000833
834 def getvar(self, name):
835 var = self.getrawvar(name)
836 if var:
837 return var.get()
838
839 def setvar(self, name, value, vartype=None):
840 var = self.getrawvar(name, vartype)
841 if var:
842 var.set(value)
843
844 def getrawvar(self, name, vartype=None):
845 var = self.vars.get(name)
846 if not var and vartype:
847 self.vars[name] = var = vartype(self.text)
848 return var
849
850 # Tk implementations of "virtual text methods" -- each platform
851 # reusing IDLE's support code needs to define these for its GUI's
852 # flavor of widget.
853
854 # Is character at text_index in a Python string? Return 0 for
855 # "guaranteed no", true for anything else. This info is expensive
856 # to compute ab initio, but is probably already known by the
857 # platform's colorizer.
858
859 def is_char_in_string(self, text_index):
860 if self.color:
861 # Return true iff colorizer hasn't (re)gotten this far
862 # yet, or the character is tagged as being in a string
863 return self.text.tag_prevrange("TODO", text_index) or \
864 "STRING" in self.text.tag_names(text_index)
865 else:
866 # The colorizer is missing: assume the worst
867 return 1
868
869 # If a selection is defined in the text widget, return (start,
870 # end) as Tkinter text indices, otherwise return (None, None)
871 def get_selection_indices(self):
872 try:
873 first = self.text.index("sel.first")
874 last = self.text.index("sel.last")
875 return first, last
876 except TclError:
877 return None, None
878
879 # Return the text widget's current view of what a tab stop means
880 # (equivalent width in spaces).
881
882 def get_tabwidth(self):
883 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
884 return int(current)
885
886 # Set the text widget's current view of what a tab stop means.
887
888 def set_tabwidth(self, newtabwidth):
889 text = self.text
890 if self.get_tabwidth() != newtabwidth:
891 pixels = text.tk.call("font", "measure", text["font"],
892 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000893 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000894 text.configure(tabs=pixels)
895
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000896### begin autoindent code ###
897
898 # usetabs true -> literal tab characters are used by indent and
899 # dedent cmds, possibly mixed with spaces if
900 # indentwidth is not a multiple of tabwidth
901 # false -> tab characters are converted to spaces by indent
902 # and dedent cmds, and ditto TAB keystrokes
903 # indentwidth is the number of characters per logical indent level.
904 # tabwidth is the display width of a literal tab character.
905 # CAUTION: telling Tk to use anything other than its default
906 # tab setting causes it to use an entirely different tabbing algorithm,
907 # treating tab stops as fixed distances from the left margin.
908 # Nobody expects this, so for now tabwidth should never be changed.
909 usetabs = 0
910 indentwidth = 4
911 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
912
913 # If context_use_ps1 is true, parsing searches back for a ps1 line;
914 # else searches for a popular (if, def, ...) Python stmt.
915 context_use_ps1 = 0
916
917 # When searching backwards for a reliable place to begin parsing,
918 # first start num_context_lines[0] lines back, then
919 # num_context_lines[1] lines back if that didn't work, and so on.
920 # The last value should be huge (larger than the # of lines in a
921 # conceivable file).
922 # Making the initial values larger slows things down more often.
923 num_context_lines = 50, 500, 5000000
924
925 def config(self, **options):
926 for key, value in options.items():
927 if key == 'usetabs':
928 self.usetabs = value
929 elif key == 'indentwidth':
930 self.indentwidth = value
931 elif key == 'tabwidth':
932 self.tabwidth = value
933 elif key == 'context_use_ps1':
934 self.context_use_ps1 = value
935 else:
936 raise KeyError, "bad option name: %s" % `key`
937
938 # If ispythonsource and guess are true, guess a good value for
939 # indentwidth based on file content (if possible), and if
940 # indentwidth != tabwidth set usetabs false.
941 # In any case, adjust the Text widget's view of what a tab
942 # character means.
943
944 def set_indentation_params(self, ispythonsource, guess=1):
945 if guess and ispythonsource:
946 i = self.guess_indent()
947 if 2 <= i <= 8:
948 self.indentwidth = i
949 if self.indentwidth != self.tabwidth:
950 self.usetabs = 0
951
952 self.set_tabwidth(self.tabwidth)
953
954 def smart_backspace_event(self, event):
955 text = self.text
956 first, last = self.get_selection_indices()
957 if first and last:
958 text.delete(first, last)
959 text.mark_set("insert", first)
960 return "break"
961 # Delete whitespace left, until hitting a real char or closest
962 # preceding virtual tab stop.
963 chars = text.get("insert linestart", "insert")
964 if chars == '':
965 if text.compare("insert", ">", "1.0"):
966 # easy: delete preceding newline
967 text.delete("insert-1c")
968 else:
969 text.bell() # at start of buffer
970 return "break"
971 if chars[-1] not in " \t":
972 # easy: delete preceding real char
973 text.delete("insert-1c")
974 return "break"
975 # Ick. It may require *inserting* spaces if we back up over a
976 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000977 tabwidth = self.tabwidth
978 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000979 assert have > 0
980 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000981 # Debug prompt is multilined....
982 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000983 ncharsdeleted = 0
984 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000985 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000986 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000987 chars = chars[:-1]
988 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000989 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000990 if have <= want or chars[-1] not in " \t":
991 break
992 text.undo_block_start()
993 text.delete("insert-%dc" % ncharsdeleted, "insert")
994 if have < want:
995 text.insert("insert", ' ' * (want - have))
996 text.undo_block_stop()
997 return "break"
998
999 def smart_indent_event(self, event):
1000 # if intraline selection:
1001 # delete it
1002 # elif multiline selection:
1003 # do indent-region & return
1004 # indent one level
1005 text = self.text
1006 first, last = self.get_selection_indices()
1007 text.undo_block_start()
1008 try:
1009 if first and last:
1010 if index2line(first) != index2line(last):
1011 return self.indent_region_event(event)
1012 text.delete(first, last)
1013 text.mark_set("insert", first)
1014 prefix = text.get("insert linestart", "insert")
1015 raw, effective = classifyws(prefix, self.tabwidth)
1016 if raw == len(prefix):
1017 # only whitespace to the left
1018 self.reindent_to(effective + self.indentwidth)
1019 else:
1020 if self.usetabs:
1021 pad = '\t'
1022 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001023 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001024 n = self.indentwidth
1025 pad = ' ' * (n - effective % n)
1026 text.insert("insert", pad)
1027 text.see("insert")
1028 return "break"
1029 finally:
1030 text.undo_block_stop()
1031
1032 def newline_and_indent_event(self, event):
1033 text = self.text
1034 first, last = self.get_selection_indices()
1035 text.undo_block_start()
1036 try:
1037 if first and last:
1038 text.delete(first, last)
1039 text.mark_set("insert", first)
1040 line = text.get("insert linestart", "insert")
1041 i, n = 0, len(line)
1042 while i < n and line[i] in " \t":
1043 i = i+1
1044 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001045 # the cursor is in or at leading indentation in a continuation
1046 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001047 text.insert("insert linestart", '\n')
1048 return "break"
1049 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001050 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001051 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001052 last_line_of_prompt = sys.ps1.split('\n')[-1]
1053 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001054 line = line[:-1]
1055 i = i+1
1056 if i:
1057 text.delete("insert - %d chars" % i, "insert")
1058 # strip whitespace after insert point
1059 while text.get("insert") in " \t":
1060 text.delete("insert")
1061 # start new line
1062 text.insert("insert", '\n')
1063
1064 # adjust indentation for continuations and block
1065 # open/close first need to find the last stmt
1066 lno = index2line(text.index('insert'))
1067 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1068 for context in self.num_context_lines:
1069 startat = max(lno - context, 1)
1070 startatindex = `startat` + ".0"
1071 rawtext = text.get(startatindex, "insert")
1072 y.set_str(rawtext)
1073 bod = y.find_good_parse_start(
1074 self.context_use_ps1,
1075 self._build_char_in_string_func(startatindex))
1076 if bod is not None or startat == 1:
1077 break
1078 y.set_lo(bod or 0)
1079 c = y.get_continuation_type()
1080 if c != PyParse.C_NONE:
1081 # The current stmt hasn't ended yet.
1082 if c == PyParse.C_STRING:
1083 # inside a string; just mimic the current indent
1084 text.insert("insert", indent)
1085 elif c == PyParse.C_BRACKET:
1086 # line up with the first (if any) element of the
1087 # last open bracket structure; else indent one
1088 # level beyond the indent of the line with the
1089 # last open bracket
1090 self.reindent_to(y.compute_bracket_indent())
1091 elif c == PyParse.C_BACKSLASH:
1092 # if more than one line in this stmt already, just
1093 # mimic the current indent; else if initial line
1094 # has a start on an assignment stmt, indent to
1095 # beyond leftmost =; else to beyond first chunk of
1096 # non-whitespace on initial line
1097 if y.get_num_lines_in_stmt() > 1:
1098 text.insert("insert", indent)
1099 else:
1100 self.reindent_to(y.compute_backslash_indent())
1101 else:
1102 assert 0, "bogus continuation type " + `c`
1103 return "break"
1104
1105 # This line starts a brand new stmt; indent relative to
1106 # indentation of initial line of closest preceding
1107 # interesting stmt.
1108 indent = y.get_base_indent_string()
1109 text.insert("insert", indent)
1110 if y.is_block_opener():
1111 self.smart_indent_event(event)
1112 elif indent and y.is_block_closer():
1113 self.smart_backspace_event(event)
1114 return "break"
1115 finally:
1116 text.see("insert")
1117 text.undo_block_stop()
1118
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001119 # Our editwin provides a is_char_in_string function that works
1120 # with a Tk text index, but PyParse only knows about offsets into
1121 # a string. This builds a function for PyParse that accepts an
1122 # offset.
1123
1124 def _build_char_in_string_func(self, startindex):
1125 def inner(offset, _startindex=startindex,
1126 _icis=self.is_char_in_string):
1127 return _icis(_startindex + "+%dc" % offset)
1128 return inner
1129
1130 def indent_region_event(self, event):
1131 head, tail, chars, lines = self.get_region()
1132 for pos in range(len(lines)):
1133 line = lines[pos]
1134 if line:
1135 raw, effective = classifyws(line, self.tabwidth)
1136 effective = effective + self.indentwidth
1137 lines[pos] = self._make_blanks(effective) + line[raw:]
1138 self.set_region(head, tail, chars, lines)
1139 return "break"
1140
1141 def dedent_region_event(self, event):
1142 head, tail, chars, lines = self.get_region()
1143 for pos in range(len(lines)):
1144 line = lines[pos]
1145 if line:
1146 raw, effective = classifyws(line, self.tabwidth)
1147 effective = max(effective - self.indentwidth, 0)
1148 lines[pos] = self._make_blanks(effective) + line[raw:]
1149 self.set_region(head, tail, chars, lines)
1150 return "break"
1151
1152 def comment_region_event(self, event):
1153 head, tail, chars, lines = self.get_region()
1154 for pos in range(len(lines) - 1):
1155 line = lines[pos]
1156 lines[pos] = '##' + line
1157 self.set_region(head, tail, chars, lines)
1158
1159 def uncomment_region_event(self, event):
1160 head, tail, chars, lines = self.get_region()
1161 for pos in range(len(lines)):
1162 line = lines[pos]
1163 if not line:
1164 continue
1165 if line[:2] == '##':
1166 line = line[2:]
1167 elif line[:1] == '#':
1168 line = line[1:]
1169 lines[pos] = line
1170 self.set_region(head, tail, chars, lines)
1171
1172 def tabify_region_event(self, event):
1173 head, tail, chars, lines = self.get_region()
1174 tabwidth = self._asktabwidth()
1175 for pos in range(len(lines)):
1176 line = lines[pos]
1177 if line:
1178 raw, effective = classifyws(line, tabwidth)
1179 ntabs, nspaces = divmod(effective, tabwidth)
1180 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1181 self.set_region(head, tail, chars, lines)
1182
1183 def untabify_region_event(self, event):
1184 head, tail, chars, lines = self.get_region()
1185 tabwidth = self._asktabwidth()
1186 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001187 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001188 self.set_region(head, tail, chars, lines)
1189
1190 def toggle_tabs_event(self, event):
1191 if self.askyesno(
1192 "Toggle tabs",
1193 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1194 parent=self.text):
1195 self.usetabs = not self.usetabs
1196 return "break"
1197
1198 # XXX this isn't bound to anything -- see class tabwidth comments
1199 def change_tabwidth_event(self, event):
1200 new = self._asktabwidth()
1201 if new != self.tabwidth:
1202 self.tabwidth = new
1203 self.set_indentation_params(0, guess=0)
1204 return "break"
1205
1206 def change_indentwidth_event(self, event):
1207 new = self.askinteger(
1208 "Indent width",
1209 "New indent width (2-16)",
1210 parent=self.text,
1211 initialvalue=self.indentwidth,
1212 minvalue=2,
1213 maxvalue=16)
1214 if new and new != self.indentwidth:
1215 self.indentwidth = new
1216 return "break"
1217
1218 def get_region(self):
1219 text = self.text
1220 first, last = self.get_selection_indices()
1221 if first and last:
1222 head = text.index(first + " linestart")
1223 tail = text.index(last + "-1c lineend +1c")
1224 else:
1225 head = text.index("insert linestart")
1226 tail = text.index("insert lineend +1c")
1227 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001228 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001229 return head, tail, chars, lines
1230
1231 def set_region(self, head, tail, chars, lines):
1232 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001233 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001234 if newchars == chars:
1235 text.bell()
1236 return
1237 text.tag_remove("sel", "1.0", "end")
1238 text.mark_set("insert", head)
1239 text.undo_block_start()
1240 text.delete(head, tail)
1241 text.insert(head, newchars)
1242 text.undo_block_stop()
1243 text.tag_add("sel", head, "insert")
1244
1245 # Make string that displays as n leading blanks.
1246
1247 def _make_blanks(self, n):
1248 if self.usetabs:
1249 ntabs, nspaces = divmod(n, self.tabwidth)
1250 return '\t' * ntabs + ' ' * nspaces
1251 else:
1252 return ' ' * n
1253
1254 # Delete from beginning of line to insert point, then reinsert
1255 # column logical (meaning use tabs if appropriate) spaces.
1256
1257 def reindent_to(self, column):
1258 text = self.text
1259 text.undo_block_start()
1260 if text.compare("insert linestart", "!=", "insert"):
1261 text.delete("insert linestart", "insert")
1262 if column:
1263 text.insert("insert", self._make_blanks(column))
1264 text.undo_block_stop()
1265
1266 def _asktabwidth(self):
1267 return self.askinteger(
1268 "Tab width",
1269 "Spaces per tab? (2-16)",
1270 parent=self.text,
1271 initialvalue=self.indentwidth,
1272 minvalue=2,
1273 maxvalue=16) or self.tabwidth
1274
1275 # Guess indentwidth from text content.
1276 # Return guessed indentwidth. This should not be believed unless
1277 # it's in a reasonable range (e.g., it will be 0 if no indented
1278 # blocks are found).
1279
1280 def guess_indent(self):
1281 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1282 if opener and indented:
1283 raw, indentsmall = classifyws(opener, self.tabwidth)
1284 raw, indentlarge = classifyws(indented, self.tabwidth)
1285 else:
1286 indentsmall = indentlarge = 0
1287 return indentlarge - indentsmall
1288
1289# "line.col" -> line, as an int
1290def index2line(index):
1291 return int(float(index))
1292
1293# Look at the leading whitespace in s.
1294# Return pair (# of leading ws characters,
1295# effective # of leading blanks after expanding
1296# tabs to width tabwidth)
1297
1298def classifyws(s, tabwidth):
1299 raw = effective = 0
1300 for ch in s:
1301 if ch == ' ':
1302 raw = raw + 1
1303 effective = effective + 1
1304 elif ch == '\t':
1305 raw = raw + 1
1306 effective = (effective // tabwidth + 1) * tabwidth
1307 else:
1308 break
1309 return raw, effective
1310
1311import tokenize
1312_tokenize = tokenize
1313del tokenize
1314
1315class IndentSearcher:
1316
1317 # .run() chews over the Text widget, looking for a block opener
1318 # and the stmt following it. Returns a pair,
1319 # (line containing block opener, line containing stmt)
1320 # Either or both may be None.
1321
1322 def __init__(self, text, tabwidth):
1323 self.text = text
1324 self.tabwidth = tabwidth
1325 self.i = self.finished = 0
1326 self.blkopenline = self.indentedline = None
1327
1328 def readline(self):
1329 if self.finished:
1330 return ""
1331 i = self.i = self.i + 1
1332 mark = `i` + ".0"
1333 if self.text.compare(mark, ">=", "end"):
1334 return ""
1335 return self.text.get(mark, mark + " lineend+1c")
1336
1337 def tokeneater(self, type, token, start, end, line,
1338 INDENT=_tokenize.INDENT,
1339 NAME=_tokenize.NAME,
1340 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1341 if self.finished:
1342 pass
1343 elif type == NAME and token in OPENERS:
1344 self.blkopenline = line
1345 elif type == INDENT and self.blkopenline:
1346 self.indentedline = line
1347 self.finished = 1
1348
1349 def run(self):
1350 save_tabsize = _tokenize.tabsize
1351 _tokenize.tabsize = self.tabwidth
1352 try:
1353 try:
1354 _tokenize.tokenize(self.readline, self.tokeneater)
1355 except _tokenize.TokenError:
1356 # since we cut off the tokenizer early, we can trigger
1357 # spurious errors
1358 pass
1359 finally:
1360 _tokenize.tabsize = save_tabsize
1361 return self.blkopenline, self.indentedline
1362
1363### end autoindent code ###
1364
David Scherer7aced172000-08-15 01:13:23 +00001365def prepstr(s):
1366 # Helper to extract the underscore from a string, e.g.
1367 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001368 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001369 if i >= 0:
1370 s = s[:i] + s[i+1:]
1371 return i, s
1372
1373
1374keynames = {
1375 'bracketleft': '[',
1376 'bracketright': ']',
1377 'slash': '/',
1378}
1379
1380def get_accelerator(keydefs, event):
1381 keylist = keydefs.get(event)
1382 if not keylist:
1383 return ""
1384 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001385 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001386 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1387 s = re.sub("Key-", "", s)
1388 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1389 s = re.sub("Control-", "Ctrl-", s)
1390 s = re.sub("-", "+", s)
1391 s = re.sub("><", " ", s)
1392 s = re.sub("<", "", s)
1393 s = re.sub(">", "", s)
1394 return s
1395
1396
1397def fixwordbreaks(root):
1398 # Make sure that Tk's double-click and next/previous word
1399 # operations use our definition of a word (i.e. an identifier)
1400 tk = root.tk
1401 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1402 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1403 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1404
1405
1406def test():
1407 root = Tk()
1408 fixwordbreaks(root)
1409 root.withdraw()
1410 if sys.argv[1:]:
1411 filename = sys.argv[1]
1412 else:
1413 filename = None
1414 edit = EditorWindow(root=root, filename=filename)
1415 edit.set_close_hook(root.quit)
1416 root.mainloop()
1417 root.destroy()
1418
1419if __name__ == '__main__':
1420 test()