blob: af60eca0b496704bb581f65118477fc812f807d7 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001import sys
2import os
David Scherer7aced172000-08-15 01:13:23 +00003import re
4import imp
5from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008
9import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000010import idlever
11import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000012import SearchDialog
13import GrepDialog
14import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000015import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000016from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000017import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000018
19# The default tab setting for a Text widget, in average-width characters.
20TK_TABWIDTH_DEFAULT = 8
21
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000022def _find_module(fullname, path=None):
23 """Version of imp.find_module() that handles hierarchical module names"""
24
25 file = None
26 for tgt in fullname.split('.'):
27 if file is not None:
28 file.close() # close intermediate files
29 (file, filename, descr) = imp.find_module(tgt, path)
30 if descr[2] == imp.PY_SOURCE:
31 break # find but not load the source file
32 module = imp.load_module(tgt, file, filename, descr)
33 path = module.__path__
34 return file, filename, descr
35
David Scherer7aced172000-08-15 01:13:23 +000036class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000037 from Percolator import Percolator
38 from ColorDelegator import ColorDelegator
39 from UndoDelegator import UndoDelegator
40 from IOBinding import IOBinding
41 import Bindings
42 from Tkinter import Toplevel
43 from MultiStatusBar import MultiStatusBar
44
David Scherer7aced172000-08-15 01:13:23 +000045 vars = {}
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000046 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000047
48 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000049 if EditorWindow.help_url is None:
50 if sys.platform.count('linux'):
51 # look for html docs in a couple of standard places
52 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
53 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
54 dochome = '/var/www/html/python/index.html'
55 else:
56 basepath = '/usr/share/doc/' # standard location
57 dochome = os.path.join(basepath, pyver,
58 'Doc', 'index.html')
59 else:
60 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
61 dochome = os.path.normpath(dochome)
62 if os.path.isfile(dochome):
63 EditorWindow.help_url = dochome
64 else:
65 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000066 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000067 self.flist = flist
68 root = root or flist.root
69 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000070 self.menubar = Menu(root)
71 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000072 if flist:
73 self.vars = flist.vars
74 #self.top.instanceDict makes flist.inversedict avalable to
75 #configDialog.py so it can access all EditorWindow instaces
76 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000077 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
78 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000079 self.vbar = vbar = Scrollbar(top, name='vbar')
80 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000081 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000082 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000083 foreground=idleConf.GetHighlight(currentTheme,
84 'normal',fgBg='fg'),
85 background=idleConf.GetHighlight(currentTheme,
86 'normal',fgBg='bg'),
87 highlightcolor=idleConf.GetHighlight(currentTheme,
88 'hilite',fgBg='fg'),
89 highlightbackground=idleConf.GetHighlight(currentTheme,
90 'hilite',fgBg='bg'),
91 insertbackground=idleConf.GetHighlight(currentTheme,
92 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000093 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +000094 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +000095
96 self.createmenubar()
97 self.apply_bindings()
98
99 self.top.protocol("WM_DELETE_WINDOW", self.close)
100 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000101 text.bind("<<cut>>", self.cut)
102 text.bind("<<copy>>", self.copy)
103 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000104 text.bind("<<center-insert>>", self.center_insert_event)
105 text.bind("<<help>>", self.help_dialog)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000106 text.bind("<<view-readme>>", self.view_readme)
David Scherer7aced172000-08-15 01:13:23 +0000107 text.bind("<<python-docs>>", self.python_docs)
108 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000109 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000110 text.bind("<<open-module>>", self.open_module)
111 text.bind("<<do-nothing>>", lambda event: "break")
112 text.bind("<<select-all>>", self.select_all)
113 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000114 text.bind("<<find>>", self.find_event)
115 text.bind("<<find-again>>", self.find_again_event)
116 text.bind("<<find-in-files>>", self.find_in_files_event)
117 text.bind("<<find-selection>>", self.find_selection_event)
118 text.bind("<<replace>>", self.replace_event)
119 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000120 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000121 text.bind("<<smart-backspace>>",self.smart_backspace_event)
122 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
123 text.bind("<<smart-indent>>",self.smart_indent_event)
124 text.bind("<<indent-region>>",self.indent_region_event)
125 text.bind("<<dedent-region>>",self.dedent_region_event)
126 text.bind("<<comment-region>>",self.comment_region_event)
127 text.bind("<<uncomment-region>>",self.uncomment_region_event)
128 text.bind("<<tabify-region>>",self.tabify_region_event)
129 text.bind("<<untabify-region>>",self.untabify_region_event)
130 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
131 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000132
David Scherer7aced172000-08-15 01:13:23 +0000133 if flist:
134 flist.inversedict[self] = key
135 if key:
136 flist.dict[key] = self
137 text.bind("<<open-new-window>>", self.flist.new_callback)
138 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
139 text.bind("<<open-class-browser>>", self.open_class_browser)
140 text.bind("<<open-path-browser>>", self.open_path_browser)
141
Steven M. Gava898a3652001-10-07 11:10:44 +0000142 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000143 vbar['command'] = text.yview
144 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000145 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000146 fontWeight='normal'
147 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
148 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000149 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000150 idleConf.GetOption('main','EditorWindow','font-size'),
151 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000152 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
153 text.pack(side=TOP, fill=BOTH, expand=1)
154 text.focus_set()
155
156 self.per = per = self.Percolator(text)
157 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000158 self.color = color = self.ColorDelegator()
159 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000160 else:
David Scherer7aced172000-08-15 01:13:23 +0000161 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000162
163 self.undo = undo = self.UndoDelegator()
164 per.insertfilter(undo)
165 text.undo_block_start = undo.undo_block_start
166 text.undo_block_stop = undo.undo_block_stop
167 undo.set_saved_change_hook(self.saved_change_hook)
168
169 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000170 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000171 io.set_filename_change_hook(self.filename_change_hook)
172
Steven M. Gava1d46e402002-03-27 08:40:46 +0000173 #create the Recent Files submenu
174 self.menuRecentFiles=Menu(self.menubar)
175 self.menudict['file'].insert_cascade(3,label='Recent Files',
176 underline=0,menu=self.menuRecentFiles)
177 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000178
David Scherer7aced172000-08-15 01:13:23 +0000179 if filename:
180 if os.path.exists(filename):
181 io.loadfile(filename)
182 else:
183 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000184 self.saved_change_hook()
185
186 self.load_extensions()
187
188 menu = self.menudict.get('windows')
189 if menu:
190 end = menu.index("end")
191 if end is None:
192 end = -1
193 if end >= 0:
194 menu.add_separator()
195 end = end + 1
196 self.wmenu_end = end
197 WindowList.register_callback(self.postwindowsmenu)
198
199 # Some abstractions so IDLE extensions are cross-IDE
200 self.askyesno = tkMessageBox.askyesno
201 self.askinteger = tkSimpleDialog.askinteger
202 self.showerror = tkMessageBox.showerror
203
204 if self.extensions.has_key('AutoIndent'):
205 self.extensions['AutoIndent'].set_indentation_params(
206 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000207
David Scherer7aced172000-08-15 01:13:23 +0000208 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000209 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000210 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
211 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
212 self.status_bar.pack(side=BOTTOM, fill=X)
213 self.text.bind('<KeyRelease>', self.set_line_and_column)
214 self.text.bind('<ButtonRelease>', self.set_line_and_column)
215 self.text.after_idle(self.set_line_and_column)
216
217 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000218 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000219 self.status_bar.set_label('column', 'Col: %s' % column)
220 self.status_bar.set_label('line', 'Ln: %s' % line)
221
222 def wakeup(self):
223 if self.top.wm_state() == "iconic":
224 self.top.wm_deiconify()
225 else:
226 self.top.tkraise()
227 self.text.focus_set()
228
229 menu_specs = [
230 ("file", "_File"),
231 ("edit", "_Edit"),
232 ("format", "F_ormat"),
233 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000234 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000235 ("windows", "_Windows"),
236 ("help", "_Help"),
237 ]
238
239 def createmenubar(self):
240 mbar = self.menubar
241 self.menudict = menudict = {}
242 for name, label in self.menu_specs:
243 underline, label = prepstr(label)
244 menudict[name] = menu = Menu(mbar, name=name)
245 mbar.add_cascade(label=label, menu=menu, underline=underline)
246 self.fill_menus()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000247 #create the ExtraHelp menu, if required
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000248 self.ResetExtraHelpMenu()
David Scherer7aced172000-08-15 01:13:23 +0000249
250 def postwindowsmenu(self):
251 # Only called when Windows menu exists
252 # XXX Actually, this Just-In-Time updating interferes badly
253 # XXX with the tear-off feature. It would be better to update
254 # XXX all Windows menus whenever the list of windows changes.
255 menu = self.menudict['windows']
256 end = menu.index("end")
257 if end is None:
258 end = -1
259 if end > self.wmenu_end:
260 menu.delete(self.wmenu_end+1, end)
261 WindowList.add_windows_to_menu(menu)
262
263 rmenu = None
264
265 def right_menu_event(self, event):
266 self.text.tag_remove("sel", "1.0", "end")
267 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
268 if not self.rmenu:
269 self.make_rmenu()
270 rmenu = self.rmenu
271 self.event = event
272 iswin = sys.platform[:3] == 'win'
273 if iswin:
274 self.text.config(cursor="arrow")
275 rmenu.tk_popup(event.x_root, event.y_root)
276 if iswin:
277 self.text.config(cursor="ibeam")
278
279 rmenu_specs = [
280 # ("Label", "<<virtual-event>>"), ...
281 ("Close", "<<close-window>>"), # Example
282 ]
283
284 def make_rmenu(self):
285 rmenu = Menu(self.text, tearoff=0)
286 for label, eventname in self.rmenu_specs:
287 def command(text=self.text, eventname=eventname):
288 text.event_generate(eventname)
289 rmenu.add_command(label=label, command=command)
290 self.rmenu = rmenu
291
292 def about_dialog(self, event=None):
Steven M. Gava7d9ed722001-07-31 07:01:47 +0000293 aboutDialog.AboutDialog(self.top,'About IDLEfork')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000294
Steven M. Gava3b55a892001-11-21 05:56:26 +0000295 def config_dialog(self, event=None):
296 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000297
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000298 def view_readme(self, event=None):
299 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'README.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000300 textView.TextViewer(self.top,'IDLEfork - README',fn)
Steven M. Gavaabdfc412001-08-11 07:46:26 +0000301
David Scherer7aced172000-08-15 01:13:23 +0000302 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000303 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000304 textView.TextViewer(self.top,'Help',fn)
305
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000306 def python_docs(self, event=None):
307 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000308 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000309 return "break"
310 else:
311 webbrowser.open(self.help_url)
312 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000313
314 def display_docs(self, url):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000315 url = os.path.normpath(url)
316 if sys.platform.count('win') or sys.platform.count('nt'):
317 os.startfile(url)
318 else:
319 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000320
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000321 def cut(self,event):
322 self.text.event_generate("<<Cut>>")
323 return "break"
324
325 def copy(self,event):
326 self.text.event_generate("<<Copy>>")
327 return "break"
328
329 def paste(self,event):
330 self.text.event_generate("<<Paste>>")
331 return "break"
332
David Scherer7aced172000-08-15 01:13:23 +0000333 def select_all(self, event=None):
334 self.text.tag_add("sel", "1.0", "end-1c")
335 self.text.mark_set("insert", "1.0")
336 self.text.see("insert")
337 return "break"
338
339 def remove_selection(self, event=None):
340 self.text.tag_remove("sel", "1.0", "end")
341 self.text.see("insert")
342
Steven M. Gavac5976402002-01-04 03:06:08 +0000343 def find_event(self, event):
344 SearchDialog.find(self.text)
345 return "break"
346
347 def find_again_event(self, event):
348 SearchDialog.find_again(self.text)
349 return "break"
350
351 def find_selection_event(self, event):
352 SearchDialog.find_selection(self.text)
353 return "break"
354
355 def find_in_files_event(self, event):
356 GrepDialog.grep(self.text, self.io, self.flist)
357 return "break"
358
359 def replace_event(self, event):
360 ReplaceDialog.replace(self.text)
361 return "break"
362
363 def goto_line_event(self, event):
364 text = self.text
365 lineno = tkSimpleDialog.askinteger("Goto",
366 "Go to line number:",parent=text)
367 if lineno is None:
368 return "break"
369 if lineno <= 0:
370 text.bell()
371 return "break"
372 text.mark_set("insert", "%d.0" % lineno)
373 text.see("insert")
374
David Scherer7aced172000-08-15 01:13:23 +0000375 def open_module(self, event=None):
376 # XXX Shouldn't this be in IOBinding or in FileList?
377 try:
378 name = self.text.get("sel.first", "sel.last")
379 except TclError:
380 name = ""
381 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000382 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000383 if not name:
384 name = tkSimpleDialog.askstring("Module",
385 "Enter the name of a Python module\n"
386 "to search on sys.path and open:",
387 parent=self.text)
388 if name:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000389 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000390 if not name:
391 return
David Scherer7aced172000-08-15 01:13:23 +0000392 # XXX Ought to insert current file's directory in front of path
393 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000394 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000395 except (NameError, ImportError), msg:
396 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
397 return
398 if type != imp.PY_SOURCE:
399 tkMessageBox.showerror("Unsupported type",
400 "%s is not a source module" % name, parent=self.text)
401 return
402 if f:
403 f.close()
404 if self.flist:
405 self.flist.open(file)
406 else:
407 self.io.loadfile(file)
408
409 def open_class_browser(self, event=None):
410 filename = self.io.filename
411 if not filename:
412 tkMessageBox.showerror(
413 "No filename",
414 "This buffer has no associated filename",
415 master=self.text)
416 self.text.focus_set()
417 return None
418 head, tail = os.path.split(filename)
419 base, ext = os.path.splitext(tail)
420 import ClassBrowser
421 ClassBrowser.ClassBrowser(self.flist, base, [head])
422
423 def open_path_browser(self, event=None):
424 import PathBrowser
425 PathBrowser.PathBrowser(self.flist)
426
427 def gotoline(self, lineno):
428 if lineno is not None and lineno > 0:
429 self.text.mark_set("insert", "%d.0" % lineno)
430 self.text.tag_remove("sel", "1.0", "end")
431 self.text.tag_add("sel", "insert", "insert +1l")
432 self.center()
433
434 def ispythonsource(self, filename):
435 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000436 return True
David Scherer7aced172000-08-15 01:13:23 +0000437 base, ext = os.path.splitext(os.path.basename(filename))
438 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000439 return True
David Scherer7aced172000-08-15 01:13:23 +0000440 try:
441 f = open(filename)
442 line = f.readline()
443 f.close()
444 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000445 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000446 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000447
448 def close_hook(self):
449 if self.flist:
450 self.flist.close_edit(self)
451
452 def set_close_hook(self, close_hook):
453 self.close_hook = close_hook
454
455 def filename_change_hook(self):
456 if self.flist:
457 self.flist.filename_changed_edit(self)
458 self.saved_change_hook()
459 if self.ispythonsource(self.io.filename):
460 self.addcolorizer()
461 else:
462 self.rmcolorizer()
463
464 def addcolorizer(self):
465 if self.color:
466 return
David Scherer7aced172000-08-15 01:13:23 +0000467 self.per.removefilter(self.undo)
468 self.color = self.ColorDelegator()
469 self.per.insertfilter(self.color)
470 self.per.insertfilter(self.undo)
471
472 def rmcolorizer(self):
473 if not self.color:
474 return
David Scherer7aced172000-08-15 01:13:23 +0000475 self.per.removefilter(self.undo)
476 self.per.removefilter(self.color)
477 self.color = None
478 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000479
Steven M. Gavab77d3432002-03-02 07:16:21 +0000480 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000481 "Update the colour theme if it is changed"
482 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000483 if self.color:
484 self.color = self.ColorDelegator()
485 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000486
Steven M. Gavab1585412002-03-12 00:21:56 +0000487 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000488 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000489 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000490 fontWeight='normal'
491 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
492 fontWeight='bold'
493 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
494 idleConf.GetOption('main','EditorWindow','font-size'),
495 fontWeight))
496
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000497 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000498 "Update the keybindings if they are changed"
499 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000500 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
501 keydefs = self.Bindings.default_keydefs
502 for event, keylist in keydefs.items():
503 self.text.event_delete(event)
504 self.apply_bindings()
505 #update menu accelerators
506 menuEventDict={}
507 for menu in self.Bindings.menudefs:
508 menuEventDict[menu[0]]={}
509 for item in menu[1]:
510 if item:
511 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
512 for menubarItem in self.menudict.keys():
513 menu=self.menudict[menubarItem]
514 end=menu.index(END)+1
515 for index in range(0,end):
516 if menu.type(index)=='command':
517 accel=menu.entrycget(index,'accelerator')
518 if accel:
519 itemName=menu.entrycget(index,'label')
520 event=''
521 if menuEventDict.has_key(menubarItem):
522 if menuEventDict[menubarItem].has_key(itemName):
523 event=menuEventDict[menubarItem][itemName]
524 if event:
525 #print 'accel was:',accel
526 accel=get_accelerator(keydefs, event)
527 menu.entryconfig(index,accelerator=accel)
528 #print 'accel now:',accel,'\n'
529
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000530 def ResetExtraHelpMenu(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000531 "Load or update the Extra Help menu if required"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000532 menuList=idleConf.GetAllExtraHelpSourcesList()
533 helpMenu=self.menudict['help']
534 cascadeIndex=helpMenu.index(END)-1
535 if menuList:
536 if not hasattr(self,'menuExtraHelp'):
537 self.menuExtraHelp=Menu(self.menubar)
538 helpMenu.insert_cascade(cascadeIndex,label='Extra Help',
539 underline=1,menu=self.menuExtraHelp)
540 self.menuExtraHelp.delete(1,END)
541 for menuItem in menuList:
542 self.menuExtraHelp.add_command(label=menuItem[0],
Steven M. Gava1d46e402002-03-27 08:40:46 +0000543 command=self.__DisplayExtraHelpCallback(menuItem[1]))
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000544 else: #no extra help items
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000545 if hasattr(self,'menuExtraHelp'):
546 helpMenu.delete(cascadeIndex-1)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000547 del(self.menuExtraHelp)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000548
Steven M. Gava1d46e402002-03-27 08:40:46 +0000549 def __DisplayExtraHelpCallback(self,helpFile):
550 def DisplayExtraHelp(helpFile=helpFile):
551 self.display_docs(helpFile)
552 return DisplayExtraHelp
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000553
Steven M. Gava1d46e402002-03-27 08:40:46 +0000554 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000555 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000556 rfList=[]
557 if os.path.exists(self.recentFilesPath):
558 RFfile=open(self.recentFilesPath,'r')
559 try:
560 rfList=RFfile.readlines()
561 finally:
562 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000563 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000564 newFile=os.path.abspath(newFile)+'\n'
565 if newFile in rfList:
566 rfList.remove(newFile)
567 rfList.insert(0,newFile)
568 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000569 #print self.flist.inversedict
570 #print self.top.instanceDict
571 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000572 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000573 if rfList:
574 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000575 menu = instance.menuRecentFiles
576 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000578 for file in rfList:
579 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000580 callback = instance.__RecentFileCallback(fileName)
581 if i > ullen: # don't underline menuitems
582 ul=None
583 menu.add_command(label=ullist[i] + " " + fileName,
584 command=callback,
585 underline=ul)
586 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000587
Steven M. Gava1d46e402002-03-27 08:40:46 +0000588 def __CleanRecentFiles(self,rfList):
589 origRfList=rfList[:]
590 count=0
591 nonFiles=[]
592 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000593 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000594 nonFiles.append(count)
595 count=count+1
596 if nonFiles:
597 nonFiles.reverse()
598 for index in nonFiles:
599 del(rfList[index])
600 if len(rfList)>19:
601 rfList=rfList[0:19]
602 #if rfList != origRfList:
603 RFfile=open(self.recentFilesPath,'w')
604 try:
605 RFfile.writelines(rfList)
606 finally:
607 RFfile.close()
608 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000609
Steven M. Gava1d46e402002-03-27 08:40:46 +0000610 def __RecentFileCallback(self,fileName):
611 def OpenRecentFile(fileName=fileName):
612 self.io.open(editFile=fileName)
613 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000614
David Scherer7aced172000-08-15 01:13:23 +0000615 def saved_change_hook(self):
616 short = self.short_title()
617 long = self.long_title()
618 if short and long:
619 title = short + " - " + long
620 elif short:
621 title = short
622 elif long:
623 title = long
624 else:
625 title = "Untitled"
626 icon = short or long or title
627 if not self.get_saved():
628 title = "*%s*" % title
629 icon = "*%s" % icon
630 self.top.wm_title(title)
631 self.top.wm_iconname(icon)
632
633 def get_saved(self):
634 return self.undo.get_saved()
635
636 def set_saved(self, flag):
637 self.undo.set_saved(flag)
638
639 def reset_undo(self):
640 self.undo.reset_undo()
641
642 def short_title(self):
643 filename = self.io.filename
644 if filename:
645 filename = os.path.basename(filename)
646 return filename
647
648 def long_title(self):
649 return self.io.filename or ""
650
651 def center_insert_event(self, event):
652 self.center()
653
654 def center(self, mark="insert"):
655 text = self.text
656 top, bot = self.getwindowlines()
657 lineno = self.getlineno(mark)
658 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000659 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000660 text.yview(float(newtop))
661
662 def getwindowlines(self):
663 text = self.text
664 top = self.getlineno("@0,0")
665 bot = self.getlineno("@0,65535")
666 if top == bot and text.winfo_height() == 1:
667 # Geometry manager hasn't run yet
668 height = int(text['height'])
669 bot = top + height - 1
670 return top, bot
671
672 def getlineno(self, mark="insert"):
673 text = self.text
674 return int(float(text.index(mark)))
675
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000676 def get_geometry(self):
677 "Return (width, height, x, y)"
678 geom = self.top.wm_geometry()
679 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
680 tuple = (map(int, m.groups()))
681 return tuple
682
David Scherer7aced172000-08-15 01:13:23 +0000683 def close_event(self, event):
684 self.close()
685
686 def maybesave(self):
687 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000688 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000689 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000690 self.top.deiconify()
691 self.top.lower()
692 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000693 return self.io.maybesave()
694
695 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000696 reply = self.maybesave()
697 if reply != "cancel":
698 self._close()
699 return reply
700
701 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000702 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000703 if self.io.filename:
704 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000705 WindowList.unregister_callback(self.postwindowsmenu)
706 if self.close_hook:
707 self.close_hook()
708 self.flist = None
709 colorizing = 0
710 self.unload_extensions()
711 self.io.close(); self.io = None
712 self.undo = None # XXX
713 if self.color:
714 colorizing = self.color.colorizing
715 doh = colorizing and self.top
716 self.color.close(doh) # Cancel colorization
717 self.text = None
718 self.vars = None
719 self.per.close(); self.per = None
720 if not colorizing:
721 self.top.destroy()
722
723 def load_extensions(self):
724 self.extensions = {}
725 self.load_standard_extensions()
726
727 def unload_extensions(self):
728 for ins in self.extensions.values():
729 if hasattr(ins, "close"):
730 ins.close()
731 self.extensions = {}
732
733 def load_standard_extensions(self):
734 for name in self.get_standard_extension_names():
735 try:
736 self.load_extension(name)
737 except:
738 print "Failed to load extension", `name`
739 import traceback
740 traceback.print_exc()
741
742 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000743 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000744
745 def load_extension(self, name):
746 mod = __import__(name, globals(), locals(), [])
747 cls = getattr(mod, name)
748 ins = cls(self)
749 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000750 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000751 if keydefs:
752 self.apply_bindings(keydefs)
753 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000754 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000755 while methodname[:1] == '<':
756 methodname = methodname[1:]
757 while methodname[-1:] == '>':
758 methodname = methodname[:-1]
759 methodname = methodname + "_event"
760 if hasattr(ins, methodname):
761 self.text.bind(vevent, getattr(ins, methodname))
762 if hasattr(ins, "menudefs"):
763 self.fill_menus(ins.menudefs, keydefs)
764 return ins
765
766 def apply_bindings(self, keydefs=None):
767 if keydefs is None:
768 keydefs = self.Bindings.default_keydefs
769 text = self.text
770 text.keydefs = keydefs
771 for event, keylist in keydefs.items():
772 if keylist:
773 apply(text.event_add, (event,) + tuple(keylist))
774
775 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000776 """Add appropriate entries to the menus and submenus
777
778 Menus that are absent or None in self.menudict are ignored.
779 """
David Scherer7aced172000-08-15 01:13:23 +0000780 if defs is None:
781 defs = self.Bindings.menudefs
782 if keydefs is None:
783 keydefs = self.Bindings.default_keydefs
784 menudict = self.menudict
785 text = self.text
786 for mname, itemlist in defs:
787 menu = menudict.get(mname)
788 if not menu:
789 continue
790 for item in itemlist:
791 if not item:
792 menu.add_separator()
793 else:
794 label, event = item
795 checkbutton = (label[:1] == '!')
796 if checkbutton:
797 label = label[1:]
798 underline, label = prepstr(label)
799 accelerator = get_accelerator(keydefs, event)
800 def command(text=text, event=event):
801 text.event_generate(event)
802 if checkbutton:
803 var = self.getrawvar(event, BooleanVar)
804 menu.add_checkbutton(label=label, underline=underline,
805 command=command, accelerator=accelerator,
806 variable=var)
807 else:
808 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000809 command=command,
810 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000811
812 def getvar(self, name):
813 var = self.getrawvar(name)
814 if var:
815 return var.get()
816
817 def setvar(self, name, value, vartype=None):
818 var = self.getrawvar(name, vartype)
819 if var:
820 var.set(value)
821
822 def getrawvar(self, name, vartype=None):
823 var = self.vars.get(name)
824 if not var and vartype:
825 self.vars[name] = var = vartype(self.text)
826 return var
827
828 # Tk implementations of "virtual text methods" -- each platform
829 # reusing IDLE's support code needs to define these for its GUI's
830 # flavor of widget.
831
832 # Is character at text_index in a Python string? Return 0 for
833 # "guaranteed no", true for anything else. This info is expensive
834 # to compute ab initio, but is probably already known by the
835 # platform's colorizer.
836
837 def is_char_in_string(self, text_index):
838 if self.color:
839 # Return true iff colorizer hasn't (re)gotten this far
840 # yet, or the character is tagged as being in a string
841 return self.text.tag_prevrange("TODO", text_index) or \
842 "STRING" in self.text.tag_names(text_index)
843 else:
844 # The colorizer is missing: assume the worst
845 return 1
846
847 # If a selection is defined in the text widget, return (start,
848 # end) as Tkinter text indices, otherwise return (None, None)
849 def get_selection_indices(self):
850 try:
851 first = self.text.index("sel.first")
852 last = self.text.index("sel.last")
853 return first, last
854 except TclError:
855 return None, None
856
857 # Return the text widget's current view of what a tab stop means
858 # (equivalent width in spaces).
859
860 def get_tabwidth(self):
861 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
862 return int(current)
863
864 # Set the text widget's current view of what a tab stop means.
865
866 def set_tabwidth(self, newtabwidth):
867 text = self.text
868 if self.get_tabwidth() != newtabwidth:
869 pixels = text.tk.call("font", "measure", text["font"],
870 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000871 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000872 text.configure(tabs=pixels)
873
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000874### begin autoindent code ###
875
876 # usetabs true -> literal tab characters are used by indent and
877 # dedent cmds, possibly mixed with spaces if
878 # indentwidth is not a multiple of tabwidth
879 # false -> tab characters are converted to spaces by indent
880 # and dedent cmds, and ditto TAB keystrokes
881 # indentwidth is the number of characters per logical indent level.
882 # tabwidth is the display width of a literal tab character.
883 # CAUTION: telling Tk to use anything other than its default
884 # tab setting causes it to use an entirely different tabbing algorithm,
885 # treating tab stops as fixed distances from the left margin.
886 # Nobody expects this, so for now tabwidth should never be changed.
887 usetabs = 0
888 indentwidth = 4
889 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
890
891 # If context_use_ps1 is true, parsing searches back for a ps1 line;
892 # else searches for a popular (if, def, ...) Python stmt.
893 context_use_ps1 = 0
894
895 # When searching backwards for a reliable place to begin parsing,
896 # first start num_context_lines[0] lines back, then
897 # num_context_lines[1] lines back if that didn't work, and so on.
898 # The last value should be huge (larger than the # of lines in a
899 # conceivable file).
900 # Making the initial values larger slows things down more often.
901 num_context_lines = 50, 500, 5000000
902
903 def config(self, **options):
904 for key, value in options.items():
905 if key == 'usetabs':
906 self.usetabs = value
907 elif key == 'indentwidth':
908 self.indentwidth = value
909 elif key == 'tabwidth':
910 self.tabwidth = value
911 elif key == 'context_use_ps1':
912 self.context_use_ps1 = value
913 else:
914 raise KeyError, "bad option name: %s" % `key`
915
916 # If ispythonsource and guess are true, guess a good value for
917 # indentwidth based on file content (if possible), and if
918 # indentwidth != tabwidth set usetabs false.
919 # In any case, adjust the Text widget's view of what a tab
920 # character means.
921
922 def set_indentation_params(self, ispythonsource, guess=1):
923 if guess and ispythonsource:
924 i = self.guess_indent()
925 if 2 <= i <= 8:
926 self.indentwidth = i
927 if self.indentwidth != self.tabwidth:
928 self.usetabs = 0
929
930 self.set_tabwidth(self.tabwidth)
931
932 def smart_backspace_event(self, event):
933 text = self.text
934 first, last = self.get_selection_indices()
935 if first and last:
936 text.delete(first, last)
937 text.mark_set("insert", first)
938 return "break"
939 # Delete whitespace left, until hitting a real char or closest
940 # preceding virtual tab stop.
941 chars = text.get("insert linestart", "insert")
942 if chars == '':
943 if text.compare("insert", ">", "1.0"):
944 # easy: delete preceding newline
945 text.delete("insert-1c")
946 else:
947 text.bell() # at start of buffer
948 return "break"
949 if chars[-1] not in " \t":
950 # easy: delete preceding real char
951 text.delete("insert-1c")
952 return "break"
953 # Ick. It may require *inserting* spaces if we back up over a
954 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000955 tabwidth = self.tabwidth
956 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000957 assert have > 0
958 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000959 # Debug prompt is multilined....
960 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000961 ncharsdeleted = 0
962 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000963 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000964 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000965 chars = chars[:-1]
966 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000967 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000968 if have <= want or chars[-1] not in " \t":
969 break
970 text.undo_block_start()
971 text.delete("insert-%dc" % ncharsdeleted, "insert")
972 if have < want:
973 text.insert("insert", ' ' * (want - have))
974 text.undo_block_stop()
975 return "break"
976
977 def smart_indent_event(self, event):
978 # if intraline selection:
979 # delete it
980 # elif multiline selection:
981 # do indent-region & return
982 # indent one level
983 text = self.text
984 first, last = self.get_selection_indices()
985 text.undo_block_start()
986 try:
987 if first and last:
988 if index2line(first) != index2line(last):
989 return self.indent_region_event(event)
990 text.delete(first, last)
991 text.mark_set("insert", first)
992 prefix = text.get("insert linestart", "insert")
993 raw, effective = classifyws(prefix, self.tabwidth)
994 if raw == len(prefix):
995 # only whitespace to the left
996 self.reindent_to(effective + self.indentwidth)
997 else:
998 if self.usetabs:
999 pad = '\t'
1000 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001001 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001002 n = self.indentwidth
1003 pad = ' ' * (n - effective % n)
1004 text.insert("insert", pad)
1005 text.see("insert")
1006 return "break"
1007 finally:
1008 text.undo_block_stop()
1009
1010 def newline_and_indent_event(self, event):
1011 text = self.text
1012 first, last = self.get_selection_indices()
1013 text.undo_block_start()
1014 try:
1015 if first and last:
1016 text.delete(first, last)
1017 text.mark_set("insert", first)
1018 line = text.get("insert linestart", "insert")
1019 i, n = 0, len(line)
1020 while i < n and line[i] in " \t":
1021 i = i+1
1022 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001023 # the cursor is in or at leading indentation in a continuation
1024 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001025 text.insert("insert linestart", '\n')
1026 return "break"
1027 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001028 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001029 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001030 last_line_of_prompt = sys.ps1.split('\n')[-1]
1031 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001032 line = line[:-1]
1033 i = i+1
1034 if i:
1035 text.delete("insert - %d chars" % i, "insert")
1036 # strip whitespace after insert point
1037 while text.get("insert") in " \t":
1038 text.delete("insert")
1039 # start new line
1040 text.insert("insert", '\n')
1041
1042 # adjust indentation for continuations and block
1043 # open/close first need to find the last stmt
1044 lno = index2line(text.index('insert'))
1045 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1046 for context in self.num_context_lines:
1047 startat = max(lno - context, 1)
1048 startatindex = `startat` + ".0"
1049 rawtext = text.get(startatindex, "insert")
1050 y.set_str(rawtext)
1051 bod = y.find_good_parse_start(
1052 self.context_use_ps1,
1053 self._build_char_in_string_func(startatindex))
1054 if bod is not None or startat == 1:
1055 break
1056 y.set_lo(bod or 0)
1057 c = y.get_continuation_type()
1058 if c != PyParse.C_NONE:
1059 # The current stmt hasn't ended yet.
1060 if c == PyParse.C_STRING:
1061 # inside a string; just mimic the current indent
1062 text.insert("insert", indent)
1063 elif c == PyParse.C_BRACKET:
1064 # line up with the first (if any) element of the
1065 # last open bracket structure; else indent one
1066 # level beyond the indent of the line with the
1067 # last open bracket
1068 self.reindent_to(y.compute_bracket_indent())
1069 elif c == PyParse.C_BACKSLASH:
1070 # if more than one line in this stmt already, just
1071 # mimic the current indent; else if initial line
1072 # has a start on an assignment stmt, indent to
1073 # beyond leftmost =; else to beyond first chunk of
1074 # non-whitespace on initial line
1075 if y.get_num_lines_in_stmt() > 1:
1076 text.insert("insert", indent)
1077 else:
1078 self.reindent_to(y.compute_backslash_indent())
1079 else:
1080 assert 0, "bogus continuation type " + `c`
1081 return "break"
1082
1083 # This line starts a brand new stmt; indent relative to
1084 # indentation of initial line of closest preceding
1085 # interesting stmt.
1086 indent = y.get_base_indent_string()
1087 text.insert("insert", indent)
1088 if y.is_block_opener():
1089 self.smart_indent_event(event)
1090 elif indent and y.is_block_closer():
1091 self.smart_backspace_event(event)
1092 return "break"
1093 finally:
1094 text.see("insert")
1095 text.undo_block_stop()
1096
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001097 # Our editwin provides a is_char_in_string function that works
1098 # with a Tk text index, but PyParse only knows about offsets into
1099 # a string. This builds a function for PyParse that accepts an
1100 # offset.
1101
1102 def _build_char_in_string_func(self, startindex):
1103 def inner(offset, _startindex=startindex,
1104 _icis=self.is_char_in_string):
1105 return _icis(_startindex + "+%dc" % offset)
1106 return inner
1107
1108 def indent_region_event(self, event):
1109 head, tail, chars, lines = self.get_region()
1110 for pos in range(len(lines)):
1111 line = lines[pos]
1112 if line:
1113 raw, effective = classifyws(line, self.tabwidth)
1114 effective = effective + self.indentwidth
1115 lines[pos] = self._make_blanks(effective) + line[raw:]
1116 self.set_region(head, tail, chars, lines)
1117 return "break"
1118
1119 def dedent_region_event(self, event):
1120 head, tail, chars, lines = self.get_region()
1121 for pos in range(len(lines)):
1122 line = lines[pos]
1123 if line:
1124 raw, effective = classifyws(line, self.tabwidth)
1125 effective = max(effective - self.indentwidth, 0)
1126 lines[pos] = self._make_blanks(effective) + line[raw:]
1127 self.set_region(head, tail, chars, lines)
1128 return "break"
1129
1130 def comment_region_event(self, event):
1131 head, tail, chars, lines = self.get_region()
1132 for pos in range(len(lines) - 1):
1133 line = lines[pos]
1134 lines[pos] = '##' + line
1135 self.set_region(head, tail, chars, lines)
1136
1137 def uncomment_region_event(self, event):
1138 head, tail, chars, lines = self.get_region()
1139 for pos in range(len(lines)):
1140 line = lines[pos]
1141 if not line:
1142 continue
1143 if line[:2] == '##':
1144 line = line[2:]
1145 elif line[:1] == '#':
1146 line = line[1:]
1147 lines[pos] = line
1148 self.set_region(head, tail, chars, lines)
1149
1150 def tabify_region_event(self, event):
1151 head, tail, chars, lines = self.get_region()
1152 tabwidth = self._asktabwidth()
1153 for pos in range(len(lines)):
1154 line = lines[pos]
1155 if line:
1156 raw, effective = classifyws(line, tabwidth)
1157 ntabs, nspaces = divmod(effective, tabwidth)
1158 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1159 self.set_region(head, tail, chars, lines)
1160
1161 def untabify_region_event(self, event):
1162 head, tail, chars, lines = self.get_region()
1163 tabwidth = self._asktabwidth()
1164 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001165 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001166 self.set_region(head, tail, chars, lines)
1167
1168 def toggle_tabs_event(self, event):
1169 if self.askyesno(
1170 "Toggle tabs",
1171 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1172 parent=self.text):
1173 self.usetabs = not self.usetabs
1174 return "break"
1175
1176 # XXX this isn't bound to anything -- see class tabwidth comments
1177 def change_tabwidth_event(self, event):
1178 new = self._asktabwidth()
1179 if new != self.tabwidth:
1180 self.tabwidth = new
1181 self.set_indentation_params(0, guess=0)
1182 return "break"
1183
1184 def change_indentwidth_event(self, event):
1185 new = self.askinteger(
1186 "Indent width",
1187 "New indent width (2-16)",
1188 parent=self.text,
1189 initialvalue=self.indentwidth,
1190 minvalue=2,
1191 maxvalue=16)
1192 if new and new != self.indentwidth:
1193 self.indentwidth = new
1194 return "break"
1195
1196 def get_region(self):
1197 text = self.text
1198 first, last = self.get_selection_indices()
1199 if first and last:
1200 head = text.index(first + " linestart")
1201 tail = text.index(last + "-1c lineend +1c")
1202 else:
1203 head = text.index("insert linestart")
1204 tail = text.index("insert lineend +1c")
1205 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001206 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001207 return head, tail, chars, lines
1208
1209 def set_region(self, head, tail, chars, lines):
1210 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001211 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001212 if newchars == chars:
1213 text.bell()
1214 return
1215 text.tag_remove("sel", "1.0", "end")
1216 text.mark_set("insert", head)
1217 text.undo_block_start()
1218 text.delete(head, tail)
1219 text.insert(head, newchars)
1220 text.undo_block_stop()
1221 text.tag_add("sel", head, "insert")
1222
1223 # Make string that displays as n leading blanks.
1224
1225 def _make_blanks(self, n):
1226 if self.usetabs:
1227 ntabs, nspaces = divmod(n, self.tabwidth)
1228 return '\t' * ntabs + ' ' * nspaces
1229 else:
1230 return ' ' * n
1231
1232 # Delete from beginning of line to insert point, then reinsert
1233 # column logical (meaning use tabs if appropriate) spaces.
1234
1235 def reindent_to(self, column):
1236 text = self.text
1237 text.undo_block_start()
1238 if text.compare("insert linestart", "!=", "insert"):
1239 text.delete("insert linestart", "insert")
1240 if column:
1241 text.insert("insert", self._make_blanks(column))
1242 text.undo_block_stop()
1243
1244 def _asktabwidth(self):
1245 return self.askinteger(
1246 "Tab width",
1247 "Spaces per tab? (2-16)",
1248 parent=self.text,
1249 initialvalue=self.indentwidth,
1250 minvalue=2,
1251 maxvalue=16) or self.tabwidth
1252
1253 # Guess indentwidth from text content.
1254 # Return guessed indentwidth. This should not be believed unless
1255 # it's in a reasonable range (e.g., it will be 0 if no indented
1256 # blocks are found).
1257
1258 def guess_indent(self):
1259 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1260 if opener and indented:
1261 raw, indentsmall = classifyws(opener, self.tabwidth)
1262 raw, indentlarge = classifyws(indented, self.tabwidth)
1263 else:
1264 indentsmall = indentlarge = 0
1265 return indentlarge - indentsmall
1266
1267# "line.col" -> line, as an int
1268def index2line(index):
1269 return int(float(index))
1270
1271# Look at the leading whitespace in s.
1272# Return pair (# of leading ws characters,
1273# effective # of leading blanks after expanding
1274# tabs to width tabwidth)
1275
1276def classifyws(s, tabwidth):
1277 raw = effective = 0
1278 for ch in s:
1279 if ch == ' ':
1280 raw = raw + 1
1281 effective = effective + 1
1282 elif ch == '\t':
1283 raw = raw + 1
1284 effective = (effective // tabwidth + 1) * tabwidth
1285 else:
1286 break
1287 return raw, effective
1288
1289import tokenize
1290_tokenize = tokenize
1291del tokenize
1292
1293class IndentSearcher:
1294
1295 # .run() chews over the Text widget, looking for a block opener
1296 # and the stmt following it. Returns a pair,
1297 # (line containing block opener, line containing stmt)
1298 # Either or both may be None.
1299
1300 def __init__(self, text, tabwidth):
1301 self.text = text
1302 self.tabwidth = tabwidth
1303 self.i = self.finished = 0
1304 self.blkopenline = self.indentedline = None
1305
1306 def readline(self):
1307 if self.finished:
1308 return ""
1309 i = self.i = self.i + 1
1310 mark = `i` + ".0"
1311 if self.text.compare(mark, ">=", "end"):
1312 return ""
1313 return self.text.get(mark, mark + " lineend+1c")
1314
1315 def tokeneater(self, type, token, start, end, line,
1316 INDENT=_tokenize.INDENT,
1317 NAME=_tokenize.NAME,
1318 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1319 if self.finished:
1320 pass
1321 elif type == NAME and token in OPENERS:
1322 self.blkopenline = line
1323 elif type == INDENT and self.blkopenline:
1324 self.indentedline = line
1325 self.finished = 1
1326
1327 def run(self):
1328 save_tabsize = _tokenize.tabsize
1329 _tokenize.tabsize = self.tabwidth
1330 try:
1331 try:
1332 _tokenize.tokenize(self.readline, self.tokeneater)
1333 except _tokenize.TokenError:
1334 # since we cut off the tokenizer early, we can trigger
1335 # spurious errors
1336 pass
1337 finally:
1338 _tokenize.tabsize = save_tabsize
1339 return self.blkopenline, self.indentedline
1340
1341### end autoindent code ###
1342
David Scherer7aced172000-08-15 01:13:23 +00001343def prepstr(s):
1344 # Helper to extract the underscore from a string, e.g.
1345 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001346 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001347 if i >= 0:
1348 s = s[:i] + s[i+1:]
1349 return i, s
1350
1351
1352keynames = {
1353 'bracketleft': '[',
1354 'bracketright': ']',
1355 'slash': '/',
1356}
1357
1358def get_accelerator(keydefs, event):
1359 keylist = keydefs.get(event)
1360 if not keylist:
1361 return ""
1362 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001363 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001364 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1365 s = re.sub("Key-", "", s)
1366 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1367 s = re.sub("Control-", "Ctrl-", s)
1368 s = re.sub("-", "+", s)
1369 s = re.sub("><", " ", s)
1370 s = re.sub("<", "", s)
1371 s = re.sub(">", "", s)
1372 return s
1373
1374
1375def fixwordbreaks(root):
1376 # Make sure that Tk's double-click and next/previous word
1377 # operations use our definition of a word (i.e. an identifier)
1378 tk = root.tk
1379 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1380 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1381 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1382
1383
1384def test():
1385 root = Tk()
1386 fixwordbreaks(root)
1387 root.withdraw()
1388 if sys.argv[1:]:
1389 filename = sys.argv[1]
1390 else:
1391 filename = None
1392 edit = EditorWindow(root=root, filename=filename)
1393 edit.set_close_hook(root.quit)
1394 root.mainloop()
1395 root.destroy()
1396
1397if __name__ == '__main__':
1398 test()