blob: fcb1612f1f6d946d05941f6dfa1415725f5a8775 [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. Kaiser5ec186b2003-01-17 04:04:06 +0000135 text.bind("<Left>", self.move_at_edge_if_selection(0))
136 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000137
David Scherer7aced172000-08-15 01:13:23 +0000138 if flist:
139 flist.inversedict[self] = key
140 if key:
141 flist.dict[key] = self
142 text.bind("<<open-new-window>>", self.flist.new_callback)
143 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
144 text.bind("<<open-class-browser>>", self.open_class_browser)
145 text.bind("<<open-path-browser>>", self.open_path_browser)
146
Steven M. Gava898a3652001-10-07 11:10:44 +0000147 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000148 vbar['command'] = text.yview
149 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000150 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000151 fontWeight='normal'
152 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
153 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000154 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000155 idleConf.GetOption('main','EditorWindow','font-size'),
156 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000157 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
158 text.pack(side=TOP, fill=BOTH, expand=1)
159 text.focus_set()
160
161 self.per = per = self.Percolator(text)
162 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000163 self.color = color = self.ColorDelegator()
164 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000165 else:
David Scherer7aced172000-08-15 01:13:23 +0000166 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000167
168 self.undo = undo = self.UndoDelegator()
169 per.insertfilter(undo)
170 text.undo_block_start = undo.undo_block_start
171 text.undo_block_stop = undo.undo_block_stop
172 undo.set_saved_change_hook(self.saved_change_hook)
173
174 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000175 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000176 io.set_filename_change_hook(self.filename_change_hook)
177
Steven M. Gava1d46e402002-03-27 08:40:46 +0000178 #create the Recent Files submenu
179 self.menuRecentFiles=Menu(self.menubar)
180 self.menudict['file'].insert_cascade(3,label='Recent Files',
181 underline=0,menu=self.menuRecentFiles)
182 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000183
David Scherer7aced172000-08-15 01:13:23 +0000184 if filename:
185 if os.path.exists(filename):
186 io.loadfile(filename)
187 else:
188 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000189 self.saved_change_hook()
190
191 self.load_extensions()
192
193 menu = self.menudict.get('windows')
194 if menu:
195 end = menu.index("end")
196 if end is None:
197 end = -1
198 if end >= 0:
199 menu.add_separator()
200 end = end + 1
201 self.wmenu_end = end
202 WindowList.register_callback(self.postwindowsmenu)
203
204 # Some abstractions so IDLE extensions are cross-IDE
205 self.askyesno = tkMessageBox.askyesno
206 self.askinteger = tkSimpleDialog.askinteger
207 self.showerror = tkMessageBox.showerror
208
209 if self.extensions.has_key('AutoIndent'):
210 self.extensions['AutoIndent'].set_indentation_params(
211 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000212
David Scherer7aced172000-08-15 01:13:23 +0000213 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000214 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000215 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
216 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
217 self.status_bar.pack(side=BOTTOM, fill=X)
218 self.text.bind('<KeyRelease>', self.set_line_and_column)
219 self.text.bind('<ButtonRelease>', self.set_line_and_column)
220 self.text.after_idle(self.set_line_and_column)
221
222 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000223 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000224 self.status_bar.set_label('column', 'Col: %s' % column)
225 self.status_bar.set_label('line', 'Ln: %s' % line)
226
227 def wakeup(self):
228 if self.top.wm_state() == "iconic":
229 self.top.wm_deiconify()
230 else:
231 self.top.tkraise()
232 self.text.focus_set()
233
234 menu_specs = [
235 ("file", "_File"),
236 ("edit", "_Edit"),
237 ("format", "F_ormat"),
238 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000239 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000240 ("windows", "_Windows"),
241 ("help", "_Help"),
242 ]
243
244 def createmenubar(self):
245 mbar = self.menubar
246 self.menudict = menudict = {}
247 for name, label in self.menu_specs:
248 underline, label = prepstr(label)
249 menudict[name] = menu = Menu(mbar, name=name)
250 mbar.add_cascade(label=label, menu=menu, underline=underline)
251 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000252 self.base_helpmenu_length = self.menudict['help'].index(END)
253 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000254
255 def postwindowsmenu(self):
256 # Only called when Windows menu exists
257 # XXX Actually, this Just-In-Time updating interferes badly
258 # XXX with the tear-off feature. It would be better to update
259 # XXX all Windows menus whenever the list of windows changes.
260 menu = self.menudict['windows']
261 end = menu.index("end")
262 if end is None:
263 end = -1
264 if end > self.wmenu_end:
265 menu.delete(self.wmenu_end+1, end)
266 WindowList.add_windows_to_menu(menu)
267
268 rmenu = None
269
270 def right_menu_event(self, event):
271 self.text.tag_remove("sel", "1.0", "end")
272 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
273 if not self.rmenu:
274 self.make_rmenu()
275 rmenu = self.rmenu
276 self.event = event
277 iswin = sys.platform[:3] == 'win'
278 if iswin:
279 self.text.config(cursor="arrow")
280 rmenu.tk_popup(event.x_root, event.y_root)
281 if iswin:
282 self.text.config(cursor="ibeam")
283
284 rmenu_specs = [
285 # ("Label", "<<virtual-event>>"), ...
286 ("Close", "<<close-window>>"), # Example
287 ]
288
289 def make_rmenu(self):
290 rmenu = Menu(self.text, tearoff=0)
291 for label, eventname in self.rmenu_specs:
292 def command(text=self.text, eventname=eventname):
293 text.event_generate(eventname)
294 rmenu.add_command(label=label, command=command)
295 self.rmenu = rmenu
296
297 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000298 aboutDialog.AboutDialog(self.top,'About IDLEfork')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000299
Steven M. Gava3b55a892001-11-21 05:56:26 +0000300 def config_dialog(self, event=None):
301 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000302
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000303 def view_readme(self, event=None):
304 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000305 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000306
David Scherer7aced172000-08-15 01:13:23 +0000307 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000308 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000309 textView.TextViewer(self.top,'Help',fn)
310
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000311 def python_docs(self, event=None):
312 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000313 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000314 return "break"
315 else:
316 webbrowser.open(self.help_url)
317 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000318
319 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000320 if not (url.startswith('www') or url.startswith('http')):
321 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000322 if sys.platform.count('win') or sys.platform.count('nt'):
323 os.startfile(url)
324 else:
325 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000326
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000327 def cut(self,event):
328 self.text.event_generate("<<Cut>>")
329 return "break"
330
331 def copy(self,event):
332 self.text.event_generate("<<Copy>>")
333 return "break"
334
335 def paste(self,event):
336 self.text.event_generate("<<Paste>>")
337 return "break"
338
David Scherer7aced172000-08-15 01:13:23 +0000339 def select_all(self, event=None):
340 self.text.tag_add("sel", "1.0", "end-1c")
341 self.text.mark_set("insert", "1.0")
342 self.text.see("insert")
343 return "break"
344
345 def remove_selection(self, event=None):
346 self.text.tag_remove("sel", "1.0", "end")
347 self.text.see("insert")
348
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000349 def move_at_edge_if_selection(self, edge_index):
350 """Cursor move begins at start or end of selection
351
352 When a left/right cursor key is pressed create and return to Tkinter a
353 function which causes a cursor move from the associated edge of the
354 selection.
355
356 """
357 self_text_index = self.text.index
358 self_text_mark_set = self.text.mark_set
359 edges_table = ("sel.first+1c", "sel.last-1c")
360 def move_at_edge(event):
361 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
362 try:
363 self_text_index("sel.first")
364 self_text_mark_set("insert", edges_table[edge_index])
365 except TclError:
366 pass
367 return move_at_edge
368
Steven M. Gavac5976402002-01-04 03:06:08 +0000369 def find_event(self, event):
370 SearchDialog.find(self.text)
371 return "break"
372
373 def find_again_event(self, event):
374 SearchDialog.find_again(self.text)
375 return "break"
376
377 def find_selection_event(self, event):
378 SearchDialog.find_selection(self.text)
379 return "break"
380
381 def find_in_files_event(self, event):
382 GrepDialog.grep(self.text, self.io, self.flist)
383 return "break"
384
385 def replace_event(self, event):
386 ReplaceDialog.replace(self.text)
387 return "break"
388
389 def goto_line_event(self, event):
390 text = self.text
391 lineno = tkSimpleDialog.askinteger("Goto",
392 "Go to line number:",parent=text)
393 if lineno is None:
394 return "break"
395 if lineno <= 0:
396 text.bell()
397 return "break"
398 text.mark_set("insert", "%d.0" % lineno)
399 text.see("insert")
400
David Scherer7aced172000-08-15 01:13:23 +0000401 def open_module(self, event=None):
402 # XXX Shouldn't this be in IOBinding or in FileList?
403 try:
404 name = self.text.get("sel.first", "sel.last")
405 except TclError:
406 name = ""
407 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000408 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000409 if not name:
410 name = tkSimpleDialog.askstring("Module",
411 "Enter the name of a Python module\n"
412 "to search on sys.path and open:",
413 parent=self.text)
414 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000415 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000416 if not name:
417 return
David Scherer7aced172000-08-15 01:13:23 +0000418 # XXX Ought to insert current file's directory in front of path
419 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000420 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000421 except (NameError, ImportError), msg:
422 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
423 return
424 if type != imp.PY_SOURCE:
425 tkMessageBox.showerror("Unsupported type",
426 "%s is not a source module" % name, parent=self.text)
427 return
428 if f:
429 f.close()
430 if self.flist:
431 self.flist.open(file)
432 else:
433 self.io.loadfile(file)
434
435 def open_class_browser(self, event=None):
436 filename = self.io.filename
437 if not filename:
438 tkMessageBox.showerror(
439 "No filename",
440 "This buffer has no associated filename",
441 master=self.text)
442 self.text.focus_set()
443 return None
444 head, tail = os.path.split(filename)
445 base, ext = os.path.splitext(tail)
446 import ClassBrowser
447 ClassBrowser.ClassBrowser(self.flist, base, [head])
448
449 def open_path_browser(self, event=None):
450 import PathBrowser
451 PathBrowser.PathBrowser(self.flist)
452
453 def gotoline(self, lineno):
454 if lineno is not None and lineno > 0:
455 self.text.mark_set("insert", "%d.0" % lineno)
456 self.text.tag_remove("sel", "1.0", "end")
457 self.text.tag_add("sel", "insert", "insert +1l")
458 self.center()
459
460 def ispythonsource(self, filename):
461 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000462 return True
David Scherer7aced172000-08-15 01:13:23 +0000463 base, ext = os.path.splitext(os.path.basename(filename))
464 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000465 return True
David Scherer7aced172000-08-15 01:13:23 +0000466 try:
467 f = open(filename)
468 line = f.readline()
469 f.close()
470 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000471 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000472 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000473
474 def close_hook(self):
475 if self.flist:
476 self.flist.close_edit(self)
477
478 def set_close_hook(self, close_hook):
479 self.close_hook = close_hook
480
481 def filename_change_hook(self):
482 if self.flist:
483 self.flist.filename_changed_edit(self)
484 self.saved_change_hook()
485 if self.ispythonsource(self.io.filename):
486 self.addcolorizer()
487 else:
488 self.rmcolorizer()
489
490 def addcolorizer(self):
491 if self.color:
492 return
David Scherer7aced172000-08-15 01:13:23 +0000493 self.per.removefilter(self.undo)
494 self.color = self.ColorDelegator()
495 self.per.insertfilter(self.color)
496 self.per.insertfilter(self.undo)
497
498 def rmcolorizer(self):
499 if not self.color:
500 return
David Scherer7aced172000-08-15 01:13:23 +0000501 self.per.removefilter(self.undo)
502 self.per.removefilter(self.color)
503 self.color = None
504 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000505
Steven M. Gavab77d3432002-03-02 07:16:21 +0000506 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000507 "Update the colour theme if it is changed"
508 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000509 if self.color:
510 self.color = self.ColorDelegator()
511 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000512
Steven M. Gavab1585412002-03-12 00:21:56 +0000513 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000514 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000515 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000516 fontWeight='normal'
517 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
518 fontWeight='bold'
519 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
520 idleConf.GetOption('main','EditorWindow','font-size'),
521 fontWeight))
522
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000523 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000524 "Update the keybindings if they are changed"
525 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000526 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
527 keydefs = self.Bindings.default_keydefs
528 for event, keylist in keydefs.items():
529 self.text.event_delete(event)
530 self.apply_bindings()
531 #update menu accelerators
532 menuEventDict={}
533 for menu in self.Bindings.menudefs:
534 menuEventDict[menu[0]]={}
535 for item in menu[1]:
536 if item:
537 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
538 for menubarItem in self.menudict.keys():
539 menu=self.menudict[menubarItem]
540 end=menu.index(END)+1
541 for index in range(0,end):
542 if menu.type(index)=='command':
543 accel=menu.entrycget(index,'accelerator')
544 if accel:
545 itemName=menu.entrycget(index,'label')
546 event=''
547 if menuEventDict.has_key(menubarItem):
548 if menuEventDict[menubarItem].has_key(itemName):
549 event=menuEventDict[menubarItem][itemName]
550 if event:
551 #print 'accel was:',accel
552 accel=get_accelerator(keydefs, event)
553 menu.entryconfig(index,accelerator=accel)
554 #print 'accel now:',accel,'\n'
555
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000556 def reset_help_menu_entries(self):
557 "Update the additional help entries on the Help menu"
558 help_list = idleConf.GetAllExtraHelpSourcesList()
559 helpmenu = self.menudict['help']
560 # first delete the extra help entries, if any
561 helpmenu_length = helpmenu.index(END)
562 if helpmenu_length > self.base_helpmenu_length:
563 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
564 # then rebuild them
565 if help_list:
566 helpmenu.add_separator()
567 for entry in help_list:
568 cmd = self.__extra_help_callback(entry[1])
569 helpmenu.add_command(label=entry[0], command=cmd)
570 # and update the menu dictionary
571 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000572
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000573 def __extra_help_callback(self, helpfile):
574 "Create a callback with the helpfile value frozen at definition time"
575 def display_extra_help(helpfile=helpfile):
576 self.display_docs(helpfile)
577 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000578
Steven M. Gava1d46e402002-03-27 08:40:46 +0000579 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000580 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000581 rfList=[]
582 if os.path.exists(self.recentFilesPath):
583 RFfile=open(self.recentFilesPath,'r')
584 try:
585 rfList=RFfile.readlines()
586 finally:
587 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000588 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000589 newFile=os.path.abspath(newFile)+'\n'
590 if newFile in rfList:
591 rfList.remove(newFile)
592 rfList.insert(0,newFile)
593 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000594 #print self.flist.inversedict
595 #print self.top.instanceDict
596 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000597 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000598 if rfList:
599 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000600 menu = instance.menuRecentFiles
601 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000602 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000603 for file in rfList:
604 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000605 callback = instance.__RecentFileCallback(fileName)
606 if i > ullen: # don't underline menuitems
607 ul=None
608 menu.add_command(label=ullist[i] + " " + fileName,
609 command=callback,
610 underline=ul)
611 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000612
Steven M. Gava1d46e402002-03-27 08:40:46 +0000613 def __CleanRecentFiles(self,rfList):
614 origRfList=rfList[:]
615 count=0
616 nonFiles=[]
617 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000618 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000619 nonFiles.append(count)
620 count=count+1
621 if nonFiles:
622 nonFiles.reverse()
623 for index in nonFiles:
624 del(rfList[index])
625 if len(rfList)>19:
626 rfList=rfList[0:19]
627 #if rfList != origRfList:
628 RFfile=open(self.recentFilesPath,'w')
629 try:
630 RFfile.writelines(rfList)
631 finally:
632 RFfile.close()
633 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000634
Steven M. Gava1d46e402002-03-27 08:40:46 +0000635 def __RecentFileCallback(self,fileName):
636 def OpenRecentFile(fileName=fileName):
637 self.io.open(editFile=fileName)
638 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000639
David Scherer7aced172000-08-15 01:13:23 +0000640 def saved_change_hook(self):
641 short = self.short_title()
642 long = self.long_title()
643 if short and long:
644 title = short + " - " + long
645 elif short:
646 title = short
647 elif long:
648 title = long
649 else:
650 title = "Untitled"
651 icon = short or long or title
652 if not self.get_saved():
653 title = "*%s*" % title
654 icon = "*%s" % icon
655 self.top.wm_title(title)
656 self.top.wm_iconname(icon)
657
658 def get_saved(self):
659 return self.undo.get_saved()
660
661 def set_saved(self, flag):
662 self.undo.set_saved(flag)
663
664 def reset_undo(self):
665 self.undo.reset_undo()
666
667 def short_title(self):
668 filename = self.io.filename
669 if filename:
670 filename = os.path.basename(filename)
671 return filename
672
673 def long_title(self):
674 return self.io.filename or ""
675
676 def center_insert_event(self, event):
677 self.center()
678
679 def center(self, mark="insert"):
680 text = self.text
681 top, bot = self.getwindowlines()
682 lineno = self.getlineno(mark)
683 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000684 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000685 text.yview(float(newtop))
686
687 def getwindowlines(self):
688 text = self.text
689 top = self.getlineno("@0,0")
690 bot = self.getlineno("@0,65535")
691 if top == bot and text.winfo_height() == 1:
692 # Geometry manager hasn't run yet
693 height = int(text['height'])
694 bot = top + height - 1
695 return top, bot
696
697 def getlineno(self, mark="insert"):
698 text = self.text
699 return int(float(text.index(mark)))
700
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000701 def get_geometry(self):
702 "Return (width, height, x, y)"
703 geom = self.top.wm_geometry()
704 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
705 tuple = (map(int, m.groups()))
706 return tuple
707
David Scherer7aced172000-08-15 01:13:23 +0000708 def close_event(self, event):
709 self.close()
710
711 def maybesave(self):
712 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000713 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000714 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000715 self.top.deiconify()
716 self.top.lower()
717 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000718 return self.io.maybesave()
719
720 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000721 reply = self.maybesave()
722 if reply != "cancel":
723 self._close()
724 return reply
725
726 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000727 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000728 if self.io.filename:
729 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000730 WindowList.unregister_callback(self.postwindowsmenu)
731 if self.close_hook:
732 self.close_hook()
733 self.flist = None
734 colorizing = 0
735 self.unload_extensions()
736 self.io.close(); self.io = None
737 self.undo = None # XXX
738 if self.color:
739 colorizing = self.color.colorizing
740 doh = colorizing and self.top
741 self.color.close(doh) # Cancel colorization
742 self.text = None
743 self.vars = None
744 self.per.close(); self.per = None
745 if not colorizing:
746 self.top.destroy()
747
748 def load_extensions(self):
749 self.extensions = {}
750 self.load_standard_extensions()
751
752 def unload_extensions(self):
753 for ins in self.extensions.values():
754 if hasattr(ins, "close"):
755 ins.close()
756 self.extensions = {}
757
758 def load_standard_extensions(self):
759 for name in self.get_standard_extension_names():
760 try:
761 self.load_extension(name)
762 except:
763 print "Failed to load extension", `name`
764 import traceback
765 traceback.print_exc()
766
767 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000768 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000769
770 def load_extension(self, name):
771 mod = __import__(name, globals(), locals(), [])
772 cls = getattr(mod, name)
773 ins = cls(self)
774 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000775 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000776 if keydefs:
777 self.apply_bindings(keydefs)
778 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000779 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000780 while methodname[:1] == '<':
781 methodname = methodname[1:]
782 while methodname[-1:] == '>':
783 methodname = methodname[:-1]
784 methodname = methodname + "_event"
785 if hasattr(ins, methodname):
786 self.text.bind(vevent, getattr(ins, methodname))
787 if hasattr(ins, "menudefs"):
788 self.fill_menus(ins.menudefs, keydefs)
789 return ins
790
791 def apply_bindings(self, keydefs=None):
792 if keydefs is None:
793 keydefs = self.Bindings.default_keydefs
794 text = self.text
795 text.keydefs = keydefs
796 for event, keylist in keydefs.items():
797 if keylist:
798 apply(text.event_add, (event,) + tuple(keylist))
799
800 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000801 """Add appropriate entries to the menus and submenus
802
803 Menus that are absent or None in self.menudict are ignored.
804 """
David Scherer7aced172000-08-15 01:13:23 +0000805 if defs is None:
806 defs = self.Bindings.menudefs
807 if keydefs is None:
808 keydefs = self.Bindings.default_keydefs
809 menudict = self.menudict
810 text = self.text
811 for mname, itemlist in defs:
812 menu = menudict.get(mname)
813 if not menu:
814 continue
815 for item in itemlist:
816 if not item:
817 menu.add_separator()
818 else:
819 label, event = item
820 checkbutton = (label[:1] == '!')
821 if checkbutton:
822 label = label[1:]
823 underline, label = prepstr(label)
824 accelerator = get_accelerator(keydefs, event)
825 def command(text=text, event=event):
826 text.event_generate(event)
827 if checkbutton:
828 var = self.getrawvar(event, BooleanVar)
829 menu.add_checkbutton(label=label, underline=underline,
830 command=command, accelerator=accelerator,
831 variable=var)
832 else:
833 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000834 command=command,
835 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000836
837 def getvar(self, name):
838 var = self.getrawvar(name)
839 if var:
840 return var.get()
841
842 def setvar(self, name, value, vartype=None):
843 var = self.getrawvar(name, vartype)
844 if var:
845 var.set(value)
846
847 def getrawvar(self, name, vartype=None):
848 var = self.vars.get(name)
849 if not var and vartype:
850 self.vars[name] = var = vartype(self.text)
851 return var
852
853 # Tk implementations of "virtual text methods" -- each platform
854 # reusing IDLE's support code needs to define these for its GUI's
855 # flavor of widget.
856
857 # Is character at text_index in a Python string? Return 0 for
858 # "guaranteed no", true for anything else. This info is expensive
859 # to compute ab initio, but is probably already known by the
860 # platform's colorizer.
861
862 def is_char_in_string(self, text_index):
863 if self.color:
864 # Return true iff colorizer hasn't (re)gotten this far
865 # yet, or the character is tagged as being in a string
866 return self.text.tag_prevrange("TODO", text_index) or \
867 "STRING" in self.text.tag_names(text_index)
868 else:
869 # The colorizer is missing: assume the worst
870 return 1
871
872 # If a selection is defined in the text widget, return (start,
873 # end) as Tkinter text indices, otherwise return (None, None)
874 def get_selection_indices(self):
875 try:
876 first = self.text.index("sel.first")
877 last = self.text.index("sel.last")
878 return first, last
879 except TclError:
880 return None, None
881
882 # Return the text widget's current view of what a tab stop means
883 # (equivalent width in spaces).
884
885 def get_tabwidth(self):
886 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
887 return int(current)
888
889 # Set the text widget's current view of what a tab stop means.
890
891 def set_tabwidth(self, newtabwidth):
892 text = self.text
893 if self.get_tabwidth() != newtabwidth:
894 pixels = text.tk.call("font", "measure", text["font"],
895 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000896 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000897 text.configure(tabs=pixels)
898
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000899### begin autoindent code ###
900
901 # usetabs true -> literal tab characters are used by indent and
902 # dedent cmds, possibly mixed with spaces if
903 # indentwidth is not a multiple of tabwidth
904 # false -> tab characters are converted to spaces by indent
905 # and dedent cmds, and ditto TAB keystrokes
906 # indentwidth is the number of characters per logical indent level.
907 # tabwidth is the display width of a literal tab character.
908 # CAUTION: telling Tk to use anything other than its default
909 # tab setting causes it to use an entirely different tabbing algorithm,
910 # treating tab stops as fixed distances from the left margin.
911 # Nobody expects this, so for now tabwidth should never be changed.
912 usetabs = 0
913 indentwidth = 4
914 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
915
916 # If context_use_ps1 is true, parsing searches back for a ps1 line;
917 # else searches for a popular (if, def, ...) Python stmt.
918 context_use_ps1 = 0
919
920 # When searching backwards for a reliable place to begin parsing,
921 # first start num_context_lines[0] lines back, then
922 # num_context_lines[1] lines back if that didn't work, and so on.
923 # The last value should be huge (larger than the # of lines in a
924 # conceivable file).
925 # Making the initial values larger slows things down more often.
926 num_context_lines = 50, 500, 5000000
927
928 def config(self, **options):
929 for key, value in options.items():
930 if key == 'usetabs':
931 self.usetabs = value
932 elif key == 'indentwidth':
933 self.indentwidth = value
934 elif key == 'tabwidth':
935 self.tabwidth = value
936 elif key == 'context_use_ps1':
937 self.context_use_ps1 = value
938 else:
939 raise KeyError, "bad option name: %s" % `key`
940
941 # If ispythonsource and guess are true, guess a good value for
942 # indentwidth based on file content (if possible), and if
943 # indentwidth != tabwidth set usetabs false.
944 # In any case, adjust the Text widget's view of what a tab
945 # character means.
946
947 def set_indentation_params(self, ispythonsource, guess=1):
948 if guess and ispythonsource:
949 i = self.guess_indent()
950 if 2 <= i <= 8:
951 self.indentwidth = i
952 if self.indentwidth != self.tabwidth:
953 self.usetabs = 0
954
955 self.set_tabwidth(self.tabwidth)
956
957 def smart_backspace_event(self, event):
958 text = self.text
959 first, last = self.get_selection_indices()
960 if first and last:
961 text.delete(first, last)
962 text.mark_set("insert", first)
963 return "break"
964 # Delete whitespace left, until hitting a real char or closest
965 # preceding virtual tab stop.
966 chars = text.get("insert linestart", "insert")
967 if chars == '':
968 if text.compare("insert", ">", "1.0"):
969 # easy: delete preceding newline
970 text.delete("insert-1c")
971 else:
972 text.bell() # at start of buffer
973 return "break"
974 if chars[-1] not in " \t":
975 # easy: delete preceding real char
976 text.delete("insert-1c")
977 return "break"
978 # Ick. It may require *inserting* spaces if we back up over a
979 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000980 tabwidth = self.tabwidth
981 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000982 assert have > 0
983 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000984 # Debug prompt is multilined....
985 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000986 ncharsdeleted = 0
987 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000988 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000989 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000990 chars = chars[:-1]
991 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000992 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000993 if have <= want or chars[-1] not in " \t":
994 break
995 text.undo_block_start()
996 text.delete("insert-%dc" % ncharsdeleted, "insert")
997 if have < want:
998 text.insert("insert", ' ' * (want - have))
999 text.undo_block_stop()
1000 return "break"
1001
1002 def smart_indent_event(self, event):
1003 # if intraline selection:
1004 # delete it
1005 # elif multiline selection:
1006 # do indent-region & return
1007 # indent one level
1008 text = self.text
1009 first, last = self.get_selection_indices()
1010 text.undo_block_start()
1011 try:
1012 if first and last:
1013 if index2line(first) != index2line(last):
1014 return self.indent_region_event(event)
1015 text.delete(first, last)
1016 text.mark_set("insert", first)
1017 prefix = text.get("insert linestart", "insert")
1018 raw, effective = classifyws(prefix, self.tabwidth)
1019 if raw == len(prefix):
1020 # only whitespace to the left
1021 self.reindent_to(effective + self.indentwidth)
1022 else:
1023 if self.usetabs:
1024 pad = '\t'
1025 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001026 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001027 n = self.indentwidth
1028 pad = ' ' * (n - effective % n)
1029 text.insert("insert", pad)
1030 text.see("insert")
1031 return "break"
1032 finally:
1033 text.undo_block_stop()
1034
1035 def newline_and_indent_event(self, event):
1036 text = self.text
1037 first, last = self.get_selection_indices()
1038 text.undo_block_start()
1039 try:
1040 if first and last:
1041 text.delete(first, last)
1042 text.mark_set("insert", first)
1043 line = text.get("insert linestart", "insert")
1044 i, n = 0, len(line)
1045 while i < n and line[i] in " \t":
1046 i = i+1
1047 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001048 # the cursor is in or at leading indentation in a continuation
1049 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001050 text.insert("insert linestart", '\n')
1051 return "break"
1052 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001053 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001054 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001055 last_line_of_prompt = sys.ps1.split('\n')[-1]
1056 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001057 line = line[:-1]
1058 i = i+1
1059 if i:
1060 text.delete("insert - %d chars" % i, "insert")
1061 # strip whitespace after insert point
1062 while text.get("insert") in " \t":
1063 text.delete("insert")
1064 # start new line
1065 text.insert("insert", '\n')
1066
1067 # adjust indentation for continuations and block
1068 # open/close first need to find the last stmt
1069 lno = index2line(text.index('insert'))
1070 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1071 for context in self.num_context_lines:
1072 startat = max(lno - context, 1)
1073 startatindex = `startat` + ".0"
1074 rawtext = text.get(startatindex, "insert")
1075 y.set_str(rawtext)
1076 bod = y.find_good_parse_start(
1077 self.context_use_ps1,
1078 self._build_char_in_string_func(startatindex))
1079 if bod is not None or startat == 1:
1080 break
1081 y.set_lo(bod or 0)
1082 c = y.get_continuation_type()
1083 if c != PyParse.C_NONE:
1084 # The current stmt hasn't ended yet.
1085 if c == PyParse.C_STRING:
1086 # inside a string; just mimic the current indent
1087 text.insert("insert", indent)
1088 elif c == PyParse.C_BRACKET:
1089 # line up with the first (if any) element of the
1090 # last open bracket structure; else indent one
1091 # level beyond the indent of the line with the
1092 # last open bracket
1093 self.reindent_to(y.compute_bracket_indent())
1094 elif c == PyParse.C_BACKSLASH:
1095 # if more than one line in this stmt already, just
1096 # mimic the current indent; else if initial line
1097 # has a start on an assignment stmt, indent to
1098 # beyond leftmost =; else to beyond first chunk of
1099 # non-whitespace on initial line
1100 if y.get_num_lines_in_stmt() > 1:
1101 text.insert("insert", indent)
1102 else:
1103 self.reindent_to(y.compute_backslash_indent())
1104 else:
1105 assert 0, "bogus continuation type " + `c`
1106 return "break"
1107
1108 # This line starts a brand new stmt; indent relative to
1109 # indentation of initial line of closest preceding
1110 # interesting stmt.
1111 indent = y.get_base_indent_string()
1112 text.insert("insert", indent)
1113 if y.is_block_opener():
1114 self.smart_indent_event(event)
1115 elif indent and y.is_block_closer():
1116 self.smart_backspace_event(event)
1117 return "break"
1118 finally:
1119 text.see("insert")
1120 text.undo_block_stop()
1121
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001122 # Our editwin provides a is_char_in_string function that works
1123 # with a Tk text index, but PyParse only knows about offsets into
1124 # a string. This builds a function for PyParse that accepts an
1125 # offset.
1126
1127 def _build_char_in_string_func(self, startindex):
1128 def inner(offset, _startindex=startindex,
1129 _icis=self.is_char_in_string):
1130 return _icis(_startindex + "+%dc" % offset)
1131 return inner
1132
1133 def indent_region_event(self, event):
1134 head, tail, chars, lines = self.get_region()
1135 for pos in range(len(lines)):
1136 line = lines[pos]
1137 if line:
1138 raw, effective = classifyws(line, self.tabwidth)
1139 effective = effective + self.indentwidth
1140 lines[pos] = self._make_blanks(effective) + line[raw:]
1141 self.set_region(head, tail, chars, lines)
1142 return "break"
1143
1144 def dedent_region_event(self, event):
1145 head, tail, chars, lines = self.get_region()
1146 for pos in range(len(lines)):
1147 line = lines[pos]
1148 if line:
1149 raw, effective = classifyws(line, self.tabwidth)
1150 effective = max(effective - self.indentwidth, 0)
1151 lines[pos] = self._make_blanks(effective) + line[raw:]
1152 self.set_region(head, tail, chars, lines)
1153 return "break"
1154
1155 def comment_region_event(self, event):
1156 head, tail, chars, lines = self.get_region()
1157 for pos in range(len(lines) - 1):
1158 line = lines[pos]
1159 lines[pos] = '##' + line
1160 self.set_region(head, tail, chars, lines)
1161
1162 def uncomment_region_event(self, event):
1163 head, tail, chars, lines = self.get_region()
1164 for pos in range(len(lines)):
1165 line = lines[pos]
1166 if not line:
1167 continue
1168 if line[:2] == '##':
1169 line = line[2:]
1170 elif line[:1] == '#':
1171 line = line[1:]
1172 lines[pos] = line
1173 self.set_region(head, tail, chars, lines)
1174
1175 def tabify_region_event(self, event):
1176 head, tail, chars, lines = self.get_region()
1177 tabwidth = self._asktabwidth()
1178 for pos in range(len(lines)):
1179 line = lines[pos]
1180 if line:
1181 raw, effective = classifyws(line, tabwidth)
1182 ntabs, nspaces = divmod(effective, tabwidth)
1183 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1184 self.set_region(head, tail, chars, lines)
1185
1186 def untabify_region_event(self, event):
1187 head, tail, chars, lines = self.get_region()
1188 tabwidth = self._asktabwidth()
1189 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001190 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001191 self.set_region(head, tail, chars, lines)
1192
1193 def toggle_tabs_event(self, event):
1194 if self.askyesno(
1195 "Toggle tabs",
1196 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1197 parent=self.text):
1198 self.usetabs = not self.usetabs
1199 return "break"
1200
1201 # XXX this isn't bound to anything -- see class tabwidth comments
1202 def change_tabwidth_event(self, event):
1203 new = self._asktabwidth()
1204 if new != self.tabwidth:
1205 self.tabwidth = new
1206 self.set_indentation_params(0, guess=0)
1207 return "break"
1208
1209 def change_indentwidth_event(self, event):
1210 new = self.askinteger(
1211 "Indent width",
1212 "New indent width (2-16)",
1213 parent=self.text,
1214 initialvalue=self.indentwidth,
1215 minvalue=2,
1216 maxvalue=16)
1217 if new and new != self.indentwidth:
1218 self.indentwidth = new
1219 return "break"
1220
1221 def get_region(self):
1222 text = self.text
1223 first, last = self.get_selection_indices()
1224 if first and last:
1225 head = text.index(first + " linestart")
1226 tail = text.index(last + "-1c lineend +1c")
1227 else:
1228 head = text.index("insert linestart")
1229 tail = text.index("insert lineend +1c")
1230 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001231 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 return head, tail, chars, lines
1233
1234 def set_region(self, head, tail, chars, lines):
1235 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001236 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001237 if newchars == chars:
1238 text.bell()
1239 return
1240 text.tag_remove("sel", "1.0", "end")
1241 text.mark_set("insert", head)
1242 text.undo_block_start()
1243 text.delete(head, tail)
1244 text.insert(head, newchars)
1245 text.undo_block_stop()
1246 text.tag_add("sel", head, "insert")
1247
1248 # Make string that displays as n leading blanks.
1249
1250 def _make_blanks(self, n):
1251 if self.usetabs:
1252 ntabs, nspaces = divmod(n, self.tabwidth)
1253 return '\t' * ntabs + ' ' * nspaces
1254 else:
1255 return ' ' * n
1256
1257 # Delete from beginning of line to insert point, then reinsert
1258 # column logical (meaning use tabs if appropriate) spaces.
1259
1260 def reindent_to(self, column):
1261 text = self.text
1262 text.undo_block_start()
1263 if text.compare("insert linestart", "!=", "insert"):
1264 text.delete("insert linestart", "insert")
1265 if column:
1266 text.insert("insert", self._make_blanks(column))
1267 text.undo_block_stop()
1268
1269 def _asktabwidth(self):
1270 return self.askinteger(
1271 "Tab width",
1272 "Spaces per tab? (2-16)",
1273 parent=self.text,
1274 initialvalue=self.indentwidth,
1275 minvalue=2,
1276 maxvalue=16) or self.tabwidth
1277
1278 # Guess indentwidth from text content.
1279 # Return guessed indentwidth. This should not be believed unless
1280 # it's in a reasonable range (e.g., it will be 0 if no indented
1281 # blocks are found).
1282
1283 def guess_indent(self):
1284 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1285 if opener and indented:
1286 raw, indentsmall = classifyws(opener, self.tabwidth)
1287 raw, indentlarge = classifyws(indented, self.tabwidth)
1288 else:
1289 indentsmall = indentlarge = 0
1290 return indentlarge - indentsmall
1291
1292# "line.col" -> line, as an int
1293def index2line(index):
1294 return int(float(index))
1295
1296# Look at the leading whitespace in s.
1297# Return pair (# of leading ws characters,
1298# effective # of leading blanks after expanding
1299# tabs to width tabwidth)
1300
1301def classifyws(s, tabwidth):
1302 raw = effective = 0
1303 for ch in s:
1304 if ch == ' ':
1305 raw = raw + 1
1306 effective = effective + 1
1307 elif ch == '\t':
1308 raw = raw + 1
1309 effective = (effective // tabwidth + 1) * tabwidth
1310 else:
1311 break
1312 return raw, effective
1313
1314import tokenize
1315_tokenize = tokenize
1316del tokenize
1317
1318class IndentSearcher:
1319
1320 # .run() chews over the Text widget, looking for a block opener
1321 # and the stmt following it. Returns a pair,
1322 # (line containing block opener, line containing stmt)
1323 # Either or both may be None.
1324
1325 def __init__(self, text, tabwidth):
1326 self.text = text
1327 self.tabwidth = tabwidth
1328 self.i = self.finished = 0
1329 self.blkopenline = self.indentedline = None
1330
1331 def readline(self):
1332 if self.finished:
1333 return ""
1334 i = self.i = self.i + 1
1335 mark = `i` + ".0"
1336 if self.text.compare(mark, ">=", "end"):
1337 return ""
1338 return self.text.get(mark, mark + " lineend+1c")
1339
1340 def tokeneater(self, type, token, start, end, line,
1341 INDENT=_tokenize.INDENT,
1342 NAME=_tokenize.NAME,
1343 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1344 if self.finished:
1345 pass
1346 elif type == NAME and token in OPENERS:
1347 self.blkopenline = line
1348 elif type == INDENT and self.blkopenline:
1349 self.indentedline = line
1350 self.finished = 1
1351
1352 def run(self):
1353 save_tabsize = _tokenize.tabsize
1354 _tokenize.tabsize = self.tabwidth
1355 try:
1356 try:
1357 _tokenize.tokenize(self.readline, self.tokeneater)
1358 except _tokenize.TokenError:
1359 # since we cut off the tokenizer early, we can trigger
1360 # spurious errors
1361 pass
1362 finally:
1363 _tokenize.tabsize = save_tabsize
1364 return self.blkopenline, self.indentedline
1365
1366### end autoindent code ###
1367
David Scherer7aced172000-08-15 01:13:23 +00001368def prepstr(s):
1369 # Helper to extract the underscore from a string, e.g.
1370 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001371 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001372 if i >= 0:
1373 s = s[:i] + s[i+1:]
1374 return i, s
1375
1376
1377keynames = {
1378 'bracketleft': '[',
1379 'bracketright': ']',
1380 'slash': '/',
1381}
1382
1383def get_accelerator(keydefs, event):
1384 keylist = keydefs.get(event)
1385 if not keylist:
1386 return ""
1387 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001388 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001389 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1390 s = re.sub("Key-", "", s)
1391 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1392 s = re.sub("Control-", "Ctrl-", s)
1393 s = re.sub("-", "+", s)
1394 s = re.sub("><", " ", s)
1395 s = re.sub("<", "", s)
1396 s = re.sub(">", "", s)
1397 return s
1398
1399
1400def fixwordbreaks(root):
1401 # Make sure that Tk's double-click and next/previous word
1402 # operations use our definition of a word (i.e. an identifier)
1403 tk = root.tk
1404 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1405 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1406 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1407
1408
1409def test():
1410 root = Tk()
1411 fixwordbreaks(root)
1412 root.withdraw()
1413 if sys.argv[1:]:
1414 filename = sys.argv[1]
1415 else:
1416 filename = None
1417 edit = EditorWindow(root=root, filename=filename)
1418 edit.set_close_hook(root.quit)
1419 root.mainloop()
1420 root.destroy()
1421
1422if __name__ == '__main__':
1423 test()