blob: 65ffe54afdf9127eaaf20532af0fda30ffa7f900 [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:
Thomas Heller84ef1532003-09-23 20:53:10 +000053 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000054 if sys.platform.count('linux'):
55 # look for html docs in a couple of standard places
56 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
57 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
58 dochome = '/var/www/html/python/index.html'
59 else:
60 basepath = '/usr/share/doc/' # standard location
61 dochome = os.path.join(basepath, pyver,
62 'Doc', 'index.html')
Thomas Heller84ef1532003-09-23 20:53:10 +000063 elif sys.platform.count('win') or sys.platform.count('nt'):
64 chmfile = os.path.join(sys.prefix, "Python%d%d.chm" % sys.version_info[:2])
65 if os.path.isfile(chmfile):
66 dochome = chmfile
67 print "dochome =", dochome
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000068 dochome = os.path.normpath(dochome)
69 if os.path.isfile(dochome):
70 EditorWindow.help_url = dochome
71 else:
72 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000073 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000074 self.flist = flist
75 root = root or flist.root
76 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000077 self.menubar = Menu(root)
78 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000079 if flist:
80 self.vars = flist.vars
81 #self.top.instanceDict makes flist.inversedict avalable to
82 #configDialog.py so it can access all EditorWindow instaces
83 self.top.instanceDict=flist.inversedict
Steven M. Gava1d46e402002-03-27 08:40:46 +000084 self.recentFilesPath=os.path.join(idleConf.GetUserCfgDir(),
85 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000086 self.vbar = vbar = Scrollbar(top, name='vbar')
87 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000088 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000089 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000090 foreground=idleConf.GetHighlight(currentTheme,
91 'normal',fgBg='fg'),
92 background=idleConf.GetHighlight(currentTheme,
93 'normal',fgBg='bg'),
94 highlightcolor=idleConf.GetHighlight(currentTheme,
95 'hilite',fgBg='fg'),
96 highlightbackground=idleConf.GetHighlight(currentTheme,
97 'hilite',fgBg='bg'),
98 insertbackground=idleConf.GetHighlight(currentTheme,
99 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000100 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000101 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000102
103 self.createmenubar()
104 self.apply_bindings()
105
106 self.top.protocol("WM_DELETE_WINDOW", self.close)
107 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000108 text.bind("<<cut>>", self.cut)
109 text.bind("<<copy>>", self.copy)
110 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000111 text.bind("<<center-insert>>", self.center_insert_event)
112 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000113 text.bind("<<python-docs>>", self.python_docs)
114 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000115 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000116 text.bind("<<open-module>>", self.open_module)
117 text.bind("<<do-nothing>>", lambda event: "break")
118 text.bind("<<select-all>>", self.select_all)
119 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000120 text.bind("<<find>>", self.find_event)
121 text.bind("<<find-again>>", self.find_again_event)
122 text.bind("<<find-in-files>>", self.find_in_files_event)
123 text.bind("<<find-selection>>", self.find_selection_event)
124 text.bind("<<replace>>", self.replace_event)
125 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000126 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000127 text.bind("<<smart-backspace>>",self.smart_backspace_event)
128 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
129 text.bind("<<smart-indent>>",self.smart_indent_event)
130 text.bind("<<indent-region>>",self.indent_region_event)
131 text.bind("<<dedent-region>>",self.dedent_region_event)
132 text.bind("<<comment-region>>",self.comment_region_event)
133 text.bind("<<uncomment-region>>",self.uncomment_region_event)
134 text.bind("<<tabify-region>>",self.tabify_region_event)
135 text.bind("<<untabify-region>>",self.untabify_region_event)
136 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
137 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000138 text.bind("<Left>", self.move_at_edge_if_selection(0))
139 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000140
David Scherer7aced172000-08-15 01:13:23 +0000141 if flist:
142 flist.inversedict[self] = key
143 if key:
144 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000145 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
147 text.bind("<<open-class-browser>>", self.open_class_browser)
148 text.bind("<<open-path-browser>>", self.open_path_browser)
149
Steven M. Gava898a3652001-10-07 11:10:44 +0000150 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000151 vbar['command'] = text.yview
152 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000153 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000154 fontWeight='normal'
155 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
156 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000157 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000158 idleConf.GetOption('main','EditorWindow','font-size'),
159 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000160 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
161 text.pack(side=TOP, fill=BOTH, expand=1)
162 text.focus_set()
163
164 self.per = per = self.Percolator(text)
165 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000166 self.color = color = self.ColorDelegator()
167 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000168 else:
David Scherer7aced172000-08-15 01:13:23 +0000169 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000170
171 self.undo = undo = self.UndoDelegator()
172 per.insertfilter(undo)
173 text.undo_block_start = undo.undo_block_start
174 text.undo_block_stop = undo.undo_block_stop
175 undo.set_saved_change_hook(self.saved_change_hook)
176
177 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000178 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000179 io.set_filename_change_hook(self.filename_change_hook)
180
Steven M. Gava1d46e402002-03-27 08:40:46 +0000181 #create the Recent Files submenu
182 self.menuRecentFiles=Menu(self.menubar)
183 self.menudict['file'].insert_cascade(3,label='Recent Files',
184 underline=0,menu=self.menuRecentFiles)
185 self.UpdateRecentFilesList()
David Scherer7aced172000-08-15 01:13:23 +0000186
David Scherer7aced172000-08-15 01:13:23 +0000187 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000188 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000189 io.loadfile(filename)
190 else:
191 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000192 self.saved_change_hook()
193
194 self.load_extensions()
195
196 menu = self.menudict.get('windows')
197 if menu:
198 end = menu.index("end")
199 if end is None:
200 end = -1
201 if end >= 0:
202 menu.add_separator()
203 end = end + 1
204 self.wmenu_end = end
205 WindowList.register_callback(self.postwindowsmenu)
206
207 # Some abstractions so IDLE extensions are cross-IDE
208 self.askyesno = tkMessageBox.askyesno
209 self.askinteger = tkSimpleDialog.askinteger
210 self.showerror = tkMessageBox.showerror
211
212 if self.extensions.has_key('AutoIndent'):
213 self.extensions['AutoIndent'].set_indentation_params(
214 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000215
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000216 def new_callback(self, event):
217 dirname, basename = self.io.defaultfilename()
218 self.flist.new(dirname)
219 return "break"
220
David Scherer7aced172000-08-15 01:13:23 +0000221 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000222 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000223 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
224 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
225 self.status_bar.pack(side=BOTTOM, fill=X)
226 self.text.bind('<KeyRelease>', self.set_line_and_column)
227 self.text.bind('<ButtonRelease>', self.set_line_and_column)
228 self.text.after_idle(self.set_line_and_column)
229
230 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000231 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000232 self.status_bar.set_label('column', 'Col: %s' % column)
233 self.status_bar.set_label('line', 'Ln: %s' % line)
234
235 def wakeup(self):
236 if self.top.wm_state() == "iconic":
237 self.top.wm_deiconify()
238 else:
239 self.top.tkraise()
240 self.text.focus_set()
241
242 menu_specs = [
243 ("file", "_File"),
244 ("edit", "_Edit"),
245 ("format", "F_ormat"),
246 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000247 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000248 ("windows", "_Windows"),
249 ("help", "_Help"),
250 ]
251
252 def createmenubar(self):
253 mbar = self.menubar
254 self.menudict = menudict = {}
255 for name, label in self.menu_specs:
256 underline, label = prepstr(label)
257 menudict[name] = menu = Menu(mbar, name=name)
258 mbar.add_cascade(label=label, menu=menu, underline=underline)
259 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000260 self.base_helpmenu_length = self.menudict['help'].index(END)
261 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000262
263 def postwindowsmenu(self):
264 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000265 menu = self.menudict['windows']
266 end = menu.index("end")
267 if end is None:
268 end = -1
269 if end > self.wmenu_end:
270 menu.delete(self.wmenu_end+1, end)
271 WindowList.add_windows_to_menu(menu)
272
273 rmenu = None
274
275 def right_menu_event(self, event):
276 self.text.tag_remove("sel", "1.0", "end")
277 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
278 if not self.rmenu:
279 self.make_rmenu()
280 rmenu = self.rmenu
281 self.event = event
282 iswin = sys.platform[:3] == 'win'
283 if iswin:
284 self.text.config(cursor="arrow")
285 rmenu.tk_popup(event.x_root, event.y_root)
286 if iswin:
287 self.text.config(cursor="ibeam")
288
289 rmenu_specs = [
290 # ("Label", "<<virtual-event>>"), ...
291 ("Close", "<<close-window>>"), # Example
292 ]
293
294 def make_rmenu(self):
295 rmenu = Menu(self.text, tearoff=0)
296 for label, eventname in self.rmenu_specs:
297 def command(text=self.text, eventname=eventname):
298 text.event_generate(eventname)
299 rmenu.add_command(label=label, command=command)
300 self.rmenu = rmenu
301
302 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000303 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000304
Steven M. Gava3b55a892001-11-21 05:56:26 +0000305 def config_dialog(self, event=None):
306 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307
David Scherer7aced172000-08-15 01:13:23 +0000308 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000309 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000310 textView.TextViewer(self.top,'Help',fn)
311
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000312 def python_docs(self, event=None):
313 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000314 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000315 return "break"
316 else:
317 webbrowser.open(self.help_url)
318 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000319
320 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000321 if not (url.startswith('www') or url.startswith('http')):
322 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000323 if sys.platform.count('win') or sys.platform.count('nt'):
324 os.startfile(url)
325 else:
326 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000327
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000328 def cut(self,event):
329 self.text.event_generate("<<Cut>>")
330 return "break"
331
332 def copy(self,event):
333 self.text.event_generate("<<Copy>>")
334 return "break"
335
336 def paste(self,event):
337 self.text.event_generate("<<Paste>>")
338 return "break"
339
David Scherer7aced172000-08-15 01:13:23 +0000340 def select_all(self, event=None):
341 self.text.tag_add("sel", "1.0", "end-1c")
342 self.text.mark_set("insert", "1.0")
343 self.text.see("insert")
344 return "break"
345
346 def remove_selection(self, event=None):
347 self.text.tag_remove("sel", "1.0", "end")
348 self.text.see("insert")
349
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000350 def move_at_edge_if_selection(self, edge_index):
351 """Cursor move begins at start or end of selection
352
353 When a left/right cursor key is pressed create and return to Tkinter a
354 function which causes a cursor move from the associated edge of the
355 selection.
356
357 """
358 self_text_index = self.text.index
359 self_text_mark_set = self.text.mark_set
360 edges_table = ("sel.first+1c", "sel.last-1c")
361 def move_at_edge(event):
362 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
363 try:
364 self_text_index("sel.first")
365 self_text_mark_set("insert", edges_table[edge_index])
366 except TclError:
367 pass
368 return move_at_edge
369
Steven M. Gavac5976402002-01-04 03:06:08 +0000370 def find_event(self, event):
371 SearchDialog.find(self.text)
372 return "break"
373
374 def find_again_event(self, event):
375 SearchDialog.find_again(self.text)
376 return "break"
377
378 def find_selection_event(self, event):
379 SearchDialog.find_selection(self.text)
380 return "break"
381
382 def find_in_files_event(self, event):
383 GrepDialog.grep(self.text, self.io, self.flist)
384 return "break"
385
386 def replace_event(self, event):
387 ReplaceDialog.replace(self.text)
388 return "break"
389
390 def goto_line_event(self, event):
391 text = self.text
392 lineno = tkSimpleDialog.askinteger("Goto",
393 "Go to line number:",parent=text)
394 if lineno is None:
395 return "break"
396 if lineno <= 0:
397 text.bell()
398 return "break"
399 text.mark_set("insert", "%d.0" % lineno)
400 text.see("insert")
401
David Scherer7aced172000-08-15 01:13:23 +0000402 def open_module(self, event=None):
403 # XXX Shouldn't this be in IOBinding or in FileList?
404 try:
405 name = self.text.get("sel.first", "sel.last")
406 except TclError:
407 name = ""
408 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000409 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000410 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, initialvalue=name)
414 if name:
415 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000416 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000417 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()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000485 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000486 if self.ispythonsource(self.io.filename):
487 self.addcolorizer()
488 else:
489 self.rmcolorizer()
490
491 def addcolorizer(self):
492 if self.color:
493 return
David Scherer7aced172000-08-15 01:13:23 +0000494 self.per.removefilter(self.undo)
495 self.color = self.ColorDelegator()
496 self.per.insertfilter(self.color)
497 self.per.insertfilter(self.undo)
498
499 def rmcolorizer(self):
500 if not self.color:
501 return
David Scherer7aced172000-08-15 01:13:23 +0000502 self.per.removefilter(self.undo)
503 self.per.removefilter(self.color)
504 self.color = None
505 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000506
Steven M. Gavab77d3432002-03-02 07:16:21 +0000507 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000508 "Update the colour theme if it is changed"
509 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000510 if self.color:
511 self.color = self.ColorDelegator()
512 self.per.insertfilter(self.color)
David Scherer7aced172000-08-15 01:13:23 +0000513
Steven M. Gavab1585412002-03-12 00:21:56 +0000514 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000515 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000516 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000517 fontWeight='normal'
518 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
519 fontWeight='bold'
520 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
521 idleConf.GetOption('main','EditorWindow','font-size'),
522 fontWeight))
523
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000524 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000525 "Update the keybindings if they are changed"
526 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000527 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
528 keydefs = self.Bindings.default_keydefs
529 for event, keylist in keydefs.items():
530 self.text.event_delete(event)
531 self.apply_bindings()
532 #update menu accelerators
533 menuEventDict={}
534 for menu in self.Bindings.menudefs:
535 menuEventDict[menu[0]]={}
536 for item in menu[1]:
537 if item:
538 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
539 for menubarItem in self.menudict.keys():
540 menu=self.menudict[menubarItem]
541 end=menu.index(END)+1
542 for index in range(0,end):
543 if menu.type(index)=='command':
544 accel=menu.entrycget(index,'accelerator')
545 if accel:
546 itemName=menu.entrycget(index,'label')
547 event=''
548 if menuEventDict.has_key(menubarItem):
549 if menuEventDict[menubarItem].has_key(itemName):
550 event=menuEventDict[menubarItem][itemName]
551 if event:
552 #print 'accel was:',accel
553 accel=get_accelerator(keydefs, event)
554 menu.entryconfig(index,accelerator=accel)
555 #print 'accel now:',accel,'\n'
556
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000557 def reset_help_menu_entries(self):
558 "Update the additional help entries on the Help menu"
559 help_list = idleConf.GetAllExtraHelpSourcesList()
560 helpmenu = self.menudict['help']
561 # first delete the extra help entries, if any
562 helpmenu_length = helpmenu.index(END)
563 if helpmenu_length > self.base_helpmenu_length:
564 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
565 # then rebuild them
566 if help_list:
567 helpmenu.add_separator()
568 for entry in help_list:
569 cmd = self.__extra_help_callback(entry[1])
570 helpmenu.add_command(label=entry[0], command=cmd)
571 # and update the menu dictionary
572 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000573
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000574 def __extra_help_callback(self, helpfile):
575 "Create a callback with the helpfile value frozen at definition time"
576 def display_extra_help(helpfile=helpfile):
577 self.display_docs(helpfile)
578 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000579
Steven M. Gava1d46e402002-03-27 08:40:46 +0000580 def UpdateRecentFilesList(self,newFile=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000581 "Load or update the recent files list, and menu if required"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000582 rfList=[]
583 if os.path.exists(self.recentFilesPath):
584 RFfile=open(self.recentFilesPath,'r')
585 try:
586 rfList=RFfile.readlines()
587 finally:
588 RFfile.close()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000589 if newFile:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000590 newFile=os.path.abspath(newFile)+'\n'
591 if newFile in rfList:
592 rfList.remove(newFile)
593 rfList.insert(0,newFile)
594 rfList=self.__CleanRecentFiles(rfList)
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000595 #print self.flist.inversedict
596 #print self.top.instanceDict
597 #print self
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000598 ullist = "1234567890ABCDEFGHIJ"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000599 if rfList:
600 for instance in self.top.instanceDict.keys():
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000601 menu = instance.menuRecentFiles
602 menu.delete(1,END)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000603 i = 0 ; ul = 0; ullen = len(ullist)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000604 for file in rfList:
605 fileName=file[0:-1]
Kurt B. Kaiserc9a5b5c2002-10-06 01:57:45 +0000606 callback = instance.__RecentFileCallback(fileName)
607 if i > ullen: # don't underline menuitems
608 ul=None
609 menu.add_command(label=ullist[i] + " " + fileName,
610 command=callback,
611 underline=ul)
612 i += 1
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000613
Steven M. Gava1d46e402002-03-27 08:40:46 +0000614 def __CleanRecentFiles(self,rfList):
615 origRfList=rfList[:]
616 count=0
617 nonFiles=[]
618 for path in rfList:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000619 if not os.path.exists(path[0:-1]):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000620 nonFiles.append(count)
621 count=count+1
622 if nonFiles:
623 nonFiles.reverse()
624 for index in nonFiles:
625 del(rfList[index])
626 if len(rfList)>19:
627 rfList=rfList[0:19]
628 #if rfList != origRfList:
629 RFfile=open(self.recentFilesPath,'w')
630 try:
631 RFfile.writelines(rfList)
632 finally:
633 RFfile.close()
634 return rfList
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000635
Steven M. Gava1d46e402002-03-27 08:40:46 +0000636 def __RecentFileCallback(self,fileName):
637 def OpenRecentFile(fileName=fileName):
638 self.io.open(editFile=fileName)
639 return OpenRecentFile
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000640
David Scherer7aced172000-08-15 01:13:23 +0000641 def saved_change_hook(self):
642 short = self.short_title()
643 long = self.long_title()
644 if short and long:
645 title = short + " - " + long
646 elif short:
647 title = short
648 elif long:
649 title = long
650 else:
651 title = "Untitled"
652 icon = short or long or title
653 if not self.get_saved():
654 title = "*%s*" % title
655 icon = "*%s" % icon
656 self.top.wm_title(title)
657 self.top.wm_iconname(icon)
658
659 def get_saved(self):
660 return self.undo.get_saved()
661
662 def set_saved(self, flag):
663 self.undo.set_saved(flag)
664
665 def reset_undo(self):
666 self.undo.reset_undo()
667
668 def short_title(self):
669 filename = self.io.filename
670 if filename:
671 filename = os.path.basename(filename)
672 return filename
673
674 def long_title(self):
675 return self.io.filename or ""
676
677 def center_insert_event(self, event):
678 self.center()
679
680 def center(self, mark="insert"):
681 text = self.text
682 top, bot = self.getwindowlines()
683 lineno = self.getlineno(mark)
684 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000685 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000686 text.yview(float(newtop))
687
688 def getwindowlines(self):
689 text = self.text
690 top = self.getlineno("@0,0")
691 bot = self.getlineno("@0,65535")
692 if top == bot and text.winfo_height() == 1:
693 # Geometry manager hasn't run yet
694 height = int(text['height'])
695 bot = top + height - 1
696 return top, bot
697
698 def getlineno(self, mark="insert"):
699 text = self.text
700 return int(float(text.index(mark)))
701
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000702 def get_geometry(self):
703 "Return (width, height, x, y)"
704 geom = self.top.wm_geometry()
705 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
706 tuple = (map(int, m.groups()))
707 return tuple
708
David Scherer7aced172000-08-15 01:13:23 +0000709 def close_event(self, event):
710 self.close()
711
712 def maybesave(self):
713 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000714 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000715 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000716 self.top.deiconify()
717 self.top.lower()
718 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000719 return self.io.maybesave()
720
721 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000722 reply = self.maybesave()
723 if reply != "cancel":
724 self._close()
725 return reply
726
727 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000728 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000729 if self.io.filename:
730 self.UpdateRecentFilesList(newFile=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000731 WindowList.unregister_callback(self.postwindowsmenu)
732 if self.close_hook:
733 self.close_hook()
734 self.flist = None
735 colorizing = 0
736 self.unload_extensions()
737 self.io.close(); self.io = None
738 self.undo = None # XXX
739 if self.color:
740 colorizing = self.color.colorizing
741 doh = colorizing and self.top
742 self.color.close(doh) # Cancel colorization
743 self.text = None
744 self.vars = None
745 self.per.close(); self.per = None
746 if not colorizing:
747 self.top.destroy()
748
749 def load_extensions(self):
750 self.extensions = {}
751 self.load_standard_extensions()
752
753 def unload_extensions(self):
754 for ins in self.extensions.values():
755 if hasattr(ins, "close"):
756 ins.close()
757 self.extensions = {}
758
759 def load_standard_extensions(self):
760 for name in self.get_standard_extension_names():
761 try:
762 self.load_extension(name)
763 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000764 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000765 import traceback
766 traceback.print_exc()
767
768 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000769 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000770
771 def load_extension(self, name):
772 mod = __import__(name, globals(), locals(), [])
773 cls = getattr(mod, name)
774 ins = cls(self)
775 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000776 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000777 if keydefs:
778 self.apply_bindings(keydefs)
779 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000780 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000781 while methodname[:1] == '<':
782 methodname = methodname[1:]
783 while methodname[-1:] == '>':
784 methodname = methodname[:-1]
785 methodname = methodname + "_event"
786 if hasattr(ins, methodname):
787 self.text.bind(vevent, getattr(ins, methodname))
788 if hasattr(ins, "menudefs"):
789 self.fill_menus(ins.menudefs, keydefs)
790 return ins
791
792 def apply_bindings(self, keydefs=None):
793 if keydefs is None:
794 keydefs = self.Bindings.default_keydefs
795 text = self.text
796 text.keydefs = keydefs
797 for event, keylist in keydefs.items():
798 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000799 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000800
801 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000802 """Add appropriate entries to the menus and submenus
803
804 Menus that are absent or None in self.menudict are ignored.
805 """
David Scherer7aced172000-08-15 01:13:23 +0000806 if defs is None:
807 defs = self.Bindings.menudefs
808 if keydefs is None:
809 keydefs = self.Bindings.default_keydefs
810 menudict = self.menudict
811 text = self.text
812 for mname, itemlist in defs:
813 menu = menudict.get(mname)
814 if not menu:
815 continue
816 for item in itemlist:
817 if not item:
818 menu.add_separator()
819 else:
820 label, event = item
821 checkbutton = (label[:1] == '!')
822 if checkbutton:
823 label = label[1:]
824 underline, label = prepstr(label)
825 accelerator = get_accelerator(keydefs, event)
826 def command(text=text, event=event):
827 text.event_generate(event)
828 if checkbutton:
829 var = self.getrawvar(event, BooleanVar)
830 menu.add_checkbutton(label=label, underline=underline,
831 command=command, accelerator=accelerator,
832 variable=var)
833 else:
834 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000835 command=command,
836 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000837
838 def getvar(self, name):
839 var = self.getrawvar(name)
840 if var:
841 return var.get()
842
843 def setvar(self, name, value, vartype=None):
844 var = self.getrawvar(name, vartype)
845 if var:
846 var.set(value)
847
848 def getrawvar(self, name, vartype=None):
849 var = self.vars.get(name)
850 if not var and vartype:
851 self.vars[name] = var = vartype(self.text)
852 return var
853
854 # Tk implementations of "virtual text methods" -- each platform
855 # reusing IDLE's support code needs to define these for its GUI's
856 # flavor of widget.
857
858 # Is character at text_index in a Python string? Return 0 for
859 # "guaranteed no", true for anything else. This info is expensive
860 # to compute ab initio, but is probably already known by the
861 # platform's colorizer.
862
863 def is_char_in_string(self, text_index):
864 if self.color:
865 # Return true iff colorizer hasn't (re)gotten this far
866 # yet, or the character is tagged as being in a string
867 return self.text.tag_prevrange("TODO", text_index) or \
868 "STRING" in self.text.tag_names(text_index)
869 else:
870 # The colorizer is missing: assume the worst
871 return 1
872
873 # If a selection is defined in the text widget, return (start,
874 # end) as Tkinter text indices, otherwise return (None, None)
875 def get_selection_indices(self):
876 try:
877 first = self.text.index("sel.first")
878 last = self.text.index("sel.last")
879 return first, last
880 except TclError:
881 return None, None
882
883 # Return the text widget's current view of what a tab stop means
884 # (equivalent width in spaces).
885
886 def get_tabwidth(self):
887 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
888 return int(current)
889
890 # Set the text widget's current view of what a tab stop means.
891
892 def set_tabwidth(self, newtabwidth):
893 text = self.text
894 if self.get_tabwidth() != newtabwidth:
895 pixels = text.tk.call("font", "measure", text["font"],
896 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000897 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000898 text.configure(tabs=pixels)
899
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000900### begin autoindent code ###
901
902 # usetabs true -> literal tab characters are used by indent and
903 # dedent cmds, possibly mixed with spaces if
904 # indentwidth is not a multiple of tabwidth
905 # false -> tab characters are converted to spaces by indent
906 # and dedent cmds, and ditto TAB keystrokes
907 # indentwidth is the number of characters per logical indent level.
908 # tabwidth is the display width of a literal tab character.
909 # CAUTION: telling Tk to use anything other than its default
910 # tab setting causes it to use an entirely different tabbing algorithm,
911 # treating tab stops as fixed distances from the left margin.
912 # Nobody expects this, so for now tabwidth should never be changed.
913 usetabs = 0
914 indentwidth = 4
915 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
916
917 # If context_use_ps1 is true, parsing searches back for a ps1 line;
918 # else searches for a popular (if, def, ...) Python stmt.
919 context_use_ps1 = 0
920
921 # When searching backwards for a reliable place to begin parsing,
922 # first start num_context_lines[0] lines back, then
923 # num_context_lines[1] lines back if that didn't work, and so on.
924 # The last value should be huge (larger than the # of lines in a
925 # conceivable file).
926 # Making the initial values larger slows things down more often.
927 num_context_lines = 50, 500, 5000000
928
929 def config(self, **options):
930 for key, value in options.items():
931 if key == 'usetabs':
932 self.usetabs = value
933 elif key == 'indentwidth':
934 self.indentwidth = value
935 elif key == 'tabwidth':
936 self.tabwidth = value
937 elif key == 'context_use_ps1':
938 self.context_use_ps1 = value
939 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000940 raise KeyError, "bad option name: %r" % (key,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000941
942 # If ispythonsource and guess are true, guess a good value for
943 # indentwidth based on file content (if possible), and if
944 # indentwidth != tabwidth set usetabs false.
945 # In any case, adjust the Text widget's view of what a tab
946 # character means.
947
948 def set_indentation_params(self, ispythonsource, guess=1):
949 if guess and ispythonsource:
950 i = self.guess_indent()
951 if 2 <= i <= 8:
952 self.indentwidth = i
953 if self.indentwidth != self.tabwidth:
954 self.usetabs = 0
955
956 self.set_tabwidth(self.tabwidth)
957
958 def smart_backspace_event(self, event):
959 text = self.text
960 first, last = self.get_selection_indices()
961 if first and last:
962 text.delete(first, last)
963 text.mark_set("insert", first)
964 return "break"
965 # Delete whitespace left, until hitting a real char or closest
966 # preceding virtual tab stop.
967 chars = text.get("insert linestart", "insert")
968 if chars == '':
969 if text.compare("insert", ">", "1.0"):
970 # easy: delete preceding newline
971 text.delete("insert-1c")
972 else:
973 text.bell() # at start of buffer
974 return "break"
975 if chars[-1] not in " \t":
976 # easy: delete preceding real char
977 text.delete("insert-1c")
978 return "break"
979 # Ick. It may require *inserting* spaces if we back up over a
980 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000981 tabwidth = self.tabwidth
982 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000983 assert have > 0
984 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000985 # Debug prompt is multilined....
986 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000987 ncharsdeleted = 0
988 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000989 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000990 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000991 chars = chars[:-1]
992 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000993 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000994 if have <= want or chars[-1] not in " \t":
995 break
996 text.undo_block_start()
997 text.delete("insert-%dc" % ncharsdeleted, "insert")
998 if have < want:
999 text.insert("insert", ' ' * (want - have))
1000 text.undo_block_stop()
1001 return "break"
1002
1003 def smart_indent_event(self, event):
1004 # if intraline selection:
1005 # delete it
1006 # elif multiline selection:
1007 # do indent-region & return
1008 # indent one level
1009 text = self.text
1010 first, last = self.get_selection_indices()
1011 text.undo_block_start()
1012 try:
1013 if first and last:
1014 if index2line(first) != index2line(last):
1015 return self.indent_region_event(event)
1016 text.delete(first, last)
1017 text.mark_set("insert", first)
1018 prefix = text.get("insert linestart", "insert")
1019 raw, effective = classifyws(prefix, self.tabwidth)
1020 if raw == len(prefix):
1021 # only whitespace to the left
1022 self.reindent_to(effective + self.indentwidth)
1023 else:
1024 if self.usetabs:
1025 pad = '\t'
1026 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001027 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001028 n = self.indentwidth
1029 pad = ' ' * (n - effective % n)
1030 text.insert("insert", pad)
1031 text.see("insert")
1032 return "break"
1033 finally:
1034 text.undo_block_stop()
1035
1036 def newline_and_indent_event(self, event):
1037 text = self.text
1038 first, last = self.get_selection_indices()
1039 text.undo_block_start()
1040 try:
1041 if first and last:
1042 text.delete(first, last)
1043 text.mark_set("insert", first)
1044 line = text.get("insert linestart", "insert")
1045 i, n = 0, len(line)
1046 while i < n and line[i] in " \t":
1047 i = i+1
1048 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001049 # the cursor is in or at leading indentation in a continuation
1050 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001051 text.insert("insert linestart", '\n')
1052 return "break"
1053 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001054 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001055 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001056 last_line_of_prompt = sys.ps1.split('\n')[-1]
1057 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001058 line = line[:-1]
1059 i = i+1
1060 if i:
1061 text.delete("insert - %d chars" % i, "insert")
1062 # strip whitespace after insert point
1063 while text.get("insert") in " \t":
1064 text.delete("insert")
1065 # start new line
1066 text.insert("insert", '\n')
1067
1068 # adjust indentation for continuations and block
1069 # open/close first need to find the last stmt
1070 lno = index2line(text.index('insert'))
1071 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1072 for context in self.num_context_lines:
1073 startat = max(lno - context, 1)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001074 startatindex = repr(startat) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001075 rawtext = text.get(startatindex, "insert")
1076 y.set_str(rawtext)
1077 bod = y.find_good_parse_start(
1078 self.context_use_ps1,
1079 self._build_char_in_string_func(startatindex))
1080 if bod is not None or startat == 1:
1081 break
1082 y.set_lo(bod or 0)
1083 c = y.get_continuation_type()
1084 if c != PyParse.C_NONE:
1085 # The current stmt hasn't ended yet.
1086 if c == PyParse.C_STRING:
1087 # inside a string; just mimic the current indent
1088 text.insert("insert", indent)
1089 elif c == PyParse.C_BRACKET:
1090 # line up with the first (if any) element of the
1091 # last open bracket structure; else indent one
1092 # level beyond the indent of the line with the
1093 # last open bracket
1094 self.reindent_to(y.compute_bracket_indent())
1095 elif c == PyParse.C_BACKSLASH:
1096 # if more than one line in this stmt already, just
1097 # mimic the current indent; else if initial line
1098 # has a start on an assignment stmt, indent to
1099 # beyond leftmost =; else to beyond first chunk of
1100 # non-whitespace on initial line
1101 if y.get_num_lines_in_stmt() > 1:
1102 text.insert("insert", indent)
1103 else:
1104 self.reindent_to(y.compute_backslash_indent())
1105 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001106 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001107 return "break"
1108
1109 # This line starts a brand new stmt; indent relative to
1110 # indentation of initial line of closest preceding
1111 # interesting stmt.
1112 indent = y.get_base_indent_string()
1113 text.insert("insert", indent)
1114 if y.is_block_opener():
1115 self.smart_indent_event(event)
1116 elif indent and y.is_block_closer():
1117 self.smart_backspace_event(event)
1118 return "break"
1119 finally:
1120 text.see("insert")
1121 text.undo_block_stop()
1122
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001123 # Our editwin provides a is_char_in_string function that works
1124 # with a Tk text index, but PyParse only knows about offsets into
1125 # a string. This builds a function for PyParse that accepts an
1126 # offset.
1127
1128 def _build_char_in_string_func(self, startindex):
1129 def inner(offset, _startindex=startindex,
1130 _icis=self.is_char_in_string):
1131 return _icis(_startindex + "+%dc" % offset)
1132 return inner
1133
1134 def indent_region_event(self, event):
1135 head, tail, chars, lines = self.get_region()
1136 for pos in range(len(lines)):
1137 line = lines[pos]
1138 if line:
1139 raw, effective = classifyws(line, self.tabwidth)
1140 effective = effective + self.indentwidth
1141 lines[pos] = self._make_blanks(effective) + line[raw:]
1142 self.set_region(head, tail, chars, lines)
1143 return "break"
1144
1145 def dedent_region_event(self, event):
1146 head, tail, chars, lines = self.get_region()
1147 for pos in range(len(lines)):
1148 line = lines[pos]
1149 if line:
1150 raw, effective = classifyws(line, self.tabwidth)
1151 effective = max(effective - self.indentwidth, 0)
1152 lines[pos] = self._make_blanks(effective) + line[raw:]
1153 self.set_region(head, tail, chars, lines)
1154 return "break"
1155
1156 def comment_region_event(self, event):
1157 head, tail, chars, lines = self.get_region()
1158 for pos in range(len(lines) - 1):
1159 line = lines[pos]
1160 lines[pos] = '##' + line
1161 self.set_region(head, tail, chars, lines)
1162
1163 def uncomment_region_event(self, event):
1164 head, tail, chars, lines = self.get_region()
1165 for pos in range(len(lines)):
1166 line = lines[pos]
1167 if not line:
1168 continue
1169 if line[:2] == '##':
1170 line = line[2:]
1171 elif line[:1] == '#':
1172 line = line[1:]
1173 lines[pos] = line
1174 self.set_region(head, tail, chars, lines)
1175
1176 def tabify_region_event(self, event):
1177 head, tail, chars, lines = self.get_region()
1178 tabwidth = self._asktabwidth()
1179 for pos in range(len(lines)):
1180 line = lines[pos]
1181 if line:
1182 raw, effective = classifyws(line, tabwidth)
1183 ntabs, nspaces = divmod(effective, tabwidth)
1184 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1185 self.set_region(head, tail, chars, lines)
1186
1187 def untabify_region_event(self, event):
1188 head, tail, chars, lines = self.get_region()
1189 tabwidth = self._asktabwidth()
1190 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001191 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001192 self.set_region(head, tail, chars, lines)
1193
1194 def toggle_tabs_event(self, event):
1195 if self.askyesno(
1196 "Toggle tabs",
1197 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1198 parent=self.text):
1199 self.usetabs = not self.usetabs
1200 return "break"
1201
1202 # XXX this isn't bound to anything -- see class tabwidth comments
1203 def change_tabwidth_event(self, event):
1204 new = self._asktabwidth()
1205 if new != self.tabwidth:
1206 self.tabwidth = new
1207 self.set_indentation_params(0, guess=0)
1208 return "break"
1209
1210 def change_indentwidth_event(self, event):
1211 new = self.askinteger(
1212 "Indent width",
1213 "New indent width (2-16)",
1214 parent=self.text,
1215 initialvalue=self.indentwidth,
1216 minvalue=2,
1217 maxvalue=16)
1218 if new and new != self.indentwidth:
1219 self.indentwidth = new
1220 return "break"
1221
1222 def get_region(self):
1223 text = self.text
1224 first, last = self.get_selection_indices()
1225 if first and last:
1226 head = text.index(first + " linestart")
1227 tail = text.index(last + "-1c lineend +1c")
1228 else:
1229 head = text.index("insert linestart")
1230 tail = text.index("insert lineend +1c")
1231 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001232 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001233 return head, tail, chars, lines
1234
1235 def set_region(self, head, tail, chars, lines):
1236 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001237 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001238 if newchars == chars:
1239 text.bell()
1240 return
1241 text.tag_remove("sel", "1.0", "end")
1242 text.mark_set("insert", head)
1243 text.undo_block_start()
1244 text.delete(head, tail)
1245 text.insert(head, newchars)
1246 text.undo_block_stop()
1247 text.tag_add("sel", head, "insert")
1248
1249 # Make string that displays as n leading blanks.
1250
1251 def _make_blanks(self, n):
1252 if self.usetabs:
1253 ntabs, nspaces = divmod(n, self.tabwidth)
1254 return '\t' * ntabs + ' ' * nspaces
1255 else:
1256 return ' ' * n
1257
1258 # Delete from beginning of line to insert point, then reinsert
1259 # column logical (meaning use tabs if appropriate) spaces.
1260
1261 def reindent_to(self, column):
1262 text = self.text
1263 text.undo_block_start()
1264 if text.compare("insert linestart", "!=", "insert"):
1265 text.delete("insert linestart", "insert")
1266 if column:
1267 text.insert("insert", self._make_blanks(column))
1268 text.undo_block_stop()
1269
1270 def _asktabwidth(self):
1271 return self.askinteger(
1272 "Tab width",
1273 "Spaces per tab? (2-16)",
1274 parent=self.text,
1275 initialvalue=self.indentwidth,
1276 minvalue=2,
1277 maxvalue=16) or self.tabwidth
1278
1279 # Guess indentwidth from text content.
1280 # Return guessed indentwidth. This should not be believed unless
1281 # it's in a reasonable range (e.g., it will be 0 if no indented
1282 # blocks are found).
1283
1284 def guess_indent(self):
1285 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1286 if opener and indented:
1287 raw, indentsmall = classifyws(opener, self.tabwidth)
1288 raw, indentlarge = classifyws(indented, self.tabwidth)
1289 else:
1290 indentsmall = indentlarge = 0
1291 return indentlarge - indentsmall
1292
1293# "line.col" -> line, as an int
1294def index2line(index):
1295 return int(float(index))
1296
1297# Look at the leading whitespace in s.
1298# Return pair (# of leading ws characters,
1299# effective # of leading blanks after expanding
1300# tabs to width tabwidth)
1301
1302def classifyws(s, tabwidth):
1303 raw = effective = 0
1304 for ch in s:
1305 if ch == ' ':
1306 raw = raw + 1
1307 effective = effective + 1
1308 elif ch == '\t':
1309 raw = raw + 1
1310 effective = (effective // tabwidth + 1) * tabwidth
1311 else:
1312 break
1313 return raw, effective
1314
1315import tokenize
1316_tokenize = tokenize
1317del tokenize
1318
1319class IndentSearcher:
1320
1321 # .run() chews over the Text widget, looking for a block opener
1322 # and the stmt following it. Returns a pair,
1323 # (line containing block opener, line containing stmt)
1324 # Either or both may be None.
1325
1326 def __init__(self, text, tabwidth):
1327 self.text = text
1328 self.tabwidth = tabwidth
1329 self.i = self.finished = 0
1330 self.blkopenline = self.indentedline = None
1331
1332 def readline(self):
1333 if self.finished:
1334 return ""
1335 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001336 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 if self.text.compare(mark, ">=", "end"):
1338 return ""
1339 return self.text.get(mark, mark + " lineend+1c")
1340
1341 def tokeneater(self, type, token, start, end, line,
1342 INDENT=_tokenize.INDENT,
1343 NAME=_tokenize.NAME,
1344 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1345 if self.finished:
1346 pass
1347 elif type == NAME and token in OPENERS:
1348 self.blkopenline = line
1349 elif type == INDENT and self.blkopenline:
1350 self.indentedline = line
1351 self.finished = 1
1352
1353 def run(self):
1354 save_tabsize = _tokenize.tabsize
1355 _tokenize.tabsize = self.tabwidth
1356 try:
1357 try:
1358 _tokenize.tokenize(self.readline, self.tokeneater)
1359 except _tokenize.TokenError:
1360 # since we cut off the tokenizer early, we can trigger
1361 # spurious errors
1362 pass
1363 finally:
1364 _tokenize.tabsize = save_tabsize
1365 return self.blkopenline, self.indentedline
1366
1367### end autoindent code ###
1368
David Scherer7aced172000-08-15 01:13:23 +00001369def prepstr(s):
1370 # Helper to extract the underscore from a string, e.g.
1371 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001372 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001373 if i >= 0:
1374 s = s[:i] + s[i+1:]
1375 return i, s
1376
1377
1378keynames = {
1379 'bracketleft': '[',
1380 'bracketright': ']',
1381 'slash': '/',
1382}
1383
1384def get_accelerator(keydefs, event):
1385 keylist = keydefs.get(event)
1386 if not keylist:
1387 return ""
1388 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001389 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001390 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1391 s = re.sub("Key-", "", s)
1392 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1393 s = re.sub("Control-", "Ctrl-", s)
1394 s = re.sub("-", "+", s)
1395 s = re.sub("><", " ", s)
1396 s = re.sub("<", "", s)
1397 s = re.sub(">", "", s)
1398 return s
1399
1400
1401def fixwordbreaks(root):
1402 # Make sure that Tk's double-click and next/previous word
1403 # operations use our definition of a word (i.e. an identifier)
1404 tk = root.tk
1405 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1406 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1407 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1408
1409
1410def test():
1411 root = Tk()
1412 fixwordbreaks(root)
1413 root.withdraw()
1414 if sys.argv[1:]:
1415 filename = sys.argv[1]
1416 else:
1417 filename = None
1418 edit = EditorWindow(root=root, filename=filename)
1419 edit.set_close_hook(root.quit)
1420 root.mainloop()
1421 root.destroy()
1422
1423if __name__ == '__main__':
1424 test()