blob: ca52ccb582fc38d6cfa32c10e6c72cdfe2a8f6fa [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
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +00005from itertools import count
David Scherer7aced172000-08-15 01:13:23 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009
10import webbrowser
David Scherer7aced172000-08-15 01:13:23 +000011import idlever
12import WindowList
Steven M. Gavac5976402002-01-04 03:06:08 +000013import SearchDialog
14import GrepDialog
15import ReplaceDialog
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +000016import PyParse
Steven M. Gavadc72f482002-01-03 11:51:07 +000017from configHandler import idleConf
Steven M. Gava3b55a892001-11-21 05:56:26 +000018import aboutDialog, textView, configDialog
David Scherer7aced172000-08-15 01:13:23 +000019
20# The default tab setting for a Text widget, in average-width characters.
21TK_TABWIDTH_DEFAULT = 8
22
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000023def _find_module(fullname, path=None):
24 """Version of imp.find_module() that handles hierarchical module names"""
25
26 file = None
27 for tgt in fullname.split('.'):
28 if file is not None:
29 file.close() # close intermediate files
30 (file, filename, descr) = imp.find_module(tgt, path)
31 if descr[2] == imp.PY_SOURCE:
32 break # find but not load the source file
33 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000034 try:
35 path = module.__path__
36 except AttributeError:
37 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000038 return file, filename, descr
39
David Scherer7aced172000-08-15 01:13:23 +000040class EditorWindow:
David Scherer7aced172000-08-15 01:13:23 +000041 from Percolator import Percolator
42 from ColorDelegator import ColorDelegator
43 from UndoDelegator import UndoDelegator
44 from IOBinding import IOBinding
45 import Bindings
46 from Tkinter import Toplevel
47 from MultiStatusBar import MultiStatusBar
48
David Scherer7aced172000-08-15 01:13:23 +000049 vars = {}
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000050 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000051
52 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000053 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000054 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000055 if sys.platform.count('linux'):
56 # look for html docs in a couple of standard places
57 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
58 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
59 dochome = '/var/www/html/python/index.html'
60 else:
61 basepath = '/usr/share/doc/' # standard location
62 dochome = os.path.join(basepath, pyver,
63 'Doc', 'index.html')
Thomas Heller84ef1532003-09-23 20:53:10 +000064 elif sys.platform.count('win') or sys.platform.count('nt'):
65 chmfile = os.path.join(sys.prefix, "Python%d%d.chm" % sys.version_info[:2])
66 if os.path.isfile(chmfile):
67 dochome = chmfile
68 print "dochome =", dochome
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000069 dochome = os.path.normpath(dochome)
70 if os.path.isfile(dochome):
71 EditorWindow.help_url = dochome
72 else:
73 EditorWindow.help_url = "http://www.python.org/doc/current"
Steven M. Gavadc72f482002-01-03 11:51:07 +000074 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000075 self.flist = flist
76 root = root or flist.root
77 self.root = root
David Scherer7aced172000-08-15 01:13:23 +000078 self.menubar = Menu(root)
79 self.top = top = self.Toplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000080 if flist:
81 self.vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000082 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +000083 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +000084 self.top.instance_dict=flist.inversedict
85 self.recent_files_path=os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +000086 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +000087 self.vbar = vbar = Scrollbar(top, name='vbar')
88 self.text_frame = text_frame = Frame(top)
Kurt B. Kaiser1061e722003-01-04 01:43:53 +000089 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaisera1dee062002-10-04 21:33:57 +000090 self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
Steven M. Gavadc72f482002-01-03 11:51:07 +000091 foreground=idleConf.GetHighlight(currentTheme,
92 'normal',fgBg='fg'),
93 background=idleConf.GetHighlight(currentTheme,
94 'normal',fgBg='bg'),
95 highlightcolor=idleConf.GetHighlight(currentTheme,
96 'hilite',fgBg='fg'),
97 highlightbackground=idleConf.GetHighlight(currentTheme,
98 'hilite',fgBg='bg'),
99 insertbackground=idleConf.GetHighlight(currentTheme,
100 'cursor',fgBg='fg'),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000101 width=self.width,
Steven M. Gavadc72f482002-01-03 11:51:07 +0000102 height=idleConf.GetOption('main','EditorWindow','height') )
David Scherer7aced172000-08-15 01:13:23 +0000103
104 self.createmenubar()
105 self.apply_bindings()
106
107 self.top.protocol("WM_DELETE_WINDOW", self.close)
108 self.top.bind("<<close-window>>", self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000109 text.bind("<<cut>>", self.cut)
110 text.bind("<<copy>>", self.copy)
111 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000112 text.bind("<<center-insert>>", self.center_insert_event)
113 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000114 text.bind("<<python-docs>>", self.python_docs)
115 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000116 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000117 text.bind("<<open-module>>", self.open_module)
118 text.bind("<<do-nothing>>", lambda event: "break")
119 text.bind("<<select-all>>", self.select_all)
120 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000121 text.bind("<<find>>", self.find_event)
122 text.bind("<<find-again>>", self.find_again_event)
123 text.bind("<<find-in-files>>", self.find_in_files_event)
124 text.bind("<<find-selection>>", self.find_selection_event)
125 text.bind("<<replace>>", self.replace_event)
126 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000127 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000128 text.bind("<<smart-backspace>>",self.smart_backspace_event)
129 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
130 text.bind("<<smart-indent>>",self.smart_indent_event)
131 text.bind("<<indent-region>>",self.indent_region_event)
132 text.bind("<<dedent-region>>",self.dedent_region_event)
133 text.bind("<<comment-region>>",self.comment_region_event)
134 text.bind("<<uncomment-region>>",self.uncomment_region_event)
135 text.bind("<<tabify-region>>",self.tabify_region_event)
136 text.bind("<<untabify-region>>",self.untabify_region_event)
137 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
138 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000139 text.bind("<Left>", self.move_at_edge_if_selection(0))
140 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000141
David Scherer7aced172000-08-15 01:13:23 +0000142 if flist:
143 flist.inversedict[self] = key
144 if key:
145 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000146 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000147 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
148 text.bind("<<open-class-browser>>", self.open_class_browser)
149 text.bind("<<open-path-browser>>", self.open_path_browser)
150
Steven M. Gava898a3652001-10-07 11:10:44 +0000151 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000152 vbar['command'] = text.yview
153 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000154 text['yscrollcommand'] = vbar.set
Steven M. Gavab1585412002-03-12 00:21:56 +0000155 fontWeight='normal'
156 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
157 fontWeight='bold'
Steven M. Gavadc72f482002-01-03 11:51:07 +0000158 text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
Steven M. Gavab1585412002-03-12 00:21:56 +0000159 idleConf.GetOption('main','EditorWindow','font-size'),
160 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000161 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
162 text.pack(side=TOP, fill=BOTH, expand=1)
163 text.focus_set()
164
165 self.per = per = self.Percolator(text)
166 if self.ispythonsource(filename):
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000167 self.color = color = self.ColorDelegator()
168 per.insertfilter(color)
David Scherer7aced172000-08-15 01:13:23 +0000169 else:
David Scherer7aced172000-08-15 01:13:23 +0000170 self.color = None
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000171
172 self.undo = undo = self.UndoDelegator()
173 per.insertfilter(undo)
174 text.undo_block_start = undo.undo_block_start
175 text.undo_block_stop = undo.undo_block_stop
176 undo.set_saved_change_hook(self.saved_change_hook)
177
178 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000179 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000180 io.set_filename_change_hook(self.filename_change_hook)
181
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000182 # Create the recent files submenu
183 self.recent_files_menu = Menu(self.menubar)
184 self.menudict['file'].insert_cascade(3, label='Recent Files',
185 underline=0,
186 menu=self.recent_files_menu)
187 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000188
David Scherer7aced172000-08-15 01:13:23 +0000189 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000190 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000191 io.loadfile(filename)
192 else:
193 io.set_filename(filename)
David Scherer7aced172000-08-15 01:13:23 +0000194 self.saved_change_hook()
195
196 self.load_extensions()
197
198 menu = self.menudict.get('windows')
199 if menu:
200 end = menu.index("end")
201 if end is None:
202 end = -1
203 if end >= 0:
204 menu.add_separator()
205 end = end + 1
206 self.wmenu_end = end
207 WindowList.register_callback(self.postwindowsmenu)
208
209 # Some abstractions so IDLE extensions are cross-IDE
210 self.askyesno = tkMessageBox.askyesno
211 self.askinteger = tkSimpleDialog.askinteger
212 self.showerror = tkMessageBox.showerror
213
214 if self.extensions.has_key('AutoIndent'):
215 self.extensions['AutoIndent'].set_indentation_params(
216 self.ispythonsource(filename))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000217
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000218 def new_callback(self, event):
219 dirname, basename = self.io.defaultfilename()
220 self.flist.new(dirname)
221 return "break"
222
David Scherer7aced172000-08-15 01:13:23 +0000223 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000224 self.status_bar = self.MultiStatusBar(self.top)
David Scherer7aced172000-08-15 01:13:23 +0000225 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
226 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
227 self.status_bar.pack(side=BOTTOM, fill=X)
228 self.text.bind('<KeyRelease>', self.set_line_and_column)
229 self.text.bind('<ButtonRelease>', self.set_line_and_column)
230 self.text.after_idle(self.set_line_and_column)
231
232 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000233 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000234 self.status_bar.set_label('column', 'Col: %s' % column)
235 self.status_bar.set_label('line', 'Ln: %s' % line)
236
237 def wakeup(self):
238 if self.top.wm_state() == "iconic":
239 self.top.wm_deiconify()
240 else:
241 self.top.tkraise()
242 self.text.focus_set()
243
244 menu_specs = [
245 ("file", "_File"),
246 ("edit", "_Edit"),
247 ("format", "F_ormat"),
248 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000249 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000250 ("windows", "_Windows"),
251 ("help", "_Help"),
252 ]
253
254 def createmenubar(self):
255 mbar = self.menubar
256 self.menudict = menudict = {}
257 for name, label in self.menu_specs:
258 underline, label = prepstr(label)
259 menudict[name] = menu = Menu(mbar, name=name)
260 mbar.add_cascade(label=label, menu=menu, underline=underline)
261 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000262 self.base_helpmenu_length = self.menudict['help'].index(END)
263 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000264
265 def postwindowsmenu(self):
266 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000267 menu = self.menudict['windows']
268 end = menu.index("end")
269 if end is None:
270 end = -1
271 if end > self.wmenu_end:
272 menu.delete(self.wmenu_end+1, end)
273 WindowList.add_windows_to_menu(menu)
274
275 rmenu = None
276
277 def right_menu_event(self, event):
278 self.text.tag_remove("sel", "1.0", "end")
279 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
280 if not self.rmenu:
281 self.make_rmenu()
282 rmenu = self.rmenu
283 self.event = event
284 iswin = sys.platform[:3] == 'win'
285 if iswin:
286 self.text.config(cursor="arrow")
287 rmenu.tk_popup(event.x_root, event.y_root)
288 if iswin:
289 self.text.config(cursor="ibeam")
290
291 rmenu_specs = [
292 # ("Label", "<<virtual-event>>"), ...
293 ("Close", "<<close-window>>"), # Example
294 ]
295
296 def make_rmenu(self):
297 rmenu = Menu(self.text, tearoff=0)
298 for label, eventname in self.rmenu_specs:
299 def command(text=self.text, eventname=eventname):
300 text.event_generate(eventname)
301 rmenu.add_command(label=label, command=command)
302 self.rmenu = rmenu
303
304 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000305 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000306
Steven M. Gava3b55a892001-11-21 05:56:26 +0000307 def config_dialog(self, event=None):
308 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000309
David Scherer7aced172000-08-15 01:13:23 +0000310 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000311 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000312 textView.TextViewer(self.top,'Help',fn)
313
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000314 def python_docs(self, event=None):
315 if sys.platform.count('win') or sys.platform.count('nt'):
Steven M. Gava931625d2002-04-22 00:38:26 +0000316 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000317 return "break"
318 else:
319 webbrowser.open(self.help_url)
320 return "break"
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000321
322 def display_docs(self, url):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000323 if not (url.startswith('www') or url.startswith('http')):
324 url = os.path.normpath(url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000325 if sys.platform.count('win') or sys.platform.count('nt'):
326 os.startfile(url)
327 else:
328 webbrowser.open(url)
David Scherer7aced172000-08-15 01:13:23 +0000329
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000330 def cut(self,event):
331 self.text.event_generate("<<Cut>>")
332 return "break"
333
334 def copy(self,event):
335 self.text.event_generate("<<Copy>>")
336 return "break"
337
338 def paste(self,event):
339 self.text.event_generate("<<Paste>>")
340 return "break"
341
David Scherer7aced172000-08-15 01:13:23 +0000342 def select_all(self, event=None):
343 self.text.tag_add("sel", "1.0", "end-1c")
344 self.text.mark_set("insert", "1.0")
345 self.text.see("insert")
346 return "break"
347
348 def remove_selection(self, event=None):
349 self.text.tag_remove("sel", "1.0", "end")
350 self.text.see("insert")
351
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000352 def move_at_edge_if_selection(self, edge_index):
353 """Cursor move begins at start or end of selection
354
355 When a left/right cursor key is pressed create and return to Tkinter a
356 function which causes a cursor move from the associated edge of the
357 selection.
358
359 """
360 self_text_index = self.text.index
361 self_text_mark_set = self.text.mark_set
362 edges_table = ("sel.first+1c", "sel.last-1c")
363 def move_at_edge(event):
364 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
365 try:
366 self_text_index("sel.first")
367 self_text_mark_set("insert", edges_table[edge_index])
368 except TclError:
369 pass
370 return move_at_edge
371
Steven M. Gavac5976402002-01-04 03:06:08 +0000372 def find_event(self, event):
373 SearchDialog.find(self.text)
374 return "break"
375
376 def find_again_event(self, event):
377 SearchDialog.find_again(self.text)
378 return "break"
379
380 def find_selection_event(self, event):
381 SearchDialog.find_selection(self.text)
382 return "break"
383
384 def find_in_files_event(self, event):
385 GrepDialog.grep(self.text, self.io, self.flist)
386 return "break"
387
388 def replace_event(self, event):
389 ReplaceDialog.replace(self.text)
390 return "break"
391
392 def goto_line_event(self, event):
393 text = self.text
394 lineno = tkSimpleDialog.askinteger("Goto",
395 "Go to line number:",parent=text)
396 if lineno is None:
397 return "break"
398 if lineno <= 0:
399 text.bell()
400 return "break"
401 text.mark_set("insert", "%d.0" % lineno)
402 text.see("insert")
403
David Scherer7aced172000-08-15 01:13:23 +0000404 def open_module(self, event=None):
405 # XXX Shouldn't this be in IOBinding or in FileList?
406 try:
407 name = self.text.get("sel.first", "sel.last")
408 except TclError:
409 name = ""
410 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000411 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000412 name = tkSimpleDialog.askstring("Module",
413 "Enter the name of a Python module\n"
414 "to search on sys.path and open:",
415 parent=self.text, initialvalue=name)
416 if name:
417 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000418 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000419 return
David Scherer7aced172000-08-15 01:13:23 +0000420 # XXX Ought to insert current file's directory in front of path
421 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000422 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000423 except (NameError, ImportError), msg:
424 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
425 return
426 if type != imp.PY_SOURCE:
427 tkMessageBox.showerror("Unsupported type",
428 "%s is not a source module" % name, parent=self.text)
429 return
430 if f:
431 f.close()
432 if self.flist:
433 self.flist.open(file)
434 else:
435 self.io.loadfile(file)
436
437 def open_class_browser(self, event=None):
438 filename = self.io.filename
439 if not filename:
440 tkMessageBox.showerror(
441 "No filename",
442 "This buffer has no associated filename",
443 master=self.text)
444 self.text.focus_set()
445 return None
446 head, tail = os.path.split(filename)
447 base, ext = os.path.splitext(tail)
448 import ClassBrowser
449 ClassBrowser.ClassBrowser(self.flist, base, [head])
450
451 def open_path_browser(self, event=None):
452 import PathBrowser
453 PathBrowser.PathBrowser(self.flist)
454
455 def gotoline(self, lineno):
456 if lineno is not None and lineno > 0:
457 self.text.mark_set("insert", "%d.0" % lineno)
458 self.text.tag_remove("sel", "1.0", "end")
459 self.text.tag_add("sel", "insert", "insert +1l")
460 self.center()
461
462 def ispythonsource(self, filename):
463 if not filename:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000464 return True
David Scherer7aced172000-08-15 01:13:23 +0000465 base, ext = os.path.splitext(os.path.basename(filename))
466 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000467 return True
David Scherer7aced172000-08-15 01:13:23 +0000468 try:
469 f = open(filename)
470 line = f.readline()
471 f.close()
472 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000473 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000474 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000475
476 def close_hook(self):
477 if self.flist:
478 self.flist.close_edit(self)
479
480 def set_close_hook(self, close_hook):
481 self.close_hook = close_hook
482
483 def filename_change_hook(self):
484 if self.flist:
485 self.flist.filename_changed_edit(self)
486 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000487 self.top.update_windowlist_registry(self)
David Scherer7aced172000-08-15 01:13:23 +0000488 if self.ispythonsource(self.io.filename):
489 self.addcolorizer()
490 else:
491 self.rmcolorizer()
492
493 def addcolorizer(self):
494 if self.color:
495 return
David Scherer7aced172000-08-15 01:13:23 +0000496 self.per.removefilter(self.undo)
497 self.color = self.ColorDelegator()
498 self.per.insertfilter(self.color)
499 self.per.insertfilter(self.undo)
500
501 def rmcolorizer(self):
502 if not self.color:
503 return
David Scherer7aced172000-08-15 01:13:23 +0000504 self.per.removefilter(self.undo)
505 self.per.removefilter(self.color)
506 self.color = None
507 self.per.insertfilter(self.undo)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000508
Steven M. Gavab77d3432002-03-02 07:16:21 +0000509 def ResetColorizer(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000510 "Update the colour theme if it is changed"
511 # Called from configDialog.py
Steven M. Gavab77d3432002-03-02 07:16:21 +0000512 if self.color:
513 self.color = self.ColorDelegator()
514 self.per.insertfilter(self.color)
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000515 theme = idleConf.GetOption('main','Theme','name')
516 self.text.config(idleConf.GetHighlight(theme, "normal"))
David Scherer7aced172000-08-15 01:13:23 +0000517
Steven M. Gavab1585412002-03-12 00:21:56 +0000518 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000519 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000520 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000521 fontWeight='normal'
522 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
523 fontWeight='bold'
524 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
525 idleConf.GetOption('main','EditorWindow','font-size'),
526 fontWeight))
527
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000528 def ResetKeybindings(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000529 "Update the keybindings if they are changed"
530 # Called from configDialog.py
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000531 self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
532 keydefs = self.Bindings.default_keydefs
533 for event, keylist in keydefs.items():
534 self.text.event_delete(event)
535 self.apply_bindings()
536 #update menu accelerators
537 menuEventDict={}
538 for menu in self.Bindings.menudefs:
539 menuEventDict[menu[0]]={}
540 for item in menu[1]:
541 if item:
542 menuEventDict[menu[0]][prepstr(item[0])[1]]=item[1]
543 for menubarItem in self.menudict.keys():
544 menu=self.menudict[menubarItem]
545 end=menu.index(END)+1
546 for index in range(0,end):
547 if menu.type(index)=='command':
548 accel=menu.entrycget(index,'accelerator')
549 if accel:
550 itemName=menu.entrycget(index,'label')
551 event=''
552 if menuEventDict.has_key(menubarItem):
553 if menuEventDict[menubarItem].has_key(itemName):
554 event=menuEventDict[menubarItem][itemName]
555 if event:
556 #print 'accel was:',accel
557 accel=get_accelerator(keydefs, event)
558 menu.entryconfig(index,accelerator=accel)
559 #print 'accel now:',accel,'\n'
560
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000561 def reset_help_menu_entries(self):
562 "Update the additional help entries on the Help menu"
563 help_list = idleConf.GetAllExtraHelpSourcesList()
564 helpmenu = self.menudict['help']
565 # first delete the extra help entries, if any
566 helpmenu_length = helpmenu.index(END)
567 if helpmenu_length > self.base_helpmenu_length:
568 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
569 # then rebuild them
570 if help_list:
571 helpmenu.add_separator()
572 for entry in help_list:
573 cmd = self.__extra_help_callback(entry[1])
574 helpmenu.add_command(label=entry[0], command=cmd)
575 # and update the menu dictionary
576 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000577
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000578 def __extra_help_callback(self, helpfile):
579 "Create a callback with the helpfile value frozen at definition time"
580 def display_extra_help(helpfile=helpfile):
581 self.display_docs(helpfile)
582 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000583
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000584 def update_recent_files_list(self, new_file=None):
585 "Load and update the recent files list and menus"
586 rf_list = []
587 if os.path.exists(self.recent_files_path):
588 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000589 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000590 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000591 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000592 rf_list_file.close()
593 if new_file:
594 new_file = os.path.abspath(new_file) + '\n'
595 if new_file in rf_list:
596 rf_list.remove(new_file) # move to top
597 rf_list.insert(0, new_file)
598 # clean and save the recent files list
599 bad_paths = []
600 for path in rf_list:
601 if '\0' in path or not os.path.exists(path[0:-1]):
602 bad_paths.append(path)
603 rf_list = [path for path in rf_list if path not in bad_paths]
604 ulchars = "1234567890ABCDEFGHIJK"
605 rf_list = rf_list[0:len(ulchars)]
606 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000607 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000608 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000609 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000610 rf_file.close()
611 # for each edit window instance, construct the recent files menu
612 for instance in self.top.instance_dict.keys():
613 menu = instance.recent_files_menu
614 menu.delete(1, END) # clear, and rebuild:
615 for i, file in zip(count(), rf_list):
616 file_name = file[0:-1] # zap \n
617 callback = instance.__recent_file_callback(file_name)
618 menu.add_command(label=ulchars[i] + " " + file_name,
619 command=callback,
620 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000621
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000622 def __recent_file_callback(self, file_name):
623 def open_recent_file(fn_closure=file_name):
624 self.io.open(editFile=fn_closure)
625 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000626
David Scherer7aced172000-08-15 01:13:23 +0000627 def saved_change_hook(self):
628 short = self.short_title()
629 long = self.long_title()
630 if short and long:
631 title = short + " - " + long
632 elif short:
633 title = short
634 elif long:
635 title = long
636 else:
637 title = "Untitled"
638 icon = short or long or title
639 if not self.get_saved():
640 title = "*%s*" % title
641 icon = "*%s" % icon
642 self.top.wm_title(title)
643 self.top.wm_iconname(icon)
644
645 def get_saved(self):
646 return self.undo.get_saved()
647
648 def set_saved(self, flag):
649 self.undo.set_saved(flag)
650
651 def reset_undo(self):
652 self.undo.reset_undo()
653
654 def short_title(self):
655 filename = self.io.filename
656 if filename:
657 filename = os.path.basename(filename)
658 return filename
659
660 def long_title(self):
661 return self.io.filename or ""
662
663 def center_insert_event(self, event):
664 self.center()
665
666 def center(self, mark="insert"):
667 text = self.text
668 top, bot = self.getwindowlines()
669 lineno = self.getlineno(mark)
670 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000671 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000672 text.yview(float(newtop))
673
674 def getwindowlines(self):
675 text = self.text
676 top = self.getlineno("@0,0")
677 bot = self.getlineno("@0,65535")
678 if top == bot and text.winfo_height() == 1:
679 # Geometry manager hasn't run yet
680 height = int(text['height'])
681 bot = top + height - 1
682 return top, bot
683
684 def getlineno(self, mark="insert"):
685 text = self.text
686 return int(float(text.index(mark)))
687
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000688 def get_geometry(self):
689 "Return (width, height, x, y)"
690 geom = self.top.wm_geometry()
691 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
692 tuple = (map(int, m.groups()))
693 return tuple
694
David Scherer7aced172000-08-15 01:13:23 +0000695 def close_event(self, event):
696 self.close()
697
698 def maybesave(self):
699 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000700 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000701 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000702 self.top.deiconify()
703 self.top.lower()
704 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000705 return self.io.maybesave()
706
707 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000708 reply = self.maybesave()
709 if reply != "cancel":
710 self._close()
711 return reply
712
713 def _close(self):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000714 #print self.io.filename
Steven M. Gava1d46e402002-03-27 08:40:46 +0000715 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000716 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000717 WindowList.unregister_callback(self.postwindowsmenu)
718 if self.close_hook:
719 self.close_hook()
720 self.flist = None
721 colorizing = 0
722 self.unload_extensions()
723 self.io.close(); self.io = None
724 self.undo = None # XXX
725 if self.color:
726 colorizing = self.color.colorizing
727 doh = colorizing and self.top
728 self.color.close(doh) # Cancel colorization
729 self.text = None
730 self.vars = None
731 self.per.close(); self.per = None
732 if not colorizing:
733 self.top.destroy()
734
735 def load_extensions(self):
736 self.extensions = {}
737 self.load_standard_extensions()
738
739 def unload_extensions(self):
740 for ins in self.extensions.values():
741 if hasattr(ins, "close"):
742 ins.close()
743 self.extensions = {}
744
745 def load_standard_extensions(self):
746 for name in self.get_standard_extension_names():
747 try:
748 self.load_extension(name)
749 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000750 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000751 import traceback
752 traceback.print_exc()
753
754 def get_standard_extension_names(self):
Steven M. Gavadc72f482002-01-03 11:51:07 +0000755 return idleConf.GetExtensions()
David Scherer7aced172000-08-15 01:13:23 +0000756
757 def load_extension(self, name):
758 mod = __import__(name, globals(), locals(), [])
759 cls = getattr(mod, name)
760 ins = cls(self)
761 self.extensions[name] = ins
Steven M. Gava72c3bf02002-01-19 10:41:51 +0000762 keydefs=idleConf.GetExtensionBindings(name)
David Scherer7aced172000-08-15 01:13:23 +0000763 if keydefs:
764 self.apply_bindings(keydefs)
765 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000766 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000767 while methodname[:1] == '<':
768 methodname = methodname[1:]
769 while methodname[-1:] == '>':
770 methodname = methodname[:-1]
771 methodname = methodname + "_event"
772 if hasattr(ins, methodname):
773 self.text.bind(vevent, getattr(ins, methodname))
774 if hasattr(ins, "menudefs"):
775 self.fill_menus(ins.menudefs, keydefs)
776 return ins
777
778 def apply_bindings(self, keydefs=None):
779 if keydefs is None:
780 keydefs = self.Bindings.default_keydefs
781 text = self.text
782 text.keydefs = keydefs
783 for event, keylist in keydefs.items():
784 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000785 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000786
787 def fill_menus(self, defs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000788 """Add appropriate entries to the menus and submenus
789
790 Menus that are absent or None in self.menudict are ignored.
791 """
David Scherer7aced172000-08-15 01:13:23 +0000792 if defs is None:
793 defs = self.Bindings.menudefs
794 if keydefs is None:
795 keydefs = self.Bindings.default_keydefs
796 menudict = self.menudict
797 text = self.text
798 for mname, itemlist in defs:
799 menu = menudict.get(mname)
800 if not menu:
801 continue
802 for item in itemlist:
803 if not item:
804 menu.add_separator()
805 else:
806 label, event = item
807 checkbutton = (label[:1] == '!')
808 if checkbutton:
809 label = label[1:]
810 underline, label = prepstr(label)
811 accelerator = get_accelerator(keydefs, event)
812 def command(text=text, event=event):
813 text.event_generate(event)
814 if checkbutton:
815 var = self.getrawvar(event, BooleanVar)
816 menu.add_checkbutton(label=label, underline=underline,
817 command=command, accelerator=accelerator,
818 variable=var)
819 else:
820 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000821 command=command,
822 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000823
824 def getvar(self, name):
825 var = self.getrawvar(name)
826 if var:
827 return var.get()
828
829 def setvar(self, name, value, vartype=None):
830 var = self.getrawvar(name, vartype)
831 if var:
832 var.set(value)
833
834 def getrawvar(self, name, vartype=None):
835 var = self.vars.get(name)
836 if not var and vartype:
837 self.vars[name] = var = vartype(self.text)
838 return var
839
840 # Tk implementations of "virtual text methods" -- each platform
841 # reusing IDLE's support code needs to define these for its GUI's
842 # flavor of widget.
843
844 # Is character at text_index in a Python string? Return 0 for
845 # "guaranteed no", true for anything else. This info is expensive
846 # to compute ab initio, but is probably already known by the
847 # platform's colorizer.
848
849 def is_char_in_string(self, text_index):
850 if self.color:
851 # Return true iff colorizer hasn't (re)gotten this far
852 # yet, or the character is tagged as being in a string
853 return self.text.tag_prevrange("TODO", text_index) or \
854 "STRING" in self.text.tag_names(text_index)
855 else:
856 # The colorizer is missing: assume the worst
857 return 1
858
859 # If a selection is defined in the text widget, return (start,
860 # end) as Tkinter text indices, otherwise return (None, None)
861 def get_selection_indices(self):
862 try:
863 first = self.text.index("sel.first")
864 last = self.text.index("sel.last")
865 return first, last
866 except TclError:
867 return None, None
868
869 # Return the text widget's current view of what a tab stop means
870 # (equivalent width in spaces).
871
872 def get_tabwidth(self):
873 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
874 return int(current)
875
876 # Set the text widget's current view of what a tab stop means.
877
878 def set_tabwidth(self, newtabwidth):
879 text = self.text
880 if self.get_tabwidth() != newtabwidth:
881 pixels = text.tk.call("font", "measure", text["font"],
882 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +0000883 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +0000884 text.configure(tabs=pixels)
885
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000886### begin autoindent code ###
887
888 # usetabs true -> literal tab characters are used by indent and
889 # dedent cmds, possibly mixed with spaces if
890 # indentwidth is not a multiple of tabwidth
891 # false -> tab characters are converted to spaces by indent
892 # and dedent cmds, and ditto TAB keystrokes
893 # indentwidth is the number of characters per logical indent level.
894 # tabwidth is the display width of a literal tab character.
895 # CAUTION: telling Tk to use anything other than its default
896 # tab setting causes it to use an entirely different tabbing algorithm,
897 # treating tab stops as fixed distances from the left margin.
898 # Nobody expects this, so for now tabwidth should never be changed.
899 usetabs = 0
900 indentwidth = 4
901 tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
902
903 # If context_use_ps1 is true, parsing searches back for a ps1 line;
904 # else searches for a popular (if, def, ...) Python stmt.
905 context_use_ps1 = 0
906
907 # When searching backwards for a reliable place to begin parsing,
908 # first start num_context_lines[0] lines back, then
909 # num_context_lines[1] lines back if that didn't work, and so on.
910 # The last value should be huge (larger than the # of lines in a
911 # conceivable file).
912 # Making the initial values larger slows things down more often.
913 num_context_lines = 50, 500, 5000000
914
915 def config(self, **options):
916 for key, value in options.items():
917 if key == 'usetabs':
918 self.usetabs = value
919 elif key == 'indentwidth':
920 self.indentwidth = value
921 elif key == 'tabwidth':
922 self.tabwidth = value
923 elif key == 'context_use_ps1':
924 self.context_use_ps1 = value
925 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000926 raise KeyError, "bad option name: %r" % (key,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000927
928 # If ispythonsource and guess are true, guess a good value for
929 # indentwidth based on file content (if possible), and if
930 # indentwidth != tabwidth set usetabs false.
931 # In any case, adjust the Text widget's view of what a tab
932 # character means.
933
934 def set_indentation_params(self, ispythonsource, guess=1):
935 if guess and ispythonsource:
936 i = self.guess_indent()
937 if 2 <= i <= 8:
938 self.indentwidth = i
939 if self.indentwidth != self.tabwidth:
940 self.usetabs = 0
941
942 self.set_tabwidth(self.tabwidth)
943
944 def smart_backspace_event(self, event):
945 text = self.text
946 first, last = self.get_selection_indices()
947 if first and last:
948 text.delete(first, last)
949 text.mark_set("insert", first)
950 return "break"
951 # Delete whitespace left, until hitting a real char or closest
952 # preceding virtual tab stop.
953 chars = text.get("insert linestart", "insert")
954 if chars == '':
955 if text.compare("insert", ">", "1.0"):
956 # easy: delete preceding newline
957 text.delete("insert-1c")
958 else:
959 text.bell() # at start of buffer
960 return "break"
961 if chars[-1] not in " \t":
962 # easy: delete preceding real char
963 text.delete("insert-1c")
964 return "break"
965 # Ick. It may require *inserting* spaces if we back up over a
966 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000967 tabwidth = self.tabwidth
968 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000969 assert have > 0
970 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000971 # Debug prompt is multilined....
972 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000973 ncharsdeleted = 0
974 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +0000975 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +0000976 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000977 chars = chars[:-1]
978 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +0000979 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000980 if have <= want or chars[-1] not in " \t":
981 break
982 text.undo_block_start()
983 text.delete("insert-%dc" % ncharsdeleted, "insert")
984 if have < want:
985 text.insert("insert", ' ' * (want - have))
986 text.undo_block_stop()
987 return "break"
988
989 def smart_indent_event(self, event):
990 # if intraline selection:
991 # delete it
992 # elif multiline selection:
993 # do indent-region & return
994 # indent one level
995 text = self.text
996 first, last = self.get_selection_indices()
997 text.undo_block_start()
998 try:
999 if first and last:
1000 if index2line(first) != index2line(last):
1001 return self.indent_region_event(event)
1002 text.delete(first, last)
1003 text.mark_set("insert", first)
1004 prefix = text.get("insert linestart", "insert")
1005 raw, effective = classifyws(prefix, self.tabwidth)
1006 if raw == len(prefix):
1007 # only whitespace to the left
1008 self.reindent_to(effective + self.indentwidth)
1009 else:
1010 if self.usetabs:
1011 pad = '\t'
1012 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001013 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001014 n = self.indentwidth
1015 pad = ' ' * (n - effective % n)
1016 text.insert("insert", pad)
1017 text.see("insert")
1018 return "break"
1019 finally:
1020 text.undo_block_stop()
1021
1022 def newline_and_indent_event(self, event):
1023 text = self.text
1024 first, last = self.get_selection_indices()
1025 text.undo_block_start()
1026 try:
1027 if first and last:
1028 text.delete(first, last)
1029 text.mark_set("insert", first)
1030 line = text.get("insert linestart", "insert")
1031 i, n = 0, len(line)
1032 while i < n and line[i] in " \t":
1033 i = i+1
1034 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001035 # the cursor is in or at leading indentation in a continuation
1036 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001037 text.insert("insert linestart", '\n')
1038 return "break"
1039 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001040 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001041 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001042 last_line_of_prompt = sys.ps1.split('\n')[-1]
1043 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001044 line = line[:-1]
1045 i = i+1
1046 if i:
1047 text.delete("insert - %d chars" % i, "insert")
1048 # strip whitespace after insert point
1049 while text.get("insert") in " \t":
1050 text.delete("insert")
1051 # start new line
1052 text.insert("insert", '\n')
1053
1054 # adjust indentation for continuations and block
1055 # open/close first need to find the last stmt
1056 lno = index2line(text.index('insert'))
1057 y = PyParse.Parser(self.indentwidth, self.tabwidth)
1058 for context in self.num_context_lines:
1059 startat = max(lno - context, 1)
Walter Dörwald70a6b492004-02-12 17:35:32 +00001060 startatindex = repr(startat) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001061 rawtext = text.get(startatindex, "insert")
1062 y.set_str(rawtext)
1063 bod = y.find_good_parse_start(
1064 self.context_use_ps1,
1065 self._build_char_in_string_func(startatindex))
1066 if bod is not None or startat == 1:
1067 break
1068 y.set_lo(bod or 0)
1069 c = y.get_continuation_type()
1070 if c != PyParse.C_NONE:
1071 # The current stmt hasn't ended yet.
1072 if c == PyParse.C_STRING:
1073 # inside a string; just mimic the current indent
1074 text.insert("insert", indent)
1075 elif c == PyParse.C_BRACKET:
1076 # line up with the first (if any) element of the
1077 # last open bracket structure; else indent one
1078 # level beyond the indent of the line with the
1079 # last open bracket
1080 self.reindent_to(y.compute_bracket_indent())
1081 elif c == PyParse.C_BACKSLASH:
1082 # if more than one line in this stmt already, just
1083 # mimic the current indent; else if initial line
1084 # has a start on an assignment stmt, indent to
1085 # beyond leftmost =; else to beyond first chunk of
1086 # non-whitespace on initial line
1087 if y.get_num_lines_in_stmt() > 1:
1088 text.insert("insert", indent)
1089 else:
1090 self.reindent_to(y.compute_backslash_indent())
1091 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001092 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001093 return "break"
1094
1095 # This line starts a brand new stmt; indent relative to
1096 # indentation of initial line of closest preceding
1097 # interesting stmt.
1098 indent = y.get_base_indent_string()
1099 text.insert("insert", indent)
1100 if y.is_block_opener():
1101 self.smart_indent_event(event)
1102 elif indent and y.is_block_closer():
1103 self.smart_backspace_event(event)
1104 return "break"
1105 finally:
1106 text.see("insert")
1107 text.undo_block_stop()
1108
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001109 # Our editwin provides a is_char_in_string function that works
1110 # with a Tk text index, but PyParse only knows about offsets into
1111 # a string. This builds a function for PyParse that accepts an
1112 # offset.
1113
1114 def _build_char_in_string_func(self, startindex):
1115 def inner(offset, _startindex=startindex,
1116 _icis=self.is_char_in_string):
1117 return _icis(_startindex + "+%dc" % offset)
1118 return inner
1119
1120 def indent_region_event(self, event):
1121 head, tail, chars, lines = self.get_region()
1122 for pos in range(len(lines)):
1123 line = lines[pos]
1124 if line:
1125 raw, effective = classifyws(line, self.tabwidth)
1126 effective = effective + self.indentwidth
1127 lines[pos] = self._make_blanks(effective) + line[raw:]
1128 self.set_region(head, tail, chars, lines)
1129 return "break"
1130
1131 def dedent_region_event(self, event):
1132 head, tail, chars, lines = self.get_region()
1133 for pos in range(len(lines)):
1134 line = lines[pos]
1135 if line:
1136 raw, effective = classifyws(line, self.tabwidth)
1137 effective = max(effective - self.indentwidth, 0)
1138 lines[pos] = self._make_blanks(effective) + line[raw:]
1139 self.set_region(head, tail, chars, lines)
1140 return "break"
1141
1142 def comment_region_event(self, event):
1143 head, tail, chars, lines = self.get_region()
1144 for pos in range(len(lines) - 1):
1145 line = lines[pos]
1146 lines[pos] = '##' + line
1147 self.set_region(head, tail, chars, lines)
1148
1149 def uncomment_region_event(self, event):
1150 head, tail, chars, lines = self.get_region()
1151 for pos in range(len(lines)):
1152 line = lines[pos]
1153 if not line:
1154 continue
1155 if line[:2] == '##':
1156 line = line[2:]
1157 elif line[:1] == '#':
1158 line = line[1:]
1159 lines[pos] = line
1160 self.set_region(head, tail, chars, lines)
1161
1162 def tabify_region_event(self, event):
1163 head, tail, chars, lines = self.get_region()
1164 tabwidth = self._asktabwidth()
1165 for pos in range(len(lines)):
1166 line = lines[pos]
1167 if line:
1168 raw, effective = classifyws(line, tabwidth)
1169 ntabs, nspaces = divmod(effective, tabwidth)
1170 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1171 self.set_region(head, tail, chars, lines)
1172
1173 def untabify_region_event(self, event):
1174 head, tail, chars, lines = self.get_region()
1175 tabwidth = self._asktabwidth()
1176 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001177 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001178 self.set_region(head, tail, chars, lines)
1179
1180 def toggle_tabs_event(self, event):
1181 if self.askyesno(
1182 "Toggle tabs",
1183 "Turn tabs " + ("on", "off")[self.usetabs] + "?",
1184 parent=self.text):
1185 self.usetabs = not self.usetabs
1186 return "break"
1187
1188 # XXX this isn't bound to anything -- see class tabwidth comments
1189 def change_tabwidth_event(self, event):
1190 new = self._asktabwidth()
1191 if new != self.tabwidth:
1192 self.tabwidth = new
1193 self.set_indentation_params(0, guess=0)
1194 return "break"
1195
1196 def change_indentwidth_event(self, event):
1197 new = self.askinteger(
1198 "Indent width",
1199 "New indent width (2-16)",
1200 parent=self.text,
1201 initialvalue=self.indentwidth,
1202 minvalue=2,
1203 maxvalue=16)
1204 if new and new != self.indentwidth:
1205 self.indentwidth = new
1206 return "break"
1207
1208 def get_region(self):
1209 text = self.text
1210 first, last = self.get_selection_indices()
1211 if first and last:
1212 head = text.index(first + " linestart")
1213 tail = text.index(last + "-1c lineend +1c")
1214 else:
1215 head = text.index("insert linestart")
1216 tail = text.index("insert lineend +1c")
1217 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001218 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 return head, tail, chars, lines
1220
1221 def set_region(self, head, tail, chars, lines):
1222 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001223 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 if newchars == chars:
1225 text.bell()
1226 return
1227 text.tag_remove("sel", "1.0", "end")
1228 text.mark_set("insert", head)
1229 text.undo_block_start()
1230 text.delete(head, tail)
1231 text.insert(head, newchars)
1232 text.undo_block_stop()
1233 text.tag_add("sel", head, "insert")
1234
1235 # Make string that displays as n leading blanks.
1236
1237 def _make_blanks(self, n):
1238 if self.usetabs:
1239 ntabs, nspaces = divmod(n, self.tabwidth)
1240 return '\t' * ntabs + ' ' * nspaces
1241 else:
1242 return ' ' * n
1243
1244 # Delete from beginning of line to insert point, then reinsert
1245 # column logical (meaning use tabs if appropriate) spaces.
1246
1247 def reindent_to(self, column):
1248 text = self.text
1249 text.undo_block_start()
1250 if text.compare("insert linestart", "!=", "insert"):
1251 text.delete("insert linestart", "insert")
1252 if column:
1253 text.insert("insert", self._make_blanks(column))
1254 text.undo_block_stop()
1255
1256 def _asktabwidth(self):
1257 return self.askinteger(
1258 "Tab width",
1259 "Spaces per tab? (2-16)",
1260 parent=self.text,
1261 initialvalue=self.indentwidth,
1262 minvalue=2,
1263 maxvalue=16) or self.tabwidth
1264
1265 # Guess indentwidth from text content.
1266 # Return guessed indentwidth. This should not be believed unless
1267 # it's in a reasonable range (e.g., it will be 0 if no indented
1268 # blocks are found).
1269
1270 def guess_indent(self):
1271 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1272 if opener and indented:
1273 raw, indentsmall = classifyws(opener, self.tabwidth)
1274 raw, indentlarge = classifyws(indented, self.tabwidth)
1275 else:
1276 indentsmall = indentlarge = 0
1277 return indentlarge - indentsmall
1278
1279# "line.col" -> line, as an int
1280def index2line(index):
1281 return int(float(index))
1282
1283# Look at the leading whitespace in s.
1284# Return pair (# of leading ws characters,
1285# effective # of leading blanks after expanding
1286# tabs to width tabwidth)
1287
1288def classifyws(s, tabwidth):
1289 raw = effective = 0
1290 for ch in s:
1291 if ch == ' ':
1292 raw = raw + 1
1293 effective = effective + 1
1294 elif ch == '\t':
1295 raw = raw + 1
1296 effective = (effective // tabwidth + 1) * tabwidth
1297 else:
1298 break
1299 return raw, effective
1300
1301import tokenize
1302_tokenize = tokenize
1303del tokenize
1304
1305class IndentSearcher:
1306
1307 # .run() chews over the Text widget, looking for a block opener
1308 # and the stmt following it. Returns a pair,
1309 # (line containing block opener, line containing stmt)
1310 # Either or both may be None.
1311
1312 def __init__(self, text, tabwidth):
1313 self.text = text
1314 self.tabwidth = tabwidth
1315 self.i = self.finished = 0
1316 self.blkopenline = self.indentedline = None
1317
1318 def readline(self):
1319 if self.finished:
1320 return ""
1321 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001322 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001323 if self.text.compare(mark, ">=", "end"):
1324 return ""
1325 return self.text.get(mark, mark + " lineend+1c")
1326
1327 def tokeneater(self, type, token, start, end, line,
1328 INDENT=_tokenize.INDENT,
1329 NAME=_tokenize.NAME,
1330 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1331 if self.finished:
1332 pass
1333 elif type == NAME and token in OPENERS:
1334 self.blkopenline = line
1335 elif type == INDENT and self.blkopenline:
1336 self.indentedline = line
1337 self.finished = 1
1338
1339 def run(self):
1340 save_tabsize = _tokenize.tabsize
1341 _tokenize.tabsize = self.tabwidth
1342 try:
1343 try:
1344 _tokenize.tokenize(self.readline, self.tokeneater)
1345 except _tokenize.TokenError:
1346 # since we cut off the tokenizer early, we can trigger
1347 # spurious errors
1348 pass
1349 finally:
1350 _tokenize.tabsize = save_tabsize
1351 return self.blkopenline, self.indentedline
1352
1353### end autoindent code ###
1354
David Scherer7aced172000-08-15 01:13:23 +00001355def prepstr(s):
1356 # Helper to extract the underscore from a string, e.g.
1357 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001358 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001359 if i >= 0:
1360 s = s[:i] + s[i+1:]
1361 return i, s
1362
1363
1364keynames = {
1365 'bracketleft': '[',
1366 'bracketright': ']',
1367 'slash': '/',
1368}
1369
1370def get_accelerator(keydefs, event):
1371 keylist = keydefs.get(event)
1372 if not keylist:
1373 return ""
1374 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001375 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001376 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1377 s = re.sub("Key-", "", s)
1378 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1379 s = re.sub("Control-", "Ctrl-", s)
1380 s = re.sub("-", "+", s)
1381 s = re.sub("><", " ", s)
1382 s = re.sub("<", "", s)
1383 s = re.sub(">", "", s)
1384 return s
1385
1386
1387def fixwordbreaks(root):
1388 # Make sure that Tk's double-click and next/previous word
1389 # operations use our definition of a word (i.e. an identifier)
1390 tk = root.tk
1391 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1392 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1393 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1394
1395
1396def test():
1397 root = Tk()
1398 fixwordbreaks(root)
1399 root.withdraw()
1400 if sys.argv[1:]:
1401 filename = sys.argv[1]
1402 else:
1403 filename = None
1404 edit = EditorWindow(root=root, filename=filename)
1405 edit.set_close_hook(root.quit)
1406 root.mainloop()
1407 root.destroy()
1408
1409if __name__ == '__main__':
1410 test()