blob: c6a4001b222db3e85b9682ec6d73c2e38270b86f [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
Georg Brandl6634bf22008-05-20 07:13:37 +00005from Tkinter import *
6import tkSimpleDialog
7import tkMessageBox
Kurt B. Kaiserfd182cd2001-07-14 03:58:25 +00008import webbrowser
Florent Xiclunad630c042010-04-02 07:24:52 +00009
10from idlelib.MultiCall import MultiCallCreator
11from idlelib import idlever
12from idlelib import WindowList
13from idlelib import SearchDialog
14from idlelib import GrepDialog
15from idlelib import ReplaceDialog
16from idlelib import PyParse
17from idlelib.configHandler import idleConf
18from idlelib import aboutDialog, textView, configDialog
19from idlelib import macosxSupport
David Scherer7aced172000-08-15 01:13:23 +000020
21# The default tab setting for a Text widget, in average-width characters.
22TK_TABWIDTH_DEFAULT = 8
23
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000024def _sphinx_version():
25 "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
26 major, minor, micro, level, serial = sys.version_info
27 release = '%s%s' % (major, minor)
28 if micro:
Benjamin Petersonfb234632009-06-13 15:42:23 +000029 release += '%s' % (micro,)
30 if level == 'candidate':
31 release += 'rc%s' % (serial,)
32 elif level != 'final':
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000033 release += '%s%s' % (level[0], serial)
34 return release
35
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000036def _find_module(fullname, path=None):
37 """Version of imp.find_module() that handles hierarchical module names"""
38
39 file = None
40 for tgt in fullname.split('.'):
41 if file is not None:
42 file.close() # close intermediate files
43 (file, filename, descr) = imp.find_module(tgt, path)
44 if descr[2] == imp.PY_SOURCE:
45 break # find but not load the source file
46 module = imp.load_module(tgt, file, filename, descr)
Kurt B. Kaiser69e8afc2003-01-10 21:25:20 +000047 try:
48 path = module.__path__
49 except AttributeError:
50 raise ImportError, 'No source for module ' + module.__name__
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +000051 return file, filename, descr
52
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +000053class EditorWindow(object):
Florent Xiclunad630c042010-04-02 07:24:52 +000054 from idlelib.Percolator import Percolator
55 from idlelib.ColorDelegator import ColorDelegator
56 from idlelib.UndoDelegator import UndoDelegator
57 from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
58 from idlelib import Bindings
Georg Brandl6634bf22008-05-20 07:13:37 +000059 from Tkinter import Toplevel
Florent Xiclunad630c042010-04-02 07:24:52 +000060 from idlelib.MultiStatusBar import MultiStatusBar
David Scherer7aced172000-08-15 01:13:23 +000061
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000062 help_url = None
David Scherer7aced172000-08-15 01:13:23 +000063
64 def __init__(self, flist=None, filename=None, key=None, root=None):
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000065 if EditorWindow.help_url is None:
Thomas Heller84ef1532003-09-23 20:53:10 +000066 dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000067 if sys.platform.count('linux'):
68 # look for html docs in a couple of standard places
69 pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
70 if os.path.isdir('/var/www/html/python/'): # "python2" rpm
71 dochome = '/var/www/html/python/index.html'
72 else:
73 basepath = '/usr/share/doc/' # standard location
74 dochome = os.path.join(basepath, pyver,
75 'Doc', 'index.html')
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +000076 elif sys.platform[:3] == 'win':
Kurt B. Kaiser090e6362004-07-21 03:33:58 +000077 chmfile = os.path.join(sys.prefix, 'Doc',
Kurt B. Kaiserf13447f2009-04-23 02:36:01 +000078 'Python%s.chm' % _sphinx_version())
Thomas Heller84ef1532003-09-23 20:53:10 +000079 if os.path.isfile(chmfile):
80 dochome = chmfile
Ronald Oussoren19302d92006-06-11 14:33:36 +000081 elif macosxSupport.runningAsOSXApp():
82 # documentation is stored inside the python framework
83 dochome = os.path.join(sys.prefix,
84 'Resources/English.lproj/Documentation/index.html')
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000085 dochome = os.path.normpath(dochome)
86 if os.path.isfile(dochome):
87 EditorWindow.help_url = dochome
Ronald Oussoren19302d92006-06-11 14:33:36 +000088 if sys.platform == 'darwin':
89 # Safari requires real file:-URLs
90 EditorWindow.help_url = 'file://' + EditorWindow.help_url
Kurt B. Kaiser114713d2003-01-10 05:07:24 +000091 else:
Raymond Hettinger7b4c2be2009-01-20 10:46:23 +000092 EditorWindow.help_url = "http://docs.python.org/%d.%d" % sys.version_info[:2]
Steven M. Gavadc72f482002-01-03 11:51:07 +000093 currentTheme=idleConf.CurrentTheme()
David Scherer7aced172000-08-15 01:13:23 +000094 self.flist = flist
95 root = root or flist.root
96 self.root = root
Kurt B. Kaiserb3c4d162006-07-24 17:13:23 +000097 try:
98 sys.ps1
99 except AttributeError:
100 sys.ps1 = '>>> '
David Scherer7aced172000-08-15 01:13:23 +0000101 self.menubar = Menu(root)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000102 self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000103 if flist:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000104 self.tkinter_vars = flist.vars
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000105 #self.top.instance_dict makes flist.inversedict avalable to
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +0000106 #configDialog.py so it can access all EditorWindow instaces
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000107 self.top.instance_dict = flist.inversedict
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000108 else:
109 self.tkinter_vars = {} # keys: Tkinter event names
110 # values: Tkinter variable instances
Kurt B. Kaisera2946a42006-07-24 18:05:51 +0000111 self.top.instance_dict = {}
112 self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
Steven M. Gava1d46e402002-03-27 08:40:46 +0000113 'recent-files.lst')
David Scherer7aced172000-08-15 01:13:23 +0000114 self.text_frame = text_frame = Frame(top)
Martin v. Löwis4ebbefe2006-11-22 08:50:02 +0000115 self.vbar = vbar = Scrollbar(text_frame, name='vbar')
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000116 self.width = idleConf.GetOption('main','EditorWindow','width')
Kurt B. Kaiserce465112009-03-30 16:22:00 +0000117 text_options = {
118 'name': 'text',
119 'padx': 5,
120 'wrap': 'none',
121 'width': self.width,
122 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
123 if TkVersion >= 8.5:
124 # Starting with tk 8.5 we have to set the new tabstyle option
125 # to 'wordprocessor' to achieve the same display of tabs as in
126 # older tk versions.
127 text_options['tabstyle'] = 'wordprocessor'
128 self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
Kurt B. Kaiser183403a2004-08-22 05:14:32 +0000129 self.top.focused_widget = self.text
David Scherer7aced172000-08-15 01:13:23 +0000130
131 self.createmenubar()
132 self.apply_bindings()
133
134 self.top.protocol("WM_DELETE_WINDOW", self.close)
135 self.top.bind("<<close-window>>", self.close_event)
Ronald Oussoren17db4952006-07-23 09:41:09 +0000136 if macosxSupport.runningAsOSXApp():
137 # Command-W on editorwindows doesn't work without this.
Ronald Oussoren3075e162006-07-25 20:28:55 +0000138 text.bind('<<close-window>>', self.close_event)
R. David Murray3f752ab2010-12-18 17:22:18 +0000139 # Some OS X systems have only one mouse button,
140 # so use control-click for pulldown menus there.
141 # (Note, AquaTk defines <2> as the right button if
142 # present and the Tk Text widget already binds <2>.)
143 text.bind("<Control-Button-1>",self.right_menu_event)
144 else:
145 # Elsewhere, use right-click for pulldown menus.
146 text.bind("<3>",self.right_menu_event)
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000147 text.bind("<<cut>>", self.cut)
148 text.bind("<<copy>>", self.copy)
149 text.bind("<<paste>>", self.paste)
David Scherer7aced172000-08-15 01:13:23 +0000150 text.bind("<<center-insert>>", self.center_insert_event)
151 text.bind("<<help>>", self.help_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000152 text.bind("<<python-docs>>", self.python_docs)
153 text.bind("<<about-idle>>", self.about_dialog)
Steven M. Gava3b55a892001-11-21 05:56:26 +0000154 text.bind("<<open-config-dialog>>", self.config_dialog)
David Scherer7aced172000-08-15 01:13:23 +0000155 text.bind("<<open-module>>", self.open_module)
156 text.bind("<<do-nothing>>", lambda event: "break")
157 text.bind("<<select-all>>", self.select_all)
158 text.bind("<<remove-selection>>", self.remove_selection)
Steven M. Gavac5976402002-01-04 03:06:08 +0000159 text.bind("<<find>>", self.find_event)
160 text.bind("<<find-again>>", self.find_again_event)
161 text.bind("<<find-in-files>>", self.find_in_files_event)
162 text.bind("<<find-selection>>", self.find_selection_event)
163 text.bind("<<replace>>", self.replace_event)
164 text.bind("<<goto-line>>", self.goto_line_event)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +0000165 text.bind("<<smart-backspace>>",self.smart_backspace_event)
166 text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
167 text.bind("<<smart-indent>>",self.smart_indent_event)
168 text.bind("<<indent-region>>",self.indent_region_event)
169 text.bind("<<dedent-region>>",self.dedent_region_event)
170 text.bind("<<comment-region>>",self.comment_region_event)
171 text.bind("<<uncomment-region>>",self.uncomment_region_event)
172 text.bind("<<tabify-region>>",self.tabify_region_event)
173 text.bind("<<untabify-region>>",self.untabify_region_event)
174 text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
175 text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000176 text.bind("<Left>", self.move_at_edge_if_selection(0))
177 text.bind("<Right>", self.move_at_edge_if_selection(1))
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000178 text.bind("<<del-word-left>>", self.del_word_left)
179 text.bind("<<del-word-right>>", self.del_word_right)
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000180 text.bind("<<beginning-of-line>>", self.home_callback)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000181
David Scherer7aced172000-08-15 01:13:23 +0000182 if flist:
183 flist.inversedict[self] = key
184 if key:
185 flist.dict[key] = self
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000186 text.bind("<<open-new-window>>", self.new_callback)
David Scherer7aced172000-08-15 01:13:23 +0000187 text.bind("<<close-all-windows>>", self.flist.close_all_callback)
188 text.bind("<<open-class-browser>>", self.open_class_browser)
189 text.bind("<<open-path-browser>>", self.open_path_browser)
190
Steven M. Gava898a3652001-10-07 11:10:44 +0000191 self.set_status_bar()
David Scherer7aced172000-08-15 01:13:23 +0000192 vbar['command'] = text.yview
193 vbar.pack(side=RIGHT, fill=Y)
David Scherer7aced172000-08-15 01:13:23 +0000194 text['yscrollcommand'] = vbar.set
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000195 fontWeight = 'normal'
196 if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
Steven M. Gavab1585412002-03-12 00:21:56 +0000197 fontWeight='bold'
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000198 text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
199 idleConf.GetOption('main', 'EditorWindow', 'font-size'),
200 fontWeight))
David Scherer7aced172000-08-15 01:13:23 +0000201 text_frame.pack(side=LEFT, fill=BOTH, expand=1)
202 text.pack(side=TOP, fill=BOTH, expand=1)
203 text.focus_set()
204
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000205 # usetabs true -> literal tab characters are used by indent and
206 # dedent cmds, possibly mixed with spaces if
207 # indentwidth is not a multiple of tabwidth,
208 # which will cause Tabnanny to nag!
209 # false -> tab characters are converted to spaces by indent
210 # and dedent cmds, and ditto TAB keystrokes
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000211 # Although use-spaces=0 can be configured manually in config-main.def,
212 # configuration of tabs v. spaces is not supported in the configuration
213 # dialog. IDLE promotes the preferred Python indentation: use spaces!
214 usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
215 self.usetabs = not usespaces
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000216
217 # tabwidth is the display width of a literal tab character.
218 # CAUTION: telling Tk to use anything other than its default
219 # tab setting causes it to use an entirely different tabbing algorithm,
220 # treating tab stops as fixed distances from the left margin.
221 # Nobody expects this, so for now tabwidth should never be changed.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000222 self.tabwidth = 8 # must remain 8 until Tk is fixed.
223
224 # indentwidth is the number of screen characters per indent level.
225 # The recommended Python indentation is four spaces.
226 self.indentwidth = self.tabwidth
227 self.set_notabs_indentwidth()
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000228
229 # If context_use_ps1 is true, parsing searches back for a ps1 line;
230 # else searches for a popular (if, def, ...) Python stmt.
231 self.context_use_ps1 = False
232
233 # When searching backwards for a reliable place to begin parsing,
234 # first start num_context_lines[0] lines back, then
235 # num_context_lines[1] lines back if that didn't work, and so on.
236 # The last value should be huge (larger than the # of lines in a
237 # conceivable file).
238 # Making the initial values larger slows things down more often.
239 self.num_context_lines = 50, 500, 5000000
240
David Scherer7aced172000-08-15 01:13:23 +0000241 self.per = per = self.Percolator(text)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000242
243 self.undo = undo = self.UndoDelegator()
244 per.insertfilter(undo)
245 text.undo_block_start = undo.undo_block_start
246 text.undo_block_stop = undo.undo_block_stop
247 undo.set_saved_change_hook(self.saved_change_hook)
248
249 # IOBinding implements file I/O and printing functionality
David Scherer7aced172000-08-15 01:13:23 +0000250 self.io = io = self.IOBinding(self)
Kurt B. Kaiserdc1e7092002-07-11 04:33:41 +0000251 io.set_filename_change_hook(self.filename_change_hook)
252
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000253 # Create the recent files submenu
254 self.recent_files_menu = Menu(self.menubar)
255 self.menudict['file'].insert_cascade(3, label='Recent Files',
256 underline=0,
257 menu=self.recent_files_menu)
258 self.update_recent_files_list()
David Scherer7aced172000-08-15 01:13:23 +0000259
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000260 self.color = None # initialized below in self.ResetColorizer
David Scherer7aced172000-08-15 01:13:23 +0000261 if filename:
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000262 if os.path.exists(filename) and not os.path.isdir(filename):
David Scherer7aced172000-08-15 01:13:23 +0000263 io.loadfile(filename)
264 else:
265 io.set_filename(filename)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000266 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000267 self.saved_change_hook()
268
Kurt B. Kaiser6af44982005-01-19 00:22:59 +0000269 self.set_indentation_params(self.ispythonsource(filename))
270
David Scherer7aced172000-08-15 01:13:23 +0000271 self.load_extensions()
272
273 menu = self.menudict.get('windows')
274 if menu:
275 end = menu.index("end")
276 if end is None:
277 end = -1
278 if end >= 0:
279 menu.add_separator()
280 end = end + 1
281 self.wmenu_end = end
282 WindowList.register_callback(self.postwindowsmenu)
283
284 # Some abstractions so IDLE extensions are cross-IDE
285 self.askyesno = tkMessageBox.askyesno
286 self.askinteger = tkSimpleDialog.askinteger
287 self.showerror = tkMessageBox.showerror
288
Martin v. Löwis307021f2005-11-27 16:59:04 +0000289 def _filename_to_unicode(self, filename):
290 """convert filename to unicode in order to display it in Tk"""
291 if isinstance(filename, unicode) or not filename:
292 return filename
293 else:
294 try:
295 return filename.decode(self.filesystemencoding)
296 except UnicodeDecodeError:
297 # XXX
298 try:
299 return filename.decode(self.encoding)
300 except UnicodeDecodeError:
301 # byte-to-byte conversion
302 return filename.decode('iso8859-1')
303
Kurt B. Kaiserd2f48612003-06-05 02:34:04 +0000304 def new_callback(self, event):
305 dirname, basename = self.io.defaultfilename()
306 self.flist.new(dirname)
307 return "break"
308
Kurt B. Kaiser93cdae52008-04-27 21:07:41 +0000309 def home_callback(self, event):
310 if (event.state & 12) != 0 and event.keysym == "Home":
311 # state&1==shift, state&4==control, state&8==alt
312 return # <Modifier-Home>; fall back to class binding
313
314 if self.text.index("iomark") and \
315 self.text.compare("iomark", "<=", "insert lineend") and \
316 self.text.compare("insert linestart", "<=", "iomark"):
317 insertpt = int(self.text.index("iomark").split(".")[1])
318 else:
319 line = self.text.get("insert linestart", "insert lineend")
320 for insertpt in xrange(len(line)):
321 if line[insertpt] not in (' ','\t'):
322 break
323 else:
324 insertpt=len(line)
325
326 lineat = int(self.text.index("insert").split('.')[1])
327
328 if insertpt == lineat:
329 insertpt = 0
330
331 dest = "insert linestart+"+str(insertpt)+"c"
332
333 if (event.state&1) == 0:
334 # shift not pressed
335 self.text.tag_remove("sel", "1.0", "end")
336 else:
337 if not self.text.index("sel.first"):
338 self.text.mark_set("anchor","insert")
339
340 first = self.text.index(dest)
341 last = self.text.index("anchor")
342
343 if self.text.compare(first,">",last):
344 first,last = last,first
345
346 self.text.tag_remove("sel", "1.0", "end")
347 self.text.tag_add("sel", first, last)
348
349 self.text.mark_set("insert", dest)
350 self.text.see("insert")
351 return "break"
352
David Scherer7aced172000-08-15 01:13:23 +0000353 def set_status_bar(self):
Steven M. Gava898a3652001-10-07 11:10:44 +0000354 self.status_bar = self.MultiStatusBar(self.top)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000355 if macosxSupport.runningAsOSXApp():
356 # Insert some padding to avoid obscuring some of the statusbar
357 # by the resize widget.
358 self.status_bar.set_label('_padding1', ' ', side=RIGHT)
David Scherer7aced172000-08-15 01:13:23 +0000359 self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
360 self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
361 self.status_bar.pack(side=BOTTOM, fill=X)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000362 self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
363 self.text.event_add("<<set-line-and-column>>",
364 "<KeyRelease>", "<ButtonRelease>")
David Scherer7aced172000-08-15 01:13:23 +0000365 self.text.after_idle(self.set_line_and_column)
366
367 def set_line_and_column(self, event=None):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000368 line, column = self.text.index(INSERT).split('.')
David Scherer7aced172000-08-15 01:13:23 +0000369 self.status_bar.set_label('column', 'Col: %s' % column)
370 self.status_bar.set_label('line', 'Ln: %s' % line)
371
David Scherer7aced172000-08-15 01:13:23 +0000372 menu_specs = [
373 ("file", "_File"),
374 ("edit", "_Edit"),
375 ("format", "F_ormat"),
376 ("run", "_Run"),
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000377 ("options", "_Options"),
David Scherer7aced172000-08-15 01:13:23 +0000378 ("windows", "_Windows"),
379 ("help", "_Help"),
380 ]
381
Ronald Oussoren19302d92006-06-11 14:33:36 +0000382 if macosxSupport.runningAsOSXApp():
383 del menu_specs[-3]
384 menu_specs[-2] = ("windows", "_Window")
385
386
David Scherer7aced172000-08-15 01:13:23 +0000387 def createmenubar(self):
388 mbar = self.menubar
389 self.menudict = menudict = {}
390 for name, label in self.menu_specs:
391 underline, label = prepstr(label)
392 menudict[name] = menu = Menu(mbar, name=name)
393 mbar.add_cascade(label=label, menu=menu, underline=underline)
Ronald Oussoren19302d92006-06-11 14:33:36 +0000394
Ronald Oussorena97063a2009-03-04 21:35:05 +0000395 if macosxSupport.runningAsOSXApp():
Ronald Oussoren19302d92006-06-11 14:33:36 +0000396 # Insert the application menu
397 menudict['application'] = menu = Menu(mbar, name='apple')
398 mbar.add_cascade(label='IDLE', menu=menu)
399
David Scherer7aced172000-08-15 01:13:23 +0000400 self.fill_menus()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000401 self.base_helpmenu_length = self.menudict['help'].index(END)
402 self.reset_help_menu_entries()
David Scherer7aced172000-08-15 01:13:23 +0000403
404 def postwindowsmenu(self):
405 # Only called when Windows menu exists
David Scherer7aced172000-08-15 01:13:23 +0000406 menu = self.menudict['windows']
407 end = menu.index("end")
408 if end is None:
409 end = -1
410 if end > self.wmenu_end:
411 menu.delete(self.wmenu_end+1, end)
412 WindowList.add_windows_to_menu(menu)
413
414 rmenu = None
415
416 def right_menu_event(self, event):
417 self.text.tag_remove("sel", "1.0", "end")
418 self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
419 if not self.rmenu:
420 self.make_rmenu()
421 rmenu = self.rmenu
422 self.event = event
423 iswin = sys.platform[:3] == 'win'
424 if iswin:
425 self.text.config(cursor="arrow")
426 rmenu.tk_popup(event.x_root, event.y_root)
427 if iswin:
428 self.text.config(cursor="ibeam")
429
430 rmenu_specs = [
431 # ("Label", "<<virtual-event>>"), ...
432 ("Close", "<<close-window>>"), # Example
433 ]
434
435 def make_rmenu(self):
436 rmenu = Menu(self.text, tearoff=0)
437 for label, eventname in self.rmenu_specs:
438 def command(text=self.text, eventname=eventname):
439 text.event_generate(eventname)
440 rmenu.add_command(label=label, command=command)
441 self.rmenu = rmenu
442
443 def about_dialog(self, event=None):
Kurt B. Kaiserd78b2302003-06-12 04:03:49 +0000444 aboutDialog.AboutDialog(self.top,'About IDLE')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000445
Steven M. Gava3b55a892001-11-21 05:56:26 +0000446 def config_dialog(self, event=None):
447 configDialog.ConfigDialog(self.top,'Settings')
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000448
David Scherer7aced172000-08-15 01:13:23 +0000449 def help_dialog(self, event=None):
Steven M. Gavab9d07b52001-07-31 11:11:38 +0000450 fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
Kurt B. Kaiserd5f49102007-10-04 02:53:07 +0000451 textView.view_file(self.top,'Help',fn)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000452
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000453 def python_docs(self, event=None):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000454 if sys.platform[:3] == 'win':
Steven M. Gava931625d2002-04-22 00:38:26 +0000455 os.startfile(self.help_url)
Kurt B. Kaiser114713d2003-01-10 05:07:24 +0000456 else:
457 webbrowser.open(self.help_url)
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000458 return "break"
David Scherer7aced172000-08-15 01:13:23 +0000459
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000460 def cut(self,event):
461 self.text.event_generate("<<Cut>>")
462 return "break"
463
464 def copy(self,event):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000465 if not self.text.tag_ranges("sel"):
466 # There is no selection, so do nothing and maybe interrupt.
467 return
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000468 self.text.event_generate("<<Copy>>")
469 return "break"
470
471 def paste(self,event):
472 self.text.event_generate("<<Paste>>")
Kurt B. Kaiser631fee62007-10-10 01:06:47 +0000473 self.text.see("insert")
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000474 return "break"
475
David Scherer7aced172000-08-15 01:13:23 +0000476 def select_all(self, event=None):
477 self.text.tag_add("sel", "1.0", "end-1c")
478 self.text.mark_set("insert", "1.0")
479 self.text.see("insert")
480 return "break"
481
482 def remove_selection(self, event=None):
483 self.text.tag_remove("sel", "1.0", "end")
484 self.text.see("insert")
485
Kurt B. Kaiser5ec186b2003-01-17 04:04:06 +0000486 def move_at_edge_if_selection(self, edge_index):
487 """Cursor move begins at start or end of selection
488
489 When a left/right cursor key is pressed create and return to Tkinter a
490 function which causes a cursor move from the associated edge of the
491 selection.
492
493 """
494 self_text_index = self.text.index
495 self_text_mark_set = self.text.mark_set
496 edges_table = ("sel.first+1c", "sel.last-1c")
497 def move_at_edge(event):
498 if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
499 try:
500 self_text_index("sel.first")
501 self_text_mark_set("insert", edges_table[edge_index])
502 except TclError:
503 pass
504 return move_at_edge
505
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000506 def del_word_left(self, event):
507 self.text.event_generate('<Meta-Delete>')
508 return "break"
509
510 def del_word_right(self, event):
511 self.text.event_generate('<Meta-d>')
512 return "break"
513
Steven M. Gavac5976402002-01-04 03:06:08 +0000514 def find_event(self, event):
515 SearchDialog.find(self.text)
516 return "break"
517
518 def find_again_event(self, event):
519 SearchDialog.find_again(self.text)
520 return "break"
521
522 def find_selection_event(self, event):
523 SearchDialog.find_selection(self.text)
524 return "break"
525
526 def find_in_files_event(self, event):
527 GrepDialog.grep(self.text, self.io, self.flist)
528 return "break"
529
530 def replace_event(self, event):
531 ReplaceDialog.replace(self.text)
532 return "break"
533
534 def goto_line_event(self, event):
535 text = self.text
536 lineno = tkSimpleDialog.askinteger("Goto",
537 "Go to line number:",parent=text)
538 if lineno is None:
539 return "break"
540 if lineno <= 0:
541 text.bell()
542 return "break"
543 text.mark_set("insert", "%d.0" % lineno)
544 text.see("insert")
545
David Scherer7aced172000-08-15 01:13:23 +0000546 def open_module(self, event=None):
547 # XXX Shouldn't this be in IOBinding or in FileList?
548 try:
549 name = self.text.get("sel.first", "sel.last")
550 except TclError:
551 name = ""
552 else:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000553 name = name.strip()
Guido van Rossum852f35b2003-06-05 11:36:55 +0000554 name = tkSimpleDialog.askstring("Module",
555 "Enter the name of a Python module\n"
556 "to search on sys.path and open:",
557 parent=self.text, initialvalue=name)
558 if name:
559 name = name.strip()
David Scherer7aced172000-08-15 01:13:23 +0000560 if not name:
Guido van Rossum852f35b2003-06-05 11:36:55 +0000561 return
David Scherer7aced172000-08-15 01:13:23 +0000562 # XXX Ought to insert current file's directory in front of path
563 try:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000564 (f, file, (suffix, mode, type)) = _find_module(name)
David Scherer7aced172000-08-15 01:13:23 +0000565 except (NameError, ImportError), msg:
566 tkMessageBox.showerror("Import error", str(msg), parent=self.text)
567 return
568 if type != imp.PY_SOURCE:
569 tkMessageBox.showerror("Unsupported type",
570 "%s is not a source module" % name, parent=self.text)
571 return
572 if f:
573 f.close()
574 if self.flist:
575 self.flist.open(file)
576 else:
577 self.io.loadfile(file)
578
579 def open_class_browser(self, event=None):
580 filename = self.io.filename
581 if not filename:
582 tkMessageBox.showerror(
583 "No filename",
584 "This buffer has no associated filename",
585 master=self.text)
586 self.text.focus_set()
587 return None
588 head, tail = os.path.split(filename)
589 base, ext = os.path.splitext(tail)
Florent Xiclunad630c042010-04-02 07:24:52 +0000590 from idlelib import ClassBrowser
David Scherer7aced172000-08-15 01:13:23 +0000591 ClassBrowser.ClassBrowser(self.flist, base, [head])
592
593 def open_path_browser(self, event=None):
Florent Xiclunad630c042010-04-02 07:24:52 +0000594 from idlelib import PathBrowser
David Scherer7aced172000-08-15 01:13:23 +0000595 PathBrowser.PathBrowser(self.flist)
596
597 def gotoline(self, lineno):
598 if lineno is not None and lineno > 0:
599 self.text.mark_set("insert", "%d.0" % lineno)
600 self.text.tag_remove("sel", "1.0", "end")
601 self.text.tag_add("sel", "insert", "insert +1l")
602 self.center()
603
604 def ispythonsource(self, filename):
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000605 if not filename or os.path.isdir(filename):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000606 return True
David Scherer7aced172000-08-15 01:13:23 +0000607 base, ext = os.path.splitext(os.path.basename(filename))
608 if os.path.normcase(ext) in (".py", ".pyw"):
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000609 return True
David Scherer7aced172000-08-15 01:13:23 +0000610 try:
611 f = open(filename)
612 line = f.readline()
613 f.close()
614 except IOError:
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000615 return False
Kurt B. Kaiser83a35602002-12-20 17:18:03 +0000616 return line.startswith('#!') and line.find('python') >= 0
David Scherer7aced172000-08-15 01:13:23 +0000617
618 def close_hook(self):
619 if self.flist:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000620 self.flist.unregister_maybe_terminate(self)
621 self.flist = None
David Scherer7aced172000-08-15 01:13:23 +0000622
623 def set_close_hook(self, close_hook):
624 self.close_hook = close_hook
625
626 def filename_change_hook(self):
627 if self.flist:
628 self.flist.filename_changed_edit(self)
629 self.saved_change_hook()
Kurt B. Kaiser260cb902003-06-06 21:58:38 +0000630 self.top.update_windowlist_registry(self)
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000631 self.ResetColorizer()
David Scherer7aced172000-08-15 01:13:23 +0000632
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000633 def _addcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000634 if self.color:
635 return
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000636 if self.ispythonsource(self.io.filename):
637 self.color = self.ColorDelegator()
638 # can add more colorizers here...
639 if self.color:
640 self.per.removefilter(self.undo)
641 self.per.insertfilter(self.color)
642 self.per.insertfilter(self.undo)
David Scherer7aced172000-08-15 01:13:23 +0000643
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000644 def _rmcolorizer(self):
David Scherer7aced172000-08-15 01:13:23 +0000645 if not self.color:
646 return
Kurt B. Kaiserdf506ea2005-06-12 04:33:30 +0000647 self.color.removecolors()
David Scherer7aced172000-08-15 01:13:23 +0000648 self.per.removefilter(self.color)
649 self.color = None
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000650
Steven M. Gavab77d3432002-03-02 07:16:21 +0000651 def ResetColorizer(self):
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000652 "Update the colour theme"
653 # Called from self.filename_change_hook and from configDialog.py
654 self._rmcolorizer()
655 self._addcolorizer()
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000656 theme = idleConf.GetOption('main','Theme','name')
Kurt B. Kaiserf05fa332008-02-15 22:25:09 +0000657 normal_colors = idleConf.GetHighlight(theme, 'normal')
658 cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
659 select_colors = idleConf.GetHighlight(theme, 'hilite')
660 self.text.config(
661 foreground=normal_colors['foreground'],
662 background=normal_colors['background'],
663 insertbackground=cursor_color,
664 selectforeground=select_colors['foreground'],
665 selectbackground=select_colors['background'],
666 )
David Scherer7aced172000-08-15 01:13:23 +0000667
Steven M. Gavab1585412002-03-12 00:21:56 +0000668 def ResetFont(self):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000669 "Update the text widgets' font if it is changed"
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000670 # Called from configDialog.py
Steven M. Gavab1585412002-03-12 00:21:56 +0000671 fontWeight='normal'
672 if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
673 fontWeight='bold'
674 self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
675 idleConf.GetOption('main','EditorWindow','font-size'),
676 fontWeight))
677
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000678 def RemoveKeybindings(self):
679 "Remove the keybindings before they are changed."
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000680 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000681 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000682 for event, keylist in keydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000683 self.text.event_delete(event, *keylist)
684 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000685 xkeydefs = idleConf.GetExtensionBindings(extensionName)
686 if xkeydefs:
687 for event, keylist in xkeydefs.items():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000688 self.text.event_delete(event, *keylist)
689
690 def ApplyKeybindings(self):
691 "Update the keybindings after they are changed"
692 # Called from configDialog.py
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000693 self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000694 self.apply_bindings()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000695 for extensionName in self.get_standard_extension_names():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000696 xkeydefs = idleConf.GetExtensionBindings(extensionName)
697 if xkeydefs:
698 self.apply_bindings(xkeydefs)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000699 #update menu accelerators
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000700 menuEventDict = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000701 for menu in self.Bindings.menudefs:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000702 menuEventDict[menu[0]] = {}
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000703 for item in menu[1]:
704 if item:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000705 menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000706 for menubarItem in self.menudict.keys():
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000707 menu = self.menudict[menubarItem]
708 end = menu.index(END) + 1
709 for index in range(0, end):
710 if menu.type(index) == 'command':
711 accel = menu.entrycget(index, 'accelerator')
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000712 if accel:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000713 itemName = menu.entrycget(index, 'label')
714 event = ''
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000715 if menubarItem in menuEventDict:
716 if itemName in menuEventDict[menubarItem]:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000717 event = menuEventDict[menubarItem][itemName]
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000718 if event:
Kurt B. Kaiser5a67f9b2005-11-22 01:47:14 +0000719 accel = get_accelerator(keydefs, event)
720 menu.entryconfig(index, accelerator=accel)
Steven M. Gavadbfe92c2002-03-18 02:38:44 +0000721
Kurt B. Kaiseracdef852005-01-31 03:34:26 +0000722 def set_notabs_indentwidth(self):
723 "Update the indentwidth if changed and not using tabs in this window"
724 # Called from configDialog.py
725 if not self.usetabs:
726 self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
727 type='int')
728
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000729 def reset_help_menu_entries(self):
730 "Update the additional help entries on the Help menu"
731 help_list = idleConf.GetAllExtraHelpSourcesList()
732 helpmenu = self.menudict['help']
733 # first delete the extra help entries, if any
734 helpmenu_length = helpmenu.index(END)
735 if helpmenu_length > self.base_helpmenu_length:
736 helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
737 # then rebuild them
738 if help_list:
739 helpmenu.add_separator()
740 for entry in help_list:
741 cmd = self.__extra_help_callback(entry[1])
742 helpmenu.add_command(label=entry[0], command=cmd)
743 # and update the menu dictionary
744 self.menudict['help'] = helpmenu
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000745
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000746 def __extra_help_callback(self, helpfile):
747 "Create a callback with the helpfile value frozen at definition time"
748 def display_extra_help(helpfile=helpfile):
Georg Brandlb2afe852006-06-09 20:43:48 +0000749 if not helpfile.startswith(('www', 'http')):
Kurt B. Kaiser8aa23922004-07-15 04:54:57 +0000750 url = os.path.normpath(helpfile)
751 if sys.platform[:3] == 'win':
752 os.startfile(helpfile)
753 else:
754 webbrowser.open(helpfile)
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000755 return display_extra_help
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000756
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000757 def update_recent_files_list(self, new_file=None):
758 "Load and update the recent files list and menus"
759 rf_list = []
760 if os.path.exists(self.recent_files_path):
761 rf_list_file = open(self.recent_files_path,'r')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000762 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000763 rf_list = rf_list_file.readlines()
Steven M. Gava1d46e402002-03-27 08:40:46 +0000764 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000765 rf_list_file.close()
766 if new_file:
767 new_file = os.path.abspath(new_file) + '\n'
768 if new_file in rf_list:
769 rf_list.remove(new_file) # move to top
770 rf_list.insert(0, new_file)
771 # clean and save the recent files list
772 bad_paths = []
773 for path in rf_list:
774 if '\0' in path or not os.path.exists(path[0:-1]):
775 bad_paths.append(path)
776 rf_list = [path for path in rf_list if path not in bad_paths]
777 ulchars = "1234567890ABCDEFGHIJK"
778 rf_list = rf_list[0:len(ulchars)]
779 rf_file = open(self.recent_files_path, 'w')
Steven M. Gava1d46e402002-03-27 08:40:46 +0000780 try:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000781 rf_file.writelines(rf_list)
Steven M. Gava1d46e402002-03-27 08:40:46 +0000782 finally:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000783 rf_file.close()
784 # for each edit window instance, construct the recent files menu
785 for instance in self.top.instance_dict.keys():
786 menu = instance.recent_files_menu
787 menu.delete(1, END) # clear, and rebuild:
Guilherme Polo86b882f2009-08-14 13:53:41 +0000788 for i, file_name in enumerate(rf_list):
789 file_name = file_name.rstrip() # zap \n
Martin v. Löwis307021f2005-11-27 16:59:04 +0000790 # make unicode string to display non-ASCII chars correctly
791 ufile_name = self._filename_to_unicode(file_name)
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000792 callback = instance.__recent_file_callback(file_name)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000793 menu.add_command(label=ulchars[i] + " " + ufile_name,
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000794 command=callback,
795 underline=0)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000796
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000797 def __recent_file_callback(self, file_name):
798 def open_recent_file(fn_closure=file_name):
799 self.io.open(editFile=fn_closure)
800 return open_recent_file
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000801
David Scherer7aced172000-08-15 01:13:23 +0000802 def saved_change_hook(self):
803 short = self.short_title()
804 long = self.long_title()
805 if short and long:
806 title = short + " - " + long
807 elif short:
808 title = short
809 elif long:
810 title = long
811 else:
812 title = "Untitled"
813 icon = short or long or title
814 if not self.get_saved():
815 title = "*%s*" % title
816 icon = "*%s" % icon
817 self.top.wm_title(title)
818 self.top.wm_iconname(icon)
819
820 def get_saved(self):
821 return self.undo.get_saved()
822
823 def set_saved(self, flag):
824 self.undo.set_saved(flag)
825
826 def reset_undo(self):
827 self.undo.reset_undo()
828
829 def short_title(self):
830 filename = self.io.filename
831 if filename:
832 filename = os.path.basename(filename)
Martin v. Löwis307021f2005-11-27 16:59:04 +0000833 # return unicode string to display non-ASCII chars correctly
834 return self._filename_to_unicode(filename)
David Scherer7aced172000-08-15 01:13:23 +0000835
836 def long_title(self):
Martin v. Löwis307021f2005-11-27 16:59:04 +0000837 # return unicode string to display non-ASCII chars correctly
838 return self._filename_to_unicode(self.io.filename or "")
David Scherer7aced172000-08-15 01:13:23 +0000839
840 def center_insert_event(self, event):
841 self.center()
842
843 def center(self, mark="insert"):
844 text = self.text
845 top, bot = self.getwindowlines()
846 lineno = self.getlineno(mark)
847 height = bot - top
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000848 newtop = max(1, lineno - height//2)
David Scherer7aced172000-08-15 01:13:23 +0000849 text.yview(float(newtop))
850
851 def getwindowlines(self):
852 text = self.text
853 top = self.getlineno("@0,0")
854 bot = self.getlineno("@0,65535")
855 if top == bot and text.winfo_height() == 1:
856 # Geometry manager hasn't run yet
857 height = int(text['height'])
858 bot = top + height - 1
859 return top, bot
860
861 def getlineno(self, mark="insert"):
862 text = self.text
863 return int(float(text.index(mark)))
864
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000865 def get_geometry(self):
866 "Return (width, height, x, y)"
867 geom = self.top.wm_geometry()
868 m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
869 tuple = (map(int, m.groups()))
870 return tuple
871
David Scherer7aced172000-08-15 01:13:23 +0000872 def close_event(self, event):
873 self.close()
874
875 def maybesave(self):
876 if self.io:
Steven M. Gava67716b52002-02-26 02:31:03 +0000877 if not self.get_saved():
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878 if self.top.state()!='normal':
Steven M. Gava67716b52002-02-26 02:31:03 +0000879 self.top.deiconify()
880 self.top.lower()
881 self.top.lift()
David Scherer7aced172000-08-15 01:13:23 +0000882 return self.io.maybesave()
883
884 def close(self):
David Scherer7aced172000-08-15 01:13:23 +0000885 reply = self.maybesave()
Matthias Klosea398e2d2007-01-11 11:44:04 +0000886 if str(reply) != "cancel":
David Scherer7aced172000-08-15 01:13:23 +0000887 self._close()
888 return reply
889
890 def _close(self):
Steven M. Gava1d46e402002-03-27 08:40:46 +0000891 if self.io.filename:
Kurt B. Kaisercf6f1b62004-04-11 03:16:07 +0000892 self.update_recent_files_list(new_file=self.io.filename)
David Scherer7aced172000-08-15 01:13:23 +0000893 WindowList.unregister_callback(self.postwindowsmenu)
David Scherer7aced172000-08-15 01:13:23 +0000894 self.unload_extensions()
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000895 self.io.close()
896 self.io = None
897 self.undo = None
David Scherer7aced172000-08-15 01:13:23 +0000898 if self.color:
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000899 self.color.close(False)
900 self.color = None
David Scherer7aced172000-08-15 01:13:23 +0000901 self.text = None
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000902 self.tkinter_vars = None
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +0000903 self.per.close()
904 self.per = None
905 self.top.destroy()
906 if self.close_hook:
907 # unless override: unregister from flist, terminate if last window
908 self.close_hook()
David Scherer7aced172000-08-15 01:13:23 +0000909
910 def load_extensions(self):
911 self.extensions = {}
912 self.load_standard_extensions()
913
914 def unload_extensions(self):
915 for ins in self.extensions.values():
916 if hasattr(ins, "close"):
917 ins.close()
918 self.extensions = {}
919
920 def load_standard_extensions(self):
921 for name in self.get_standard_extension_names():
922 try:
923 self.load_extension(name)
924 except:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000925 print "Failed to load extension", repr(name)
David Scherer7aced172000-08-15 01:13:23 +0000926 import traceback
927 traceback.print_exc()
928
929 def get_standard_extension_names(self):
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000930 return idleConf.GetExtensions(editor_only=True)
David Scherer7aced172000-08-15 01:13:23 +0000931
932 def load_extension(self, name):
Kurt B. Kaiserb00e89f2005-01-18 00:54:58 +0000933 try:
934 mod = __import__(name, globals(), locals(), [])
935 except ImportError:
936 print "\nFailed to import extension: ", name
937 return
David Scherer7aced172000-08-15 01:13:23 +0000938 cls = getattr(mod, name)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000939 keydefs = idleConf.GetExtensionBindings(name)
940 if hasattr(cls, "menudefs"):
941 self.fill_menus(cls.menudefs, keydefs)
David Scherer7aced172000-08-15 01:13:23 +0000942 ins = cls(self)
943 self.extensions[name] = ins
David Scherer7aced172000-08-15 01:13:23 +0000944 if keydefs:
945 self.apply_bindings(keydefs)
946 for vevent in keydefs.keys():
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +0000947 methodname = vevent.replace("-", "_")
David Scherer7aced172000-08-15 01:13:23 +0000948 while methodname[:1] == '<':
949 methodname = methodname[1:]
950 while methodname[-1:] == '>':
951 methodname = methodname[:-1]
952 methodname = methodname + "_event"
953 if hasattr(ins, methodname):
954 self.text.bind(vevent, getattr(ins, methodname))
David Scherer7aced172000-08-15 01:13:23 +0000955
956 def apply_bindings(self, keydefs=None):
957 if keydefs is None:
958 keydefs = self.Bindings.default_keydefs
959 text = self.text
960 text.keydefs = keydefs
961 for event, keylist in keydefs.items():
962 if keylist:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000963 text.event_add(event, *keylist)
David Scherer7aced172000-08-15 01:13:23 +0000964
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000965 def fill_menus(self, menudefs=None, keydefs=None):
Kurt B. Kaiser83118c62002-06-24 17:03:37 +0000966 """Add appropriate entries to the menus and submenus
967
968 Menus that are absent or None in self.menudict are ignored.
969 """
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000970 if menudefs is None:
971 menudefs = self.Bindings.menudefs
David Scherer7aced172000-08-15 01:13:23 +0000972 if keydefs is None:
973 keydefs = self.Bindings.default_keydefs
974 menudict = self.menudict
975 text = self.text
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000976 for mname, entrylist in menudefs:
David Scherer7aced172000-08-15 01:13:23 +0000977 menu = menudict.get(mname)
978 if not menu:
979 continue
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000980 for entry in entrylist:
981 if not entry:
David Scherer7aced172000-08-15 01:13:23 +0000982 menu.add_separator()
983 else:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000984 label, eventname = entry
David Scherer7aced172000-08-15 01:13:23 +0000985 checkbutton = (label[:1] == '!')
986 if checkbutton:
987 label = label[1:]
988 underline, label = prepstr(label)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000989 accelerator = get_accelerator(keydefs, eventname)
990 def command(text=text, eventname=eventname):
991 text.event_generate(eventname)
David Scherer7aced172000-08-15 01:13:23 +0000992 if checkbutton:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +0000993 var = self.get_var_obj(eventname, BooleanVar)
David Scherer7aced172000-08-15 01:13:23 +0000994 menu.add_checkbutton(label=label, underline=underline,
995 command=command, accelerator=accelerator,
996 variable=var)
997 else:
998 menu.add_command(label=label, underline=underline,
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000999 command=command,
1000 accelerator=accelerator)
David Scherer7aced172000-08-15 01:13:23 +00001001
1002 def getvar(self, name):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001003 var = self.get_var_obj(name)
David Scherer7aced172000-08-15 01:13:23 +00001004 if var:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001005 value = var.get()
1006 return value
1007 else:
1008 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001009
1010 def setvar(self, name, value, vartype=None):
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001011 var = self.get_var_obj(name, vartype)
David Scherer7aced172000-08-15 01:13:23 +00001012 if var:
1013 var.set(value)
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001014 else:
1015 raise NameError, name
David Scherer7aced172000-08-15 01:13:23 +00001016
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001017 def get_var_obj(self, name, vartype=None):
1018 var = self.tkinter_vars.get(name)
David Scherer7aced172000-08-15 01:13:23 +00001019 if not var and vartype:
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001020 # create a Tkinter variable object with self.text as master:
1021 self.tkinter_vars[name] = var = vartype(self.text)
David Scherer7aced172000-08-15 01:13:23 +00001022 return var
1023
1024 # Tk implementations of "virtual text methods" -- each platform
1025 # reusing IDLE's support code needs to define these for its GUI's
1026 # flavor of widget.
1027
1028 # Is character at text_index in a Python string? Return 0 for
1029 # "guaranteed no", true for anything else. This info is expensive
1030 # to compute ab initio, but is probably already known by the
1031 # platform's colorizer.
1032
1033 def is_char_in_string(self, text_index):
1034 if self.color:
1035 # Return true iff colorizer hasn't (re)gotten this far
1036 # yet, or the character is tagged as being in a string
1037 return self.text.tag_prevrange("TODO", text_index) or \
1038 "STRING" in self.text.tag_names(text_index)
1039 else:
1040 # The colorizer is missing: assume the worst
1041 return 1
1042
1043 # If a selection is defined in the text widget, return (start,
1044 # end) as Tkinter text indices, otherwise return (None, None)
1045 def get_selection_indices(self):
1046 try:
1047 first = self.text.index("sel.first")
1048 last = self.text.index("sel.last")
1049 return first, last
1050 except TclError:
1051 return None, None
1052
1053 # Return the text widget's current view of what a tab stop means
1054 # (equivalent width in spaces).
1055
1056 def get_tabwidth(self):
1057 current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
1058 return int(current)
1059
1060 # Set the text widget's current view of what a tab stop means.
1061
1062 def set_tabwidth(self, newtabwidth):
1063 text = self.text
1064 if self.get_tabwidth() != newtabwidth:
1065 pixels = text.tk.call("font", "measure", text["font"],
1066 "-displayof", text.master,
Kurt B. Kaiserafdf71b2001-07-13 03:35:32 +00001067 "n" * newtabwidth)
David Scherer7aced172000-08-15 01:13:23 +00001068 text.configure(tabs=pixels)
1069
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001070 # If ispythonsource and guess are true, guess a good value for
1071 # indentwidth based on file content (if possible), and if
1072 # indentwidth != tabwidth set usetabs false.
1073 # In any case, adjust the Text widget's view of what a tab
1074 # character means.
1075
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001076 def set_indentation_params(self, ispythonsource, guess=True):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001077 if guess and ispythonsource:
1078 i = self.guess_indent()
1079 if 2 <= i <= 8:
1080 self.indentwidth = i
1081 if self.indentwidth != self.tabwidth:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001082 self.usetabs = False
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001083 self.set_tabwidth(self.tabwidth)
1084
1085 def smart_backspace_event(self, event):
1086 text = self.text
1087 first, last = self.get_selection_indices()
1088 if first and last:
1089 text.delete(first, last)
1090 text.mark_set("insert", first)
1091 return "break"
1092 # Delete whitespace left, until hitting a real char or closest
1093 # preceding virtual tab stop.
1094 chars = text.get("insert linestart", "insert")
1095 if chars == '':
1096 if text.compare("insert", ">", "1.0"):
1097 # easy: delete preceding newline
1098 text.delete("insert-1c")
1099 else:
1100 text.bell() # at start of buffer
1101 return "break"
1102 if chars[-1] not in " \t":
1103 # easy: delete preceding real char
1104 text.delete("insert-1c")
1105 return "break"
1106 # Ick. It may require *inserting* spaces if we back up over a
1107 # tab character! This is written to be clear, not fast.
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001108 tabwidth = self.tabwidth
1109 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001110 assert have > 0
1111 want = ((have - 1) // self.indentwidth) * self.indentwidth
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001112 # Debug prompt is multilined....
1113 last_line_of_prompt = sys.ps1.split('\n')[-1]
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001114 ncharsdeleted = 0
1115 while 1:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001116 if chars == last_line_of_prompt:
Kurt B. Kaiser1bdca5e2002-12-16 22:25:10 +00001117 break
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001118 chars = chars[:-1]
1119 ncharsdeleted = ncharsdeleted + 1
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001120 have = len(chars.expandtabs(tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001121 if have <= want or chars[-1] not in " \t":
1122 break
1123 text.undo_block_start()
1124 text.delete("insert-%dc" % ncharsdeleted, "insert")
1125 if have < want:
1126 text.insert("insert", ' ' * (want - have))
1127 text.undo_block_stop()
1128 return "break"
1129
1130 def smart_indent_event(self, event):
1131 # if intraline selection:
1132 # delete it
1133 # elif multiline selection:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001134 # do indent-region
1135 # else:
1136 # indent one level
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001137 text = self.text
1138 first, last = self.get_selection_indices()
1139 text.undo_block_start()
1140 try:
1141 if first and last:
1142 if index2line(first) != index2line(last):
1143 return self.indent_region_event(event)
1144 text.delete(first, last)
1145 text.mark_set("insert", first)
1146 prefix = text.get("insert linestart", "insert")
1147 raw, effective = classifyws(prefix, self.tabwidth)
1148 if raw == len(prefix):
1149 # only whitespace to the left
1150 self.reindent_to(effective + self.indentwidth)
1151 else:
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001152 # tab to the next 'stop' within or to right of line's text:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001153 if self.usetabs:
1154 pad = '\t'
1155 else:
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001156 effective = len(prefix.expandtabs(self.tabwidth))
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001157 n = self.indentwidth
1158 pad = ' ' * (n - effective % n)
1159 text.insert("insert", pad)
1160 text.see("insert")
1161 return "break"
1162 finally:
1163 text.undo_block_stop()
1164
1165 def newline_and_indent_event(self, event):
1166 text = self.text
1167 first, last = self.get_selection_indices()
1168 text.undo_block_start()
1169 try:
1170 if first and last:
1171 text.delete(first, last)
1172 text.mark_set("insert", first)
1173 line = text.get("insert linestart", "insert")
1174 i, n = 0, len(line)
1175 while i < n and line[i] in " \t":
1176 i = i+1
1177 if i == n:
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001178 # the cursor is in or at leading indentation in a continuation
1179 # line; just inject an empty line at the start
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001180 text.insert("insert linestart", '\n')
1181 return "break"
1182 indent = line[:i]
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001183 # strip whitespace before insert point unless it's in the prompt
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001184 i = 0
Kurt B. Kaiser4ada7ad2002-12-29 22:03:38 +00001185 last_line_of_prompt = sys.ps1.split('\n')[-1]
1186 while line and line[-1] in " \t" and line != last_line_of_prompt:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001187 line = line[:-1]
1188 i = i+1
1189 if i:
1190 text.delete("insert - %d chars" % i, "insert")
1191 # strip whitespace after insert point
1192 while text.get("insert") in " \t":
1193 text.delete("insert")
1194 # start new line
1195 text.insert("insert", '\n')
1196
1197 # adjust indentation for continuations and block
1198 # open/close first need to find the last stmt
1199 lno = index2line(text.index('insert'))
1200 y = PyParse.Parser(self.indentwidth, self.tabwidth)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001201 if not self.context_use_ps1:
1202 for context in self.num_context_lines:
1203 startat = max(lno - context, 1)
Florent Xiclunadfd36182010-04-02 08:30:21 +00001204 startatindex = repr(startat) + ".0"
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001205 rawtext = text.get(startatindex, "insert")
1206 y.set_str(rawtext)
1207 bod = y.find_good_parse_start(
1208 self.context_use_ps1,
1209 self._build_char_in_string_func(startatindex))
1210 if bod is not None or startat == 1:
1211 break
1212 y.set_lo(bod or 0)
1213 else:
1214 r = text.tag_prevrange("console", "insert")
1215 if r:
1216 startatindex = r[1]
1217 else:
1218 startatindex = "1.0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001219 rawtext = text.get(startatindex, "insert")
1220 y.set_str(rawtext)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001221 y.set_lo(0)
1222
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001223 c = y.get_continuation_type()
1224 if c != PyParse.C_NONE:
1225 # The current stmt hasn't ended yet.
Kurt B. Kaiserb61602c2005-11-15 07:20:06 +00001226 if c == PyParse.C_STRING_FIRST_LINE:
1227 # after the first line of a string; do not indent at all
1228 pass
1229 elif c == PyParse.C_STRING_NEXT_LINES:
1230 # inside a string which started before this line;
1231 # just mimic the current indent
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001232 text.insert("insert", indent)
1233 elif c == PyParse.C_BRACKET:
1234 # line up with the first (if any) element of the
1235 # last open bracket structure; else indent one
1236 # level beyond the indent of the line with the
1237 # last open bracket
1238 self.reindent_to(y.compute_bracket_indent())
1239 elif c == PyParse.C_BACKSLASH:
1240 # if more than one line in this stmt already, just
1241 # mimic the current indent; else if initial line
1242 # has a start on an assignment stmt, indent to
1243 # beyond leftmost =; else to beyond first chunk of
1244 # non-whitespace on initial line
1245 if y.get_num_lines_in_stmt() > 1:
1246 text.insert("insert", indent)
1247 else:
1248 self.reindent_to(y.compute_backslash_indent())
1249 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +00001250 assert 0, "bogus continuation type %r" % (c,)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001251 return "break"
1252
1253 # This line starts a brand new stmt; indent relative to
1254 # indentation of initial line of closest preceding
1255 # interesting stmt.
1256 indent = y.get_base_indent_string()
1257 text.insert("insert", indent)
1258 if y.is_block_opener():
1259 self.smart_indent_event(event)
1260 elif indent and y.is_block_closer():
1261 self.smart_backspace_event(event)
1262 return "break"
1263 finally:
1264 text.see("insert")
1265 text.undo_block_stop()
1266
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001267 # Our editwin provides a is_char_in_string function that works
1268 # with a Tk text index, but PyParse only knows about offsets into
1269 # a string. This builds a function for PyParse that accepts an
1270 # offset.
1271
1272 def _build_char_in_string_func(self, startindex):
1273 def inner(offset, _startindex=startindex,
1274 _icis=self.is_char_in_string):
1275 return _icis(_startindex + "+%dc" % offset)
1276 return inner
1277
1278 def indent_region_event(self, event):
1279 head, tail, chars, lines = self.get_region()
1280 for pos in range(len(lines)):
1281 line = lines[pos]
1282 if line:
1283 raw, effective = classifyws(line, self.tabwidth)
1284 effective = effective + self.indentwidth
1285 lines[pos] = self._make_blanks(effective) + line[raw:]
1286 self.set_region(head, tail, chars, lines)
1287 return "break"
1288
1289 def dedent_region_event(self, event):
1290 head, tail, chars, lines = self.get_region()
1291 for pos in range(len(lines)):
1292 line = lines[pos]
1293 if line:
1294 raw, effective = classifyws(line, self.tabwidth)
1295 effective = max(effective - self.indentwidth, 0)
1296 lines[pos] = self._make_blanks(effective) + line[raw:]
1297 self.set_region(head, tail, chars, lines)
1298 return "break"
1299
1300 def comment_region_event(self, event):
1301 head, tail, chars, lines = self.get_region()
1302 for pos in range(len(lines) - 1):
1303 line = lines[pos]
1304 lines[pos] = '##' + line
1305 self.set_region(head, tail, chars, lines)
1306
1307 def uncomment_region_event(self, event):
1308 head, tail, chars, lines = self.get_region()
1309 for pos in range(len(lines)):
1310 line = lines[pos]
1311 if not line:
1312 continue
1313 if line[:2] == '##':
1314 line = line[2:]
1315 elif line[:1] == '#':
1316 line = line[1:]
1317 lines[pos] = line
1318 self.set_region(head, tail, chars, lines)
1319
1320 def tabify_region_event(self, event):
1321 head, tail, chars, lines = self.get_region()
1322 tabwidth = self._asktabwidth()
1323 for pos in range(len(lines)):
1324 line = lines[pos]
1325 if line:
1326 raw, effective = classifyws(line, tabwidth)
1327 ntabs, nspaces = divmod(effective, tabwidth)
1328 lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
1329 self.set_region(head, tail, chars, lines)
1330
1331 def untabify_region_event(self, event):
1332 head, tail, chars, lines = self.get_region()
1333 tabwidth = self._asktabwidth()
1334 for pos in range(len(lines)):
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001335 lines[pos] = lines[pos].expandtabs(tabwidth)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001336 self.set_region(head, tail, chars, lines)
1337
1338 def toggle_tabs_event(self, event):
1339 if self.askyesno(
1340 "Toggle tabs",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001341 "Turn tabs " + ("on", "off")[self.usetabs] +
1342 "?\nIndent width " +
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001343 ("will be", "remains at")[self.usetabs] + " 8." +
1344 "\n Note: a tab is always 8 columns",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001345 parent=self.text):
1346 self.usetabs = not self.usetabs
Kurt B. Kaiser53f2b5f2006-08-09 20:34:46 +00001347 # Try to prevent inconsistent indentation.
1348 # User must change indent width manually after using tabs.
1349 self.indentwidth = 8
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001350 return "break"
1351
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001352 # XXX this isn't bound to anything -- see tabwidth comments
1353## def change_tabwidth_event(self, event):
1354## new = self._asktabwidth()
1355## if new != self.tabwidth:
1356## self.tabwidth = new
1357## self.set_indentation_params(0, guess=0)
1358## return "break"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001359
1360 def change_indentwidth_event(self, event):
1361 new = self.askinteger(
1362 "Indent width",
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001363 "New indent width (2-16)\n(Always use 8 when using tabs)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001364 parent=self.text,
1365 initialvalue=self.indentwidth,
1366 minvalue=2,
1367 maxvalue=16)
Kurt B. Kaiser6af44982005-01-19 00:22:59 +00001368 if new and new != self.indentwidth and not self.usetabs:
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001369 self.indentwidth = new
1370 return "break"
1371
1372 def get_region(self):
1373 text = self.text
1374 first, last = self.get_selection_indices()
1375 if first and last:
1376 head = text.index(first + " linestart")
1377 tail = text.index(last + "-1c lineend +1c")
1378 else:
1379 head = text.index("insert linestart")
1380 tail = text.index("insert lineend +1c")
1381 chars = text.get(head, tail)
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001382 lines = chars.split("\n")
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001383 return head, tail, chars, lines
1384
1385 def set_region(self, head, tail, chars, lines):
1386 text = self.text
Kurt B. Kaiser1b3c2692002-09-15 21:31:30 +00001387 newchars = "\n".join(lines)
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001388 if newchars == chars:
1389 text.bell()
1390 return
1391 text.tag_remove("sel", "1.0", "end")
1392 text.mark_set("insert", head)
1393 text.undo_block_start()
1394 text.delete(head, tail)
1395 text.insert(head, newchars)
1396 text.undo_block_stop()
1397 text.tag_add("sel", head, "insert")
1398
1399 # Make string that displays as n leading blanks.
1400
1401 def _make_blanks(self, n):
1402 if self.usetabs:
1403 ntabs, nspaces = divmod(n, self.tabwidth)
1404 return '\t' * ntabs + ' ' * nspaces
1405 else:
1406 return ' ' * n
1407
1408 # Delete from beginning of line to insert point, then reinsert
1409 # column logical (meaning use tabs if appropriate) spaces.
1410
1411 def reindent_to(self, column):
1412 text = self.text
1413 text.undo_block_start()
1414 if text.compare("insert linestart", "!=", "insert"):
1415 text.delete("insert linestart", "insert")
1416 if column:
1417 text.insert("insert", self._make_blanks(column))
1418 text.undo_block_stop()
1419
1420 def _asktabwidth(self):
1421 return self.askinteger(
1422 "Tab width",
Kurt B. Kaiserca7329c2005-06-12 05:19:23 +00001423 "Columns per tab? (2-16)",
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001424 parent=self.text,
1425 initialvalue=self.indentwidth,
1426 minvalue=2,
1427 maxvalue=16) or self.tabwidth
1428
1429 # Guess indentwidth from text content.
1430 # Return guessed indentwidth. This should not be believed unless
1431 # it's in a reasonable range (e.g., it will be 0 if no indented
1432 # blocks are found).
1433
1434 def guess_indent(self):
1435 opener, indented = IndentSearcher(self.text, self.tabwidth).run()
1436 if opener and indented:
1437 raw, indentsmall = classifyws(opener, self.tabwidth)
1438 raw, indentlarge = classifyws(indented, self.tabwidth)
1439 else:
1440 indentsmall = indentlarge = 0
1441 return indentlarge - indentsmall
1442
1443# "line.col" -> line, as an int
1444def index2line(index):
1445 return int(float(index))
1446
1447# Look at the leading whitespace in s.
1448# Return pair (# of leading ws characters,
1449# effective # of leading blanks after expanding
1450# tabs to width tabwidth)
1451
1452def classifyws(s, tabwidth):
1453 raw = effective = 0
1454 for ch in s:
1455 if ch == ' ':
1456 raw = raw + 1
1457 effective = effective + 1
1458 elif ch == '\t':
1459 raw = raw + 1
1460 effective = (effective // tabwidth + 1) * tabwidth
1461 else:
1462 break
1463 return raw, effective
1464
1465import tokenize
1466_tokenize = tokenize
1467del tokenize
1468
Kurt B. Kaiserdcba6622004-12-21 22:10:32 +00001469class IndentSearcher(object):
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001470
1471 # .run() chews over the Text widget, looking for a block opener
1472 # and the stmt following it. Returns a pair,
1473 # (line containing block opener, line containing stmt)
1474 # Either or both may be None.
1475
1476 def __init__(self, text, tabwidth):
1477 self.text = text
1478 self.tabwidth = tabwidth
1479 self.i = self.finished = 0
1480 self.blkopenline = self.indentedline = None
1481
1482 def readline(self):
1483 if self.finished:
1484 return ""
1485 i = self.i = self.i + 1
Walter Dörwald70a6b492004-02-12 17:35:32 +00001486 mark = repr(i) + ".0"
Kurt B. Kaisercb7a3832002-09-14 02:34:23 +00001487 if self.text.compare(mark, ">=", "end"):
1488 return ""
1489 return self.text.get(mark, mark + " lineend+1c")
1490
1491 def tokeneater(self, type, token, start, end, line,
1492 INDENT=_tokenize.INDENT,
1493 NAME=_tokenize.NAME,
1494 OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
1495 if self.finished:
1496 pass
1497 elif type == NAME and token in OPENERS:
1498 self.blkopenline = line
1499 elif type == INDENT and self.blkopenline:
1500 self.indentedline = line
1501 self.finished = 1
1502
1503 def run(self):
1504 save_tabsize = _tokenize.tabsize
1505 _tokenize.tabsize = self.tabwidth
1506 try:
1507 try:
1508 _tokenize.tokenize(self.readline, self.tokeneater)
1509 except _tokenize.TokenError:
1510 # since we cut off the tokenizer early, we can trigger
1511 # spurious errors
1512 pass
1513 finally:
1514 _tokenize.tabsize = save_tabsize
1515 return self.blkopenline, self.indentedline
1516
1517### end autoindent code ###
1518
David Scherer7aced172000-08-15 01:13:23 +00001519def prepstr(s):
1520 # Helper to extract the underscore from a string, e.g.
1521 # prepstr("Co_py") returns (2, "Copy").
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001522 i = s.find('_')
David Scherer7aced172000-08-15 01:13:23 +00001523 if i >= 0:
1524 s = s[:i] + s[i+1:]
1525 return i, s
1526
1527
1528keynames = {
1529 'bracketleft': '[',
1530 'bracketright': ']',
1531 'slash': '/',
1532}
1533
Kurt B. Kaiser610c7e02004-04-24 03:01:48 +00001534def get_accelerator(keydefs, eventname):
1535 keylist = keydefs.get(eventname)
David Scherer7aced172000-08-15 01:13:23 +00001536 if not keylist:
1537 return ""
1538 s = keylist[0]
Kurt B. Kaiser220ecbc2002-09-16 02:13:15 +00001539 s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
David Scherer7aced172000-08-15 01:13:23 +00001540 s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
1541 s = re.sub("Key-", "", s)
1542 s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
1543 s = re.sub("Control-", "Ctrl-", s)
1544 s = re.sub("-", "+", s)
1545 s = re.sub("><", " ", s)
1546 s = re.sub("<", "", s)
1547 s = re.sub(">", "", s)
1548 return s
1549
1550
1551def fixwordbreaks(root):
1552 # Make sure that Tk's double-click and next/previous word
1553 # operations use our definition of a word (i.e. an identifier)
1554 tk = root.tk
1555 tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
1556 tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
1557 tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
1558
1559
1560def test():
1561 root = Tk()
1562 fixwordbreaks(root)
1563 root.withdraw()
1564 if sys.argv[1:]:
1565 filename = sys.argv[1]
1566 else:
1567 filename = None
1568 edit = EditorWindow(root=root, filename=filename)
1569 edit.set_close_hook(root.quit)
Kurt B. Kaiser0b634ef2007-10-04 02:09:17 +00001570 edit.text.bind("<<close-all-windows>>", edit.close_event)
David Scherer7aced172000-08-15 01:13:23 +00001571 root.mainloop()
1572 root.destroy()
1573
1574if __name__ == '__main__':
1575 test()