blob: 7cc69cbf004b2b2cdc8ccd04b6c1739e2eec53d2 [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
Georg Brandl6634bf22008-05-20 07:13:37 +00006from Tkinter import *
7import tkSimpleDialog
8import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00009import webbrowser
Ezio Melotti4c6daf12010-08-02 20:40:20 +000010
11from idlelib.MultiCall import MultiCallCreator
12from idlelib import idlever
13from idlelib import WindowList
14from idlelib import SearchDialog
15from idlelib import GrepDialog
16from idlelib import ReplaceDialog
17from idlelib import PyParse
18from idlelib.configHandler import idleConf
19from idlelib import aboutDialog, textView, configDialog
20from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000021
22# The default tab setting for a Text widget, in average-width characters.
23TK_TABWIDTH_DEFAULT = 8
24
Kurt B. Kaiser834b7ab2009-04-25 17:26:39 +000025def _sphinx_version():
26 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
27 major, minor, micro, level, serial = sys.version_info
28 release = '%s%s' % (major, minor)
29 if micro:
30 release += '%s' % micro
31 if level != 'final':
32 release += '%s%s' % (level[0], serial)
33 return release
34
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000035def _find_module(fullname, path=None):
36 """Version of imp.find_module() that handles hierarchical module names"""
37
38 file = None
39 for tgt in fullname.split('.'):
40 if file is not None:
41 file.close() # close intermediate files
42 (file, filename, descr) = imp.find_module(tgt, path)
43 if descr[2] == imp.PY_SOURCE:
44 break # find but not load the source file
45 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000046 try:
47 path = module.__path__
48 except AttributeError:
49 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000050 return file, filename, descr
51
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000052class EditorWindow(object):
Ezio Melotti4c6daf12010-08-02 20:40:20 +000053 from idlelib.Percolator import Percolator
54 from idlelib.ColorDelegator import ColorDelegator
55 from idlelib.UndoDelegator import UndoDelegator
56 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
57 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000058 from Tkinter import Toplevel
Ezio Melotti4c6daf12010-08-02 20:40:20 +000059 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000060
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000061 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000062
63 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000064 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000065 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000066 if sys.platform.count('linux'):
67 # look for html docs in a couple of standard places
68 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
69 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
70 dochome = '/var/www/html/python/index.html'
71 else:
72 basepath = '/usr/share/doc/' # standard location
73 dochome = os.path.join(basepath, pyver,
74 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000075 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000076 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiser834b7ab2009-04-25 17:26:39 +000077 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000078 if os.path.isfile(chmfile):
79 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000080 elif macosxSupport.runningAsOSXApp():
81 # documentation is stored inside the python framework
82 dochome = os.path.join(sys.prefix,
83 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000084 dochome = os.path.normpath(dochome)
85 if os.path.isfile(dochome):
86 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +000087 if sys.platform == 'darwin':
88 # Safari requires real file:-URLs
89 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000090 else:
Kurt B. Kaiserd6cec0c2009-04-25 17:30:40 +000091 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000092 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000093 self.flist = flist
94 root = root or flist.root
95 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +000096 try:
97 sys.ps1
98 except AttributeError:
99 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000100 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000101 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000102 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000103 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000104 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000105 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000106 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000107 else:
108 self.tkinter_vars = {} # keys: Tkinter event names
109 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000110 self.top.instance_dict = {}
111 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000112 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000113 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000114 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000115 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserf4eadb42009-04-25 18:18:54 +0000116 text_options = {
117 'name': 'text',
118 'padx': 5,
119 'wrap': 'none',
120 'width': self.width,
121 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
122 if TkVersion >= 8.5:
123 # Starting with tk 8.5 we have to set the new tabstyle option
124 # to 'wordprocessor' to achieve the same display of tabs as in
125 # older tk versions.
126 text_options['tabstyle'] = 'wordprocessor'
127 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000128 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000129
130 self.createmenubar()
131 self.apply_bindings()
132
133 self.top.protocol("WM_DELETE_WINDOW", self.close)
134 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000135 if macosxSupport.runningAsOSXApp():
136 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000137 text.bind('<<close-window>>', self.close_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000138 text.bind("<<cut>>", self.cut)
139 text.bind("<<copy>>", self.copy)
140 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000141 text.bind("<<center-insert>>", self.center_insert_event)
142 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000143 text.bind("<<python-docs>>", self.python_docs)
144 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000145 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000146 text.bind("<<open-module>>", self.open_module)
147 text.bind("<<do-nothing>>", lambda event: "break")
148 text.bind("<<select-all>>", self.select_all)
149 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000150 text.bind("<<find>>", self.find_event)
151 text.bind("<<find-again>>", self.find_again_event)
152 text.bind("<<find-in-files>>", self.find_in_files_event)
153 text.bind("<<find-selection>>", self.find_selection_event)
154 text.bind("<<replace>>", self.replace_event)
155 text.bind("<<goto-line>>", self.goto_line_event)
David Scherer7aced172000-08-15 01:13:23 +0000156 text.bind("<3>", self.right_menu_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000157 text.bind("<<smart-backspace>>",self.smart_backspace_event)
158 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
159 text.bind("<<smart-indent>>",self.smart_indent_event)
160 text.bind("<<indent-region>>",self.indent_region_event)
161 text.bind("<<dedent-region>>",self.dedent_region_event)
162 text.bind("<<comment-region>>",self.comment_region_event)
163 text.bind("<<uncomment-region>>",self.uncomment_region_event)
164 text.bind("<<tabify-region>>",self.tabify_region_event)
165 text.bind("<<untabify-region>>",self.untabify_region_event)
166 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
167 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000168 text.bind("<Left>", self.move_at_edge_if_selection(0))
169 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000170 text.bind("<<del-word-left>>", self.del_word_left)
171 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000172 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000173
David Scherer7aced172000-08-15 01:13:23 +0000174 if flist:
175 flist.inversedict[self] = key
176 if key:
177 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000178 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000179 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
180 text.bind("<<open-class-browser>>", self.open_class_browser)
181 text.bind("<<open-path-browser>>", self.open_path_browser)
182
Steven M. Gava898a3652001-10-07 11:10:44 +0000183 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000184 vbar['command'] = text.yview
185 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000186 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000187 fontWeight = 'normal'
188 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000189 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000190 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
191 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
192 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000193 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
194 text.pack(side=TOP, fill=BOTH, expand=1)
195 text.focus_set()
196
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000197 # usetabs true -> literal tab characters are used by indent and
198 # dedent cmds, possibly mixed with spaces if
199 # indentwidth is not a multiple of tabwidth,
200 # which will cause Tabnanny to nag!
201 # false -> tab characters are converted to spaces by indent
202 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000203 # Although use-spaces=0 can be configured manually in config-main.def,
204 # configuration of tabs v. spaces is not supported in the configuration
205 # dialog. IDLE promotes the preferred Python indentation: use spaces!
206 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
207 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000208
209 # tabwidth is the display width of a literal tab character.
210 # CAUTION: telling Tk to use anything other than its default
211 # tab setting causes it to use an entirely different tabbing algorithm,
212 # treating tab stops as fixed distances from the left margin.
213 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000214 self.tabwidth = 8 # must remain 8 until Tk is fixed.
215
216 # indentwidth is the number of screen characters per indent level.
217 # The recommended Python indentation is four spaces.
218 self.indentwidth = self.tabwidth
219 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000220
221 # If context_use_ps1 is true, parsing searches back for a ps1 line;
222 # else searches for a popular (if, def, ...) Python stmt.
223 self.context_use_ps1 = False
224
225 # When searching backwards for a reliable place to begin parsing,
226 # first start num_context_lines[0] lines back, then
227 # num_context_lines[1] lines back if that didn't work, and so on.
228 # The last value should be huge (larger than the # of lines in a
229 # conceivable file).
230 # Making the initial values larger slows things down more often.
231 self.num_context_lines = 50, 500, 5000000
232
David Scherer7aced172000-08-15 01:13:23 +0000233 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000234
235 self.undo = undo = self.UndoDelegator()
236 per.insertfilter(undo)
237 text.undo_block_start = undo.undo_block_start
238 text.undo_block_stop = undo.undo_block_stop
239 undo.set_saved_change_hook(self.saved_change_hook)
240
241 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000242 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000243 io.set_filename_change_hook(self.filename_change_hook)
244
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000245 # Create the recent files submenu
246 self.recent_files_menu = Menu(self.menubar)
247 self.menudict['file'].insert_cascade(3, label='Recent Files',
248 underline=0,
249 menu=self.recent_files_menu)
250 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000251
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000252 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000253 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000254 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000255 io.loadfile(filename)
256 else:
257 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000258 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000259 self.saved_change_hook()
260
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000261 self.set_indentation_params(self.ispythonsource(filename))
262
David Scherer7aced172000-08-15 01:13:23 +0000263 self.load_extensions()
264
265 menu = self.menudict.get('windows')
266 if menu:
267 end = menu.index("end")
268 if end is None:
269 end = -1
270 if end >= 0:
271 menu.add_separator()
272 end = end + 1
273 self.wmenu_end = end
274 WindowList.register_callback(self.postwindowsmenu)
275
276 # Some abstractions so IDLE extensions are cross-IDE
277 self.askyesno = tkMessageBox.askyesno
278 self.askinteger = tkSimpleDialog.askinteger
279 self.showerror = tkMessageBox.showerror
280
Martin v. Löwis307021f2005-11-27 16:59:04 +0000281 def _filename_to_unicode(self, filename):
282 """convert filename to unicode in order to display it in Tk"""
283 if isinstance(filename, unicode) or not filename:
284 return filename
285 else:
286 try:
287 return filename.decode(self.filesystemencoding)
288 except UnicodeDecodeError:
289 # XXX
290 try:
291 return filename.decode(self.encoding)
292 except UnicodeDecodeError:
293 # byte-to-byte conversion
294 return filename.decode('iso8859-1')
295
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000296 def new_callback(self, event):
297 dirname, basename = self.io.defaultfilename()
298 self.flist.new(dirname)
299 return "break"
300
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000301 def home_callback(self, event):
302 if (event.state & 12) != 0 and event.keysym == "Home":
303 # state&1==shift, state&4==control, state&8==alt
304 return # <Modifier-Home>; fall back to class binding
305
306 if self.text.index("iomark") and \
307 self.text.compare("iomark", "<=", "insert lineend") and \
308 self.text.compare("insert linestart", "<=", "iomark"):
309 insertpt = int(self.text.index("iomark").split(".")[1])
310 else:
311 line = self.text.get("insert linestart", "insert lineend")
312 for insertpt in xrange(len(line)):
313 if line[insertpt] not in (' ','\t'):
314 break
315 else:
316 insertpt=len(line)
317
318 lineat = int(self.text.index("insert").split('.')[1])
319
320 if insertpt == lineat:
321 insertpt = 0
322
323 dest = "insert linestart+"+str(insertpt)+"c"
324
325 if (event.state&1) == 0:
326 # shift not pressed
327 self.text.tag_remove("sel", "1.0", "end")
328 else:
329 if not self.text.index("sel.first"):
330 self.text.mark_set("anchor","insert")
331
332 first = self.text.index(dest)
333 last = self.text.index("anchor")
334
335 if self.text.compare(first,">",last):
336 first,last = last,first
337
338 self.text.tag_remove("sel", "1.0", "end")
339 self.text.tag_add("sel", first, last)
340
341 self.text.mark_set("insert", dest)
342 self.text.see("insert")
343 return "break"
344
David Scherer7aced172000-08-15 01:13:23 +0000345 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000346 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000347 if macosxSupport.runningAsOSXApp():
348 # Insert some padding to avoid obscuring some of the statusbar
349 # by the resize widget.
350 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000351 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
352 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
353 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000354 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
355 self.text.event_add("<<set-line-and-column>>",
356 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000357 self.text.after_idle(self.set_line_and_column)
358
359 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000360 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000361 self.status_bar.set_label('column', 'Col: %s' % column)
362 self.status_bar.set_label('line', 'Ln: %s' % line)
363
David Scherer7aced172000-08-15 01:13:23 +0000364 menu_specs = [
365 ("file", "_File"),
366 ("edit", "_Edit"),
367 ("format", "F_ormat"),
368 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000369 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000370 ("windows", "_Windows"),
371 ("help", "_Help"),
372 ]
373
Ronald Oussoren19302d92006-06-11 14:33:36 +0000374 if macosxSupport.runningAsOSXApp():
375 del menu_specs[-3]
376 menu_specs[-2] = ("windows", "_Window")
377
378
David Scherer7aced172000-08-15 01:13:23 +0000379 def createmenubar(self):
380 mbar = self.menubar
381 self.menudict = menudict = {}
382 for name, label in self.menu_specs:
383 underline, label = prepstr(label)
384 menudict[name] = menu = Menu(mbar, name=name)
385 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000386
Ronald Oussorende3dfc12009-03-04 21:35:38 +0000387 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000388 # Insert the application menu
389 menudict['application'] = menu = Menu(mbar, name='apple')
390 mbar.add_cascade(label='IDLE', menu=menu)
391
David Scherer7aced172000-08-15 01:13:23 +0000392 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000393 self.base_helpmenu_length = self.menudict['help'].index(END)
394 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000395
396 def postwindowsmenu(self):
397 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000398 menu = self.menudict['windows']
399 end = menu.index("end")
400 if end is None:
401 end = -1
402 if end > self.wmenu_end:
403 menu.delete(self.wmenu_end+1, end)
404 WindowList.add_windows_to_menu(menu)
405
406 rmenu = None
407
408 def right_menu_event(self, event):
409 self.text.tag_remove("sel", "1.0", "end")
410 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
411 if not self.rmenu:
412 self.make_rmenu()
413 rmenu = self.rmenu
414 self.event = event
415 iswin = sys.platform[:3] == 'win'
416 if iswin:
417 self.text.config(cursor="arrow")
418 rmenu.tk_popup(event.x_root, event.y_root)
419 if iswin:
420 self.text.config(cursor="ibeam")
421
422 rmenu_specs = [
423 # ("Label", "<<virtual-event>>"), ...
424 ("Close", "<<close-window>>"), # Example
425 ]
426
427 def make_rmenu(self):
428 rmenu = Menu(self.text, tearoff=0)
429 for label, eventname in self.rmenu_specs:
430 def command(text=self.text, eventname=eventname):
431 text.event_generate(eventname)
432 rmenu.add_command(label=label, command=command)
433 self.rmenu = rmenu
434
435 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000436 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000437
Steven M. Gava3b55a892001-11-21 05:56:26 +0000438 def config_dialog(self, event=None):
439 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000440
David Scherer7aced172000-08-15 01:13:23 +0000441 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000442 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000443 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000444
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000445 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000446 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000447 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000448 else:
449 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000450 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000451
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000452 def cut(self,event):
453 self.text.event_generate("<<Cut>>")
454 return "break"
455
456 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000457 if not self.text.tag_ranges("sel"):
458 # There is no selection, so do nothing and maybe interrupt.
459 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000460 self.text.event_generate("<<Copy>>")
461 return "break"
462
463 def paste(self,event):
464 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000465 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000466 return "break"
467
David Scherer7aced172000-08-15 01:13:23 +0000468 def select_all(self, event=None):
469 self.text.tag_add("sel", "1.0", "end-1c")
470 self.text.mark_set("insert", "1.0")
471 self.text.see("insert")
472 return "break"
473
474 def remove_selection(self, event=None):
475 self.text.tag_remove("sel", "1.0", "end")
476 self.text.see("insert")
477
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000478 def move_at_edge_if_selection(self, edge_index):
479 """Cursor move begins at start or end of selection
480
481 When a left/right cursor key is pressed create and return to Tkinter a
482 function which causes a cursor move from the associated edge of the
483 selection.
484
485 """
486 self_text_index = self.text.index
487 self_text_mark_set = self.text.mark_set
488 edges_table = ("sel.first+1c", "sel.last-1c")
489 def move_at_edge(event):
490 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
491 try:
492 self_text_index("sel.first")
493 self_text_mark_set("insert", edges_table[edge_index])
494 except TclError:
495 pass
496 return move_at_edge
497
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000498 def del_word_left(self, event):
499 self.text.event_generate('<Meta-Delete>')
500 return "break"
501
502 def del_word_right(self, event):
503 self.text.event_generate('<Meta-d>')
504 return "break"
505
Steven M. Gavac5976402002-01-04 03:06:08 +0000506 def find_event(self, event):
507 SearchDialog.find(self.text)
508 return "break"
509
510 def find_again_event(self, event):
511 SearchDialog.find_again(self.text)
512 return "break"
513
514 def find_selection_event(self, event):
515 SearchDialog.find_selection(self.text)
516 return "break"
517
518 def find_in_files_event(self, event):
519 GrepDialog.grep(self.text, self.io, self.flist)
520 return "break"
521
522 def replace_event(self, event):
523 ReplaceDialog.replace(self.text)
524 return "break"
525
526 def goto_line_event(self, event):
527 text = self.text
528 lineno = tkSimpleDialog.askinteger("Goto",
529 "Go to line number:",parent=text)
530 if lineno is None:
531 return "break"
532 if lineno <= 0:
533 text.bell()
534 return "break"
535 text.mark_set("insert", "%d.0" % lineno)
536 text.see("insert")
537
David Scherer7aced172000-08-15 01:13:23 +0000538 def open_module(self, event=None):
539 # XXX Shouldn't this be in IOBinding or in FileList?
540 try:
541 name = self.text.get("sel.first", "sel.last")
542 except TclError:
543 name = ""
544 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000545 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000546 name = tkSimpleDialog.askstring("Module",
547 "Enter the name of a Python module\n"
548 "to search on sys.path and open:",
549 parent=self.text, initialvalue=name)
550 if name:
551 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000552 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000553 return
David Scherer7aced172000-08-15 01:13:23 +0000554 # XXX Ought to insert current file's directory in front of path
555 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000556 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000557 except (NameError, ImportError), msg:
558 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
559 return
560 if type != imp.PY_SOURCE:
561 tkMessageBox.showerror("Unsupported type",
562 "%s is not a source module" % name, parent=self.text)
563 return
564 if f:
565 f.close()
566 if self.flist:
567 self.flist.open(file)
568 else:
569 self.io.loadfile(file)
570
571 def open_class_browser(self, event=None):
572 filename = self.io.filename
573 if not filename:
574 tkMessageBox.showerror(
575 "No filename",
576 "This buffer has no associated filename",
577 master=self.text)
578 self.text.focus_set()
579 return None
580 head, tail = os.path.split(filename)
581 base, ext = os.path.splitext(tail)
Ezio Melotti4c6daf12010-08-02 20:40:20 +0000582 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000583 ClassBrowser.ClassBrowser(self.flist, base, [head])
584
585 def open_path_browser(self, event=None):
Ezio Melotti4c6daf12010-08-02 20:40:20 +0000586 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000587 PathBrowser.PathBrowser(self.flist)
588
589 def gotoline(self, lineno):
590 if lineno is not None and lineno > 0:
591 self.text.mark_set("insert", "%d.0" % lineno)
592 self.text.tag_remove("sel", "1.0", "end")
593 self.text.tag_add("sel", "insert", "insert +1l")
594 self.center()
595
596 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000597 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000598 return True
David Scherer7aced172000-08-15 01:13:23 +0000599 base, ext = os.path.splitext(os.path.basename(filename))
600 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000601 return True
David Scherer7aced172000-08-15 01:13:23 +0000602 try:
603 f = open(filename)
604 line = f.readline()
605 f.close()
606 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000607 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000608 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000609
610 def close_hook(self):
611 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000612 self.flist.unregister_maybe_terminate(self)
613 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000614
615 def set_close_hook(self, close_hook):
616 self.close_hook = close_hook
617
618 def filename_change_hook(self):
619 if self.flist:
620 self.flist.filename_changed_edit(self)
621 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000622 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000623 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000624
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000625 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000626 if self.color:
627 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000628 if self.ispythonsource(self.io.filename):
629 self.color = self.ColorDelegator()
630 # can add more colorizers here...
631 if self.color:
632 self.per.removefilter(self.undo)
633 self.per.insertfilter(self.color)
634 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000635
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000636 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000637 if not self.color:
638 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000639 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000640 self.per.removefilter(self.color)
641 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000642
Steven M. Gavab77d3432002-03-02 07:16:21 +0000643 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000644 "Update the colour theme"
645 # Called from self.filename_change_hook and from configDialog.py
646 self._rmcolorizer()
647 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000648 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000649 normal_colors = idleConf.GetHighlight(theme, 'normal')
650 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
651 select_colors = idleConf.GetHighlight(theme, 'hilite')
652 self.text.config(
653 foreground=normal_colors['foreground'],
654 background=normal_colors['background'],
655 insertbackground=cursor_color,
656 selectforeground=select_colors['foreground'],
657 selectbackground=select_colors['background'],
658 )
David Scherer7aced172000-08-15 01:13:23 +0000659
Steven M. Gavab1585412002-03-12 00:21:56 +0000660 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000661 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000662 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000663 fontWeight='normal'
664 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
665 fontWeight='bold'
666 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
667 idleConf.GetOption('main','EditorWindow','font-size'),
668 fontWeight))
669
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000670 def RemoveKeybindings(self):
671 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000672 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000673 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000674 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000675 self.text.event_delete(event, *keylist)
676 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000677 xkeydefs = idleConf.GetExtensionBindings(extensionName)
678 if xkeydefs:
679 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000680 self.text.event_delete(event, *keylist)
681
682 def ApplyKeybindings(self):
683 "Update the keybindings after they are changed"
684 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000685 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000686 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000687 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000688 xkeydefs = idleConf.GetExtensionBindings(extensionName)
689 if xkeydefs:
690 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000691 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000692 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000693 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000694 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000695 for item in menu[1]:
696 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000697 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000698 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000699 menu = self.menudict[menubarItem]
700 end = menu.index(END) + 1
701 for index in range(0, end):
702 if menu.type(index) == 'command':
703 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000704 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000705 itemName = menu.entrycget(index, 'label')
706 event = ''
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000707 if menuEventDict.has_key(menubarItem):
708 if menuEventDict[menubarItem].has_key(itemName):
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000709 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000710 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000711 accel = get_accelerator(keydefs, event)
712 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000713
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000714 def set_notabs_indentwidth(self):
715 "Update the indentwidth if changed and not using tabs in this window"
716 # Called from configDialog.py
717 if not self.usetabs:
718 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
719 type='int')
720
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000721 def reset_help_menu_entries(self):
722 "Update the additional help entries on the Help menu"
723 help_list = idleConf.GetAllExtraHelpSourcesList()
724 helpmenu = self.menudict['help']
725 # first delete the extra help entries, if any
726 helpmenu_length = helpmenu.index(END)
727 if helpmenu_length > self.base_helpmenu_length:
728 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
729 # then rebuild them
730 if help_list:
731 helpmenu.add_separator()
732 for entry in help_list:
733 cmd = self.__extra_help_callback(entry[1])
734 helpmenu.add_command(label=entry[0], command=cmd)
735 # and update the menu dictionary
736 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000737
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000738 def __extra_help_callback(self, helpfile):
739 "Create a callback with the helpfile value frozen at definition time"
740 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000741 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000742 url = os.path.normpath(helpfile)
743 if sys.platform[:3] == 'win':
744 os.startfile(helpfile)
745 else:
746 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000747 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000748
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000749 def update_recent_files_list(self, new_file=None):
750 "Load and update the recent files list and menus"
751 rf_list = []
752 if os.path.exists(self.recent_files_path):
753 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000754 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000755 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000756 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000757 rf_list_file.close()
758 if new_file:
759 new_file = os.path.abspath(new_file) + '\n'
760 if new_file in rf_list:
761 rf_list.remove(new_file) # move to top
762 rf_list.insert(0, new_file)
763 # clean and save the recent files list
764 bad_paths = []
765 for path in rf_list:
766 if '\0' in path or not os.path.exists(path[0:-1]):
767 bad_paths.append(path)
768 rf_list = [path for path in rf_list if path not in bad_paths]
769 ulchars = "1234567890ABCDEFGHIJK"
770 rf_list = rf_list[0:len(ulchars)]
771 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000772 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000773 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000774 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000775 rf_file.close()
776 # for each edit window instance, construct the recent files menu
777 for instance in self.top.instance_dict.keys():
778 menu = instance.recent_files_menu
779 menu.delete(1, END) # clear, and rebuild:
780 for i, file in zip(count(), rf_list):
781 file_name = file[0:-1] # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000782 # make unicode string to display non-ASCII chars correctly
783 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000784 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000785 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000786 command=callback,
787 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000788
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000789 def __recent_file_callback(self, file_name):
790 def open_recent_file(fn_closure=file_name):
791 self.io.open(editFile=fn_closure)
792 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000793
David Scherer7aced172000-08-15 01:13:23 +0000794 def saved_change_hook(self):
795 short = self.short_title()
796 long = self.long_title()
797 if short and long:
798 title = short + " - " + long
799 elif short:
800 title = short
801 elif long:
802 title = long
803 else:
804 title = "Untitled"
805 icon = short or long or title
806 if not self.get_saved():
807 title = "*%s*" % title
808 icon = "*%s" % icon
809 self.top.wm_title(title)
810 self.top.wm_iconname(icon)
811
812 def get_saved(self):
813 return self.undo.get_saved()
814
815 def set_saved(self, flag):
816 self.undo.set_saved(flag)
817
818 def reset_undo(self):
819 self.undo.reset_undo()
820
821 def short_title(self):
822 filename = self.io.filename
823 if filename:
824 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000825 # return unicode string to display non-ASCII chars correctly
826 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000827
828 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000829 # return unicode string to display non-ASCII chars correctly
830 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000831
832 def center_insert_event(self, event):
833 self.center()
834
835 def center(self, mark="insert"):
836 text = self.text
837 top, bot = self.getwindowlines()
838 lineno = self.getlineno(mark)
839 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000840 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000841 text.yview(float(newtop))
842
843 def getwindowlines(self):
844 text = self.text
845 top = self.getlineno("@0,0")
846 bot = self.getlineno("@0,65535")
847 if top == bot and text.winfo_height() == 1:
848 # Geometry manager hasn't run yet
849 height = int(text['height'])
850 bot = top + height - 1
851 return top, bot
852
853 def getlineno(self, mark="insert"):
854 text = self.text
855 return int(float(text.index(mark)))
856
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000857 def get_geometry(self):
858 "Return (width, height, x, y)"
859 geom = self.top.wm_geometry()
860 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
861 tuple = (map(int, m.groups()))
862 return tuple
863
David Scherer7aced172000-08-15 01:13:23 +0000864 def close_event(self, event):
865 self.close()
866
867 def maybesave(self):
868 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000869 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000870 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000871 self.top.deiconify()
872 self.top.lower()
873 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000874 return self.io.maybesave()
875
876 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000877 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000878 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000879 self._close()
880 return reply
881
882 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000883 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000884 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000885 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000886 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000887 self.io.close()
888 self.io = None
889 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000890 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000891 self.color.close(False)
892 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000893 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000894 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000895 self.per.close()
896 self.per = None
897 self.top.destroy()
898 if self.close_hook:
899 # unless override: unregister from flist, terminate if last window
900 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000901
902 def load_extensions(self):
903 self.extensions = {}
904 self.load_standard_extensions()
905
906 def unload_extensions(self):
907 for ins in self.extensions.values():
908 if hasattr(ins, "close"):
909 ins.close()
910 self.extensions = {}
911
912 def load_standard_extensions(self):
913 for name in self.get_standard_extension_names():
914 try:
915 self.load_extension(name)
916 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000917 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000918 import traceback
919 traceback.print_exc()
920
921 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000922 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000923
924 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000925 try:
926 mod = __import__(name, globals(), locals(), [])
927 except ImportError:
928 print "\nFailed to import extension: ", name
929 return
David Scherer7aced172000-08-15 01:13:23 +0000930 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000931 keydefs = idleConf.GetExtensionBindings(name)
932 if hasattr(cls, "menudefs"):
933 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000934 ins = cls(self)
935 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000936 if keydefs:
937 self.apply_bindings(keydefs)
938 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000939 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000940 while methodname[:1] == '<':
941 methodname = methodname[1:]
942 while methodname[-1:] == '>':
943 methodname = methodname[:-1]
944 methodname = methodname + "_event"
945 if hasattr(ins, methodname):
946 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000947
948 def apply_bindings(self, keydefs=None):
949 if keydefs is None:
950 keydefs = self.Bindings.default_keydefs
951 text = self.text
952 text.keydefs = keydefs
953 for event, keylist in keydefs.items():
954 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000955 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000956
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000957 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000958 """Add appropriate entries to the menus and submenus
959
960 Menus that are absent or None in self.menudict are ignored.
961 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000962 if menudefs is None:
963 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000964 if keydefs is None:
965 keydefs = self.Bindings.default_keydefs
966 menudict = self.menudict
967 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000968 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000969 menu = menudict.get(mname)
970 if not menu:
971 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000972 for entry in entrylist:
973 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000974 menu.add_separator()
975 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000976 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000977 checkbutton = (label[:1] == '!')
978 if checkbutton:
979 label = label[1:]
980 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000981 accelerator = get_accelerator(keydefs, eventname)
982 def command(text=text, eventname=eventname):
983 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000984 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000985 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000986 menu.add_checkbutton(label=label, underline=underline,
987 command=command, accelerator=accelerator,
988 variable=var)
989 else:
990 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000991 command=command,
992 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +0000993
994 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000995 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +0000996 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000997 value = var.get()
998 return value
999 else:
1000 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001001
1002 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001004 if var:
1005 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001006 else:
1007 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001008
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001009 def get_var_obj(self, name, vartype=None):
1010 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001011 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001012 # create a Tkinter variable object with self.text as master:
1013 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001014 return var
1015
1016 # Tk implementations of "virtual text methods" -- each platform
1017 # reusing IDLE's support code needs to define these for its GUI's
1018 # flavor of widget.
1019
1020 # Is character at text_index in a Python string? Return 0 for
1021 # "guaranteed no", true for anything else. This info is expensive
1022 # to compute ab initio, but is probably already known by the
1023 # platform's colorizer.
1024
1025 def is_char_in_string(self, text_index):
1026 if self.color:
1027 # Return true iff colorizer hasn't (re)gotten this far
1028 # yet, or the character is tagged as being in a string
1029 return self.text.tag_prevrange("TODO", text_index) or \
1030 "STRING" in self.text.tag_names(text_index)
1031 else:
1032 # The colorizer is missing: assume the worst
1033 return 1
1034
1035 # If a selection is defined in the text widget, return (start,
1036 # end) as Tkinter text indices, otherwise return (None, None)
1037 def get_selection_indices(self):
1038 try:
1039 first = self.text.index("sel.first")
1040 last = self.text.index("sel.last")
1041 return first, last
1042 except TclError:
1043 return None, None
1044
1045 # Return the text widget's current view of what a tab stop means
1046 # (equivalent width in spaces).
1047
1048 def get_tabwidth(self):
1049 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1050 return int(current)
1051
1052 # Set the text widget's current view of what a tab stop means.
1053
1054 def set_tabwidth(self, newtabwidth):
1055 text = self.text
1056 if self.get_tabwidth() != newtabwidth:
1057 pixels = text.tk.call("font", "measure", text["font"],
1058 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001059 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001060 text.configure(tabs=pixels)
1061
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001062 # If ispythonsource and guess are true, guess a good value for
1063 # indentwidth based on file content (if possible), and if
1064 # indentwidth != tabwidth set usetabs false.
1065 # In any case, adjust the Text widget's view of what a tab
1066 # character means.
1067
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001068 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001069 if guess and ispythonsource:
1070 i = self.guess_indent()
1071 if 2 <= i <= 8:
1072 self.indentwidth = i
1073 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001074 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001075 self.set_tabwidth(self.tabwidth)
1076
1077 def smart_backspace_event(self, event):
1078 text = self.text
1079 first, last = self.get_selection_indices()
1080 if first and last:
1081 text.delete(first, last)
1082 text.mark_set("insert", first)
1083 return "break"
1084 # Delete whitespace left, until hitting a real char or closest
1085 # preceding virtual tab stop.
1086 chars = text.get("insert linestart", "insert")
1087 if chars == '':
1088 if text.compare("insert", ">", "1.0"):
1089 # easy: delete preceding newline
1090 text.delete("insert-1c")
1091 else:
1092 text.bell() # at start of buffer
1093 return "break"
1094 if chars[-1] not in " \t":
1095 # easy: delete preceding real char
1096 text.delete("insert-1c")
1097 return "break"
1098 # Ick. It may require *inserting* spaces if we back up over a
1099 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001100 tabwidth = self.tabwidth
1101 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001102 assert have > 0
1103 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001104 # Debug prompt is multilined....
1105 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001106 ncharsdeleted = 0
1107 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001108 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001109 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001110 chars = chars[:-1]
1111 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001112 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001113 if have <= want or chars[-1] not in " \t":
1114 break
1115 text.undo_block_start()
1116 text.delete("insert-%dc" % ncharsdeleted, "insert")
1117 if have < want:
1118 text.insert("insert", ' ' * (want - have))
1119 text.undo_block_stop()
1120 return "break"
1121
1122 def smart_indent_event(self, event):
1123 # if intraline selection:
1124 # delete it
1125 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001126 # do indent-region
1127 # else:
1128 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001129 text = self.text
1130 first, last = self.get_selection_indices()
1131 text.undo_block_start()
1132 try:
1133 if first and last:
1134 if index2line(first) != index2line(last):
1135 return self.indent_region_event(event)
1136 text.delete(first, last)
1137 text.mark_set("insert", first)
1138 prefix = text.get("insert linestart", "insert")
1139 raw, effective = classifyws(prefix, self.tabwidth)
1140 if raw == len(prefix):
1141 # only whitespace to the left
1142 self.reindent_to(effective + self.indentwidth)
1143 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001144 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001145 if self.usetabs:
1146 pad = '\t'
1147 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001148 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001149 n = self.indentwidth
1150 pad = ' ' * (n - effective % n)
1151 text.insert("insert", pad)
1152 text.see("insert")
1153 return "break"
1154 finally:
1155 text.undo_block_stop()
1156
1157 def newline_and_indent_event(self, event):
1158 text = self.text
1159 first, last = self.get_selection_indices()
1160 text.undo_block_start()
1161 try:
1162 if first and last:
1163 text.delete(first, last)
1164 text.mark_set("insert", first)
1165 line = text.get("insert linestart", "insert")
1166 i, n = 0, len(line)
1167 while i < n and line[i] in " \t":
1168 i = i+1
1169 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001170 # the cursor is in or at leading indentation in a continuation
1171 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001172 text.insert("insert linestart", '\n')
1173 return "break"
1174 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001175 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001176 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001177 last_line_of_prompt = sys.ps1.split('\n')[-1]
1178 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001179 line = line[:-1]
1180 i = i+1
1181 if i:
1182 text.delete("insert - %d chars" % i, "insert")
1183 # strip whitespace after insert point
1184 while text.get("insert") in " \t":
1185 text.delete("insert")
1186 # start new line
1187 text.insert("insert", '\n')
1188
1189 # adjust indentation for continuations and block
1190 # open/close first need to find the last stmt
1191 lno = index2line(text.index('insert'))
1192 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001193 if not self.context_use_ps1:
1194 for context in self.num_context_lines:
1195 startat = max(lno - context, 1)
Ezio Melotti4c6daf12010-08-02 20:40:20 +00001196 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001197 rawtext = text.get(startatindex, "insert")
1198 y.set_str(rawtext)
1199 bod = y.find_good_parse_start(
1200 self.context_use_ps1,
1201 self._build_char_in_string_func(startatindex))
1202 if bod is not None or startat == 1:
1203 break
1204 y.set_lo(bod or 0)
1205 else:
1206 r = text.tag_prevrange("console", "insert")
1207 if r:
1208 startatindex = r[1]
1209 else:
1210 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001211 rawtext = text.get(startatindex, "insert")
1212 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001213 y.set_lo(0)
1214
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001215 c = y.get_continuation_type()
1216 if c != PyParse.C_NONE:
1217 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001218 if c == PyParse.C_STRING_FIRST_LINE:
1219 # after the first line of a string; do not indent at all
1220 pass
1221 elif c == PyParse.C_STRING_NEXT_LINES:
1222 # inside a string which started before this line;
1223 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001224 text.insert("insert", indent)
1225 elif c == PyParse.C_BRACKET:
1226 # line up with the first (if any) element of the
1227 # last open bracket structure; else indent one
1228 # level beyond the indent of the line with the
1229 # last open bracket
1230 self.reindent_to(y.compute_bracket_indent())
1231 elif c == PyParse.C_BACKSLASH:
1232 # if more than one line in this stmt already, just
1233 # mimic the current indent; else if initial line
1234 # has a start on an assignment stmt, indent to
1235 # beyond leftmost =; else to beyond first chunk of
1236 # non-whitespace on initial line
1237 if y.get_num_lines_in_stmt() > 1:
1238 text.insert("insert", indent)
1239 else:
1240 self.reindent_to(y.compute_backslash_indent())
1241 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001242 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001243 return "break"
1244
1245 # This line starts a brand new stmt; indent relative to
1246 # indentation of initial line of closest preceding
1247 # interesting stmt.
1248 indent = y.get_base_indent_string()
1249 text.insert("insert", indent)
1250 if y.is_block_opener():
1251 self.smart_indent_event(event)
1252 elif indent and y.is_block_closer():
1253 self.smart_backspace_event(event)
1254 return "break"
1255 finally:
1256 text.see("insert")
1257 text.undo_block_stop()
1258
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001259 # Our editwin provides a is_char_in_string function that works
1260 # with a Tk text index, but PyParse only knows about offsets into
1261 # a string. This builds a function for PyParse that accepts an
1262 # offset.
1263
1264 def _build_char_in_string_func(self, startindex):
1265 def inner(offset, _startindex=startindex,
1266 _icis=self.is_char_in_string):
1267 return _icis(_startindex + "+%dc" % offset)
1268 return inner
1269
1270 def indent_region_event(self, event):
1271 head, tail, chars, lines = self.get_region()
1272 for pos in range(len(lines)):
1273 line = lines[pos]
1274 if line:
1275 raw, effective = classifyws(line, self.tabwidth)
1276 effective = effective + self.indentwidth
1277 lines[pos] = self._make_blanks(effective) + line[raw:]
1278 self.set_region(head, tail, chars, lines)
1279 return "break"
1280
1281 def dedent_region_event(self, event):
1282 head, tail, chars, lines = self.get_region()
1283 for pos in range(len(lines)):
1284 line = lines[pos]
1285 if line:
1286 raw, effective = classifyws(line, self.tabwidth)
1287 effective = max(effective - self.indentwidth, 0)
1288 lines[pos] = self._make_blanks(effective) + line[raw:]
1289 self.set_region(head, tail, chars, lines)
1290 return "break"
1291
1292 def comment_region_event(self, event):
1293 head, tail, chars, lines = self.get_region()
1294 for pos in range(len(lines) - 1):
1295 line = lines[pos]
1296 lines[pos] = '##' + line
1297 self.set_region(head, tail, chars, lines)
1298
1299 def uncomment_region_event(self, event):
1300 head, tail, chars, lines = self.get_region()
1301 for pos in range(len(lines)):
1302 line = lines[pos]
1303 if not line:
1304 continue
1305 if line[:2] == '##':
1306 line = line[2:]
1307 elif line[:1] == '#':
1308 line = line[1:]
1309 lines[pos] = line
1310 self.set_region(head, tail, chars, lines)
1311
1312 def tabify_region_event(self, event):
1313 head, tail, chars, lines = self.get_region()
1314 tabwidth = self._asktabwidth()
1315 for pos in range(len(lines)):
1316 line = lines[pos]
1317 if line:
1318 raw, effective = classifyws(line, tabwidth)
1319 ntabs, nspaces = divmod(effective, tabwidth)
1320 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1321 self.set_region(head, tail, chars, lines)
1322
1323 def untabify_region_event(self, event):
1324 head, tail, chars, lines = self.get_region()
1325 tabwidth = self._asktabwidth()
1326 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001327 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001328 self.set_region(head, tail, chars, lines)
1329
1330 def toggle_tabs_event(self, event):
1331 if self.askyesno(
1332 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001333 "Turn tabs " + ("on", "off")[self.usetabs] +
1334 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001335 ("will be", "remains at")[self.usetabs] + " 8." +
1336 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001337 parent=self.text):
1338 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001339 # Try to prevent inconsistent indentation.
1340 # User must change indent width manually after using tabs.
1341 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001342 return "break"
1343
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001344 # XXX this isn't bound to anything -- see tabwidth comments
1345## def change_tabwidth_event(self, event):
1346## new = self._asktabwidth()
1347## if new != self.tabwidth:
1348## self.tabwidth = new
1349## self.set_indentation_params(0, guess=0)
1350## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001351
1352 def change_indentwidth_event(self, event):
1353 new = self.askinteger(
1354 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001355 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001356 parent=self.text,
1357 initialvalue=self.indentwidth,
1358 minvalue=2,
1359 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001360 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001361 self.indentwidth = new
1362 return "break"
1363
1364 def get_region(self):
1365 text = self.text
1366 first, last = self.get_selection_indices()
1367 if first and last:
1368 head = text.index(first + " linestart")
1369 tail = text.index(last + "-1c lineend +1c")
1370 else:
1371 head = text.index("insert linestart")
1372 tail = text.index("insert lineend +1c")
1373 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001374 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001375 return head, tail, chars, lines
1376
1377 def set_region(self, head, tail, chars, lines):
1378 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001379 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001380 if newchars == chars:
1381 text.bell()
1382 return
1383 text.tag_remove("sel", "1.0", "end")
1384 text.mark_set("insert", head)
1385 text.undo_block_start()
1386 text.delete(head, tail)
1387 text.insert(head, newchars)
1388 text.undo_block_stop()
1389 text.tag_add("sel", head, "insert")
1390
1391 # Make string that displays as n leading blanks.
1392
1393 def _make_blanks(self, n):
1394 if self.usetabs:
1395 ntabs, nspaces = divmod(n, self.tabwidth)
1396 return '\t' * ntabs + ' ' * nspaces
1397 else:
1398 return ' ' * n
1399
1400 # Delete from beginning of line to insert point, then reinsert
1401 # column logical (meaning use tabs if appropriate) spaces.
1402
1403 def reindent_to(self, column):
1404 text = self.text
1405 text.undo_block_start()
1406 if text.compare("insert linestart", "!=", "insert"):
1407 text.delete("insert linestart", "insert")
1408 if column:
1409 text.insert("insert", self._make_blanks(column))
1410 text.undo_block_stop()
1411
1412 def _asktabwidth(self):
1413 return self.askinteger(
1414 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001415 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001416 parent=self.text,
1417 initialvalue=self.indentwidth,
1418 minvalue=2,
1419 maxvalue=16) or self.tabwidth
1420
1421 # Guess indentwidth from text content.
1422 # Return guessed indentwidth. This should not be believed unless
1423 # it's in a reasonable range (e.g., it will be 0 if no indented
1424 # blocks are found).
1425
1426 def guess_indent(self):
1427 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1428 if opener and indented:
1429 raw, indentsmall = classifyws(opener, self.tabwidth)
1430 raw, indentlarge = classifyws(indented, self.tabwidth)
1431 else:
1432 indentsmall = indentlarge = 0
1433 return indentlarge - indentsmall
1434
1435# "line.col" -> line, as an int
1436def index2line(index):
1437 return int(float(index))
1438
1439# Look at the leading whitespace in s.
1440# Return pair (# of leading ws characters,
1441# effective # of leading blanks after expanding
1442# tabs to width tabwidth)
1443
1444def classifyws(s, tabwidth):
1445 raw = effective = 0
1446 for ch in s:
1447 if ch == ' ':
1448 raw = raw + 1
1449 effective = effective + 1
1450 elif ch == '\t':
1451 raw = raw + 1
1452 effective = (effective // tabwidth + 1) * tabwidth
1453 else:
1454 break
1455 return raw, effective
1456
1457import tokenize
1458_tokenize = tokenize
1459del tokenize
1460
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001461class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001462
1463 # .run() chews over the Text widget, looking for a block opener
1464 # and the stmt following it. Returns a pair,
1465 # (line containing block opener, line containing stmt)
1466 # Either or both may be None.
1467
1468 def __init__(self, text, tabwidth):
1469 self.text = text
1470 self.tabwidth = tabwidth
1471 self.i = self.finished = 0
1472 self.blkopenline = self.indentedline = None
1473
1474 def readline(self):
1475 if self.finished:
1476 return ""
1477 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001478 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001479 if self.text.compare(mark, ">=", "end"):
1480 return ""
1481 return self.text.get(mark, mark + " lineend+1c")
1482
1483 def tokeneater(self, type, token, start, end, line,
1484 INDENT=_tokenize.INDENT,
1485 NAME=_tokenize.NAME,
1486 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1487 if self.finished:
1488 pass
1489 elif type == NAME and token in OPENERS:
1490 self.blkopenline = line
1491 elif type == INDENT and self.blkopenline:
1492 self.indentedline = line
1493 self.finished = 1
1494
1495 def run(self):
1496 save_tabsize = _tokenize.tabsize
1497 _tokenize.tabsize = self.tabwidth
1498 try:
1499 try:
1500 _tokenize.tokenize(self.readline, self.tokeneater)
1501 except _tokenize.TokenError:
1502 # since we cut off the tokenizer early, we can trigger
1503 # spurious errors
1504 pass
1505 finally:
1506 _tokenize.tabsize = save_tabsize
1507 return self.blkopenline, self.indentedline
1508
1509### end autoindent code ###
1510
David Scherer7aced172000-08-15 01:13:23 +00001511def prepstr(s):
1512 # Helper to extract the underscore from a string, e.g.
1513 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001514 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001515 if i >= 0:
1516 s = s[:i] + s[i+1:]
1517 return i, s
1518
1519
1520keynames = {
1521 'bracketleft': '[',
1522 'bracketright': ']',
1523 'slash': '/',
1524}
1525
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001526def get_accelerator(keydefs, eventname):
1527 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001528 if not keylist:
1529 return ""
1530 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001531 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001532 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1533 s = re.sub("Key-", "", s)
1534 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1535 s = re.sub("Control-", "Ctrl-", s)
1536 s = re.sub("-", "+", s)
1537 s = re.sub("><", " ", s)
1538 s = re.sub("<", "", s)
1539 s = re.sub(">", "", s)
1540 return s
1541
1542
1543def fixwordbreaks(root):
1544 # Make sure that Tk's double-click and next/previous word
1545 # operations use our definition of a word (i.e. an identifier)
1546 tk = root.tk
1547 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1548 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1549 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1550
1551
1552def test():
1553 root = Tk()
1554 fixwordbreaks(root)
1555 root.withdraw()
1556 if sys.argv[1:]:
1557 filename = sys.argv[1]
1558 else:
1559 filename = None
1560 edit = EditorWindow(root=root, filename=filename)
1561 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001562 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001563 root.mainloop()
1564 root.destroy()
1565
1566if __name__ == '__main__':
1567 test()